Compare commits

...

11 commits

9 changed files with 186 additions and 131 deletions

View file

@ -1,23 +1,42 @@
import glob import argparse
import os import os
import sys import sys
import tempfile import tempfile
if len(sys.argv) < 3: from voussoirkit import betterhelp
raise ValueError() from voussoirkit import ffmpegtools
from voussoirkit import pathclass
from voussoirkit import vlogging
from voussoirkit import winglob
output_filename = sys.argv.pop(-1) log = vlogging.getLogger(__name__, 'autocat')
patterns = sys.argv[1:]
names = [name for pattern in patterns for name in glob.glob(pattern)] def autocat_argparse(args):
names = [os.path.abspath(x) for x in names] if len(args.names) < 3:
cat_lines = [f'file \'{x}\'' for x in names] raise ValueError('Should have at least three arguments: two inputs and one output.')
cat_text = '\n'.join(cat_lines)
cat_file = tempfile.TemporaryFile('w', encoding='utf-8', delete=False)
cat_file.write(cat_text)
cat_file.close()
cmd = f'ffmpeg -f concat -safe 0 -i {cat_file.name} -map 0:v? -map 0:a? -map 0:s? -c copy "{output_filename}"' output_file = pathclass.Path(args.names.pop(-1))
os.system(cmd) 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
View 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
View file

@ -1,6 +1,5 @@
import pyperclip import pyperclip
from voussoirkit import pipeable from voussoirkit import pipeable
text = pipeable.input('!i', split_lines=False) text = pipeable.input('!i', split_lines=False)
pyperclip.copy(text) pyperclip.copy(text.encode('utf-8', 'replace').decode())

View file

@ -4,19 +4,24 @@ import sys
from voussoirkit import betterhelp from voussoirkit import betterhelp
from voussoirkit import pathclass from voussoirkit import pathclass
from voussoirkit import pipeable
def namedpython_argparse(args): def named_python(name):
this_python = pathclass.Path(sys.executable) 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] base = this_python.replace_extension('').basename.split('-', 1)[0]
name = args.name.strip()
extension = this_python.extension.with_dot extension = this_python.extension.with_dot
named_python = this_python.parent.with_child(f'{base}-{name}{extension}') named_python = this_python.parent.with_child(f'{base}-{name}{extension}')
if named_python.exists: if named_python.exists:
return 0 return named_python
os.link(this_python.absolute_path, named_python.absolute_path) 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 return 0
def main(argv): def main(argv):

View file

@ -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+)?$' 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 # Already optimized filenames need not apply
# This is also important when the filename and the exif disagree # This is also important when the filename and the exif disagree
if re.match(final_pattern, old) and not read_exif: if re.match(final_pattern, old) and not read_exif and not read_mtime:
return file # return file
pass
# Microsoft ICE # Microsoft ICE
new = re.sub( 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 # Especially in cases where the user has edited a photo with software that
# reset the exif but the filename refers to the original date. # 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. # 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) new = makename_exif(file, old)
if new == old and re.match(final_pattern, new):
return file
if new == old and read_mtime: if new == old and read_mtime:
mtime = file.stat.st_mtime mtime = file.stat.st_mtime
if minus_duration: if minus_duration:
@ -166,6 +164,9 @@ def makename(file, read_exif=False, read_mtime=False, minus_duration=False):
date = datetime.datetime.fromtimestamp(mtime) date = datetime.datetime.fromtimestamp(mtime)
new = date.strftime('%Y-%m-%d_%H-%M-%S') new = date.strftime('%Y-%m-%d_%H-%M-%S')
if new == old:
return file
new = file.parent.with_child(new).add_extension(file.extension) new = file.parent.with_child(new).add_extension(file.extension)
return new return new

27
q.py
View file

@ -2,6 +2,7 @@ import argparse
import hashlib import hashlib
import os import os
import re import re
import send2trash
import sys import sys
import time import time
import traceback import traceback
@ -46,19 +47,20 @@ def filter_collaborate(files, collaborate):
return newfiles return newfiles
def get_extension_command(extension): 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 isinstance(key, str):
if key == extension: if key == extension:
return (command, argument) return qcommand
continue continue
match = re.match(key, extension) match = re.match(key, extension)
if not match: if not match:
continue continue
groups = match.groups() groups = match.groups()
if not groups: if not groups:
return (command, argument) return qcommand
command = re.sub(key, command, extension) qcommand = qcommand.copy()
return (command, argument) qcommand['command'] = re.sub(key, qcommand['command'], extension)
return qcommand
def handle_blacklist(file, reason=''): def handle_blacklist(file, reason=''):
if reason: if reason:
@ -136,16 +138,18 @@ def process_file(file, args=None):
commands = [] 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) links = read_file_links(file)
(command, argument) = get_extension_command(extension)
commands.extend(f'{command} "{link}"' for link in links) commands.extend(f'{command} "{link}"' for link in links)
else: else:
(command, argument) = get_extension_command(extension)
base = file.replace_extension('').basename base = file.replace_extension('').basename
argument = argument.format(id=base) argument = argument.format(id=base, abspath=file.absolute_path)
commands.append(f'{command} {argument}') commands.append(f'{command} -- {argument}')
exit_code = 0 exit_code = 0
@ -159,7 +163,8 @@ def process_file(file, args=None):
if exit_code == 0: if exit_code == 0:
try: try:
os.remove(file) # os.remove(file)
send2trash.send2trash(file)
except FileNotFoundError: except FileNotFoundError:
# Race condition # Race condition
pass pass

166
rarpar.py
View file

@ -11,6 +11,7 @@ from voussoirkit import betterhelp
from voussoirkit import bytestring from voussoirkit import bytestring
from voussoirkit import operatornotify from voussoirkit import operatornotify
from voussoirkit import pathclass from voussoirkit import pathclass
from voussoirkit import pipeable
from voussoirkit import subproctools from voussoirkit import subproctools
from voussoirkit import vlogging from voussoirkit import vlogging
from voussoirkit import winglob from voussoirkit import winglob
@ -26,6 +27,9 @@ RESERVE_SPACE_ON_DRIVE = 5 * bytestring.GIBIBYTE
COMPRESSION_STORE = 0 COMPRESSION_STORE = 0
COMPRESSION_MAX = 5 COMPRESSION_MAX = 5
DATE_STRFTIME = '%Y-%m-%d'
DATETIME_STRFTIME = '%Y-%m-%d_%H-%M-%S'
class RarParException(Exception): class RarParException(Exception):
pass pass
@ -35,7 +39,8 @@ class RarExists(RarParException):
class NotEnoughSpace(RarParException): class NotEnoughSpace(RarParException):
pass pass
def RARCOMMAND( def build_rarcommand(
*,
path, path,
basename, basename,
workdir, workdir,
@ -49,6 +54,8 @@ def RARCOMMAND(
volume=None, volume=None,
): ):
''' '''
winrar [options] destination files_to_include
----------------------------------------------------------------------------
winrar winrar
a = make archive a = make archive
-cp{profile} = use compression profile. this must come first so that -cp{profile} = use compression profile. this must come first so that
@ -74,8 +81,8 @@ def RARCOMMAND(
-x = exclude certain filenames -x = exclude certain filenames
destination destination
workdir/basename.rar workdir/basename.rar
files to include files_to_include
input_pattern glob pattern
''' '''
command = [WINRAR, 'a'] command = [WINRAR, 'a']
@ -125,14 +132,30 @@ def RARCOMMAND(
return command 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 phpar2
c = create pars c = create pars
-t1 = thread count: 1 -t1 = thread count: 1
-r{x} = x% recovery -r{x} = x% recovery
destination destination
workdir/basename.par2 workdir/basename.par2
files_to_include
glob pattern
''' '''
command = [ command = [
PAR2, PAR2,
@ -144,6 +167,18 @@ def PARCOMMAND(workdir, basename, par):
] ]
return command 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): def assert_enough_space(path, size, rec, rev, par):
plus_percent = (rec + rev + par) / 100 plus_percent = (rec + rev + par) / 100
needed = size * (1 + plus_percent) needed = size * (1 + plus_percent)
@ -152,12 +187,6 @@ def assert_enough_space(path, size, rec, rev, par):
path.assert_disk_space(reserve) 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): def normalize_compression(compression):
if compression is None: if compression is None:
return None return None
@ -283,41 +312,6 @@ def normalize_volume(volume, pathsize):
raise ValueError('Volume must be >= 1.') raise ValueError('Volume must be >= 1.')
return volume 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( def rarpar(
path, path,
*, *,
@ -382,8 +376,8 @@ def rarpar(
par=par or 0, par=par or 0,
) )
date = time.strftime('%Y-%m-%d') date = time.strftime(DATE_STRFTIME)
timestamp = time.strftime('%Y-%m-%d_%H-%M-%S') timestamp = time.strftime(DATETIME_STRFTIME)
if basename is not None: if basename is not None:
basename = re.sub(r'\.rar$', '', basename) basename = re.sub(r'\.rar$', '', basename)
@ -402,11 +396,11 @@ def rarpar(
if existing: if existing:
raise RarExists(f'{existing[0].absolute_path} already exists.') raise RarExists(f'{existing[0].absolute_path} already exists.')
# Script building ############################################################################## output_files = []
script = [] ################################################################################################
rarcommand = RARCOMMAND( rar_kwargs = dict(
path=path, path=path,
basename=basename, basename=basename,
compression=compression, compression=compression,
@ -419,51 +413,50 @@ def rarpar(
volume=volume, volume=volume,
workdir=workdir, 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(): par_kwargs = dict(
move(f'{workdir.absolute_path}\\{basename}*.rar', f'{moveto.absolute_path}') 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(): if moveto and not dry:
move(f'{workdir.absolute_path}\\{basename}*.par2', f'{moveto.absolute_path}') 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 recycle_original and not dry:
if True:
script.append(move_rars)
if rev:
script.append(move_revs)
if par:
script.append(move_pars)
def recycle():
send2trash.send2trash(path.absolute_path) send2trash.send2trash(path.absolute_path)
if recycle_original: return output_files
script.append(recycle)
#### ####
status = run_script(script, dry)
return status
####################################################################################################
# COMMAND LINE ##################################################################################### # COMMAND LINE #####################################################################################
####################################################################################################
def rarpar_argparse(args): def rarpar_argparse(args):
status = 0
try: try:
return rarpar( output_files = rarpar(
path=args.path, path=args.path,
volume=args.volume, volume=args.volume,
basename=args.basename, basename=args.basename,
@ -480,11 +473,12 @@ def rarpar_argparse(args):
solid=args.solid, solid=args.solid,
workdir=args.workdir, workdir=args.workdir,
) )
for file in output_files:
pipeable.stdout(file.absolute_path)
return 0
except (RarExists, pathclass.NotEnoughSpace) as exc: except (RarExists, pathclass.NotEnoughSpace) as exc:
log.fatal(exc) log.fatal(exc)
status = 1 return 1
return status
@operatornotify.main_decorator(subject='rarpar.py') @operatornotify.main_decorator(subject='rarpar.py')
@vlogging.main_decorator @vlogging.main_decorator

View file

@ -1,7 +1,10 @@
import shutil
import argparse import argparse
import os
import shutil
import sys import sys
import photo_rename
from voussoirkit import betterhelp from voussoirkit import betterhelp
from voussoirkit import pathclass from voussoirkit import pathclass
from voussoirkit import spinal from voussoirkit import spinal
@ -30,6 +33,10 @@ def sdingest_all():
# Panasonic HC-X1500/HC-X2000 # Panasonic HC-X1500/HC-X2000
panasonic = mount.with_child('PRIVATE').with_child('PANA_GRP').with_child('001YAQAM') panasonic = mount.with_child('PRIVATE').with_child('PANA_GRP').with_child('001YAQAM')
if panasonic.is_folder: 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) sdingest_one(panasonic)
continue continue
@ -40,11 +47,14 @@ def sdingest_all():
continue continue
# Sony ICD UX570 # Sony ICD UX570
if info.get('name').upper() == 'MEMORY CARD': folder = mount.join('PRIVATE\\SONY\\REC_FILE')
folder = mount.join('PRIVATE\\SONY\\REC_FILE') if info.get('name').upper() == 'MEMORY CARD' and folder.is_folder:
if folder.exists: files = list(folder.walk_files())
sdingest_one(folder) pairs = photo_rename.makenames(files, read_mtime=True)
continue for (old, new) in pairs.items():
os.rename(old.absolute_path, new.absolute_path)
sdingest_one(folder)
continue
if dcim is None: if dcim is None:
return 1 return 1

View file

@ -17,6 +17,7 @@
algorithm = histogram algorithm = histogram
[gui] [gui]
diffopts = --patience diffopts = --patience
gcwarning = false
# In the user's .gitconfig file, inclue these lines at the top: # In the user's .gitconfig file, inclue these lines at the top:
# [include] # [include]