diff --git a/pyproject.toml b/pyproject.toml index 15dba81..fc49716 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,7 @@ classifiers = [ "Topic :: System :: Distributed Computing" ] keywords = ["Personal tools and recipes"] -dependencies = ["pydantic", "tinytag"] +dependencies = ["pydantic", "tinytag", "pillow", "pillow_heif"] [project.urls] Repository = "https://git.jmsgrogan.com/jgrogan/recipes" diff --git a/src/jgutils/converters.py b/src/jgutils/converters.py index c5b6282..6eafe9c 100644 --- a/src/jgutils/converters.py +++ b/src/jgutils/converters.py @@ -4,6 +4,7 @@ from typing import Callable import logging from jgutils.filesystem import get_files_recursive +from jgutils.tasks import run_tasks logger = logging.getLogger(__name__) @@ -19,19 +20,29 @@ def _get_converted_path(input_path: Path, output_filename = str(input_path.stem) + f".{output_ext}" 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, input_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]) - output_files = [_get_converted_path(f.relative_to(input_dir), + if input_path.is_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_ext) for f in input_files] 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) diff --git a/src/jgutils/filesystem/cli.py b/src/jgutils/filesystem/cli.py new file mode 100644 index 0000000..d03a08b --- /dev/null +++ b/src/jgutils/filesystem/cli.py @@ -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) + diff --git a/src/jgutils/filesystem/filesystem.py b/src/jgutils/filesystem/filesystem.py index 5a2d013..df9176b 100644 --- a/src/jgutils/filesystem/filesystem.py +++ b/src/jgutils/filesystem/filesystem.py @@ -1,7 +1,11 @@ from pathlib import Path import os 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]: if not extensions: diff --git a/src/jgutils/image/__init__.py b/src/jgutils/image/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/jgutils/image/cli.py b/src/jgutils/image/cli.py new file mode 100644 index 0000000..99f4d58 --- /dev/null +++ b/src/jgutils/image/cli.py @@ -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) + diff --git a/src/jgutils/image/convert.py b/src/jgutils/image/convert.py new file mode 100644 index 0000000..1ae1cfd --- /dev/null +++ b/src/jgutils/image/convert.py @@ -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}') diff --git a/src/jgutils/main_cli.py b/src/jgutils/main_cli.py index 2fd9f5c..1cdd87d 100644 --- a/src/jgutils/main_cli.py +++ b/src/jgutils/main_cli.py @@ -1,57 +1,24 @@ import argparse import logging 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__) -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(): parser = argparse.ArgumentParser() subparsers = parser.add_subparsers(required=True) - music_parser = subparsers.add_parser("music") - music_subparsers = music_parser.add_subparsers(required=True) - - 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) - + jgutils.filesystem.cli.add_parser(subparsers) + jgutils.image.cli.add_parser(subparsers) + jgutils.music.cli.add_parser(subparsers) + logging.basicConfig() logging.getLogger().setLevel(logging.INFO) diff --git a/src/jgutils/music/cli.py b/src/jgutils/music/cli.py new file mode 100644 index 0000000..95ac687 --- /dev/null +++ b/src/jgutils/music/cli.py @@ -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) + diff --git a/src/jgutils/photos/convert_heic.sh b/src/jgutils/photos/convert_heic.sh deleted file mode 100755 index 55c0d12..0000000 --- a/src/jgutils/photos/convert_heic.sh +++ /dev/null @@ -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;