Compare commits

..

No commits in common. "31b8047381478775d9fa39158582c3054c1e5430" and "d441c69fefeaef7ab7e58becc787d1f353ae9365" have entirely different histories.

9 changed files with 131 additions and 186 deletions

View file

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

View file

@ -1,21 +0,0 @@
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,5 +1,6 @@
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.encode('utf-8', 'replace').decode()) pyperclip.copy(text)

View file

@ -4,24 +4,19 @@ import sys
from voussoirkit import betterhelp from voussoirkit import betterhelp
from voussoirkit import pathclass from voussoirkit import pathclass
from voussoirkit import pipeable
def named_python(name): def namedpython_argparse(args):
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 named_python return 0
os.link(this_python.absolute_path, named_python.absolute_path) os.link(this_python.absolute_path, named_python.absolute_path)
return named_python print(named_python.absolute_path)
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,9 +21,8 @@ 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 and not read_mtime: if re.match(final_pattern, old) and not read_exif:
# return file return file
pass
# Microsoft ICE # Microsoft ICE
new = re.sub( new = re.sub(
@ -154,9 +153,12 @@ 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', 'dng'}: if new == old and read_exif and file.extension in {'jpg', 'jpeg'}:
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:
@ -164,9 +166,6 @@ 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,7 +2,6 @@ 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
@ -47,20 +46,19 @@ def filter_collaborate(files, collaborate):
return newfiles return newfiles
def get_extension_command(extension): def get_extension_command(extension):
for (key, qcommand) in qcommands.EXTENSION_COMMANDS.items(): for (key, (command, argument)) in qcommands.EXTENSION_COMMANDS.items():
if isinstance(key, str): if isinstance(key, str):
if key == extension: if key == extension:
return qcommand return (command, argument)
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 qcommand return (command, argument)
qcommand = qcommand.copy() command = re.sub(key, command, extension)
qcommand['command'] = re.sub(key, qcommand['command'], extension) return (command, argument)
return qcommand
def handle_blacklist(file, reason=''): def handle_blacklist(file, reason=''):
if reason: if reason:
@ -138,18 +136,16 @@ def process_file(file, args=None):
commands = [] commands = []
qcommand = get_extension_command(extension) if file.size > 0:
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, abspath=file.absolute_path) argument = argument.format(id=base)
commands.append(f'{command} -- {argument}') commands.append(f'{command} {argument}')
exit_code = 0 exit_code = 0
@ -163,8 +159,7 @@ 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,7 +11,6 @@ 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
@ -27,9 +26,6 @@ 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
@ -39,8 +35,7 @@ class RarExists(RarParException):
class NotEnoughSpace(RarParException): class NotEnoughSpace(RarParException):
pass pass
def build_rarcommand( def RARCOMMAND(
*,
path, path,
basename, basename,
workdir, workdir,
@ -54,8 +49,6 @@ def build_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
@ -81,8 +74,8 @@ def build_rarcommand(
-x = exclude certain filenames -x = exclude certain filenames
destination destination
workdir/basename.rar workdir/basename.rar
files_to_include files to include
glob pattern input_pattern
''' '''
command = [WINRAR, 'a'] command = [WINRAR, 'a']
@ -132,30 +125,14 @@ def build_rarcommand(
return command return command
def run_rar(**kwargs) -> list: def PARCOMMAND(workdir, basename, par):
''' '''
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,
@ -167,18 +144,6 @@ def build_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)
@ -187,6 +152,12 @@ 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
@ -312,6 +283,41 @@ 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,
*, *,
@ -376,8 +382,8 @@ def rarpar(
par=par or 0, par=par or 0,
) )
date = time.strftime(DATE_STRFTIME) date = time.strftime('%Y-%m-%d')
timestamp = time.strftime(DATETIME_STRFTIME) timestamp = time.strftime('%Y-%m-%d_%H-%M-%S')
if basename is not None: if basename is not None:
basename = re.sub(r'\.rar$', '', basename) basename = re.sub(r'\.rar$', '', basename)
@ -396,11 +402,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.')
output_files = [] # Script building ##############################################################################
################################################################################################ script = []
rar_kwargs = dict( rarcommand = RARCOMMAND(
path=path, path=path,
basename=basename, basename=basename,
compression=compression, compression=compression,
@ -413,50 +419,51 @@ def rarpar(
volume=volume, volume=volume,
workdir=workdir, workdir=workdir,
) )
if dry: script.append(rarcommand)
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)
par_kwargs = dict( def move_rars():
basename=basename, move(f'{workdir.absolute_path}\\{basename}*.rar', f'{moveto.absolute_path}')
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}')
if moveto and not dry: def move_pars():
moved_files = [] move(f'{workdir.absolute_path}\\{basename}*.par2', f'{moveto.absolute_path}')
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 recycle_original and not dry: if moveto:
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)
return output_files if recycle_original:
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:
output_files = rarpar( return rarpar(
path=args.path, path=args.path,
volume=args.volume, volume=args.volume,
basename=args.basename, basename=args.basename,
@ -473,12 +480,11 @@ 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)
return 1 status = 1
return status
@operatornotify.main_decorator(subject='rarpar.py') @operatornotify.main_decorator(subject='rarpar.py')
@vlogging.main_decorator @vlogging.main_decorator

View file

@ -1,10 +1,7 @@
import argparse
import os
import shutil import shutil
import argparse
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
@ -33,10 +30,6 @@ 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
@ -47,14 +40,11 @@ def sdingest_all():
continue continue
# Sony ICD UX570 # Sony ICD UX570
folder = mount.join('PRIVATE\\SONY\\REC_FILE') if info.get('name').upper() == 'MEMORY CARD':
if info.get('name').upper() == 'MEMORY CARD' and folder.is_folder: folder = mount.join('PRIVATE\\SONY\\REC_FILE')
files = list(folder.walk_files()) if folder.exists:
pairs = photo_rename.makenames(files, read_mtime=True) sdingest_one(folder)
for (old, new) in pairs.items(): continue
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,7 +17,6 @@
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]