checkdupes cli function, add readme
This commit is contained in:
parent
4f218f068a
commit
6530d86617
|
@ -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`.
|
|
@ -1,6 +1,8 @@
|
||||||
import argparse
|
import argparse
|
||||||
import requests
|
import requests
|
||||||
from requests.exceptions import HTTPError
|
from requests.exceptions import HTTPError
|
||||||
|
from photoapp.utils import get_extension
|
||||||
|
from photoapp.types import known_extensions
|
||||||
|
|
||||||
|
|
||||||
class PhotoApiClient(object):
|
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")
|
p_ingest.add_argument("-c", "--copy-of", help="existing uuid the imported images will be placed under")
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
print(args)
|
|
||||||
|
|
||||||
client = PhotoApiClient(args.host)
|
client = PhotoApiClient(args.host)
|
||||||
|
|
||||||
|
@ -48,20 +49,24 @@ def main():
|
||||||
if not line:
|
if not line:
|
||||||
continue
|
continue
|
||||||
sha, path = line.split(maxsplit=1)
|
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:
|
else:
|
||||||
raise NotImplementedError("must pass --sha-files for now")
|
raise NotImplementedError("must pass --sha-files for now")
|
||||||
|
|
||||||
for sha, path in hashes.items():
|
for sha, path in hashes.items():
|
||||||
# hit http://localhost:8080/api/v1/byhash?sha=afe49172f709725a4503c9219fb4c6a9db8ad0354fc493f2f500269ac6faeaf6
|
# 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:
|
try:
|
||||||
print(client.byhash(sha))
|
client.byhash(sha)
|
||||||
|
# if the file is a dupe, do nothing
|
||||||
except HTTPError as he:
|
except HTTPError as he:
|
||||||
print(repr(he.response.status_code))
|
# if the file is original, print its path
|
||||||
|
if he.response.status_code == 404:
|
||||||
# print(hashes)
|
print(path)
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
|
@ -4,9 +4,10 @@ import traceback
|
||||||
from photoapp.library import PhotoLibrary
|
from photoapp.library import PhotoLibrary
|
||||||
from photoapp.image import get_jpg_info, get_hash, get_mtime
|
from photoapp.image import get_jpg_info, get_hash, get_mtime
|
||||||
from itertools import chain
|
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
|
import os
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Photo sorting rules:
|
Photo sorting rules:
|
||||||
|
|
||||||
|
@ -20,11 +21,6 @@ mov, video, or other
|
||||||
modification date
|
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):
|
def pprogress(done, total=None):
|
||||||
print(" complete: {}{}\r".format(done, " / {} ".format(total) if total else ''), end='')
|
print(" complete: {}{}\r".format(done, " / {} ".format(total) if total else ''), end='')
|
||||||
|
|
|
@ -7,6 +7,12 @@ import uuid
|
||||||
import enum
|
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):
|
class PhotoStatus(enum.Enum):
|
||||||
private = 0
|
private = 0
|
||||||
public = 1
|
public = 1
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import os
|
||||||
import cherrypy
|
import cherrypy
|
||||||
from photoapp.types import PhotoSet, PhotoStatus
|
from photoapp.types import PhotoSet, PhotoStatus
|
||||||
|
|
||||||
|
@ -15,6 +16,13 @@ def mime2ext(mime):
|
||||||
"video/quicktime": "mov"}[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():
|
def auth():
|
||||||
"""
|
"""
|
||||||
Return the currently authorized username (per request) or None
|
Return the currently authorized username (per request) or None
|
||||||
|
|
Loading…
Reference in New Issue