else
This commit is contained in:
parent
b0950c3df1
commit
9b46a7b927
7 changed files with 266 additions and 4 deletions
|
@ -375,9 +375,17 @@ def safeprint(*texts, **kwargs):
|
||||||
print(*texts, **kwargs)
|
print(*texts, **kwargs)
|
||||||
|
|
||||||
def sanitize_filename(text, exclusions=''):
|
def sanitize_filename(text, exclusions=''):
|
||||||
bet = FILENAME_BADCHARS.replace(exclusions, '')
|
to_remove = FILENAME_BADCHARS
|
||||||
for char in bet:
|
for exclude in exclusions:
|
||||||
|
to_remove = to_remove.replace(exclude, '')
|
||||||
|
|
||||||
|
for char in to_remove:
|
||||||
text = text.replace(char, '')
|
text = text.replace(char, '')
|
||||||
|
|
||||||
|
(drive, path) = os.path.splitdrive(text)
|
||||||
|
path = path.replace(':', '')
|
||||||
|
text = drive + path
|
||||||
|
|
||||||
return text
|
return text
|
||||||
|
|
||||||
def sanitize_url(url):
|
def sanitize_url(url):
|
||||||
|
|
|
@ -84,6 +84,7 @@ def copy(source, file_args=None, file_kwargs=None, dir_args=None, dir_kwargs=Non
|
||||||
def copy_dir(
|
def copy_dir(
|
||||||
source,
|
source,
|
||||||
destination=None,
|
destination=None,
|
||||||
|
*,
|
||||||
bytes_per_second=None,
|
bytes_per_second=None,
|
||||||
callback_directory=None,
|
callback_directory=None,
|
||||||
callback_exclusion=None,
|
callback_exclusion=None,
|
||||||
|
@ -277,6 +278,7 @@ def copy_dir(
|
||||||
def copy_file(
|
def copy_file(
|
||||||
source,
|
source,
|
||||||
destination=None,
|
destination=None,
|
||||||
|
*,
|
||||||
destination_new_root=None,
|
destination_new_root=None,
|
||||||
bytes_per_second=None,
|
bytes_per_second=None,
|
||||||
callback_progress=None,
|
callback_progress=None,
|
||||||
|
@ -565,6 +567,7 @@ def verify_hash(path, known_size, known_hash, callback=None):
|
||||||
|
|
||||||
def walk_generator(
|
def walk_generator(
|
||||||
path='.',
|
path='.',
|
||||||
|
*,
|
||||||
callback_exclusion=None,
|
callback_exclusion=None,
|
||||||
callback_permission_denied=None,
|
callback_permission_denied=None,
|
||||||
depth_first=True,
|
depth_first=True,
|
||||||
|
|
|
@ -46,7 +46,7 @@ class ThreadQueue:
|
||||||
break
|
break
|
||||||
lam = self._lambdas.pop(0)
|
lam = self._lambdas.pop(0)
|
||||||
thread = threading.Thread(target=lam)
|
thread = threading.Thread(target=lam)
|
||||||
#thread.daemon = True
|
thread.daemon = True
|
||||||
thread.start()
|
thread.start()
|
||||||
self._threads.append(thread)
|
self._threads.append(thread)
|
||||||
|
|
||||||
|
|
73
Toolbox/drawn_quartered.py
Normal file
73
Toolbox/drawn_quartered.py
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
'''
|
||||||
|
This script takes an image and splits it up into pieces as separate files.
|
||||||
|
|
||||||
|
drawn_quartered test.jpg --width 2 --height 2
|
||||||
|
drawn_quartered test.jpg outputname.jpg --width 3 --height 4
|
||||||
|
'''
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import math
|
||||||
|
import os
|
||||||
|
import PIL.Image
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from voussoirkit import pathclass
|
||||||
|
|
||||||
|
def drawquarter(image, width=2, height=2):
|
||||||
|
pieces = []
|
||||||
|
(image_width, image_height) = image.size
|
||||||
|
step_x = image_width / width
|
||||||
|
step_y = image_height / height
|
||||||
|
if (step_x != int(step_x)):
|
||||||
|
print('Warning: Imperfect x', step_x)
|
||||||
|
if (step_y != int(step_y)):
|
||||||
|
print('Warning: Imperfect y', step_y)
|
||||||
|
step_x = math.ceil(step_x)
|
||||||
|
step_y = math.ceil(step_y)
|
||||||
|
for y in range(height):
|
||||||
|
end_y = y + 1
|
||||||
|
for x in range(width):
|
||||||
|
end_x = x + 1
|
||||||
|
coords = (step_x * x, step_y * y, step_x * end_x, step_y * end_y)
|
||||||
|
piece = image.crop(coords)
|
||||||
|
pieces.append(piece)
|
||||||
|
return pieces
|
||||||
|
|
||||||
|
def drawquarter_argparse(args):
|
||||||
|
image = PIL.Image.open(args.input_filename)
|
||||||
|
|
||||||
|
if args.output_filename is not None:
|
||||||
|
output_filename = args.output_filename
|
||||||
|
else:
|
||||||
|
output_filename = args.input_filename
|
||||||
|
|
||||||
|
output_path = pathclass.Path(output_filename)
|
||||||
|
output_directory = output_path.parent
|
||||||
|
os.makedirs(output_directory.absolute_path, exist_ok=True)
|
||||||
|
output_filename_format = output_path.basename
|
||||||
|
output_filename_format = output_filename_format.rsplit('.', 1)[0]
|
||||||
|
output_filename_format += '_%dx%d_{ycoord}-{xcoord}.' % (args.width, args.height)
|
||||||
|
output_filename_format += args.input_filename.rsplit('.', 1)[1]
|
||||||
|
|
||||||
|
pieces = drawquarter(image, width=args.width, height=args.height)
|
||||||
|
for (index, piece) in enumerate(pieces):
|
||||||
|
(ycoord, xcoord) = divmod(index, args.height)
|
||||||
|
output_filename = output_filename_format.format(xcoord=xcoord, ycoord=ycoord)
|
||||||
|
output_filename = output_directory.with_child(output_filename)
|
||||||
|
print(output_filename.relative_path)
|
||||||
|
piece.save(output_filename.absolute_path)
|
||||||
|
|
||||||
|
def main(argv):
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
|
||||||
|
parser.add_argument('input_filename')
|
||||||
|
parser.add_argument('output_filename', nargs='?', default=None)
|
||||||
|
parser.add_argument('--width', dest='width', type=int, default=2)
|
||||||
|
parser.add_argument('--height', dest='height', type=int, default=2)
|
||||||
|
parser.set_defaults(func=drawquarter_argparse)
|
||||||
|
|
||||||
|
args = parser.parse_args(argv)
|
||||||
|
args.func(args)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main(sys.argv[1:])
|
171
Toolbox/mp3slice.py
Normal file
171
Toolbox/mp3slice.py
Normal file
|
@ -0,0 +1,171 @@
|
||||||
|
'''
|
||||||
|
This script cuts an audio file into multiple files when you provide the
|
||||||
|
timestamps and titles for each.
|
||||||
|
|
||||||
|
> mp3slice bigfile.mp3 00:00-01:00 part1.mp3 01:00-02:00 part2.mp3
|
||||||
|
'''
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from voussoirkit import bytestring
|
||||||
|
|
||||||
|
|
||||||
|
def parse_rules(lines):
|
||||||
|
rules = []
|
||||||
|
for (times, title) in lines[::-1]:
|
||||||
|
rule = {'title': title}
|
||||||
|
(start, end) = hyphen_range(times)
|
||||||
|
if start is None:
|
||||||
|
raise ValueError('Null start')
|
||||||
|
rule['start'] = start
|
||||||
|
if end is None and len(rules) > 0:
|
||||||
|
end = rules[-1]['start']
|
||||||
|
rule['end'] = end
|
||||||
|
rules.append(rule)
|
||||||
|
rules.sort(key=lambda x: x.get('start'))
|
||||||
|
return rules
|
||||||
|
|
||||||
|
def read_rulefile(filename):
|
||||||
|
text = None
|
||||||
|
for encoding in [None, 'utf-8']:
|
||||||
|
try:
|
||||||
|
with open(filename, 'r', encoding=encoding) as handle:
|
||||||
|
text = handle.read()
|
||||||
|
break
|
||||||
|
except UnicodeError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
raise UnicodeError()
|
||||||
|
|
||||||
|
lines = [l.strip() for l in text.strip().splitlines()]
|
||||||
|
lines = [l for l in lines if l]
|
||||||
|
rules = [l.split(maxsplit=1) for l in lines]
|
||||||
|
return parse_rules(rules)
|
||||||
|
|
||||||
|
def chunk_sequence(sequence, chunk_length, allow_incomplete=True):
|
||||||
|
'''
|
||||||
|
Given a sequence, divide it into sequences of length `chunk_length`.
|
||||||
|
|
||||||
|
allow_incomplete:
|
||||||
|
If True, allow the final chunk to be shorter if the
|
||||||
|
given sequence is not an exact multiple of `chunk_length`.
|
||||||
|
If False, the incomplete chunk will be discarded.
|
||||||
|
'''
|
||||||
|
(complete, leftover) = divmod(len(sequence), chunk_length)
|
||||||
|
if not allow_incomplete:
|
||||||
|
leftover = 0
|
||||||
|
|
||||||
|
chunk_count = complete + min(leftover, 1)
|
||||||
|
|
||||||
|
chunks = []
|
||||||
|
for x in range(chunk_count):
|
||||||
|
left = chunk_length * x
|
||||||
|
right = left + chunk_length
|
||||||
|
chunks.append(sequence[left:right])
|
||||||
|
|
||||||
|
return chunks
|
||||||
|
|
||||||
|
def hyphen_range(s):
|
||||||
|
'''
|
||||||
|
Given a string like '1-3', return numbers (1, 3) representing lower
|
||||||
|
and upper bounds.
|
||||||
|
|
||||||
|
Supports bytestring.parsebytes and hh:mm:ss format, for example
|
||||||
|
'1k-2k', '10:00-20:00', '4gib-'
|
||||||
|
'''
|
||||||
|
s = s.strip()
|
||||||
|
s = s.replace(' ', '')
|
||||||
|
if not s:
|
||||||
|
return (None, None)
|
||||||
|
parts = s.split('-')
|
||||||
|
parts = [part.strip() or None for part in parts]
|
||||||
|
if len(parts) == 1:
|
||||||
|
low = parts[0]
|
||||||
|
high = None
|
||||||
|
elif len(parts) == 2:
|
||||||
|
(low, high) = parts
|
||||||
|
else:
|
||||||
|
raise ValueError('Too many hyphens')
|
||||||
|
|
||||||
|
low = _unitconvert(low)
|
||||||
|
high = _unitconvert(high)
|
||||||
|
if low is not None and high is not None and low > high:
|
||||||
|
raise exceptions.OutOfOrder(range=s, min=low, max=high)
|
||||||
|
return low, high
|
||||||
|
|
||||||
|
def hms_to_seconds(hms):
|
||||||
|
'''
|
||||||
|
Convert hh:mm:ss string to an integer seconds.
|
||||||
|
'''
|
||||||
|
hms = hms.split(':')
|
||||||
|
seconds = 0
|
||||||
|
if len(hms) == 3:
|
||||||
|
seconds += int(hms[0])*3600
|
||||||
|
hms.pop(0)
|
||||||
|
if len(hms) == 2:
|
||||||
|
seconds += int(hms[0])*60
|
||||||
|
hms.pop(0)
|
||||||
|
if len(hms) == 1:
|
||||||
|
seconds += float(hms[0])
|
||||||
|
return seconds
|
||||||
|
|
||||||
|
def _unitconvert(value):
|
||||||
|
'''
|
||||||
|
When parsing hyphenated ranges, this function is used to convert
|
||||||
|
strings like "1k" to 1024 and "1:00" to 60.
|
||||||
|
'''
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
|
if ':' in value:
|
||||||
|
return hms_to_seconds(value)
|
||||||
|
elif all(c in '0123456789.' for c in value):
|
||||||
|
return float(value)
|
||||||
|
else:
|
||||||
|
return bytestring.parsebytes(value)
|
||||||
|
|
||||||
|
def example_argparse(args):
|
||||||
|
if len(args.rules) == 1 and os.path.isfile(args.rules[0]):
|
||||||
|
rules = read_rulefile(args.rules[0])
|
||||||
|
else:
|
||||||
|
rules = args.rules
|
||||||
|
rules = chunk_sequence(rules, 2)
|
||||||
|
if len(rules[-1]) != 2:
|
||||||
|
raise ValueError('Odd-number parameters')
|
||||||
|
rules = parse_rules(rules)
|
||||||
|
|
||||||
|
extension = os.path.splitext(args.input_filename)[1]
|
||||||
|
outputters = []
|
||||||
|
for rule in rules:
|
||||||
|
outputter = []
|
||||||
|
if not rule['title'].endswith(extension):
|
||||||
|
rule['title'] += extension
|
||||||
|
outputter.append('-ss')
|
||||||
|
outputter.append(str(rule['start']))
|
||||||
|
if rule['end'] is not None:
|
||||||
|
outputter.append('-to')
|
||||||
|
outputter.append(str(rule['end']))
|
||||||
|
outputter.append(' -c copy')
|
||||||
|
outputter.append('"%s"' % rule['title'])
|
||||||
|
print(outputter)
|
||||||
|
outputter = ' '.join(outputter)
|
||||||
|
outputters.append(outputter)
|
||||||
|
outputters = ' '.join(outputters)
|
||||||
|
command = 'ffmpeg -i "%s" %s' % (args.input_filename, outputters)
|
||||||
|
print(command)
|
||||||
|
os.system(command)
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv):
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
|
||||||
|
parser.add_argument('input_filename')
|
||||||
|
parser.add_argument('rules', nargs='+', default=None)
|
||||||
|
parser.set_defaults(func=example_argparse)
|
||||||
|
|
||||||
|
args = parser.parse_args(argv)
|
||||||
|
args.func(args)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main(sys.argv[1:])
|
7
Toolbox/reverse.py
Normal file
7
Toolbox/reverse.py
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from voussoirkit import clipext
|
||||||
|
|
||||||
|
arg = clipext.resolve(sys.argv[1])
|
||||||
|
arg = ''.join(reversed(arg))
|
||||||
|
print(arg)
|
|
@ -54,7 +54,7 @@ def search(
|
||||||
'not_all': not_all,
|
'not_all': not_all,
|
||||||
'not_any': not_any
|
'not_any': not_any
|
||||||
}
|
}
|
||||||
terms = {k: (v or []) for (k, v) in terms.items()}
|
terms = {k: ([v] if isinstance(v, str) else v or []) for (k, v) in terms.items()}
|
||||||
#print(terms, content_args)
|
#print(terms, content_args)
|
||||||
|
|
||||||
if all(v == [] for v in terms.values()) and not content_args:
|
if all(v == [] for v in terms.values()) and not content_args:
|
||||||
|
|
Loading…
Reference in a new issue