Compare commits
11 commits
d441c69fef
...
31b8047381
| Author | SHA1 | Date | |
|---|---|---|---|
| 31b8047381 | |||
| 98c59bce13 | |||
| 487302ea30 | |||
| aef467dfdd | |||
| ad072fb39b | |||
| 6f1cc8862f | |||
| 8b6054511b | |||
| 6016966be4 | |||
| a4a80e0df9 | |||
| d97b32f250 | |||
| d02caa5124 |
9 changed files with 186 additions and 131 deletions
49
autocat.py
49
autocat.py
|
|
@ -1,23 +1,42 @@
|
|||
import glob
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
if len(sys.argv) < 3:
|
||||
raise ValueError()
|
||||
from voussoirkit import betterhelp
|
||||
from voussoirkit import ffmpegtools
|
||||
from voussoirkit import pathclass
|
||||
from voussoirkit import vlogging
|
||||
from voussoirkit import winglob
|
||||
|
||||
output_filename = sys.argv.pop(-1)
|
||||
patterns = sys.argv[1:]
|
||||
log = vlogging.getLogger(__name__, 'autocat')
|
||||
|
||||
names = [name for pattern in patterns for name in glob.glob(pattern)]
|
||||
names = [os.path.abspath(x) for x in names]
|
||||
cat_lines = [f'file \'{x}\'' for x in names]
|
||||
cat_text = '\n'.join(cat_lines)
|
||||
cat_file = tempfile.TemporaryFile('w', encoding='utf-8', delete=False)
|
||||
cat_file.write(cat_text)
|
||||
cat_file.close()
|
||||
def autocat_argparse(args):
|
||||
if len(args.names) < 3:
|
||||
raise ValueError('Should have at least three arguments: two inputs and one output.')
|
||||
|
||||
cmd = f'ffmpeg -f concat -safe 0 -i {cat_file.name} -map 0:v? -map 0:a? -map 0:s? -c copy "{output_filename}"'
|
||||
os.system(cmd)
|
||||
output_file = pathclass.Path(args.names.pop(-1))
|
||||
patterns = args.names
|
||||
|
||||
os.remove(cat_file.name)
|
||||
input_files = list(pathclass.glob_many_files(patterns))
|
||||
output_file = ffmpegtools.concatenate(input_files, output_file)
|
||||
return 0
|
||||
|
||||
@vlogging.main_decorator
|
||||
def main(argv):
|
||||
parser = argparse.ArgumentParser(
|
||||
description='''
|
||||
''',
|
||||
)
|
||||
parser.add_argument(
|
||||
'names',
|
||||
nargs='+',
|
||||
help='''
|
||||
''',
|
||||
)
|
||||
parser.set_defaults(func=autocat_argparse)
|
||||
|
||||
return betterhelp.go(parser, argv)
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main(sys.argv[1:]))
|
||||
|
|
|
|||
21
autocat_sendto.py
Normal file
21
autocat_sendto.py
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import send2trash
|
||||
import sys
|
||||
|
||||
from voussoirkit import pathclass
|
||||
from voussoirkit import ffmpegtools
|
||||
from voussoirkit import stringtools
|
||||
from voussoirkit import timetools
|
||||
|
||||
if len(sys.argv) < 3:
|
||||
raise ValueError()
|
||||
|
||||
input_files = sys.argv[1:]
|
||||
input_files = [pathclass.Path(p) for p in input_files]
|
||||
input_files.sort(key=lambda f: stringtools.natural_sorter(f.normcase))
|
||||
first_file = pathclass.Path(input_files[0])
|
||||
output_file = first_file.parent.with_child(first_file.replace_extension('').basename + '_' + str(int(timetools.now().timestamp()))).add_extension(first_file.extension)
|
||||
|
||||
ffmpegtools.concatenate(input_files, output_file)
|
||||
|
||||
for file in input_files:
|
||||
send2trash.send2trash(file.absolute_path)
|
||||
3
cup.py
3
cup.py
|
|
@ -1,6 +1,5 @@
|
|||
import pyperclip
|
||||
|
||||
from voussoirkit import pipeable
|
||||
|
||||
text = pipeable.input('!i', split_lines=False)
|
||||
pyperclip.copy(text)
|
||||
pyperclip.copy(text.encode('utf-8', 'replace').decode())
|
||||
|
|
|
|||
|
|
@ -4,19 +4,24 @@ import sys
|
|||
|
||||
from voussoirkit import betterhelp
|
||||
from voussoirkit import pathclass
|
||||
from voussoirkit import pipeable
|
||||
|
||||
def namedpython_argparse(args):
|
||||
def named_python(name):
|
||||
this_python = pathclass.Path(sys.executable)
|
||||
|
||||
name = name.strip()
|
||||
# If this is running via another named python, we'll cut off the dash first.
|
||||
base = this_python.replace_extension('').basename.split('-', 1)[0]
|
||||
name = args.name.strip()
|
||||
extension = this_python.extension.with_dot
|
||||
named_python = this_python.parent.with_child(f'{base}-{name}{extension}')
|
||||
if named_python.exists:
|
||||
return 0
|
||||
return named_python
|
||||
|
||||
os.link(this_python.absolute_path, named_python.absolute_path)
|
||||
print(named_python.absolute_path)
|
||||
return named_python
|
||||
|
||||
def namedpython_argparse(args):
|
||||
exe = named_python(args.name)
|
||||
pipeable.stdout(exe.absolute_path)
|
||||
return 0
|
||||
|
||||
def main(argv):
|
||||
|
|
|
|||
|
|
@ -21,8 +21,9 @@ def makename(file, read_exif=False, read_mtime=False, minus_duration=False):
|
|||
final_pattern = r'^(\d\d\d\d)-(\d\d)-(\d\d)_(\d\d)-(\d\d)-(\d\d)(?:x\d+)?$'
|
||||
# Already optimized filenames need not apply
|
||||
# This is also important when the filename and the exif disagree
|
||||
if re.match(final_pattern, old) and not read_exif:
|
||||
return file
|
||||
if re.match(final_pattern, old) and not read_exif and not read_mtime:
|
||||
# return file
|
||||
pass
|
||||
|
||||
# Microsoft ICE
|
||||
new = re.sub(
|
||||
|
|
@ -153,12 +154,9 @@ def makename(file, read_exif=False, read_mtime=False, minus_duration=False):
|
|||
# Especially in cases where the user has edited a photo with software that
|
||||
# reset the exif but the filename refers to the original date.
|
||||
# I'm sure cases could be made either way but I'm starting here.
|
||||
if new == old and read_exif and file.extension in {'jpg', 'jpeg'}:
|
||||
if new == old and read_exif and file.extension in {'jpg', 'jpeg', 'dng'}:
|
||||
new = makename_exif(file, old)
|
||||
|
||||
if new == old and re.match(final_pattern, new):
|
||||
return file
|
||||
|
||||
if new == old and read_mtime:
|
||||
mtime = file.stat.st_mtime
|
||||
if minus_duration:
|
||||
|
|
@ -166,6 +164,9 @@ def makename(file, read_exif=False, read_mtime=False, minus_duration=False):
|
|||
date = datetime.datetime.fromtimestamp(mtime)
|
||||
new = date.strftime('%Y-%m-%d_%H-%M-%S')
|
||||
|
||||
if new == old:
|
||||
return file
|
||||
|
||||
new = file.parent.with_child(new).add_extension(file.extension)
|
||||
return new
|
||||
|
||||
|
|
|
|||
27
q.py
27
q.py
|
|
@ -2,6 +2,7 @@ import argparse
|
|||
import hashlib
|
||||
import os
|
||||
import re
|
||||
import send2trash
|
||||
import sys
|
||||
import time
|
||||
import traceback
|
||||
|
|
@ -46,19 +47,20 @@ def filter_collaborate(files, collaborate):
|
|||
return newfiles
|
||||
|
||||
def get_extension_command(extension):
|
||||
for (key, (command, argument)) in qcommands.EXTENSION_COMMANDS.items():
|
||||
for (key, qcommand) in qcommands.EXTENSION_COMMANDS.items():
|
||||
if isinstance(key, str):
|
||||
if key == extension:
|
||||
return (command, argument)
|
||||
return qcommand
|
||||
continue
|
||||
match = re.match(key, extension)
|
||||
if not match:
|
||||
continue
|
||||
groups = match.groups()
|
||||
if not groups:
|
||||
return (command, argument)
|
||||
command = re.sub(key, command, extension)
|
||||
return (command, argument)
|
||||
return qcommand
|
||||
qcommand = qcommand.copy()
|
||||
qcommand['command'] = re.sub(key, qcommand['command'], extension)
|
||||
return qcommand
|
||||
|
||||
def handle_blacklist(file, reason=''):
|
||||
if reason:
|
||||
|
|
@ -136,16 +138,18 @@ def process_file(file, args=None):
|
|||
|
||||
commands = []
|
||||
|
||||
if file.size > 0:
|
||||
qcommand = get_extension_command(extension)
|
||||
command = qcommand['command']
|
||||
argument = qcommand['argument']
|
||||
|
||||
if file.size > 0 and qcommand.get('read_file', True):
|
||||
links = read_file_links(file)
|
||||
(command, argument) = get_extension_command(extension)
|
||||
commands.extend(f'{command} "{link}"' for link in links)
|
||||
|
||||
else:
|
||||
(command, argument) = get_extension_command(extension)
|
||||
base = file.replace_extension('').basename
|
||||
argument = argument.format(id=base)
|
||||
commands.append(f'{command} {argument}')
|
||||
argument = argument.format(id=base, abspath=file.absolute_path)
|
||||
commands.append(f'{command} -- {argument}')
|
||||
|
||||
exit_code = 0
|
||||
|
||||
|
|
@ -159,7 +163,8 @@ def process_file(file, args=None):
|
|||
|
||||
if exit_code == 0:
|
||||
try:
|
||||
os.remove(file)
|
||||
# os.remove(file)
|
||||
send2trash.send2trash(file)
|
||||
except FileNotFoundError:
|
||||
# Race condition
|
||||
pass
|
||||
|
|
|
|||
166
rarpar.py
166
rarpar.py
|
|
@ -11,6 +11,7 @@ from voussoirkit import betterhelp
|
|||
from voussoirkit import bytestring
|
||||
from voussoirkit import operatornotify
|
||||
from voussoirkit import pathclass
|
||||
from voussoirkit import pipeable
|
||||
from voussoirkit import subproctools
|
||||
from voussoirkit import vlogging
|
||||
from voussoirkit import winglob
|
||||
|
|
@ -26,6 +27,9 @@ RESERVE_SPACE_ON_DRIVE = 5 * bytestring.GIBIBYTE
|
|||
COMPRESSION_STORE = 0
|
||||
COMPRESSION_MAX = 5
|
||||
|
||||
DATE_STRFTIME = '%Y-%m-%d'
|
||||
DATETIME_STRFTIME = '%Y-%m-%d_%H-%M-%S'
|
||||
|
||||
class RarParException(Exception):
|
||||
pass
|
||||
|
||||
|
|
@ -35,7 +39,8 @@ class RarExists(RarParException):
|
|||
class NotEnoughSpace(RarParException):
|
||||
pass
|
||||
|
||||
def RARCOMMAND(
|
||||
def build_rarcommand(
|
||||
*,
|
||||
path,
|
||||
basename,
|
||||
workdir,
|
||||
|
|
@ -49,6 +54,8 @@ def RARCOMMAND(
|
|||
volume=None,
|
||||
):
|
||||
'''
|
||||
winrar [options] destination files_to_include
|
||||
----------------------------------------------------------------------------
|
||||
winrar
|
||||
a = make archive
|
||||
-cp{profile} = use compression profile. this must come first so that
|
||||
|
|
@ -74,8 +81,8 @@ def RARCOMMAND(
|
|||
-x = exclude certain filenames
|
||||
destination
|
||||
workdir/basename.rar
|
||||
files to include
|
||||
input_pattern
|
||||
files_to_include
|
||||
glob pattern
|
||||
'''
|
||||
command = [WINRAR, 'a']
|
||||
|
||||
|
|
@ -125,14 +132,30 @@ def RARCOMMAND(
|
|||
|
||||
return command
|
||||
|
||||
def PARCOMMAND(workdir, basename, par):
|
||||
def run_rar(**kwargs) -> list:
|
||||
'''
|
||||
Run the winrar command and return a list of rar and rev files.
|
||||
'''
|
||||
workdir = kwargs['workdir']
|
||||
basename = kwargs['basename']
|
||||
command = build_rarcommand(**kwargs)
|
||||
creationflags = subprocess.CREATE_NO_WINDOW if sys.stdout is None else 0
|
||||
log.info(subproctools.format_command(command))
|
||||
status = subprocess.run(command, creationflags=creationflags).returncode
|
||||
return (workdir.glob_files(f'{basename}*.rar') + workdir.glob_files(f'{basename}*.rev'))
|
||||
|
||||
def build_parcommand(workdir, basename, par):
|
||||
'''
|
||||
phpar2 [options] destination files_to_include
|
||||
----------------------------------------------------------------------------
|
||||
phpar2
|
||||
c = create pars
|
||||
-t1 = thread count: 1
|
||||
-r{x} = x% recovery
|
||||
destination
|
||||
workdir/basename.par2
|
||||
files_to_include
|
||||
glob pattern
|
||||
'''
|
||||
command = [
|
||||
PAR2,
|
||||
|
|
@ -144,6 +167,18 @@ def PARCOMMAND(workdir, basename, par):
|
|||
]
|
||||
return command
|
||||
|
||||
def run_par(**kwargs) -> list:
|
||||
'''
|
||||
Run the par command and return a list of par2 files.
|
||||
'''
|
||||
workdir = kwargs['workdir']
|
||||
basename = kwargs['basename']
|
||||
command = build_parcommand(**kwargs)
|
||||
creationflags = subprocess.CREATE_NO_WINDOW if sys.stdout is None else 0
|
||||
log.info(subproctools.format_command(command))
|
||||
status = subprocess.run(command, creationflags=creationflags).returncode
|
||||
return workdir.glob_files(f'{basename}*.par2')
|
||||
|
||||
def assert_enough_space(path, size, rec, rev, par):
|
||||
plus_percent = (rec + rev + par) / 100
|
||||
needed = size * (1 + plus_percent)
|
||||
|
|
@ -152,12 +187,6 @@ def assert_enough_space(path, size, rec, rev, par):
|
|||
|
||||
path.assert_disk_space(reserve)
|
||||
|
||||
def move(pattern, directory):
|
||||
files = winglob.glob(pattern)
|
||||
for file in files:
|
||||
print(file)
|
||||
shutil.move(file, directory)
|
||||
|
||||
def normalize_compression(compression):
|
||||
if compression is None:
|
||||
return None
|
||||
|
|
@ -283,41 +312,6 @@ def normalize_volume(volume, pathsize):
|
|||
raise ValueError('Volume must be >= 1.')
|
||||
return volume
|
||||
|
||||
def run_script(script, dry=False):
|
||||
'''
|
||||
`script` can be a list of strings, which are command line commands, or
|
||||
callable Python functions. They will be run in order, and the sequence
|
||||
will terminate if any step returns a bad status code. Your Python functions
|
||||
must return either 0 or None to be considered successful, all other return
|
||||
values will be considered failures.
|
||||
'''
|
||||
status = 0
|
||||
|
||||
for command in script:
|
||||
if isinstance(command, str):
|
||||
log.info(command)
|
||||
elif isinstance(command, list):
|
||||
log.info(subproctools.format_command(command))
|
||||
else:
|
||||
log.info(command)
|
||||
|
||||
if dry:
|
||||
continue
|
||||
|
||||
if isinstance(command, str):
|
||||
status = os.system(command)
|
||||
elif isinstance(command, list):
|
||||
# sys.stdout is None indicates pythonw.
|
||||
creationflags = subprocess.CREATE_NO_WINDOW if sys.stdout is None else 0
|
||||
status = subprocess.run(command, creationflags=creationflags).returncode
|
||||
else:
|
||||
status = command()
|
||||
if status not in [0, None]:
|
||||
log.error('!!!! error status: %s', status)
|
||||
break
|
||||
|
||||
return status
|
||||
|
||||
def rarpar(
|
||||
path,
|
||||
*,
|
||||
|
|
@ -382,8 +376,8 @@ def rarpar(
|
|||
par=par or 0,
|
||||
)
|
||||
|
||||
date = time.strftime('%Y-%m-%d')
|
||||
timestamp = time.strftime('%Y-%m-%d_%H-%M-%S')
|
||||
date = time.strftime(DATE_STRFTIME)
|
||||
timestamp = time.strftime(DATETIME_STRFTIME)
|
||||
|
||||
if basename is not None:
|
||||
basename = re.sub(r'\.rar$', '', basename)
|
||||
|
|
@ -402,11 +396,11 @@ def rarpar(
|
|||
if existing:
|
||||
raise RarExists(f'{existing[0].absolute_path} already exists.')
|
||||
|
||||
# Script building ##############################################################################
|
||||
output_files = []
|
||||
|
||||
script = []
|
||||
################################################################################################
|
||||
|
||||
rarcommand = RARCOMMAND(
|
||||
rar_kwargs = dict(
|
||||
path=path,
|
||||
basename=basename,
|
||||
compression=compression,
|
||||
|
|
@ -419,51 +413,50 @@ def rarpar(
|
|||
volume=volume,
|
||||
workdir=workdir,
|
||||
)
|
||||
script.append(rarcommand)
|
||||
if dry:
|
||||
rarcommand = build_rarcommand(**rar_kwargs)
|
||||
log.info(subproctools.format_command(rarcommand))
|
||||
else:
|
||||
rar_files = run_rar(**rar_kwargs)
|
||||
output_files.extend(rar_files)
|
||||
|
||||
if par:
|
||||
parcommand = PARCOMMAND(
|
||||
basename=basename,
|
||||
par=par,
|
||||
workdir=workdir,
|
||||
)
|
||||
script.append(parcommand)
|
||||
################################################################################################
|
||||
|
||||
def move_rars():
|
||||
move(f'{workdir.absolute_path}\\{basename}*.rar', f'{moveto.absolute_path}')
|
||||
par_kwargs = dict(
|
||||
basename=basename,
|
||||
par=par,
|
||||
workdir=workdir,
|
||||
)
|
||||
if not par:
|
||||
pass
|
||||
elif dry:
|
||||
parcommand = build_parcommand(**par_kwargs)
|
||||
log.info(subproctools.format_command(parcommand))
|
||||
else:
|
||||
par_files = run_par(**par_kwargs)
|
||||
output_files.extend(par_files)
|
||||
|
||||
def move_revs():
|
||||
move(f'{workdir.absolute_path}\\{basename}*.rev', f'{moveto.absolute_path}')
|
||||
################################################################################################
|
||||
|
||||
def move_pars():
|
||||
move(f'{workdir.absolute_path}\\{basename}*.par2', f'{moveto.absolute_path}')
|
||||
if moveto and not dry:
|
||||
moved_files = []
|
||||
for file in output_files:
|
||||
shutil.move(file.absolute_path, moveto.absolute_path)
|
||||
moved_files.append(moveto.with_child(file.basename))
|
||||
output_files = moved_files
|
||||
|
||||
if moveto:
|
||||
if True:
|
||||
script.append(move_rars)
|
||||
if rev:
|
||||
script.append(move_revs)
|
||||
if par:
|
||||
script.append(move_pars)
|
||||
|
||||
def recycle():
|
||||
if recycle_original and not dry:
|
||||
send2trash.send2trash(path.absolute_path)
|
||||
|
||||
if recycle_original:
|
||||
script.append(recycle)
|
||||
|
||||
#### ####
|
||||
|
||||
status = run_script(script, dry)
|
||||
|
||||
return status
|
||||
return output_files
|
||||
|
||||
####################################################################################################
|
||||
# COMMAND LINE #####################################################################################
|
||||
####################################################################################################
|
||||
|
||||
def rarpar_argparse(args):
|
||||
status = 0
|
||||
try:
|
||||
return rarpar(
|
||||
output_files = rarpar(
|
||||
path=args.path,
|
||||
volume=args.volume,
|
||||
basename=args.basename,
|
||||
|
|
@ -480,11 +473,12 @@ def rarpar_argparse(args):
|
|||
solid=args.solid,
|
||||
workdir=args.workdir,
|
||||
)
|
||||
for file in output_files:
|
||||
pipeable.stdout(file.absolute_path)
|
||||
return 0
|
||||
except (RarExists, pathclass.NotEnoughSpace) as exc:
|
||||
log.fatal(exc)
|
||||
status = 1
|
||||
|
||||
return status
|
||||
return 1
|
||||
|
||||
@operatornotify.main_decorator(subject='rarpar.py')
|
||||
@vlogging.main_decorator
|
||||
|
|
|
|||
22
sdingest.py
22
sdingest.py
|
|
@ -1,7 +1,10 @@
|
|||
import shutil
|
||||
import argparse
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
|
||||
import photo_rename
|
||||
|
||||
from voussoirkit import betterhelp
|
||||
from voussoirkit import pathclass
|
||||
from voussoirkit import spinal
|
||||
|
|
@ -30,6 +33,10 @@ def sdingest_all():
|
|||
# Panasonic HC-X1500/HC-X2000
|
||||
panasonic = mount.with_child('PRIVATE').with_child('PANA_GRP').with_child('001YAQAM')
|
||||
if panasonic.is_folder:
|
||||
files = list(panasonic.walk_files())
|
||||
pairs = photo_rename.makenames(files, read_mtime=True, minus_duration=True)
|
||||
for (old, new) in pairs.items():
|
||||
os.rename(old.absolute_path, new.absolute_path)
|
||||
sdingest_one(panasonic)
|
||||
continue
|
||||
|
||||
|
|
@ -40,11 +47,14 @@ def sdingest_all():
|
|||
continue
|
||||
|
||||
# Sony ICD UX570
|
||||
if info.get('name').upper() == 'MEMORY CARD':
|
||||
folder = mount.join('PRIVATE\\SONY\\REC_FILE')
|
||||
if folder.exists:
|
||||
sdingest_one(folder)
|
||||
continue
|
||||
folder = mount.join('PRIVATE\\SONY\\REC_FILE')
|
||||
if info.get('name').upper() == 'MEMORY CARD' and folder.is_folder:
|
||||
files = list(folder.walk_files())
|
||||
pairs = photo_rename.makenames(files, read_mtime=True)
|
||||
for (old, new) in pairs.items():
|
||||
os.rename(old.absolute_path, new.absolute_path)
|
||||
sdingest_one(folder)
|
||||
continue
|
||||
|
||||
if dcim is None:
|
||||
return 1
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
algorithm = histogram
|
||||
[gui]
|
||||
diffopts = --patience
|
||||
gcwarning = false
|
||||
|
||||
# In the user's .gitconfig file, inclue these lines at the top:
|
||||
# [include]
|
||||
|
|
|
|||
Loading…
Reference in a new issue