yrasdasda
This commit is contained in:
parent
1a8d1db86c
commit
95faa0c0ff
114
config/scripts/camera_name_date
Executable file
114
config/scripts/camera_name_date
Executable file
|
@ -0,0 +1,114 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
"""
|
||||||
|
Same as picture_name_date
|
||||||
|
except it's tailored for OpenCamera.
|
||||||
|
It uses filenames, that way:
|
||||||
|
- Easier to get metadata
|
||||||
|
- JPG/DNG, MP4/SRT keep the same filename
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
|
||||||
|
import coloredlogs
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
coloredlogs.install(level="DEBUG", fmt="%(levelname)s %(message)s", logger=log)
|
||||||
|
|
||||||
|
PATTERNS = [
|
||||||
|
re.compile( # OpenCamera
|
||||||
|
r"^(?P<type>IMG|VID)_"
|
||||||
|
# No TRIM. We don't want those as they're a short copy of an existing VID.
|
||||||
|
r"(?P<Y>\d\d\d\d)(?P<M>\d\d)(?P<D>\d\d)_"
|
||||||
|
r"(?P<h>\d\d)(?P<m>\d\d)(?P<s>\d\d)"
|
||||||
|
r"(?P<dup>_(\d+))?"
|
||||||
|
r"(?P<spec>_(PANO|HDR))?"
|
||||||
|
r"\.(?P<ext>jpg|dng|mp4|srt)$"
|
||||||
|
),
|
||||||
|
re.compile( # Samsung camera app (?)
|
||||||
|
r"(?P<Y>\d\d\d\d)(?P<M>\d\d)(?P<D>\d\d)_"
|
||||||
|
r"(?P<h>\d\d)(?P<m>\d\d)(?P<s>\d\d)"
|
||||||
|
r"\.(?P<ext>jpg|mp4)$"
|
||||||
|
),
|
||||||
|
re.compile( # Telegram Media Downloader photo
|
||||||
|
r"photo_"
|
||||||
|
r"(?P<Y>\d\d\d\d)-(?P<M>\d\d)-(?P<D>\d\d)_"
|
||||||
|
r"(?P<h>\d\d)-(?P<m>\d\d)-(?P<s>\d\d)"
|
||||||
|
r"(?P<dup>_\d{19})"
|
||||||
|
r"\.(?P<ext>jpg)$"
|
||||||
|
), # Time of publication, not time of photo!
|
||||||
|
re.compile( # Telegram Media Downloader video
|
||||||
|
r"video_"
|
||||||
|
r"(?P<Y>\d\d\d\d)-(?P<M>\d\d)-(?P<D>\d\d)_"
|
||||||
|
r"(?P<h>\d\d)-(?P<m>\d\d)-(?P<s>\d\d)"
|
||||||
|
r"(?P<dup>_\d{19})"
|
||||||
|
r"\.(?P<ext>mp4)$"
|
||||||
|
), # Time of publication, not time of video!
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def main(args: argparse.Namespace) -> None:
|
||||||
|
for root, _, files in os.walk(args.dir):
|
||||||
|
for filename in files:
|
||||||
|
full_path = os.path.join(root, filename)
|
||||||
|
for pattern in PATTERNS:
|
||||||
|
match = re.match(pattern, filename)
|
||||||
|
if match:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
log.warning(f"{full_path} doesn't any pattern")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Build new filename
|
||||||
|
m = match.groupdict()
|
||||||
|
new_filename = (
|
||||||
|
f"{m['Y']}-{m['M']}-{m['D']}_"
|
||||||
|
f"{m['h']}-{m['m']}-{m['s']}"
|
||||||
|
f"{m.get('dup', '')}"
|
||||||
|
f"{m.get('spec', '')}"
|
||||||
|
f"{args.suffix}"
|
||||||
|
f".{m['ext']}"
|
||||||
|
)
|
||||||
|
new_path = os.path.join(args.dir, new_filename)
|
||||||
|
# TODO Allow keeping image in same folder
|
||||||
|
|
||||||
|
# Rename file
|
||||||
|
if full_path == new_path:
|
||||||
|
log.debug(f"{full_path} already at required filename")
|
||||||
|
continue
|
||||||
|
log.info(f"{full_path} →\t{new_path}")
|
||||||
|
if os.path.exists(new_path):
|
||||||
|
raise FileExistsError(f"{new_path} already exists!")
|
||||||
|
if not args.dry:
|
||||||
|
os.rename(full_path, new_path)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="Rename OpenCamera files based on their dates"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"dir",
|
||||||
|
metavar="DIRECTORY",
|
||||||
|
type=str,
|
||||||
|
default=".",
|
||||||
|
nargs="?",
|
||||||
|
help="Directory containing the pictures",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-d",
|
||||||
|
"--dry",
|
||||||
|
action="store_true",
|
||||||
|
help="Do not actually rename, just show old and new path",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-s",
|
||||||
|
"--suffix",
|
||||||
|
default="",
|
||||||
|
help="Text to add before the extension",
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
main(args)
|
|
@ -1,20 +1,22 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import datetime
|
||||||
|
import logging
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import typing
|
import typing
|
||||||
import datetime
|
|
||||||
|
|
||||||
import PIL.ExifTags
|
import coloredlogs
|
||||||
import PIL.Image
|
import exifread
|
||||||
import progressbar
|
import progressbar
|
||||||
|
|
||||||
EXTENSION_PATTERN = re.compile(r"\.JPE?G", re.I)
|
log = logging.getLogger(__name__)
|
||||||
|
coloredlogs.install(level="DEBUG", fmt="%(levelname)s %(message)s", logger=log)
|
||||||
|
|
||||||
|
EXTENSION_PATTERN = re.compile(r"\.(JPE?G|DNG)", re.I)
|
||||||
COMMON_PATTERN = re.compile(r"(IMG|DSC[NF]?|100|P10|f|t)_?\d+", re.I)
|
COMMON_PATTERN = re.compile(r"(IMG|DSC[NF]?|100|P10|f|t)_?\d+", re.I)
|
||||||
EXIF_TAG_NAME = "DateTimeOriginal"
|
EXIF_TAG_ID = 0x9003 # DateTimeOriginal
|
||||||
EXIF_TAG_ID = list(PIL.ExifTags.TAGS.keys())[
|
|
||||||
list(PIL.ExifTags.TAGS.values()).index(EXIF_TAG_NAME)
|
|
||||||
]
|
|
||||||
EXIF_DATE_FORMAT = "%Y:%m:%d %H:%M:%S"
|
EXIF_DATE_FORMAT = "%Y:%m:%d %H:%M:%S"
|
||||||
|
|
||||||
|
|
||||||
|
@ -23,7 +25,6 @@ def get_pictures(directory: str = ".", skip_renamed: bool = True) -> typing.Gene
|
||||||
for filename in files:
|
for filename in files:
|
||||||
filename_trunk, extension = os.path.splitext(filename)
|
filename_trunk, extension = os.path.splitext(filename)
|
||||||
|
|
||||||
# if extension.upper() not in ('.JPEG', '.JPG'):
|
|
||||||
if not re.match(EXTENSION_PATTERN, extension):
|
if not re.match(EXTENSION_PATTERN, extension):
|
||||||
continue
|
continue
|
||||||
if skip_renamed:
|
if skip_renamed:
|
||||||
|
@ -33,23 +34,85 @@ def get_pictures(directory: str = ".", skip_renamed: bool = True) -> typing.Gene
|
||||||
yield full_path
|
yield full_path
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main(args: argparse.Namespace) -> None:
|
||||||
print("Counting files...")
|
log.warning("Counting files...")
|
||||||
nb_imgs = len(list(get_pictures()))
|
kwargs = {"directory": args.dir, "skip_renamed": args.skip_renamed}
|
||||||
print("Processing files...")
|
log.warning("Processing files...")
|
||||||
iterator = progressbar.progressbar(get_pictures(), max_value=nb_imgs)
|
if args.hide_bar:
|
||||||
|
iterator = get_pictures(**kwargs)
|
||||||
|
else:
|
||||||
|
nb_imgs = len(list(get_pictures(**kwargs)))
|
||||||
|
iterator = progressbar.progressbar(get_pictures(**kwargs), max_value=nb_imgs)
|
||||||
for full_path in iterator:
|
for full_path in iterator:
|
||||||
img = PIL.Image.open(full_path)
|
# Find date
|
||||||
exif_data = img._getexif()
|
with open(full_path, 'rb') as fd:
|
||||||
if exif_data and EXIF_TAG_ID in exif_data:
|
exif_data = exifread.process_file(fd)
|
||||||
date_raw = exif_data[EXIF_TAG_ID]
|
if not exif_data:
|
||||||
date = datetime.datetime.strptime(date_raw, EXIF_DATE_FORMAT)
|
log.warning(f"{full_path} does not have EXIF data")
|
||||||
new_name = date.isoformat().replace(":", "-") + ".jpg" # For NTFS
|
for ifd_tag in exif_data.values():
|
||||||
print(full_path, new_name)
|
if ifd_tag.tag == EXIF_TAG_ID:
|
||||||
os.rename(full_path, new_name) # TODO FOLDER
|
date_raw = ifd_tag.values
|
||||||
img.close()
|
break
|
||||||
|
else:
|
||||||
|
log.warning(f"{full_path} does not have required EXIF tag")
|
||||||
|
continue
|
||||||
|
date = datetime.datetime.strptime(date_raw, EXIF_DATE_FORMAT)
|
||||||
|
|
||||||
|
# Determine new filename
|
||||||
|
ext = os.path.splitext(full_path)[1].lower()
|
||||||
|
if ext == '.jpeg':
|
||||||
|
ext = '.jpg'
|
||||||
|
new_name = date.isoformat().replace(":", "-").replace("T", "_")
|
||||||
|
# First substitution is to allow images being sent to a NTFS filesystem
|
||||||
|
# Second substitution is for esthetics
|
||||||
|
new_path = os.path.join(args.dir, f"{new_name}{ext}")
|
||||||
|
# TODO Allow keeping image in same folder
|
||||||
|
i = 0
|
||||||
|
while os.path.exists(new_path):
|
||||||
|
if full_path == new_path:
|
||||||
|
break
|
||||||
|
log.debug(f"{full_path} already exists, incrementing")
|
||||||
|
i += 1
|
||||||
|
new_path = os.path.join(args.dir, f"{new_name}_{i}{ext}")
|
||||||
|
|
||||||
|
# Rename file
|
||||||
|
if full_path == new_path:
|
||||||
|
log.debug(f"{full_path} already at required filename")
|
||||||
|
continue
|
||||||
|
log.info(f"{full_path} →\t{new_path}")
|
||||||
|
if os.path.exists(new_path):
|
||||||
|
raise FileExistsError(f"Won't overwrite {new_path}")
|
||||||
|
if not args.dry:
|
||||||
|
os.rename(full_path, new_path)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# TODO Arguments parsing
|
parser = argparse.ArgumentParser(description="Rename images based on their dates")
|
||||||
main()
|
parser.add_argument(
|
||||||
|
"dir",
|
||||||
|
metavar="DIRECTORY",
|
||||||
|
type=str,
|
||||||
|
default=".",
|
||||||
|
nargs="?",
|
||||||
|
help="Directory containing the pictures",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-d",
|
||||||
|
"--dry",
|
||||||
|
action="store_true",
|
||||||
|
help="Do not actually rename, just show old and new path",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-s",
|
||||||
|
"--skip-renamed",
|
||||||
|
action="store_true",
|
||||||
|
help="Skip images whose filename doesn't match usual camera output filenames.",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-b",
|
||||||
|
"--hide-bar",
|
||||||
|
action="store_true",
|
||||||
|
help="Do not show a progress bar. Also skip counting images",
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
main(args)
|
||||||
|
|
68
config/scripts/raw_move_precomp
Executable file
68
config/scripts/raw_move_precomp
Executable file
|
@ -0,0 +1,68 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
"""
|
||||||
|
Same as picture_name_date
|
||||||
|
except it's tailored for OpenCamera.
|
||||||
|
It uses filenames, that way:
|
||||||
|
- Easier to get metadata
|
||||||
|
- JPG/DNG, MP4/SRT keep the same filename
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
|
import coloredlogs
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
coloredlogs.install(level="DEBUG", fmt="%(levelname)s %(message)s", logger=log)
|
||||||
|
|
||||||
|
RAW_EXTENSIONS = [".dng"]
|
||||||
|
PRECOMP_EXTENSIONS = [".jpg", ".jpeg"]
|
||||||
|
PRECOMP_DIRECTORY = ".precomp"
|
||||||
|
|
||||||
|
|
||||||
|
def main(args: argparse.Namespace) -> None:
|
||||||
|
for root, _, files in os.walk(args.dir):
|
||||||
|
for filename in files:
|
||||||
|
raw_path = os.path.join(root, filename)
|
||||||
|
basename, raw_ext = os.path.splitext(filename)
|
||||||
|
if raw_ext.lower() not in RAW_EXTENSIONS:
|
||||||
|
log.debug(f"{raw_path} isn't a RAW file")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# TODO Search for upper case extension
|
||||||
|
for precomp_ext in PRECOMP_EXTENSIONS:
|
||||||
|
precomp_filename = basename + precomp_ext
|
||||||
|
precomp_path = os.path.join(root, precomp_filename)
|
||||||
|
if not os.path.exists(precomp_path):
|
||||||
|
continue
|
||||||
|
precomp_dir = os.path.join(root, PRECOMP_DIRECTORY)
|
||||||
|
precomp_dest = os.path.join(precomp_dir, precomp_filename)
|
||||||
|
log.info(f"{precomp_path} -> {precomp_dest} because of {raw_path}")
|
||||||
|
if not args.dry:
|
||||||
|
os.makedirs(precomp_dir, exist_ok=True)
|
||||||
|
os.rename(precomp_path, precomp_dest)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="Move pre-composed JPEG to a directory "
|
||||||
|
"when a matching raw picture is found"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"dir",
|
||||||
|
metavar="DIRECTORY",
|
||||||
|
type=str,
|
||||||
|
default=".",
|
||||||
|
nargs="?",
|
||||||
|
help="Directory containing the pictures",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-d",
|
||||||
|
"--dry",
|
||||||
|
action="store_true",
|
||||||
|
help="Do not actually rename, just show old and new path",
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
main(args)
|
|
@ -50,6 +50,7 @@ if __name__ == "__main__":
|
||||||
|
|
||||||
parser.add_argument("-f", "--from", env_var="FROM")
|
parser.add_argument("-f", "--from", env_var="FROM")
|
||||||
parser.add_argument("-t", "--to", env_var="TO")
|
parser.add_argument("-t", "--to", env_var="TO")
|
||||||
|
parser.add_argument("-T", "--reply-to", env_var="REPLYTO")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-j",
|
"-j",
|
||||||
"--subject",
|
"--subject",
|
||||||
|
@ -77,6 +78,8 @@ if __name__ == "__main__":
|
||||||
setattr(args, "from", args.sender)
|
setattr(args, "from", args.sender)
|
||||||
if args.to is None:
|
if args.to is None:
|
||||||
args.to = args.receiver[0]
|
args.to = args.receiver[0]
|
||||||
|
if args.reply_to is None:
|
||||||
|
args.reply_to = args.to
|
||||||
if args.password:
|
if args.password:
|
||||||
password = args.password
|
password = args.password
|
||||||
args.password = "********"
|
args.password = "********"
|
||||||
|
@ -98,6 +101,7 @@ XJS*C4JDBQADN1.NSBN3*2IDNEN*GTUBE-STANDARD-ANTI-UBE-TEST-EMAIL*C.34X"""
|
||||||
From: {args.me} <{getattr(args, 'from')}>
|
From: {args.me} <{getattr(args, 'from')}>
|
||||||
Subject: {args.subject}
|
Subject: {args.subject}
|
||||||
To: {args.to}
|
To: {args.to}
|
||||||
|
Reply-To: {args.reply_to}
|
||||||
Message-ID: {mid}
|
Message-ID: {mid}
|
||||||
|
|
||||||
Hello there,
|
Hello there,
|
||||||
|
|
Loading…
Reference in a new issue