Add photo conversion
This commit is contained in:
parent
c09179f5da
commit
3e7ce98755
10 changed files with 215 additions and 54 deletions
|
@ -15,7 +15,7 @@ classifiers = [
|
||||||
"Topic :: System :: Distributed Computing"
|
"Topic :: System :: Distributed Computing"
|
||||||
]
|
]
|
||||||
keywords = ["Personal tools and recipes"]
|
keywords = ["Personal tools and recipes"]
|
||||||
dependencies = ["pydantic", "tinytag"]
|
dependencies = ["pydantic", "tinytag", "pillow", "pillow_heif"]
|
||||||
|
|
||||||
[project.urls]
|
[project.urls]
|
||||||
Repository = "https://git.jmsgrogan.com/jgrogan/recipes"
|
Repository = "https://git.jmsgrogan.com/jgrogan/recipes"
|
||||||
|
|
|
@ -4,6 +4,7 @@ from typing import Callable
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from jgutils.filesystem import get_files_recursive
|
from jgutils.filesystem import get_files_recursive
|
||||||
|
from jgutils.tasks import run_tasks
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -19,19 +20,29 @@ def _get_converted_path(input_path: Path,
|
||||||
output_filename = str(input_path.stem) + f".{output_ext}"
|
output_filename = str(input_path.stem) + f".{output_ext}"
|
||||||
return output_dir / input_path.parent / output_filename
|
return output_dir / input_path.parent / output_filename
|
||||||
|
|
||||||
def convert(input_dir: Path,
|
def _should_convert(overwrite_existing, path):
|
||||||
|
if overwrite_existing:
|
||||||
|
return True
|
||||||
|
return not path.exists()
|
||||||
|
|
||||||
|
def convert(input_path: Path,
|
||||||
output_dir: Path,
|
output_dir: Path,
|
||||||
input_ext: str,
|
input_ext: str,
|
||||||
output_ext: str,
|
output_ext: str,
|
||||||
conversion_func: Callable):
|
conversion_func: Callable,
|
||||||
|
overwrite_existing: bool = False):
|
||||||
|
|
||||||
logger.info("Converting files in %s to %s", input_dir, output_dir)
|
logger.info("Converting files in %s to %s", input_path, output_dir)
|
||||||
|
|
||||||
input_files = get_files_recursive(input_dir.resolve(), [input_ext])
|
if input_path.is_dir():
|
||||||
output_files = [_get_converted_path(f.relative_to(input_dir),
|
input_files = get_files_recursive(input_path.resolve(), [input_ext])
|
||||||
|
else:
|
||||||
|
input_files = [input_path]
|
||||||
|
output_files = [_get_converted_path(f.relative_to(input_path),
|
||||||
output_dir,
|
output_dir,
|
||||||
output_ext) for f in input_files]
|
output_ext) for f in input_files]
|
||||||
conversion_pairs = [io_paths for io_paths in
|
conversion_pairs = [io_paths for io_paths in
|
||||||
zip(input_files, output_files) if not io_paths[1].exists()]
|
zip(input_files, output_files)
|
||||||
|
if _should_convert(overwrite_existing, io_paths[1])]
|
||||||
|
|
||||||
run_tasks(conversion_func, conversion_pairs)
|
run_tasks(conversion_func, conversion_pairs)
|
||||||
|
|
65
src/jgutils/filesystem/cli.py
Normal file
65
src/jgutils/filesystem/cli.py
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
import logging
|
||||||
|
from pathlib import Path
|
||||||
|
from functools import partial
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
from .filesystem import for_each_file
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
def _remove_file(path: Path):
|
||||||
|
path.unlink()
|
||||||
|
|
||||||
|
def cli_file_remove(args):
|
||||||
|
|
||||||
|
input_path = args.path.resolve()
|
||||||
|
extension = args.ext
|
||||||
|
|
||||||
|
logger.info("Removing files with extension %s from %s",
|
||||||
|
extension, input_path)
|
||||||
|
for_each_file(input_path, [extension], _remove_file)
|
||||||
|
|
||||||
|
|
||||||
|
def _flatten(parent: Path, path: Path):
|
||||||
|
relative_path = path.relative_to(parent)
|
||||||
|
relative_dir = relative_path.parent
|
||||||
|
|
||||||
|
new_filename = "_".join(relative_dir.parts)
|
||||||
|
new_filename += "_" + path.name
|
||||||
|
new_path = parent / new_filename
|
||||||
|
shutil.move(path, new_path)
|
||||||
|
|
||||||
|
def cli_dir_flatten(args):
|
||||||
|
|
||||||
|
input_path = args.path.resolve()
|
||||||
|
|
||||||
|
logger.info("Flattening directory at: %s", input_path)
|
||||||
|
for_each_file(input_path, [], partial(_flatten, input_path))
|
||||||
|
|
||||||
|
|
||||||
|
def add_parser(subparsers):
|
||||||
|
|
||||||
|
parser = subparsers.add_parser("file")
|
||||||
|
parsers = parser.add_subparsers(required=True)
|
||||||
|
|
||||||
|
remove_parser = parsers.add_parser("remove")
|
||||||
|
remove_parser.add_argument(
|
||||||
|
"--path",
|
||||||
|
type=Path,
|
||||||
|
help="Path to directory to remove files from",
|
||||||
|
)
|
||||||
|
remove_parser.add_argument(
|
||||||
|
"--ext",
|
||||||
|
type=str,
|
||||||
|
help="Extension of file to remove",
|
||||||
|
)
|
||||||
|
remove_parser.set_defaults(func=cli_file_remove)
|
||||||
|
|
||||||
|
flatten_parser = parsers.add_parser("flatten")
|
||||||
|
flatten_parser.add_argument(
|
||||||
|
"--path",
|
||||||
|
type=Path,
|
||||||
|
help="Path to directory to flatten",
|
||||||
|
)
|
||||||
|
flatten_parser.set_defaults(func=cli_dir_flatten)
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
from typing import Callable
|
||||||
|
|
||||||
|
def for_each_file(path: Path, extensions: list[str] | None, func: Callable):
|
||||||
|
for eachFile in get_files_recursive(path, extensions):
|
||||||
|
func(eachFile)
|
||||||
|
|
||||||
def get_files_recursive(path: Path, extensions: list[str] | None) -> list[Path]:
|
def get_files_recursive(path: Path, extensions: list[str] | None) -> list[Path]:
|
||||||
if not extensions:
|
if not extensions:
|
||||||
|
|
0
src/jgutils/image/__init__.py
Normal file
0
src/jgutils/image/__init__.py
Normal file
35
src/jgutils/image/cli.py
Normal file
35
src/jgutils/image/cli.py
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
from pathlib import Path
|
||||||
|
import os
|
||||||
|
|
||||||
|
from jgutils import converters
|
||||||
|
from jgutils.image.convert import pillow_convert
|
||||||
|
|
||||||
|
|
||||||
|
def cli_image_convert(args):
|
||||||
|
|
||||||
|
input_path = args.input.resolve()
|
||||||
|
if not input_path.is_dir():
|
||||||
|
output_dir = input_path.parent
|
||||||
|
else:
|
||||||
|
output_dir = input_path
|
||||||
|
|
||||||
|
converters.convert(input_path,
|
||||||
|
output_dir,
|
||||||
|
"HEIC",
|
||||||
|
"png",
|
||||||
|
pillow_convert,
|
||||||
|
True)
|
||||||
|
|
||||||
|
def add_parser(subparsers):
|
||||||
|
parser = subparsers.add_parser("image")
|
||||||
|
parsers = parser.add_subparsers(required=True)
|
||||||
|
convert_parser = parsers.add_parser("convert")
|
||||||
|
|
||||||
|
convert_parser.add_argument(
|
||||||
|
"--input",
|
||||||
|
type=Path,
|
||||||
|
default=Path(os.getcwd()),
|
||||||
|
help="Path to run the conversion from",
|
||||||
|
)
|
||||||
|
convert_parser.set_defaults(func=cli_image_convert)
|
||||||
|
|
35
src/jgutils/image/convert.py
Normal file
35
src/jgutils/image/convert.py
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
from pathlib import Path
|
||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from PIL import Image
|
||||||
|
from pillow_heif import HeifImagePlugin
|
||||||
|
from PIL.ExifTags import TAGS
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def pillow_convert(paths: tuple[Path]):
|
||||||
|
input_path, output_path = paths
|
||||||
|
logging.info("Converting %s to %s: ", input_path, output_path)
|
||||||
|
with Image.open(input_path) as im:
|
||||||
|
im.save(output_path, exif=im.getexif())
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
|
||||||
|
path = sys.argv[1]
|
||||||
|
image = Image.open(path)
|
||||||
|
print(image.info)
|
||||||
|
print(image.getxmp())
|
||||||
|
exifdata = image.getexif()
|
||||||
|
print(exifdata)
|
||||||
|
for tag_id in exifdata:
|
||||||
|
print(tag_id)
|
||||||
|
tag = TAGS.get(tag_id,tag_id)
|
||||||
|
print(tag)
|
||||||
|
data = exifdata.get(tag_id)
|
||||||
|
print(data)
|
||||||
|
# we decode bytes
|
||||||
|
if isinstance(data,bytes):
|
||||||
|
data = data.decode()
|
||||||
|
print(f'{tag:25}: {data}')
|
|
@ -1,56 +1,23 @@
|
||||||
import argparse
|
import argparse
|
||||||
import logging
|
import logging
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
import os
|
||||||
|
|
||||||
|
import jgutils.filesystem.cli
|
||||||
|
import jgutils.image.cli
|
||||||
|
import jgutils.music.cli
|
||||||
|
|
||||||
from jgutils import music
|
|
||||||
from jgutils.serialization import write_model
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
def cli_music_read(args):
|
|
||||||
collection = music.collection.read(args.collection.resolve())
|
|
||||||
write_model(collection)
|
|
||||||
|
|
||||||
def cli_music_refresh(args):
|
|
||||||
music.refresh(args.collection.resolve(),
|
|
||||||
args.work_dir.resolve())
|
|
||||||
|
|
||||||
def main_cli():
|
def main_cli():
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
subparsers = parser.add_subparsers(required=True)
|
subparsers = parser.add_subparsers(required=True)
|
||||||
|
|
||||||
music_parser = subparsers.add_parser("music")
|
jgutils.filesystem.cli.add_parser(subparsers)
|
||||||
music_subparsers = music_parser.add_subparsers(required=True)
|
jgutils.image.cli.add_parser(subparsers)
|
||||||
|
jgutils.music.cli.add_parser(subparsers)
|
||||||
music_read_parser = music_subparsers.add_parser("read")
|
|
||||||
music_read_parser.add_argument(
|
|
||||||
"--collection",
|
|
||||||
type=Path,
|
|
||||||
default=Path(),
|
|
||||||
help="Directory with input music files.",
|
|
||||||
)
|
|
||||||
music_read_parser.add_argument(
|
|
||||||
"--output_path",
|
|
||||||
type=Path,
|
|
||||||
default=Path() / "music_collection.json",
|
|
||||||
help="Path to save collection info json to.",
|
|
||||||
)
|
|
||||||
music_read_parser.set_defaults(func=cli_music_read)
|
|
||||||
|
|
||||||
music_refresh_parser = music_subparsers.add_parser("refresh")
|
|
||||||
music_refresh_parser.add_argument(
|
|
||||||
"--collection",
|
|
||||||
type=Path,
|
|
||||||
default=Path(),
|
|
||||||
help="Directory with the music collection.",
|
|
||||||
)
|
|
||||||
music_refresh_parser.add_argument(
|
|
||||||
"--work_dir",
|
|
||||||
type=Path,
|
|
||||||
default=Path(),
|
|
||||||
help="Directory for intermediate storage.",
|
|
||||||
)
|
|
||||||
music_refresh_parser.set_defaults(func=cli_music_refresh)
|
|
||||||
|
|
||||||
logging.basicConfig()
|
logging.basicConfig()
|
||||||
logging.getLogger().setLevel(logging.INFO)
|
logging.getLogger().setLevel(logging.INFO)
|
||||||
|
|
48
src/jgutils/music/cli.py
Normal file
48
src/jgutils/music/cli.py
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from jgutils.music import collection
|
||||||
|
from jgutils.serialization import write_model
|
||||||
|
|
||||||
|
|
||||||
|
def cli_music_read(args):
|
||||||
|
collection = collection.read(args.collection.resolve())
|
||||||
|
write_model(collection)
|
||||||
|
|
||||||
|
def cli_music_refresh(args):
|
||||||
|
collection.refresh(args.collection.resolve(),
|
||||||
|
args.work_dir.resolve())
|
||||||
|
|
||||||
|
def add_parser(subparsers):
|
||||||
|
parser = subparsers.add_parser("music")
|
||||||
|
parsers = parser.add_subparsers(required=True)
|
||||||
|
|
||||||
|
read_parser = parsers.add_parser("read")
|
||||||
|
read_parser.add_argument(
|
||||||
|
"--collection",
|
||||||
|
type=Path,
|
||||||
|
default=Path(),
|
||||||
|
help="Directory with input music files.",
|
||||||
|
)
|
||||||
|
read_parser.add_argument(
|
||||||
|
"--output_path",
|
||||||
|
type=Path,
|
||||||
|
default=Path() / "music_collection.json",
|
||||||
|
help="Path to save collection info json to.",
|
||||||
|
)
|
||||||
|
read_parser.set_defaults(func=cli_music_read)
|
||||||
|
|
||||||
|
refresh_parser = parsers.add_parser("refresh")
|
||||||
|
refresh_parser.add_argument(
|
||||||
|
"--collection",
|
||||||
|
type=Path,
|
||||||
|
default=Path(),
|
||||||
|
help="Directory with the music collection.",
|
||||||
|
)
|
||||||
|
refresh_parser.add_argument(
|
||||||
|
"--work_dir",
|
||||||
|
type=Path,
|
||||||
|
default=Path(),
|
||||||
|
help="Directory for intermediate storage.",
|
||||||
|
)
|
||||||
|
refresh_parser.set_defaults(func=cli_music_refresh)
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
for f in $(find . -name '*.HEIC');
|
|
||||||
do echo "Converting $f";
|
|
||||||
/home/jgrogan/code/ImageMagick-7.1.1-35/utilities/magick mogrify -format png -quality 100% "$f";
|
|
||||||
done;
|
|
Loading…
Reference in a new issue