added --export-as-hardlink option

This commit is contained in:
britiscurious
2020-05-08 21:13:50 +02:00
parent 7444b6d173
commit 5eb0876e33
4 changed files with 57 additions and 5 deletions

View File

@@ -89,7 +89,7 @@ class ExportCommand(click.Command):
)
formatter.write("\n")
formatter.write_text(
"The templating system may also be used with the --keyword-template option "
"The templating system may also be used with the --keyword-template option "
+ "to set keywords on export (with --exiftool or --sidecar), "
+ "for example, to set a new keyword in format 'folder/subfolder/album' to "
+ 'preserve the folder/album structure, you can use --keyword-template "{folder_album}"'
@@ -860,6 +860,11 @@ def query(
@DB_OPTION
@click.option("--verbose", "-V", is_flag=True, help="Print verbose output.")
@query_options
@click.option(
"--export-as-hardlink",
is_flag=True,
help="Hardlink files instead of copying them. ",
)
@click.option(
"--overwrite",
is_flag=True,
@@ -1004,6 +1009,7 @@ def export(
from_date,
to_date,
verbose,
export_as_hardlink,
overwrite,
export_by_date,
skip_edited,
@@ -1195,6 +1201,7 @@ def export(
verbose,
export_by_date,
sidecar,
export_as_hardlink,
overwrite,
export_edited,
original_name,
@@ -1216,6 +1223,7 @@ def export(
verbose,
export_by_date,
sidecar,
export_as_hardlink,
overwrite,
export_edited,
original_name,
@@ -1605,6 +1613,7 @@ def export_photo(
verbose,
export_by_date,
sidecar,
export_as_hardlink,
overwrite,
export_edited,
original_name,
@@ -1624,6 +1633,7 @@ def export_photo(
verbose: boolean; print verbose output
export_by_date: boolean; create export folder in form dest/YYYY/MM/DD
sidecar: list zero, 1 or 2 of ["json","xmp"] of sidecar variety to export
export_as_hardlink: boolean; hardlink files instead of copying them
overwrite: boolean; overwrite dest file if it already exists
original_name: boolean; use original filename instead of current filename
export_live: boolean; also export live video component if photo is a live photo
@@ -1715,6 +1725,7 @@ def export_photo(
sidecar_xmp=sidecar_xmp,
live_photo=export_live,
raw_photo=export_raw,
export_as_hardlink=export_as_hardlink,
overwrite=overwrite,
use_photos_export=use_photos_export,
exiftool=exiftool,
@@ -1752,6 +1763,7 @@ def export_photo(
edited_name,
sidecar_json=sidecar_json,
sidecar_xmp=sidecar_xmp,
export_as_hardlink=export_as_hardlink,
overwrite=overwrite,
edited=True,
use_photos_export=use_photos_export,

View File

@@ -40,6 +40,7 @@ from .template import (
TEMPLATE_SUBSTITUTIONS_MULTI_VALUED,
)
from .utils import (
_hardlink_file,
_copy_file,
_export_photo_uuid_applescript,
_get_resource_loc,
@@ -635,6 +636,7 @@ class PhotoInfo:
edited=False,
live_photo=False,
raw_photo=False,
export_as_hardlink=False,
overwrite=False,
increment=True,
sidecar_json=False,
@@ -660,6 +662,7 @@ class PhotoInfo:
(or raise exception if no edited version)
live_photo: (boolean, default=False); if True, will also export the associted .mov for live photos
raw_photo: (boolean, default=False); if True, will also export the associted RAW photo
export_as_hardlink: (boolean, default=False); if True, will hardlink files instead of copying them
overwrite: (boolean, default=False); if True will overwrite files if they alreay exist
increment: (boolean, default=True); if True, will increment file name until a non-existant name is found
if overwrite=False and increment=False, export will fail if destination file already exists
@@ -802,7 +805,10 @@ class PhotoInfo:
)
# copy the file, _copy_file uses ditto to preserve Mac extended attributes
_copy_file(src, dest, norsrc=no_xattr)
if export_as_hardlink:
_hardlink_file(src, dest)
else:
_copy_file(src, dest, norsrc=no_xattr)
exported_files.append(str(dest))
# copy live photo associated .mov if requested
@@ -814,7 +820,10 @@ class PhotoInfo:
logging.debug(
f"Exporting live photo video of {filename} as {live_name.name}"
)
_copy_file(src_live, str(live_name), norsrc=no_xattr)
if export_as_hardlink:
_hardlink_file(src_live, str(live_name))
else:
_copy_file(src_live, str(live_name), norsrc=no_xattr)
exported_files.append(str(live_name))
else:
logging.warning(f"Skipping missing live movie for {filename}")
@@ -828,7 +837,10 @@ class PhotoInfo:
logging.debug(
f"Exporting RAW photo of {filename} as {raw_name.name}"
)
_copy_file(str(raw_path), str(raw_name), norsrc=no_xattr)
if export_as_hardlink:
_hardlink_file(str(raw_path), str(raw_name))
else:
_copy_file(str(raw_path), str(raw_name), norsrc=no_xattr)
exported_files.append(str(raw_name))
else:
logging.warning(f"Skipping missing RAW photo for {filename}")

View File

@@ -118,6 +118,32 @@ def _dd_to_dms(dd):
return int(deg_), int(min_), sec_
def _hardlink_file(src, dest):
""" Hardlinks a file from src path to dest path
src: source path as string
dest: destination path as string
Raises exception if linking fails or either path is None """
if src is None or dest is None:
raise ValueError("src and dest must not be None", src, dest)
if not os.path.isfile(src):
raise FileNotFoundError("src file does not appear to exist", src)
command = ["ln", src, dest]
# if error on copy, subprocess will raise CalledProcessError
try:
result = subprocess.run(command, check=True, stderr=subprocess.PIPE)
except subprocess.CalledProcessError as e:
logging.critical(
f"ln returned error: {e.returncode} {e.stderr.decode(sys.getfilesystemencoding()).rstrip()}"
)
raise e
return result.returncode
def _copy_file(src, dest, norsrc=False):
""" Copies a file from src path to dest path
src: source path as string