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 sys
import tempfile
from voussoirkit import betterhelp
from voussoirkit import ffmpegtools
from voussoirkit import pathclass
from voussoirkit import vlogging
from voussoirkit import winglob
if len(sys.argv) < 3:
raise ValueError()
log = vlogging.getLogger(__name__, 'autocat')
output_filename = sys.argv.pop(-1)
patterns = sys.argv[1:]
def autocat_argparse(args):
if len(args.names) < 3:
raise ValueError('Should have at least three arguments: two inputs and one output.')
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()
output_file = pathclass.Path(args.names.pop(-1))
patterns = args.names
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)
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:]))
os.remove(cat_file.name)

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
from voussoirkit import pipeable
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 pathclass
from voussoirkit import pipeable
def named_python(name):
def namedpython_argparse(args):
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 named_python
return 0
os.link(this_python.absolute_path, named_python.absolute_path)
return named_python
def namedpython_argparse(args):
exe = named_python(args.name)
pipeable.stdout(exe.absolute_path)
print(named_python.absolute_path)
return 0
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+)?$'
# 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 and not read_mtime:
# return file
pass
if re.match(final_pattern, old) and not read_exif:
return file
# Microsoft ICE
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
# 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', 'dng'}:
if new == old and read_exif and file.extension in {'jpg', 'jpeg'}:
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:
@ -164,9 +166,6 @@ 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
View file

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

166
rarpar.py
View file

@ -11,7 +11,6 @@ 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
@ -27,9 +26,6 @@ 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
@ -39,8 +35,7 @@ class RarExists(RarParException):
class NotEnoughSpace(RarParException):
pass
def build_rarcommand(
*,
def RARCOMMAND(
path,
basename,
workdir,
@ -54,8 +49,6 @@ def build_rarcommand(
volume=None,
):
'''
winrar [options] destination files_to_include
----------------------------------------------------------------------------
winrar
a = make archive
-cp{profile} = use compression profile. this must come first so that
@ -81,8 +74,8 @@ def build_rarcommand(
-x = exclude certain filenames
destination
workdir/basename.rar
files_to_include
glob pattern
files to include
input_pattern
'''
command = [WINRAR, 'a']
@ -132,30 +125,14 @@ def build_rarcommand(
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
c = create pars
-t1 = thread count: 1
-r{x} = x% recovery
destination
workdir/basename.par2
files_to_include
glob pattern
'''
command = [
PAR2,
@ -167,18 +144,6 @@ def build_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)
@ -187,6 +152,12 @@ 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
@ -312,6 +283,41 @@ 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,
*,
@ -376,8 +382,8 @@ def rarpar(
par=par or 0,
)
date = time.strftime(DATE_STRFTIME)
timestamp = time.strftime(DATETIME_STRFTIME)
date = time.strftime('%Y-%m-%d')
timestamp = time.strftime('%Y-%m-%d_%H-%M-%S')
if basename is not None:
basename = re.sub(r'\.rar$', '', basename)
@ -396,11 +402,11 @@ def rarpar(
if existing:
raise RarExists(f'{existing[0].absolute_path} already exists.')
output_files = []
# Script building ##############################################################################
################################################################################################
script = []
rar_kwargs = dict(
rarcommand = RARCOMMAND(
path=path,
basename=basename,
compression=compression,
@ -413,50 +419,51 @@ def rarpar(
volume=volume,
workdir=workdir,
)
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)
script.append(rarcommand)
################################################################################################
if par:
parcommand = PARCOMMAND(
basename=basename,
par=par,
workdir=workdir,
)
script.append(parcommand)
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_rars():
move(f'{workdir.absolute_path}\\{basename}*.rar', f'{moveto.absolute_path}')
################################################################################################
def move_revs():
move(f'{workdir.absolute_path}\\{basename}*.rev', 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
def move_pars():
move(f'{workdir.absolute_path}\\{basename}*.par2', f'{moveto.absolute_path}')
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)
return output_files
if recycle_original:
script.append(recycle)
#### ####
status = run_script(script, dry)
return status
####################################################################################################
# COMMAND LINE #####################################################################################
####################################################################################################
def rarpar_argparse(args):
status = 0
try:
output_files = rarpar(
return rarpar(
path=args.path,
volume=args.volume,
basename=args.basename,
@ -473,12 +480,11 @@ 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)
return 1
status = 1
return status
@operatornotify.main_decorator(subject='rarpar.py')
@vlogging.main_decorator

View file

@ -1,10 +1,7 @@
import argparse
import os
import shutil
import argparse
import sys
import photo_rename
from voussoirkit import betterhelp
from voussoirkit import pathclass
from voussoirkit import spinal
@ -33,10 +30,6 @@ 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
@ -47,14 +40,11 @@ def sdingest_all():
continue
# Sony ICD UX570
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 info.get('name').upper() == 'MEMORY CARD':
folder = mount.join('PRIVATE\\SONY\\REC_FILE')
if folder.exists:
sdingest_one(folder)
continue
if dcim is None:
return 1

View file

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