Created
October 13, 2016 01:53
-
-
Save lukas-hetzenecker/9925f7f57e891dc1aa7b8ff02a5c46e0 to your computer and use it in GitHub Desktop.
Export marked photos to Synology PhotoStation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
import re | |
import os | |
import pickle | |
from libxmp import XMPFiles, consts | |
import glob | |
import sys | |
import argparse | |
#BASE_DIR = '/var/services/homes/lukas/photos/' | |
#BASE_DIR = '/files/photos/' | |
KNOWN_FILTERS = ('all', 'tag', 'color', 'filetype') | |
parser = argparse.ArgumentParser() | |
parser.add_argument("base", help="Base directory") | |
args = parser.parse_args() | |
#### | |
# http://stackoverflow.com/questions/431684/how-do-i-cd-in-python | |
from contextlib import contextmanager | |
import os | |
@contextmanager | |
def cd(newdir): | |
prevdir = os.getcwd() | |
os.chdir(os.path.expanduser(newdir)) | |
try: | |
yield | |
finally: | |
os.chdir(prevdir) | |
#### | |
if os.path.isfile('metadata_cache.p'): | |
old_metadata_cache = pickle.load(open("metadata_cache.p", "rb")) | |
else: | |
old_metadata_cache = {} | |
metadata_cache = {} | |
def insensitive_iglob(pattern): | |
def either(c): | |
return '[%s%s]'%(c.lower(),c.upper()) if c.isalpha() else c | |
return glob.iglob(''.join(map(either,pattern))) | |
def metadata(filepath): | |
if filepath not in metadata_cache: | |
size = os.path.getsize(filepath) | |
cached_size, color, tags = old_metadata_cache.get(filepath, (None, None, None)) | |
if cached_size != size: | |
xmpfile = XMPFiles( file_path=filepath, open_forupdate=False) | |
xmp = xmpfile.get_xmp() | |
if xmp is not None: | |
color = xmp.get_property(consts.XMP_NS_XMP, 'Label').lower() if xmp.does_property_exist (consts.XMP_NS_XMP, 'Label') else None | |
tags = [xmp.get_array_item(consts.XMP_NS_DC, 'subject', i).lower() for i in range(1, xmp.count_array_items(consts.XMP_NS_DC, 'subject')+1)] | |
else: | |
color, tags = None, [] | |
xmpfile.close_file() | |
metadata_cache[filepath] = (size, color, tags) | |
return metadata_cache[filepath] | |
def check_filters(filepath, pos_filters, neg_filters): | |
if not pos_filters: | |
return False | |
if not neg_filters and len(pos_filters) == 1 and pos_filters[0][0] == 'all': | |
return True | |
size, color, tags = metadata(filepath) | |
filetype = os.path.splitext(filepath)[1] | |
for key, value in neg_filters: | |
if key not in KNOWN_FILTERS: | |
print("Unknown filter: %s" % key) | |
sys.exit(1) | |
if key == 'filetype' and value == filetype: | |
return False | |
if key == 'tag' and value in tags: | |
return False | |
if key == 'color' and value == color: | |
return False | |
for key, value in pos_filters: | |
if key not in KNOWN_FILTERS: | |
print("Unknown filter: %s" % key) | |
sys.exit(1) | |
if key == 'all': | |
return True | |
if key == 'filetype' and value == filetype: | |
return True | |
if key == 'tag' and value in tags: | |
return True | |
if key == 'color' and value == color: | |
return True | |
return False | |
exportes_pictures = [] | |
all_destinations = set() | |
for line in open(args.base + '/export', 'r').readlines(): | |
line = line.strip() | |
line = re.sub('\t+', '\t', line) | |
if line.startswith('#'): | |
continue | |
sources, destinations, filters = line.split('\t') | |
sources = re.findall(r'\"(.+?)\"', sources) | |
destinations = re.findall(r'\"(.+?)\"', destinations) | |
filters = re.findall(r"(\+|\-)?(\S+):(\S*)", filters) | |
all_destinations.update(destinations) | |
pos_filters = [(key.lower(), value.lower()) for modifier, key, value in filters if modifier in ('+', '')] | |
neg_filters = [(key.lower(), value.lower()) for modifier, key, value in filters if modifier in ('-',)] | |
for source in sources: | |
full_source = args.base + source | |
for filepath in insensitive_iglob(full_source): | |
if check_filters(filepath, pos_filters, neg_filters): | |
exportes_pictures.append((filepath, destinations)) | |
with cd('../../../photo'): | |
for picture, destinations in exportes_pictures: | |
for destination in destinations: | |
os.makedirs(destination, exist_ok=True) | |
with cd(destination): | |
link_path = os.sep.join(picture[len(args.base):].split(os.sep)[1:]) | |
parent_folder = os.path.dirname(link_path) or '.' | |
picture_filename = os.path.basename(link_path) | |
os.makedirs(parent_folder, exist_ok=True) | |
with cd(parent_folder): | |
if not os.path.islink(picture_filename): | |
print("CREATE %s -> %s" % (picture, picture_filename)) | |
os.symlink(picture, picture_filename) | |
exported_paths = set([picture for picture, destinations in exportes_pictures]) | |
for destination in all_destinations: | |
for root, directories, filenames in os.walk(destination): | |
if '@eaDir' in root: | |
continue | |
for filename in filenames: | |
pathname = os.path.join(root, filename) | |
if os.path.islink(pathname) and os.readlink(pathname) not in exported_paths: | |
print('REMOVE ', pathname, os.readlink(pathname)) | |
os.unlink(pathname) | |
pickle.dump(metadata_cache, open("metadata_cache.p", "wb")) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment