most of api upload

This commit is contained in:
dave 2019-06-25 21:34:32 -07:00
parent 506c6e9c9a
commit 2c1ebea31c
5 changed files with 113 additions and 48 deletions

View File

@ -1,6 +1,7 @@
import os import os
import cherrypy import cherrypy
import logging import logging
import json
from datetime import datetime, timedelta from datetime import datetime, timedelta
from photoapp.library import PhotoLibrary from photoapp.library import PhotoLibrary
from photoapp.types import Photo, PhotoSet, Tag, TagItem, PhotoStatus, User from photoapp.types import Photo, PhotoSet, Tag, TagItem, PhotoStatus, User
@ -29,7 +30,35 @@ class PhotosApiV1(object):
@cherrypy.expose @cherrypy.expose
def upload(self, files, meta): def upload(self, files, meta):
pass """
upload accepts one photoset (multiple images)
metadata format
"""
meta = json.loads(meta)
if type(files) != list:
files = [files]
for file in files:
print("File name:", file.filename)
import hashlib
sha = hashlib.sha256()
total = 0
while True:
b = file.file.read(1024)
if not b:
break
sha.update(b)
total += len(b)
print("Read length:", total)
print("Read sha256:", sha.hexdigest())
if str(file.filename) not in meta["files"].keys():
raise cherrypy.HTTPError(400, f"no mdatadata provided for filename '{file.filename}'")
print("we have metadata for this file:", meta["files"][file.filename])
print("____")
@cherrypy.expose @cherrypy.expose
@cherrypy.tools.json_out() @cherrypy.tools.json_out()

View File

@ -1,14 +1,19 @@
import os
import json
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.utils import get_extension
from photoapp.types import known_extensions from photoapp.types import known_extensions
from photoapp.common import pwhash from photoapp.common import pwhash
from photoapp.ingest import get_photosets
class PhotoApiClient(object): class PhotoApiClient(object):
def __init__(self, base_url): def __init__(self, base_url, passwd=None):
self.session = requests.Session() self.session = requests.Session()
if passwd:
self.session.auth = passwd # user, pass tuple
self.base_url = base_url self.base_url = base_url
def byhash(self, sha): def byhash(self, sha):
@ -23,8 +28,8 @@ class PhotoApiClient(object):
def delete(self, url, **params): def delete(self, url, **params):
return self.do("delete", url, **params) return self.do("delete", url, **params)
def do(self, method, url, **params): def do(self, method, url, **kwargs):
resp = getattr(self.session, method)(self.base_url + "/api/v1/" + url, **params) resp = getattr(self.session, method)(self.base_url + "/api/v1/" + url, **kwargs)
resp.raise_for_status() resp.raise_for_status()
return resp return resp
@ -38,10 +43,17 @@ class PhotoApiClient(object):
def delete_user(self, username): def delete_user(self, username):
return self.delete("user", params={"username": username}) return self.delete("user", params={"username": username})
def upload(self, files, metadata):
# print(">>>>>>", metadata)
return self.post("upload", files=files, data={"meta": json.dumps(metadata)})
def get_args(): def get_args():
parser = argparse.ArgumentParser(description="photo library cli") parser = argparse.ArgumentParser(description="photo library cli")
parser.add_argument("-s", "--host", required=True, help="photo library server address") # TODO nicer uri parser
parser.add_argument("--host", required=True, help="photo library server address")
parser.add_argument("--user", required=True)
parser.add_argument("--password", required=True)
sp_action = parser.add_subparsers(dest="action", help="action to take") sp_action = parser.add_subparsers(dest="action", help="action to take")
@ -50,8 +62,8 @@ def get_args():
p_dupes.add_argument("files", nargs="+", help="files to check") p_dupes.add_argument("files", nargs="+", help="files to check")
p_ingest = sp_action.add_parser("ingest", help="import images into the library") p_ingest = sp_action.add_parser("ingest", help="import images into the library")
p_ingest.add_argument("files", nargs="+", help="files to import")
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")
p_ingest.add_argument("files", nargs="+", help="files to import")
# User section # User section
p_adduser = sp_action.add_parser("user", help="user manipulation functions") p_adduser = sp_action.add_parser("user", help="user manipulation functions")
@ -73,7 +85,7 @@ def main():
args = get_args() args = get_args()
print(args) print(args)
client = PhotoApiClient(args.host) client = PhotoApiClient(args.host, (args.user, args.password, ))
if args.action == "checkdupes": if args.action == "checkdupes":
hashes = {} hashes = {}
@ -107,7 +119,25 @@ def main():
raise raise
elif args.action == "ingest": elif args.action == "ingest":
pass if args.copy_of:
raise NotImplementedError("--copy-of isn't implemented")
sets, skipped = get_photosets(args.files)
#TODO y/n confirmation and auto flag
#TODO optional progress printing
print("skipping:", skipped)
print("sets:", [[f.path for f in s.files] for s in sets])
for set_ in sets:
payload = set_.to_json()
payload["files"] = {os.path.basename(photo.path): photo.to_json() for photo in set_.files}
files = []
for file in set_.files:
files.append(("files", (os.path.basename(file.path), open(file.path, 'rb'), file.format), ))
client.upload(files, payload)
elif args.action == "user": elif args.action == "user":
if args.action_user == "create": if args.action_user == "create":

View File

@ -19,8 +19,8 @@ def get_jpg_info(fpath):
raise Exception("fuk") raise Exception("fuk")
# gps is set to 0,0 if unavailable # gps is set to 0,0 if unavailable
lat, lon = gps or [0, 0] lat, lon = gps or [None, None]
dimensions = dimensions or (0, 0) dimensions = dimensions or (None, None)
mime = magic.from_file(fpath, mime=True) mime = magic.from_file(fpath, mime=True)
size = os.path.getsize(fpath) size = os.path.getsize(fpath)

View File

@ -4,7 +4,7 @@ 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, known_extensions, regular_images, files_raw, files_video from photoapp.types import Photo, PhotoSet, known_extensions, regular_images, files_raw, files_video, map_extension
import os import os
@ -26,76 +26,74 @@ 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='')
def batch_ingest(library, files): def group_by_extension(files):
# group by extension
byext = {k: [] for k in known_extensions} byext = {k: [] for k in known_extensions}
excluded = []
total = len(files)
print("processing {} items".format(total))
print("Pre-sorting files")
for item in files: for item in files:
if not os.path.isfile(item): if not os.path.isfile(item): # Not a file
print("Skipping due to not a file: {}".format(item))
continue continue
extension = item.split(".") extension = item.split(".")
if len(extension) < 2: if len(extension) < 2: # no extension
print("Skipping due to no extension: {}".format(item)) excluded.append(item)
continue continue
extension = extension[-1].lower() extension = map_extension(extension[-1].lower())
if extension == "jpeg": if extension not in known_extensions: # an extension we don't support
extension = "jpg" excluded.append(item)
if extension not in known_extensions:
print("Skipping due to unknown extension: {}".format(item))
continue continue
byext[extension.lower()].append(item) byext[extension.lower()].append(item)
print("Scanning images") return (byext, excluded)
photos = []
def get_photosets(files):
byext, skipped = group_by_extension(files)
photosets = []
# process regular images first. # process regular images first.
for item in chain(*[byext[ext] for ext in regular_images]): for item in chain(*[byext[ext] for ext in regular_images]):
photos.append(get_jpg_info(item)) photosets.append(get_jpg_info(item))
pprogress(len(photos), total)
print("\nScanning RAWs")
# process raws # process raws
done = len(photos)
for item in chain(*[byext[ext] for ext in files_raw]): for item in chain(*[byext[ext] for ext in files_raw]):
itemmeta = Photo(hash=get_hash(item), path=item, size=os.path.getsize(item), itemmeta = Photo(hash=get_hash(item), path=item, size=os.path.getsize(item),
format=special_magic(item)) format=special_magic(item))
fprefix = os.path.basename(item)[::-1].split(".", 1)[-1][::-1] fprefix = os.path.basename(item)[::-1].split(".", 1)[-1][::-1]
fmatch = "{}.jpg".format(fprefix.lower()) fmatch = "{}.jpg".format(fprefix.lower()) # if we're inspecting "foobar.raw", match it with "foobar.jpg"
# TODO does this account for extension mappinh like jpeg->jpg?
foundmatch = False foundmatch = False
for photo in photos: for photo in photosets:
for fmt in photo.files[:]: for fmt in photo.files[:]:
if os.path.basename(fmt.path).lower() == fmatch: if os.path.basename(fmt.path).lower() == fmatch:
foundmatch = True foundmatch = True
photo.files.append(itemmeta) photo.files.append(itemmeta)
done += 1
pprogress(done, total)
break break
if foundmatch: if foundmatch:
break break
if not foundmatch: if not foundmatch:
mtime = get_mtime(item) mtime = get_mtime(item)
photos.append(PhotoSet(date=mtime, date_real=mtime, lat=0, lon=0, files=[itemmeta])) print("no match found for", itemmeta.path, "but importing anyway")
done += 1 photosets.append(PhotoSet(date=mtime, date_real=mtime, files=[itemmeta]))
pprogress(done, total) # TODO handle any xmp without an associated regular image or cr2
# TODO prune any xmp without an associated regular image or cr2
print("\nScanning other files") # process other known formats
# process all other formats
for item in chain(*[byext[ext] for ext in files_video]): for item in chain(*[byext[ext] for ext in files_video]):
itemmeta = Photo(hash=get_hash(item), path=item, size=os.path.getsize(item), itemmeta = Photo(hash=get_hash(item), path=item, size=os.path.getsize(item),
format=special_magic(item)) format=special_magic(item))
mtime = get_mtime(item) mtime = get_mtime(item)
photos.append(PhotoSet(date=mtime, date_real=mtime, lat=0, lon=0, files=[itemmeta])) photosets.append(PhotoSet(date=mtime, date_real=mtime, files=[itemmeta]))
done += 1
pprogress(done, total) return photosets, skipped
def batch_ingest(library, files):
sets, skipped = get_photosets(files)
print("\nUpdating database") print("\nUpdating database")
done = 0 done = 0
total = len(photos) total = len(sets)
for photoset in photos: for photoset in sets:
try: try:
library.add_photoset(photoset) library.add_photoset(photoset)
pprogress(done, total) pprogress(done, total)

View File

@ -11,6 +11,14 @@ known_extensions = ["jpg", "png", "cr2", "xmp", "mp4", "mov"]
regular_images = ["jpg", "png"] regular_images = ["jpg", "png"]
files_raw = ["cr2", "xmp"] files_raw = ["cr2", "xmp"]
files_video = ["mp4", "mov"] files_video = ["mp4", "mov"]
mapped_extensions = {"jpg": {"jpeg", }} # target: aliases
def map_extension(ext):
for target, aliases in mapped_extensions.items():
if ext in aliases:
return target
return ext
class PhotoStatus(enum.Enum): class PhotoStatus(enum.Enum):
@ -41,8 +49,8 @@ class PhotoSet(Base):
def to_json(self): def to_json(self):
s = {attr: getattr(self, attr) for attr in {"uuid", "title", "description"}} s = {attr: getattr(self, attr) for attr in {"uuid", "title", "description"}}
s["lat"] = str(self.lat) s["lat"] = str(self.lat) if self.lat else None
s["lon"] = str(self.lon) s["lon"] = str(self.lon) if self.lon else None
s["date"] = self.date.isoformat() s["date"] = self.date.isoformat()
s["files"] = {i.uuid: i.to_json() for i in self.files} s["files"] = {i.uuid: i.to_json() for i in self.files}
s["tags"] = [t.name for t in self.tags] s["tags"] = [t.name for t in self.tags]