diff --git a/README.md b/README.md new file mode 100644 index 0000000..2fb307a --- /dev/null +++ b/README.md @@ -0,0 +1,54 @@ +photolib +======== + +Cloud-native photo and related media library software, with a focus for photography hobbyists. + +TODO longer description + + +Installation +------------ + +TODO + + +Usage +----- + +After installation, photolib provides a CLI tool, `photocli`. It can be used as follows: + +* `photocli checkdupes` - scan files and print those not already in the library: + + +``` +$ find source -type f -exec shasum -a 256 {} \; | tee shas.txt +544b5a4f3898abae73260930ee70931a3992c048649692cef89b37576e886f69 source/2018-12-09 13.52.22.jpg +d2c2c30ca4986d364026644881d667162031008e60aac6a69d7b18230b7ea98c source/2019-04-08 20.05.02.jpg +9d42859ed92d7fb978cf73b41293480e1eab03d2d3a14c185c4daf3a49d324ab source/2019-06-19 16.28.07.png +... + +$ photocli -s http://localhost:8080 checkdupes --sha-files shas.txt + +``` + +TODO: support not using `--sha-files` + + +* `photocli ingest` - import new photos into the library: + + +``` +$ photocli -s http://localhost:8080 ingest photos/*.jpg raws/*.cr2 + +... + +``` + +TODO: implement this + +There is some non-obvious behavior in the ingest process that is important to understand. Many photographers shoot in +multiple formats, in the sense that one click of the shutter produces multiple formats. Jpeg and Raw is common. During +ingest, photocli assumes that files with the same filename are multiple formats of the same image. E.g., if you have +both `IMG_1234.JPG` and `IMG_1234.cr2`, the two images will be grouped in the library. This combination action is only +applied during ingest; images already in the library that happen to share a filename with a file being imported will +not be merged. To add a format to a photo already in the library, see `photocli addformat`. diff --git a/photoapp/cli.py b/photoapp/cli.py index 4bbe532..c18b066 100644 --- a/photoapp/cli.py +++ b/photoapp/cli.py @@ -1,6 +1,8 @@ import argparse import requests from requests.exceptions import HTTPError +from photoapp.utils import get_extension +from photoapp.types import known_extensions class PhotoApiClient(object): @@ -32,7 +34,6 @@ def main(): p_ingest.add_argument("-c", "--copy-of", help="existing uuid the imported images will be placed under") args = parser.parse_args() - print(args) client = PhotoApiClient(args.host) @@ -48,20 +49,24 @@ def main(): if not line: continue sha, path = line.split(maxsplit=1) - hashes[sha] = path.rstrip("\n") + path = path.rstrip("\n") + if get_extension(path) not in known_extensions: + continue + hashes[sha] = path else: raise NotImplementedError("must pass --sha-files for now") for sha, path in hashes.items(): # hit http://localhost:8080/api/v1/byhash?sha=afe49172f709725a4503c9219fb4c6a9db8ad0354fc493f2f500269ac6faeaf6 - # if the file is a dupe, do nothing - # if the file is original, print its path try: - print(client.byhash(sha)) + client.byhash(sha) + # if the file is a dupe, do nothing except HTTPError as he: - print(repr(he.response.status_code)) - - # print(hashes) + # if the file is original, print its path + if he.response.status_code == 404: + print(path) + else: + raise if __name__ == "__main__": diff --git a/photoapp/ingest.py b/photoapp/ingest.py index 4a67c16..28a3e46 100644 --- a/photoapp/ingest.py +++ b/photoapp/ingest.py @@ -4,9 +4,10 @@ import traceback from photoapp.library import PhotoLibrary from photoapp.image import get_jpg_info, get_hash, get_mtime from itertools import chain -from photoapp.types import Photo, PhotoSet +from photoapp.types import Photo, PhotoSet, known_extensions, regular_images, files_raw, files_video import os + """ Photo sorting rules: @@ -20,11 +21,6 @@ mov, video, or other modification date """ -known_extensions = ["jpg", "png", "cr2", "xmp", "mp4", "mov"] -regular_images = ["jpg", "png"] -files_raw = ["cr2", "xmp"] -files_video = ["mp4", "mov"] - def pprogress(done, total=None): print(" complete: {}{}\r".format(done, " / {} ".format(total) if total else ''), end='') diff --git a/photoapp/types.py b/photoapp/types.py index 2e8965e..932c247 100644 --- a/photoapp/types.py +++ b/photoapp/types.py @@ -7,6 +7,12 @@ import uuid import enum +known_extensions = ["jpg", "png", "cr2", "xmp", "mp4", "mov"] +regular_images = ["jpg", "png"] +files_raw = ["cr2", "xmp"] +files_video = ["mp4", "mov"] + + class PhotoStatus(enum.Enum): private = 0 public = 1 diff --git a/photoapp/utils.py b/photoapp/utils.py index 9af93f2..74297cd 100644 --- a/photoapp/utils.py +++ b/photoapp/utils.py @@ -1,3 +1,4 @@ +import os import cherrypy from photoapp.types import PhotoSet, PhotoStatus @@ -15,6 +16,13 @@ def mime2ext(mime): "video/quicktime": "mov"}[mime] +def get_extension(fname): + parts = os.path.basename(fname).split(".") + if len(parts) == 1: + return None + return parts[-1].lower() + + def auth(): """ Return the currently authorized username (per request) or None