Initial commit.
This commit is contained in:
commit
f44e46aab5
46 changed files with 2125 additions and 0 deletions
6
README.md
Normal file
6
README.md
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
cmd
|
||||||
|
===
|
||||||
|
|
||||||
|
This is a collection of programs that I use from the command line.
|
||||||
|
|
||||||
|
Wherever you download this repository, don't forget to add that directory to your PATH environment variable.
|
15
allexecutables.py
Normal file
15
allexecutables.py
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import os
|
||||||
|
|
||||||
|
paths = os.getenv('PATH').split(';')
|
||||||
|
paths = [p for p in paths if os.path.exists(p)]
|
||||||
|
|
||||||
|
extensions = os.getenv('PATHEXT').split(';')
|
||||||
|
extensions = [e.lower() for e in extensions]
|
||||||
|
print('Extensions according to PATHEXT:', extensions)
|
||||||
|
|
||||||
|
for path in paths:
|
||||||
|
print(path)
|
||||||
|
files = os.listdir(path)
|
||||||
|
files = [f for f in files if any(f.lower().endswith(e) for e in extensions)]
|
||||||
|
files = [' ' + f for f in files]
|
||||||
|
print('\n'.join(files))
|
50
bitrate_chart.py
Normal file
50
bitrate_chart.py
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
'''
|
||||||
|
bitrate | 01 | 1:00 | 30:00 | 1:00:00 | 1:30:00 | 2:00:00
|
||||||
|
-: | -: | -: | -: | -: | -: | -:
|
||||||
|
128 kbps | 16.000 KiB | 960.000 KiB | 28.125 MiB | 56.250 MiB | 84.375 MiB | 112.500 MiB
|
||||||
|
256 kbps | 32.000 KiB | 1.875 MiB | 56.250 MiB | 112.500 MiB | 168.750 MiB | 225.000 MiB
|
||||||
|
320 kbps | 40.000 KiB | 2.344 MiB | 70.312 MiB | 140.625 MiB | 210.938 MiB | 281.250 MiB
|
||||||
|
500 kbps | 62.500 KiB | 3.662 MiB | 109.863 MiB | 219.727 MiB | 329.590 MiB | 439.453 MiB
|
||||||
|
640 kbps | 80.000 KiB | 4.688 MiB | 140.625 MiB | 281.250 MiB | 421.875 MiB | 562.500 MiB
|
||||||
|
738 kbps | 92.250 KiB | 5.405 MiB | 162.158 MiB | 324.316 MiB | 486.475 MiB | 648.633 MiB
|
||||||
|
1024 kbps | 128.000 KiB | 7.500 MiB | 225.000 MiB | 450.000 MiB | 675.000 MiB | 900.000 MiB
|
||||||
|
2048 kbps | 256.000 KiB | 15.000 MiB | 450.000 MiB | 900.000 MiB | 1.318 GiB | 1.758 GiB
|
||||||
|
2330 kbps | 291.271 KiB | 17.067 MiB | 512.000 MiB | 1.000 GiB | 1.500 GiB | 2.000 GiB
|
||||||
|
3072 kbps | 384.000 KiB | 22.500 MiB | 675.000 MiB | 1.318 GiB | 1.978 GiB | 2.637 GiB
|
||||||
|
4096 kbps | 512.000 KiB | 30.000 MiB | 900.000 MiB | 1.758 GiB | 2.637 GiB | 3.516 GiB
|
||||||
|
4660 kbps | 582.543 KiB | 34.133 MiB | 1.000 GiB | 2.000 GiB | 3.000 GiB | 4.000 GiB
|
||||||
|
6144 kbps | 768.000 KiB | 45.000 MiB | 1.318 GiB | 2.637 GiB | 3.955 GiB | 5.273 GiB
|
||||||
|
8192 kbps | 1.000 MiB | 60.000 MiB | 1.758 GiB | 3.516 GiB | 5.273 GiB | 7.031 GiB
|
||||||
|
12288 kbps | 1.500 MiB | 90.000 MiB | 2.637 GiB | 5.273 GiB | 7.910 GiB | 10.547 GiB
|
||||||
|
16384 kbps | 2.000 MiB | 120.000 MiB | 3.516 GiB | 7.031 GiB | 10.547 GiB | 14.062 GiB
|
||||||
|
'''
|
||||||
|
import sys
|
||||||
|
import kbps
|
||||||
|
|
||||||
|
from voussoirkit import bytestring
|
||||||
|
|
||||||
|
times = ['01', '1:00', '30:00', '1:00:00', '1:30:00', '2:00:00']
|
||||||
|
rates = [128, 256, 320, 500, 640, 738, 1024, 2048, 3072, 4096, 6144, 8192, 12288, 16384, 2330.17, 4660.34]
|
||||||
|
|
||||||
|
times.sort(key=lambda x: kbps.hms_s(x))
|
||||||
|
rates.sort()
|
||||||
|
|
||||||
|
table = []
|
||||||
|
table.append('bitrate | ' + ' | '.join(times))
|
||||||
|
table.append('-: | ' * (len(times)+1))
|
||||||
|
for r in rates:
|
||||||
|
l = []
|
||||||
|
l.append('%d kbps' % r)
|
||||||
|
for t in times:
|
||||||
|
l.append(kbps.kbps(time=t, kbps=r))
|
||||||
|
l = ' | '.join(l)
|
||||||
|
table.append(l)
|
||||||
|
|
||||||
|
#print('\n'.join(table))
|
||||||
|
columns = [[b.strip() for b in a] for a in zip(*[x.split('|') for x in table])]
|
||||||
|
for (index, column) in enumerate(columns):
|
||||||
|
width = max((len(x) for x in column))
|
||||||
|
columns[index] = [x.rjust(width, ' ') for x in column]
|
||||||
|
table = [' | '.join(a) for a in zip(*columns)]
|
||||||
|
#print(columns)
|
||||||
|
print('\n'.join(table))
|
112
brename.py
Normal file
112
brename.py
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
'''
|
||||||
|
Batch rename files by providing a string to be `eval`ed, using variable `x` as
|
||||||
|
the current filename.
|
||||||
|
Yes I know this is weird, but for certain tasks it's just too quick and easy to pass up.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
Prefix all the files:
|
||||||
|
brename.py "'Test_' + x"
|
||||||
|
|
||||||
|
Keep the first word and extension:
|
||||||
|
brename.py "(x.split(' ')[0] + '.' + x.split('.')[-1]) if ' ' in x else x"
|
||||||
|
'''
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
import random
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from voussoirkit import safeprint
|
||||||
|
|
||||||
|
dot = '.'
|
||||||
|
quote = '"'
|
||||||
|
apostrophe = "'"
|
||||||
|
space = ' '
|
||||||
|
|
||||||
|
def brename(transformation, autoyes=False):
|
||||||
|
old = os.listdir()
|
||||||
|
new = []
|
||||||
|
for (index, x) in enumerate(old):
|
||||||
|
(noext, ext) = os.path.splitext(x)
|
||||||
|
x = eval(transformation)
|
||||||
|
new.append(x)
|
||||||
|
pairs = []
|
||||||
|
for (x, y) in zip(old, new):
|
||||||
|
if x == y:
|
||||||
|
continue
|
||||||
|
pairs.append((x, y))
|
||||||
|
|
||||||
|
if not loop(pairs, dry=True):
|
||||||
|
print('Nothing to replace')
|
||||||
|
return
|
||||||
|
|
||||||
|
ok = autoyes
|
||||||
|
if not ok:
|
||||||
|
print('Is this correct? y/n')
|
||||||
|
ok = input('>').lower() in ('y', 'yes', 'yeehaw')
|
||||||
|
|
||||||
|
if ok:
|
||||||
|
loop(pairs, dry=False)
|
||||||
|
|
||||||
|
def excise(s, mark_left, mark_right):
|
||||||
|
'''
|
||||||
|
Remove the text between the left and right landmarks, inclusive, returning
|
||||||
|
the rest of the text.
|
||||||
|
|
||||||
|
excise('What a wonderful day [soundtrack].mp3', ' [', ']') ->
|
||||||
|
returns 'What a wonderful day.mp3'
|
||||||
|
'''
|
||||||
|
return s.split(mark_left)[0] + s.split(mark_right)[-1]
|
||||||
|
|
||||||
|
def longest_length(li):
|
||||||
|
longest = 0
|
||||||
|
for item in li:
|
||||||
|
longest = max(longest, len(item))
|
||||||
|
return longest
|
||||||
|
|
||||||
|
def loop(pairs, dry=False):
|
||||||
|
has_content = False
|
||||||
|
for (x, y) in pairs:
|
||||||
|
if dry:
|
||||||
|
line = '{old}\n{new}\n'
|
||||||
|
line = line.format(old=x, new=y)
|
||||||
|
#print(line.encode('utf-8'))
|
||||||
|
safeprint.safeprint(line)
|
||||||
|
has_content = True
|
||||||
|
else:
|
||||||
|
os.rename(x, y)
|
||||||
|
return has_content
|
||||||
|
|
||||||
|
def title(text):
|
||||||
|
(text, extension) = os.path.splitext(text)
|
||||||
|
text = text.title()
|
||||||
|
if ' ' in text:
|
||||||
|
(first, rest) = text.split(' ', 1)
|
||||||
|
else:
|
||||||
|
(first, rest) = (text, '')
|
||||||
|
rest = ' %s ' % rest
|
||||||
|
for article in ['The', 'A', 'An', 'At', 'To', 'In', 'Of', 'From', 'And']:
|
||||||
|
article = ' %s ' % article
|
||||||
|
rest = rest.replace(article, article.lower())
|
||||||
|
rest = rest.strip()
|
||||||
|
if rest != '':
|
||||||
|
rest = ' ' + rest
|
||||||
|
text = first + rest + extension
|
||||||
|
return text
|
||||||
|
|
||||||
|
def brename_argparse(args):
|
||||||
|
brename(args.transformation, autoyes=args.autoyes)
|
||||||
|
|
||||||
|
def main(argv):
|
||||||
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
|
|
||||||
|
parser.add_argument('transformation', help='python command using x as variable name')
|
||||||
|
parser.add_argument('-y', '--yes', dest='autoyes', action='store_true', help='accept results without confirming')
|
||||||
|
parser.set_defaults(func=brename_argparse)
|
||||||
|
|
||||||
|
args = parser.parse_args(argv)
|
||||||
|
args.func(args)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
raise SystemExit(main(sys.argv[1:]))
|
24
breplace.py
Normal file
24
breplace.py
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
'''
|
||||||
|
Batch rename files by replacing the first argument with the second.
|
||||||
|
'''
|
||||||
|
import argparse
|
||||||
|
import brename
|
||||||
|
import sys
|
||||||
|
|
||||||
|
def breplace_argparse(args):
|
||||||
|
command = f'x.replace("{args.replace_from}", "{args.replace_to}")'
|
||||||
|
brename.brename(command, autoyes=args.autoyes)
|
||||||
|
|
||||||
|
def main(argv):
|
||||||
|
parser = argparse.ArgumentParser(__doc__)
|
||||||
|
|
||||||
|
parser.add_argument('replace_from')
|
||||||
|
parser.add_argument('replace_to')
|
||||||
|
parser.add_argument('-y', '--yes', dest='autoyes', action='store_true', help='accept results without confirming')
|
||||||
|
parser.set_defaults(func=breplace_argparse)
|
||||||
|
|
||||||
|
args = parser.parse_args(argv)
|
||||||
|
args.func(args)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
raise SystemExit(main(sys.argv[1:]))
|
14
clipboard.py
Normal file
14
clipboard.py
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
'''
|
||||||
|
Dump the clipboard to stdout. I use this for redirecting to files.
|
||||||
|
'''
|
||||||
|
import pyperclip
|
||||||
|
import sys
|
||||||
|
|
||||||
|
if len(sys.argv) > 1:
|
||||||
|
from voussoirkit import clipext
|
||||||
|
stuff = clipext.resolve(sys.argv[1])
|
||||||
|
pyperclip.copy(stuff)
|
||||||
|
else:
|
||||||
|
text = pyperclip.paste()
|
||||||
|
text = text.replace('\r', '')
|
||||||
|
print(text)
|
65
contentreplace.py
Normal file
65
contentreplace.py
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
import argparse
|
||||||
|
import codecs
|
||||||
|
import glob
|
||||||
|
import sys
|
||||||
|
import pyperclip
|
||||||
|
|
||||||
|
|
||||||
|
def contentreplace(filename, replace_from, replace_to, autoyes=False):
|
||||||
|
f = open(filename, 'r', encoding='utf-8')
|
||||||
|
with f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
occurances = content.count(replace_from)
|
||||||
|
|
||||||
|
print(f'{filename}: Found {occurances} occurences.')
|
||||||
|
if occurances == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
permission = autoyes or (input('Replace? ').lower() in ('y', 'yes'))
|
||||||
|
if not permission:
|
||||||
|
return
|
||||||
|
|
||||||
|
content = content.replace(replace_from, replace_to)
|
||||||
|
|
||||||
|
f = open(filename, 'w', encoding='utf-8')
|
||||||
|
with f:
|
||||||
|
f.write(content)
|
||||||
|
|
||||||
|
def contentreplace_argparse(args):
|
||||||
|
filenames = glob.glob(args.filename_glob)
|
||||||
|
|
||||||
|
if args.clip_prompt:
|
||||||
|
replace_from = input('Ready from')
|
||||||
|
if not replace_from:
|
||||||
|
replace_from = pyperclip.paste()
|
||||||
|
replace_to = input('Ready to')
|
||||||
|
if not replace_to:
|
||||||
|
replace_to = pyperclip.paste()
|
||||||
|
else:
|
||||||
|
replace_from = codecs.decode(args.replace_from, 'unicode_escape')
|
||||||
|
replace_to = codecs.decode(args.replace_to, 'unicode_escape')
|
||||||
|
|
||||||
|
for filename in filenames:
|
||||||
|
contentreplace(
|
||||||
|
filename,
|
||||||
|
replace_from,
|
||||||
|
replace_to,
|
||||||
|
autoyes=args.autoyes,
|
||||||
|
)
|
||||||
|
|
||||||
|
def main(argv):
|
||||||
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
|
|
||||||
|
parser.add_argument('filename_glob')
|
||||||
|
parser.add_argument('replace_from')
|
||||||
|
parser.add_argument('replace_to')
|
||||||
|
parser.add_argument('-y', '--yes', dest='autoyes', action='store_true', help='accept results without confirming')
|
||||||
|
parser.add_argument('--clip_prompt', dest='clip_prompt', action='store_true')
|
||||||
|
parser.set_defaults(func=contentreplace_argparse)
|
||||||
|
|
||||||
|
args = parser.parse_args(argv)
|
||||||
|
args.func(args)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
raise SystemExit(main(sys.argv[1:]))
|
31
crlf.py
Normal file
31
crlf.py
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
'''
|
||||||
|
Convert LF line endings to CRLF.
|
||||||
|
'''
|
||||||
|
|
||||||
|
import glob
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from voussoirkit import pipeable
|
||||||
|
|
||||||
|
|
||||||
|
CR = b'\x0D'
|
||||||
|
LF = b'\x0A'
|
||||||
|
CRLF = CR + LF
|
||||||
|
|
||||||
|
def crlf(filename):
|
||||||
|
with open(filename, 'rb') as handle:
|
||||||
|
content = handle.read()
|
||||||
|
content = content.replace(CRLF, LF)
|
||||||
|
content = content.replace(LF, CRLF)
|
||||||
|
with open(filename, 'wb') as handle:
|
||||||
|
handle.write(content)
|
||||||
|
|
||||||
|
def main(args):
|
||||||
|
for line in pipeable.go(args, strip=True, skip_blank=True):
|
||||||
|
for filename in glob.glob(line):
|
||||||
|
pipeable.output(filename)
|
||||||
|
crlf(filename)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
raise SystemExit(main(sys.argv[1:]))
|
||||||
|
|
21
crop.py
Normal file
21
crop.py
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
filename = sys.argv[1]
|
||||||
|
(name, extension) = os.path.splitext(filename)
|
||||||
|
newname = '%s_cropped%s' % (name, extension)
|
||||||
|
|
||||||
|
crops = sys.argv[2:]
|
||||||
|
crops = ' '.join(crops)
|
||||||
|
crops = crops.replace(',', ' ')
|
||||||
|
crops = crops.replace(' ', ' ')
|
||||||
|
crops = crops.split(' ')
|
||||||
|
crops = [int(x) for x in crops]
|
||||||
|
crops = list(crops)
|
||||||
|
print(crops)
|
||||||
|
i = Image.open(filename)
|
||||||
|
if len(crops) == 2:
|
||||||
|
crops.extend(i.size)
|
||||||
|
i = i.crop(crops)
|
||||||
|
i.save(newname, quality=100)
|
7
do_cmd.py
Normal file
7
do_cmd.py
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import os
|
||||||
|
|
||||||
|
from voussoirkit import pipeable
|
||||||
|
|
||||||
|
|
||||||
|
for line in pipeable.go():
|
||||||
|
os.system(line)
|
73
drawn_quartered.py
Normal file
73
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:])
|
28
eval.py
Normal file
28
eval.py
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
import glob
|
||||||
|
import math
|
||||||
|
import os
|
||||||
|
import random
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from voussoirkit import pipeable
|
||||||
|
|
||||||
|
|
||||||
|
lines = pipeable.input(sys.argv[1])
|
||||||
|
pattern = sys.argv[2]
|
||||||
|
|
||||||
|
def quote(s):
|
||||||
|
return '"%s"' % s
|
||||||
|
|
||||||
|
def apostrophe(s):
|
||||||
|
return "'%s'" % s
|
||||||
|
|
||||||
|
def random_hex(length=12):
|
||||||
|
randbytes = os.urandom(math.ceil(length / 2))
|
||||||
|
token = ''.join('{:02x}'.format(x) for x in randbytes)
|
||||||
|
token = token[:length]
|
||||||
|
return token
|
||||||
|
|
||||||
|
for line in lines:
|
||||||
|
x = line
|
||||||
|
pipeable.output(eval(pattern))
|
26
filenameorderedrandomness.pyw
Normal file
26
filenameorderedrandomness.pyw
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
'''
|
||||||
|
Drag multiple files on top of this .py file. The first file will have its
|
||||||
|
name randomly scrambled into 12 digits. The others will increment that number b
|
||||||
|
1.
|
||||||
|
'''
|
||||||
|
|
||||||
|
print('hi')
|
||||||
|
import os
|
||||||
|
import random
|
||||||
|
import string
|
||||||
|
import sys
|
||||||
|
|
||||||
|
argv = sys.argv[1:]
|
||||||
|
print(''.join(c for c in argv if c in string.printable))
|
||||||
|
|
||||||
|
randname = [random.choice(string.digits) for x in range(12)]
|
||||||
|
randname = int(''.join(randname))
|
||||||
|
for filepath in argv:
|
||||||
|
folder = os.path.dirname(filepath)
|
||||||
|
basename = os.path.basename(filepath)
|
||||||
|
extension = os.path.splitext(basename)[1]
|
||||||
|
newname = str(randname).rjust(12, '0')
|
||||||
|
randname += 1
|
||||||
|
newname = '%s\\%s%s' % (folder, newname, extension)
|
||||||
|
os.rename(filepath, newname)
|
||||||
|
print('%s -> %s' % (filepath, newname))
|
23
filenamescramble.py
Normal file
23
filenamescramble.py
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
'''
|
||||||
|
Drag a file on top of this .py file, and it will have its
|
||||||
|
filename scrambled into a combination of 16 upper and lowercase
|
||||||
|
letters.
|
||||||
|
'''
|
||||||
|
|
||||||
|
import os
|
||||||
|
import random
|
||||||
|
import string
|
||||||
|
import sys
|
||||||
|
|
||||||
|
argv = sys.argv[1:]
|
||||||
|
print(''.join(c for c in argv if c in string.printable))
|
||||||
|
for filepath in argv:
|
||||||
|
folder = os.path.dirname(filepath)
|
||||||
|
basename = os.path.basename(filepath)
|
||||||
|
extension = os.path.splitext(basename)[1]
|
||||||
|
newname = [random.choice(string.ascii_lowercase) for x in range(9)]
|
||||||
|
newname = ''.join(newname)
|
||||||
|
newname = newname + extension
|
||||||
|
newname = os.path.join(folder, newname)
|
||||||
|
#os.rename(filepath, newname)
|
||||||
|
print('%s -> %s' % (filepath, newname))
|
21
filenamescrambleint.pyw
Normal file
21
filenamescrambleint.pyw
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
'''
|
||||||
|
Drag a file on top of this .py file, and it will have its
|
||||||
|
filename scrambled into a combination of 12 digits.
|
||||||
|
'''
|
||||||
|
|
||||||
|
import os
|
||||||
|
import random
|
||||||
|
import string
|
||||||
|
import sys
|
||||||
|
|
||||||
|
argv = sys.argv[1:]
|
||||||
|
print(''.join(c for c in argv if c in string.printable))
|
||||||
|
for filepath in argv:
|
||||||
|
folder = os.path.dirname(filepath)
|
||||||
|
basename = os.path.basename(filepath)
|
||||||
|
extension = os.path.splitext(basename)[1]
|
||||||
|
newname = [random.choice(string.digits) for x in range(12)]
|
||||||
|
newname = ''.join(newname)
|
||||||
|
newname = '%s\\%s%s' % (folder, newname, extension)
|
||||||
|
os.rename(filepath, newname)
|
||||||
|
print('%s -> %s' % (filepath, newname))
|
112
fileprefix.py
Normal file
112
fileprefix.py
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
'''
|
||||||
|
When you run this file from the commandline given a single argument, all
|
||||||
|
of the files in the current working directory will be renamed in the format
|
||||||
|
{argument}_{count} where argument is your cmd input and count is a zero-padded
|
||||||
|
integer that counts each file in the folder.
|
||||||
|
'''
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
import random
|
||||||
|
import string
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from voussoirkit import pathclass
|
||||||
|
from voussoirkit import safeprint
|
||||||
|
|
||||||
|
IGNORE_EXTENSIONS = ['py', 'lnk', 'ini']
|
||||||
|
|
||||||
|
|
||||||
|
def natural_sorter(x):
|
||||||
|
'''
|
||||||
|
http://stackoverflow.com/a/11150413
|
||||||
|
'''
|
||||||
|
convert = lambda text: int(text) if text.isdigit() else text.lower()
|
||||||
|
alphanum_key = lambda key: [convert(c) for c in re.split('([0-9]+)', key)]
|
||||||
|
return alphanum_key(x)
|
||||||
|
|
||||||
|
def fileprefix(
|
||||||
|
prefix='',
|
||||||
|
sep='_',
|
||||||
|
ctime=False,
|
||||||
|
autoyes=False,
|
||||||
|
):
|
||||||
|
current_directory = pathclass.Path('.')
|
||||||
|
|
||||||
|
prefix = prefix.strip()
|
||||||
|
if prefix == ':':
|
||||||
|
prefix = current_directory.basename + ' - '
|
||||||
|
elif prefix != '':
|
||||||
|
prefix += sep
|
||||||
|
|
||||||
|
filepaths = current_directory.listdir()
|
||||||
|
filepaths = [f for f in filepaths if f.is_file]
|
||||||
|
filepaths = [f for f in filepaths if f.extension.lower() not in IGNORE_EXTENSIONS]
|
||||||
|
|
||||||
|
try:
|
||||||
|
pyfile = pathclass.Path(__file__)
|
||||||
|
filepaths.remove(pyfile)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# trust me on this.
|
||||||
|
zeropadding = len(str(len(filepaths)))
|
||||||
|
zeropadding = max(2, zeropadding)
|
||||||
|
zeropadding = str(zeropadding)
|
||||||
|
|
||||||
|
format = '{{prefix}}{{index:0{pad}d}}{{extension}}'.format(pad=zeropadding)
|
||||||
|
|
||||||
|
if ctime:
|
||||||
|
print('Sorting by time')
|
||||||
|
filepaths.sort(key=lambda x: x.stat.st_ctime)
|
||||||
|
else:
|
||||||
|
print('Sorting by name')
|
||||||
|
filepaths.sort(key=lambda x: natural_sorter(x.basename))
|
||||||
|
|
||||||
|
rename_pairs = []
|
||||||
|
|
||||||
|
for (index, filepath) in enumerate(filepaths):
|
||||||
|
extension = filepath.extension
|
||||||
|
if extension != '':
|
||||||
|
extension = '.' + extension
|
||||||
|
|
||||||
|
newname = format.format(prefix=prefix, index=index, extension=extension)
|
||||||
|
if filepath.basename != newname:
|
||||||
|
rename_pairs.append((filepath.absolute_path, newname))
|
||||||
|
for (oldname, newname) in rename_pairs:
|
||||||
|
message = f'{oldname} -> {newname}'
|
||||||
|
safeprint.safeprint(message)
|
||||||
|
|
||||||
|
ok = autoyes
|
||||||
|
if not ok:
|
||||||
|
print('Is this correct? y/n')
|
||||||
|
ok = input('>').lower() in ('y', 'yes', 'yeehaw')
|
||||||
|
|
||||||
|
if ok:
|
||||||
|
for (oldname, newname) in rename_pairs:
|
||||||
|
os.rename(oldname, newname)
|
||||||
|
|
||||||
|
|
||||||
|
def fileprefix_argparse(args):
|
||||||
|
return fileprefix(
|
||||||
|
prefix=args.prefix,
|
||||||
|
sep=args.sep,
|
||||||
|
ctime=args.ctime,
|
||||||
|
autoyes=args.autoyes,
|
||||||
|
)
|
||||||
|
|
||||||
|
def main(argv):
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
|
||||||
|
parser.add_argument('prefix', nargs='?', default='')
|
||||||
|
parser.add_argument('--sep', dest='sep', default=' ', help='the character between the prefix and remainder')
|
||||||
|
parser.add_argument('--ctime', dest='ctime', action='store_true', help='sort by ctime instead of filename')
|
||||||
|
parser.add_argument('-y', '--yes', dest='autoyes', action='store_true', help='accept results without confirming')
|
||||||
|
parser.set_defaults(func=fileprefix_argparse)
|
||||||
|
|
||||||
|
args = parser.parse_args(argv)
|
||||||
|
args.func(args)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main(sys.argv[1:])
|
57
filepull.py
Normal file
57
filepull.py
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
'''
|
||||||
|
Pull all of the files in nested directories into the current directory.
|
||||||
|
'''
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from voussoirkit import spinal
|
||||||
|
|
||||||
|
def filepull(pull_from='.', autoyes=False):
|
||||||
|
files = list(spinal.walk_generator(pull_from))
|
||||||
|
cwd = os.getcwd()
|
||||||
|
files = [f for f in files if os.path.split(f.absolute_path)[0] != cwd]
|
||||||
|
|
||||||
|
if len(files) == 0:
|
||||||
|
print('No files to move')
|
||||||
|
return
|
||||||
|
|
||||||
|
duplicate_count = {}
|
||||||
|
for f in files:
|
||||||
|
basename = f.basename
|
||||||
|
duplicate_count.setdefault(basename, [])
|
||||||
|
duplicate_count[basename].append(f.absolute_path)
|
||||||
|
|
||||||
|
duplicates = ['\n'.join(sorted(copies)) for (basename, copies) in duplicate_count.items() if len(copies) > 1]
|
||||||
|
duplicates = sorted(duplicates)
|
||||||
|
if len(duplicates) > 0:
|
||||||
|
raise Exception('duplicate names:\n' + '\n'.join(duplicates))
|
||||||
|
|
||||||
|
for f in files:
|
||||||
|
print(f.basename)
|
||||||
|
|
||||||
|
ok = autoyes
|
||||||
|
if not ok:
|
||||||
|
print('Move %d files?' % len(files))
|
||||||
|
ok = input('> ').lower() in ['y', 'yes']
|
||||||
|
|
||||||
|
if ok:
|
||||||
|
for f in files:
|
||||||
|
local = os.path.join('.', f.basename)
|
||||||
|
os.rename(f.absolute_path, local)
|
||||||
|
|
||||||
|
def filepull_argparse(args):
|
||||||
|
filepull(pull_from=args.pull_from, autoyes=args.autoyes)
|
||||||
|
|
||||||
|
def main(argv):
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
|
||||||
|
parser.add_argument('pull_from', nargs='?', default='.')
|
||||||
|
parser.add_argument('-y', '--yes', dest='autoyes', action='store_true')
|
||||||
|
parser.set_defaults(func=filepull_argparse)
|
||||||
|
|
||||||
|
args = parser.parse_args(argv)
|
||||||
|
args.func(args)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main(sys.argv[1:])
|
5
fluidsynth.bat
Normal file
5
fluidsynth.bat
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
@echo off
|
||||||
|
set filename=%1
|
||||||
|
set filename=%filename:.mid=.wav%
|
||||||
|
|
||||||
|
D:\software\fluidsynth\fluidsynth.exe --gain 1 -F %filename% D:\software\fluidsynth\Scc1t2.sf2 %1%
|
14
forline.py
Normal file
14
forline.py
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from voussoirkit import clipext
|
||||||
|
|
||||||
|
text = sys.argv[1]
|
||||||
|
command = sys.argv[2:]
|
||||||
|
command = ['"%s"' % x if (' ' in x or x == '%x') else x for x in command]
|
||||||
|
command = ' '.join(command)
|
||||||
|
text = clipext.resolve(text)
|
||||||
|
|
||||||
|
for line in text.splitlines():
|
||||||
|
thiscomm = command.replace('%x', line)
|
||||||
|
os.system(thiscomm)
|
142
getcrx.py
Normal file
142
getcrx.py
Normal file
|
@ -0,0 +1,142 @@
|
||||||
|
import argparse
|
||||||
|
import io
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import requests
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import traceback
|
||||||
|
import zipfile
|
||||||
|
|
||||||
|
from voussoirkit import clipext
|
||||||
|
|
||||||
|
FILENAME_BADCHARS = '\\/:*?<>|"'
|
||||||
|
|
||||||
|
WEBSTORE_URL = 'https://chrome.google.com/webstore/detail/x/{extension_id}'
|
||||||
|
CRX_URL = 'https://clients2.google.com/service/update2/crx?response=redirect&prodversion=59.0&x=id%3D{extension_id}%26installsource%3Dondemand%26uc'
|
||||||
|
|
||||||
|
def sanitize_filename(name):
|
||||||
|
for c in FILENAME_BADCHARS:
|
||||||
|
name = name.replace(c, '-')
|
||||||
|
return name
|
||||||
|
|
||||||
|
def prompt_permission(prompt):
|
||||||
|
answer = input(prompt)
|
||||||
|
return answer.lower() in {'yes', 'y'}
|
||||||
|
|
||||||
|
def get_webstore_name_version(extension_id):
|
||||||
|
url = WEBSTORE_URL.format(extension_id=extension_id)
|
||||||
|
response = requests.get(url)
|
||||||
|
try:
|
||||||
|
name = response.text
|
||||||
|
name = name.split('meta property="og:title" content="')[1]
|
||||||
|
name = name.split('"')[0]
|
||||||
|
except IndexError:
|
||||||
|
name = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
version = response.text
|
||||||
|
version = version.split('meta itemprop="version" content="')[1]
|
||||||
|
version = version.split('"')[0]
|
||||||
|
except IndexError:
|
||||||
|
version = None
|
||||||
|
|
||||||
|
return (name, version)
|
||||||
|
|
||||||
|
def get_crx_name_version(crx_bytes):
|
||||||
|
crx_handle = io.BytesIO(crx_bytes)
|
||||||
|
crx_archive = zipfile.ZipFile(crx_handle)
|
||||||
|
manifest = json.loads(crx_archive.read('manifest.json'))
|
||||||
|
name = manifest.get('name', None)
|
||||||
|
version = manifest.get('version', None)
|
||||||
|
return (name, version)
|
||||||
|
|
||||||
|
def getcrx(extension_id, auto_overwrite=None):
|
||||||
|
url = CRX_URL.format(extension_id=extension_id)
|
||||||
|
response = requests.get(url)
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
|
(name, version) = get_webstore_name_version(extension_id)
|
||||||
|
if name is None or version is None:
|
||||||
|
(crx_name, crx_ver) = get_crx_name_version(response.content)
|
||||||
|
name = name or crx_name
|
||||||
|
version = version or crx_version
|
||||||
|
|
||||||
|
name = name or extension_id
|
||||||
|
version = version or time.strftime('%Y%m%d')
|
||||||
|
|
||||||
|
version = version or response.url.split('/')[-1]
|
||||||
|
|
||||||
|
crx_filename = '{name} ({id}) [{version}]'
|
||||||
|
crx_filename = crx_filename.format(
|
||||||
|
name=name,
|
||||||
|
id=extension_id,
|
||||||
|
version=version,
|
||||||
|
)
|
||||||
|
|
||||||
|
if not crx_filename.endswith('.crx'):
|
||||||
|
crx_filename += '.crx'
|
||||||
|
|
||||||
|
crx_filename = sanitize_filename(crx_filename)
|
||||||
|
if os.path.isfile(crx_filename):
|
||||||
|
if auto_overwrite is None:
|
||||||
|
message = '"%s" already exists. Overwrite?' % crx_filename
|
||||||
|
permission = prompt_permission(message)
|
||||||
|
else:
|
||||||
|
permission = False
|
||||||
|
else:
|
||||||
|
permission = True
|
||||||
|
|
||||||
|
if permission:
|
||||||
|
crx_handle = open(crx_filename, 'wb')
|
||||||
|
crx_handle.write(response.content)
|
||||||
|
print('Downloaded "%s".' % crx_filename)
|
||||||
|
|
||||||
|
def getcrx_argparse(args):
|
||||||
|
extension_ids = []
|
||||||
|
|
||||||
|
if len(args.extension_ids) == 1:
|
||||||
|
extension_ids.extend(clipext.resolve(args.extension_ids[0], split_lines=True))
|
||||||
|
|
||||||
|
elif args.extension_ids:
|
||||||
|
extension_ids.extend(args.extension_ids)
|
||||||
|
|
||||||
|
if args.file:
|
||||||
|
with open(args.file, 'r') as handle:
|
||||||
|
lines = handle.readlines()
|
||||||
|
extension_ids.extend(lines)
|
||||||
|
|
||||||
|
extension_ids = [x.split('/')[-1].strip() for x in extension_ids]
|
||||||
|
|
||||||
|
if args.overwrite and not args.dont_overwrite:
|
||||||
|
auto_overwrite = True
|
||||||
|
elif args.dont_overwrite and not args.overwrite:
|
||||||
|
auto_overwrite = False
|
||||||
|
else:
|
||||||
|
auto_overwrite = None
|
||||||
|
|
||||||
|
for extension_id in extension_ids:
|
||||||
|
try:
|
||||||
|
getcrx(extension_id, auto_overwrite=auto_overwrite)
|
||||||
|
except Exception:
|
||||||
|
if args.fail_early:
|
||||||
|
raise
|
||||||
|
else:
|
||||||
|
traceback.print_exc()
|
||||||
|
print('Resuming...')
|
||||||
|
|
||||||
|
def main(argv):
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
|
||||||
|
parser.add_argument('extension_ids', nargs='*', default=None)
|
||||||
|
parser.add_argument('--file', dest='file', default=None)
|
||||||
|
parser.add_argument('--fail_early', dest='fail_early', action='store_true')
|
||||||
|
parser.add_argument('--overwrite', dest='overwrite', action='store_true')
|
||||||
|
parser.add_argument('--dont_overwrite', dest='dont_overwrite', action='store_true')
|
||||||
|
parser.set_defaults(func=getcrx_argparse)
|
||||||
|
|
||||||
|
args = parser.parse_args(argv)
|
||||||
|
args.func(args)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main(sys.argv[1:])
|
3
gif_mp4.bat
Normal file
3
gif_mp4.bat
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
set basename=%~n1
|
||||||
|
set newname="%basename%.mp4"
|
||||||
|
ffmpeg -i %1 -pix_fmt yuv420p -preset placebo -vf "scale=trunc(iw/2)*2:trunc(ih/2)*2" %newname%
|
16
head.py
Normal file
16
head.py
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
'''
|
||||||
|
Perform a HEAD request and print the results.
|
||||||
|
'''
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from voussoirkit import clipext
|
||||||
|
|
||||||
|
urls = clipext.resolve(sys.argv[1], split_lines=True)
|
||||||
|
for url in urls:
|
||||||
|
page = requests.head(url)
|
||||||
|
headers = dict(page.headers)
|
||||||
|
headers = json.dumps(headers, indent=4, sort_keys=True)
|
||||||
|
print(page)
|
||||||
|
print(headers)
|
63
hexdump.py
Normal file
63
hexdump.py
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
import argparse
|
||||||
|
import sys
|
||||||
|
|
||||||
|
DEFAULT_WIDTH = 16
|
||||||
|
|
||||||
|
def hexy(i, width=0):
|
||||||
|
return hex(i)[2:].upper().rjust(width, '0')
|
||||||
|
|
||||||
|
def hexdump(handle, width=DEFAULT_WIDTH, ellipse=False, start=None, end=None):
|
||||||
|
if start is not None:
|
||||||
|
start = int(start, 16)
|
||||||
|
handle.seek(start)
|
||||||
|
address = start
|
||||||
|
else:
|
||||||
|
address = 0
|
||||||
|
if end is not None:
|
||||||
|
end = int(end, 16)
|
||||||
|
|
||||||
|
did_ellipse = False
|
||||||
|
previous_line = None
|
||||||
|
while True:
|
||||||
|
if end is not None:
|
||||||
|
if address > end:
|
||||||
|
break
|
||||||
|
this_width = min(width, end - address)
|
||||||
|
else:
|
||||||
|
this_width = width
|
||||||
|
line = handle.read(this_width)
|
||||||
|
if not line:
|
||||||
|
break
|
||||||
|
line = [hexy(x, 2) for x in line]
|
||||||
|
line = ' '.join(line)
|
||||||
|
if ellipse:
|
||||||
|
if line == previous_line:
|
||||||
|
if not did_ellipse:
|
||||||
|
print('...')
|
||||||
|
did_ellipse = True
|
||||||
|
address += width
|
||||||
|
continue
|
||||||
|
previous_line = line
|
||||||
|
print('%s | ' % hexy(address, 8), end='', flush=False)
|
||||||
|
print(line)
|
||||||
|
address += width
|
||||||
|
|
||||||
|
def hexdump_argparse(args):
|
||||||
|
handle = open(args.filename, 'rb')
|
||||||
|
return hexdump(handle, width=args.width, ellipse=args.ellipse, start=args.start, end=args.end)
|
||||||
|
|
||||||
|
def main(argv):
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
|
||||||
|
parser.add_argument('filename')
|
||||||
|
parser.add_argument('--width', dest='width', default=DEFAULT_WIDTH, type=int)
|
||||||
|
parser.add_argument('--start', dest='start', default=None)
|
||||||
|
parser.add_argument('--end', dest='end', default=None)
|
||||||
|
parser.add_argument('--ellipse', dest='ellipse', action='store_true')
|
||||||
|
parser.set_defaults(func=hexdump_argparse)
|
||||||
|
|
||||||
|
args = parser.parse_args(argv)
|
||||||
|
args.func(args)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main(sys.argv[1:])
|
46
hexpng.py
Normal file
46
hexpng.py
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
'''
|
||||||
|
Generate a png file of a solid color, specified by a hex code.
|
||||||
|
'''
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
import PIL.Image
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def full_hex(h):
|
||||||
|
h = h.replace('#', '')
|
||||||
|
if len(h) in [3, 4]:
|
||||||
|
h = ''.join([c * 2 for c in h])
|
||||||
|
if len(h) == 6:
|
||||||
|
h += 'ff'
|
||||||
|
return h
|
||||||
|
|
||||||
|
def hex_to_rgb(h):
|
||||||
|
rgb = [int(h[(2*i):(2*i)+2], 16) for i in range(len(h)//2)]
|
||||||
|
return tuple(rgb)
|
||||||
|
|
||||||
|
def make_hexpng(h, width=1, height=1):
|
||||||
|
h = full_hex(h)
|
||||||
|
rgb = hex_to_rgb(h)
|
||||||
|
filename = f'{h}.png'
|
||||||
|
i = PIL.Image.new('RGBA', size=[width, height], color=rgb)
|
||||||
|
print(filename)
|
||||||
|
i.save(filename)
|
||||||
|
|
||||||
|
def hexpng_argparse(args):
|
||||||
|
make_hexpng(args.hex_value, width=args.width, height=args.height)
|
||||||
|
|
||||||
|
def main(argv):
|
||||||
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
|
|
||||||
|
parser.add_argument('hex_value')
|
||||||
|
parser.add_argument('--width', dest='width', type=int, default=1)
|
||||||
|
parser.add_argument('--height', dest='height', type=int, default=1)
|
||||||
|
parser.set_defaults(func=hexpng_argparse)
|
||||||
|
|
||||||
|
args = parser.parse_args(argv)
|
||||||
|
args.func(args)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
raise SystemExit(main(sys.argv[1:]))
|
57
hms_s.py
Normal file
57
hms_s.py
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
import math
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from voussoirkit import pipeable
|
||||||
|
|
||||||
|
|
||||||
|
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 seconds_to_hms(seconds):
|
||||||
|
'''
|
||||||
|
Convert integer number of seconds to an hh:mm:ss string.
|
||||||
|
Only the necessary fields are used.
|
||||||
|
'''
|
||||||
|
(minutes, seconds) = divmod(seconds, 60)
|
||||||
|
(hours, minutes) = divmod(minutes, 60)
|
||||||
|
|
||||||
|
parts = []
|
||||||
|
if hours:
|
||||||
|
parts.append(f'{int(hours):02d}')
|
||||||
|
if minutes:
|
||||||
|
parts.append(f'{int(minutes):02d}')
|
||||||
|
if seconds == int(seconds):
|
||||||
|
parts.append(f'{int(seconds):02d}')
|
||||||
|
else:
|
||||||
|
parts.append(f'{seconds:0.3f}')
|
||||||
|
hms = ':'.join(parts)
|
||||||
|
|
||||||
|
return hms
|
||||||
|
|
||||||
|
def main(args):
|
||||||
|
for line in pipeable.go(args, strip=True, skip_blank=True):
|
||||||
|
if ':' in line:
|
||||||
|
line = hms_to_seconds(line)
|
||||||
|
else:
|
||||||
|
line = float(line)
|
||||||
|
if line > 60:
|
||||||
|
line = seconds_to_hms(line)
|
||||||
|
|
||||||
|
pipeable.output(f'{line}')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
raise SystemExit(main(sys.argv[1:]))
|
75
kbps.py
Normal file
75
kbps.py
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
'''
|
||||||
|
Find time, filesize, or bitrate, given two of the three.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
kbps.py --time 1:00:00 --size 2g
|
||||||
|
kbps.py --time 1:00:00 --kbps 4660
|
||||||
|
kbps.py --size 2g --kpbps 4660
|
||||||
|
'''
|
||||||
|
import argparse
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from voussoirkit import bytestring
|
||||||
|
|
||||||
|
def hms_s(hms):
|
||||||
|
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 += int(hms[0])
|
||||||
|
return seconds
|
||||||
|
|
||||||
|
def s_hms(s):
|
||||||
|
(minutes, seconds) = divmod(s, 60)
|
||||||
|
(hours, minutes) = divmod(minutes, 60)
|
||||||
|
return '%02d:%02d:%02d' % (hours, minutes, seconds)
|
||||||
|
|
||||||
|
def kbps(time=None, size=None, kbps=None):
|
||||||
|
if [time, size, kbps].count(None) != 1:
|
||||||
|
raise ValueError('Incorrect number of unknowns')
|
||||||
|
|
||||||
|
if size is None:
|
||||||
|
seconds = hms_s(time)
|
||||||
|
kibs = int(kbps) / 8
|
||||||
|
size = kibs * 1024
|
||||||
|
size *= seconds
|
||||||
|
out = bytestring.bytestring(size)
|
||||||
|
return out
|
||||||
|
|
||||||
|
if time is None:
|
||||||
|
size = bytestring.parsebytes(size)
|
||||||
|
kilobits = size / 128
|
||||||
|
time = kilobits / int(kbps)
|
||||||
|
return s_hms(time)
|
||||||
|
|
||||||
|
if kbps is None:
|
||||||
|
seconds = hms_s(time)
|
||||||
|
size = bytestring.parsebytes(size)
|
||||||
|
kibs = size / 1024
|
||||||
|
kilobits = kibs * 8
|
||||||
|
kbps = kilobits / seconds
|
||||||
|
kbps = '%d kbps' % int(round(kbps))
|
||||||
|
return kbps
|
||||||
|
|
||||||
|
def example_argparse(args):
|
||||||
|
print(kbps(time=args.time, size=args.size, kbps=args.kbps))
|
||||||
|
|
||||||
|
def main(argv):
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
|
||||||
|
parser.add_argument('-t', '--time', dest='time', default=None)
|
||||||
|
parser.add_argument('-s', '--size', dest='size', default=None)
|
||||||
|
parser.add_argument('-k', '--kbps', dest='kbps', default=None)
|
||||||
|
parser.set_defaults(func=example_argparse)
|
||||||
|
|
||||||
|
args = parser.parse_args(argv)
|
||||||
|
args.func(args)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main(sys.argv[1:])
|
12
linenumbers.py
Normal file
12
linenumbers.py
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from voussoirkit import clipext
|
||||||
|
|
||||||
|
if len(sys.argv) == 1:
|
||||||
|
sys.argv.append('!i')
|
||||||
|
text = clipext.resolve(sys.argv[1])
|
||||||
|
lines = text.splitlines()
|
||||||
|
digits = len(str(len(lines)))
|
||||||
|
form = '{no:>0%d} | {line}' % digits
|
||||||
|
for (index, line) in enumerate(lines):
|
||||||
|
print(form.format(no=index+1, line=line))
|
7
lowercase.py
Normal file
7
lowercase.py
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from voussoirkit import pipeable
|
||||||
|
|
||||||
|
|
||||||
|
for line in pipeable.go():
|
||||||
|
pipeable.output(line.lower())
|
171
mp3slice.py
Normal file
171
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:])
|
46
randomfile.py
Normal file
46
randomfile.py
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
import math
|
||||||
|
import random
|
||||||
|
import sys
|
||||||
|
from voussoirkit import bytestring
|
||||||
|
CHUNK_SIZE = 512 * (2 ** 10)
|
||||||
|
def listget(li, index, fallback=None):
|
||||||
|
try:
|
||||||
|
return li[index]
|
||||||
|
except IndexError:
|
||||||
|
return fallback
|
||||||
|
|
||||||
|
def rid(length=8):
|
||||||
|
import random
|
||||||
|
bits = length * 4
|
||||||
|
bits = random.getrandbits(bits)
|
||||||
|
identifier = '{:02x}'.format(bits).rjust(length, '0')
|
||||||
|
return identifier
|
||||||
|
|
||||||
|
def make_randomfile(length, filename=None):
|
||||||
|
if filename is None:
|
||||||
|
filename = rid(8) + '.txt'
|
||||||
|
chunks = math.ceil(length / CHUNK_SIZE)
|
||||||
|
written = 0
|
||||||
|
f = open(filename, 'w')
|
||||||
|
for x in range(chunks):
|
||||||
|
b = min(CHUNK_SIZE, length-written)
|
||||||
|
f.write(rid(b))
|
||||||
|
written += b
|
||||||
|
f.close()
|
||||||
|
print('Created %s' % filename)
|
||||||
|
|
||||||
|
|
||||||
|
bytes = listget(sys.argv, 1, None)
|
||||||
|
if bytes is None:
|
||||||
|
bytes = 2 ** 10
|
||||||
|
else:
|
||||||
|
bytes = bytestring.parsebytes(bytes)
|
||||||
|
|
||||||
|
filecount = 1
|
||||||
|
filename = listget(sys.argv, 2, None)
|
||||||
|
if filename is not None and filename.isdigit():
|
||||||
|
filecount = int(filename)
|
||||||
|
filename = None
|
||||||
|
|
||||||
|
for x in range(filecount):
|
||||||
|
make_randomfile(bytes, filename)
|
48
rejpg.py
Normal file
48
rejpg.py
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
'''
|
||||||
|
Recompress all jpg images in the current directory.
|
||||||
|
Add /r to do nested directories as well.
|
||||||
|
'''
|
||||||
|
|
||||||
|
from voussoirkit import bytestring
|
||||||
|
import io
|
||||||
|
import os
|
||||||
|
import PIL.Image
|
||||||
|
import PIL.ImageFile
|
||||||
|
import string
|
||||||
|
import sys
|
||||||
|
|
||||||
|
PIL.ImageFile.LOAD_TRUNCATED_IMAGES = True
|
||||||
|
|
||||||
|
if '/r' in sys.argv:
|
||||||
|
from voussoirkit import spinal
|
||||||
|
walker = spinal.walk_generator()
|
||||||
|
files = list(walker)
|
||||||
|
files = [f.absolute_path for f in files]
|
||||||
|
|
||||||
|
else:
|
||||||
|
files = os.listdir()
|
||||||
|
files = [f for f in files if os.path.isfile(f)]
|
||||||
|
|
||||||
|
files = [f for f in files if any(ext in f.lower() for ext in ['.jpg', '.jpeg'])]
|
||||||
|
|
||||||
|
bytes_saved = 0
|
||||||
|
remaining_size = 0
|
||||||
|
for filename in files:
|
||||||
|
print(''.join(c for c in filename if c in string.printable))
|
||||||
|
bytesio = io.BytesIO()
|
||||||
|
i = PIL.Image.open(filename)
|
||||||
|
i.save(bytesio, format='jpeg', quality=80)
|
||||||
|
|
||||||
|
bytesio.seek(0)
|
||||||
|
new_bytes = bytesio.read()
|
||||||
|
old_size = os.path.getsize(filename)
|
||||||
|
new_size = len(new_bytes)
|
||||||
|
remaining_size += new_size
|
||||||
|
if new_size < old_size:
|
||||||
|
bytes_saved += (old_size - new_size)
|
||||||
|
f = open(filename, 'wb')
|
||||||
|
f.write(new_bytes)
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
print('Saved', bytestring.bytestring(bytes_saved))
|
||||||
|
print('Remaining are', bytestring.bytestring(remaining_size))
|
16
repeat.py
Normal file
16
repeat.py
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
'''
|
||||||
|
Repeat the input as many times as you want
|
||||||
|
|
||||||
|
> repeat "hello" 8
|
||||||
|
> echo hi | repeat !i 4
|
||||||
|
'''
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from voussoirkit import clipext
|
||||||
|
|
||||||
|
text = clipext.resolve(sys.argv[1])
|
||||||
|
repeat_times = int(sys.argv[2])
|
||||||
|
|
||||||
|
for t in range(repeat_times):
|
||||||
|
print(text)
|
12
replace.py
Normal file
12
replace.py
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import glob
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from voussoirkit import pipeable
|
||||||
|
|
||||||
|
|
||||||
|
lines = pipeable.input(sys.argv[1])
|
||||||
|
replace_from = sys.argv[2]
|
||||||
|
replace_to = sys.argv[3]
|
||||||
|
|
||||||
|
for line in lines:
|
||||||
|
pipeable.output(line.replace(replace_from, replace_to))
|
5
repr.py
Normal file
5
repr.py
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
from voussoirkit import pipeable
|
||||||
|
|
||||||
|
|
||||||
|
for line in pipeable.go():
|
||||||
|
print(repr(line))
|
52
resize.py
Normal file
52
resize.py
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
import glob
|
||||||
|
import os
|
||||||
|
from PIL import Image
|
||||||
|
import sys
|
||||||
|
|
||||||
|
def fit_into_bounds(image_width, image_height, frame_width, frame_height):
|
||||||
|
'''
|
||||||
|
Given the w+h of the image and the w+h of the frame,
|
||||||
|
return new w+h that fits the image into the frame
|
||||||
|
while maintaining the aspect ratio.
|
||||||
|
|
||||||
|
(1920, 1080, 400, 400) -> (400, 225)
|
||||||
|
'''
|
||||||
|
width_ratio = frame_width / image_width
|
||||||
|
height_ratio = frame_height / image_height
|
||||||
|
ratio = min(width_ratio, height_ratio)
|
||||||
|
|
||||||
|
new_width = int(image_width * ratio)
|
||||||
|
new_height = int(image_height * ratio)
|
||||||
|
|
||||||
|
return (new_width, new_height)
|
||||||
|
|
||||||
|
filenames = sys.argv[1]
|
||||||
|
|
||||||
|
filenames = glob.glob(filenames)
|
||||||
|
for filename in filenames:
|
||||||
|
i = Image.open(filename)
|
||||||
|
if all(x.isdigit() for x in sys.argv[2:3]):
|
||||||
|
new_x = int(sys.argv[2])
|
||||||
|
new_y = int(sys.argv[3])
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
ratio = float(sys.argv[2])
|
||||||
|
new_x = int(i.size[0] * ratio)
|
||||||
|
new_y = int(i.size[1] * ratio)
|
||||||
|
except ValueError:
|
||||||
|
print('you did it wrong')
|
||||||
|
quit()
|
||||||
|
|
||||||
|
(image_width, image_height) = i.size
|
||||||
|
|
||||||
|
if new_x == 0:
|
||||||
|
(new_x, new_y) = fit_into_bounds(image_width, image_height, 10000000, new_y)
|
||||||
|
if new_y == 0:
|
||||||
|
(new_x, new_y) = fit_into_bounds(image_width, image_height, new_x, 10000000)
|
||||||
|
|
||||||
|
print(i.size, new_x, new_y)
|
||||||
|
i = i.resize( (new_x, new_y), Image.ANTIALIAS)
|
||||||
|
suffix = '_{width}x{height}'.format(width=new_x, height=new_y)
|
||||||
|
(base, extension) = os.path.splitext(filename)
|
||||||
|
newname = base + suffix + extension
|
||||||
|
i.save(newname, quality=100)
|
7
reverse.py
Normal file
7
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)
|
25
sdate.py
Normal file
25
sdate.py
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import datetime
|
||||||
|
import time
|
||||||
|
|
||||||
|
EPOCH = datetime.datetime(
|
||||||
|
year=1993,
|
||||||
|
month=9,
|
||||||
|
day=1,
|
||||||
|
tzinfo=datetime.timezone.utc,
|
||||||
|
)
|
||||||
|
|
||||||
|
def sdate():
|
||||||
|
(day, hms) = sdate_tuple()
|
||||||
|
return f'1993 September {day} {hms}'
|
||||||
|
|
||||||
|
def sdate_tuple():
|
||||||
|
now = datetime.datetime.now(datetime.timezone.utc)
|
||||||
|
diff = now - EPOCH
|
||||||
|
day = diff.days + 1
|
||||||
|
(minutes, seconds) = divmod(diff.seconds, 60)
|
||||||
|
(hours, minutes) = divmod(minutes, 60)
|
||||||
|
hms = f'{hours:02}:{minutes:02}:{seconds:02}'
|
||||||
|
return (day, hms)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
print(sdate())
|
224
search.py
Normal file
224
search.py
Normal file
|
@ -0,0 +1,224 @@
|
||||||
|
import argparse
|
||||||
|
import fnmatch
|
||||||
|
import itertools
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import stat
|
||||||
|
import sys
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
from voussoirkit import clipext
|
||||||
|
from voussoirkit import expressionmatch
|
||||||
|
from voussoirkit import pathclass
|
||||||
|
from voussoirkit import safeprint
|
||||||
|
from voussoirkit import spinal
|
||||||
|
|
||||||
|
# Thanks georg
|
||||||
|
# http://stackoverflow.com/a/13443424
|
||||||
|
STDIN_MODE = os.fstat(sys.stdin.fileno()).st_mode
|
||||||
|
if stat.S_ISFIFO(STDIN_MODE):
|
||||||
|
STDIN_MODE = 'pipe'
|
||||||
|
else:
|
||||||
|
STDIN_MODE = 'terminal'
|
||||||
|
|
||||||
|
def all_terms_match(search_text, terms, match_function):
|
||||||
|
matches = (
|
||||||
|
(not terms['yes_all'] or all(match_function(search_text, term) for term in terms['yes_all'])) and
|
||||||
|
(not terms['yes_any'] or any(match_function(search_text, term) for term in terms['yes_any'])) and
|
||||||
|
(not terms['not_all'] or not all(match_function(search_text, term) for term in terms['not_all'])) and
|
||||||
|
(not terms['not_any'] or not any(match_function(search_text, term) for term in terms['not_any']))
|
||||||
|
)
|
||||||
|
return matches
|
||||||
|
|
||||||
|
def search(
|
||||||
|
*,
|
||||||
|
yes_all=None,
|
||||||
|
yes_any=None,
|
||||||
|
not_all=None,
|
||||||
|
not_any=None,
|
||||||
|
case_sensitive=False,
|
||||||
|
content_args=None,
|
||||||
|
do_expression=False,
|
||||||
|
do_glob=False,
|
||||||
|
do_regex=False,
|
||||||
|
line_numbers=False,
|
||||||
|
local_only=False,
|
||||||
|
text=None,
|
||||||
|
):
|
||||||
|
terms = {
|
||||||
|
'yes_all': yes_all,
|
||||||
|
'yes_any': yes_any,
|
||||||
|
'not_all': not_all,
|
||||||
|
'not_any': not_any
|
||||||
|
}
|
||||||
|
terms = {k: ([v] if isinstance(v, str) else v or []) for (k, v) in terms.items()}
|
||||||
|
#print(terms, content_args)
|
||||||
|
|
||||||
|
if all(v == [] for v in terms.values()) and not content_args:
|
||||||
|
raise ValueError('No terms supplied')
|
||||||
|
|
||||||
|
def term_matches(line, term):
|
||||||
|
if not case_sensitive:
|
||||||
|
line = line.lower()
|
||||||
|
|
||||||
|
if do_expression:
|
||||||
|
return term.evaluate(line)
|
||||||
|
|
||||||
|
return (
|
||||||
|
(term in line) or
|
||||||
|
(do_regex and re.search(term, line)) or
|
||||||
|
(do_glob and fnmatch.fnmatch(line, term))
|
||||||
|
)
|
||||||
|
|
||||||
|
if do_expression:
|
||||||
|
# The value still needs to be a list so the upcoming any() / all()
|
||||||
|
# receives an iterable as it expects. It just happens to be 1 tree.
|
||||||
|
trees = {}
|
||||||
|
for (key, value) in terms.items():
|
||||||
|
if value == []:
|
||||||
|
trees[key] = []
|
||||||
|
continue
|
||||||
|
tree = ' '.join(value)
|
||||||
|
tree = expressionmatch.ExpressionTree.parse(tree)
|
||||||
|
if not case_sensitive:
|
||||||
|
tree.map(str.lower)
|
||||||
|
trees[key] = [tree]
|
||||||
|
terms = trees
|
||||||
|
|
||||||
|
elif not case_sensitive:
|
||||||
|
terms = {k: [x.lower() for x in v] for (k, v) in terms.items()}
|
||||||
|
|
||||||
|
if text is None:
|
||||||
|
search_objects = spinal.walk_generator(
|
||||||
|
depth_first=False,
|
||||||
|
recurse=not local_only,
|
||||||
|
yield_directories=True,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
search_objects = text.splitlines()
|
||||||
|
|
||||||
|
for (index, search_object) in enumerate(search_objects):
|
||||||
|
if index % 10 == 0:
|
||||||
|
#print(index, end='\r', flush=True)
|
||||||
|
pass
|
||||||
|
if isinstance(search_object, pathclass.Path):
|
||||||
|
search_text = search_object.basename
|
||||||
|
result_text = search_object.absolute_path
|
||||||
|
else:
|
||||||
|
search_text = search_object
|
||||||
|
result_text = search_object
|
||||||
|
if line_numbers:
|
||||||
|
result_text = '%4d | %s' % (index+1, result_text)
|
||||||
|
|
||||||
|
if all_terms_match(search_text, terms, term_matches):
|
||||||
|
if not content_args:
|
||||||
|
yield result_text
|
||||||
|
else:
|
||||||
|
filepath = pathclass.Path(search_object)
|
||||||
|
if not filepath.is_file:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
with open(filepath.absolute_path, 'r') as handle:
|
||||||
|
text = handle.read()
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
try:
|
||||||
|
with open(filepath.absolute_path, 'r', encoding='utf-8') as handle:
|
||||||
|
text = handle.read()
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
#safeprint.safeprint(filepath.absolute_path)
|
||||||
|
#traceback.print_exc()
|
||||||
|
continue
|
||||||
|
except Exception:
|
||||||
|
safeprint.safeprint(filepath.absolute_path)
|
||||||
|
traceback.print_exc()
|
||||||
|
continue
|
||||||
|
|
||||||
|
content_args['text'] = text
|
||||||
|
content_args['line_numbers'] = True
|
||||||
|
results = search(**content_args)
|
||||||
|
results = list(results)
|
||||||
|
if not results:
|
||||||
|
continue
|
||||||
|
|
||||||
|
yield filepath.absolute_path
|
||||||
|
yield from results
|
||||||
|
yield ''
|
||||||
|
|
||||||
|
def argparse_to_dict(args):
|
||||||
|
text = args.text
|
||||||
|
if text is not None:
|
||||||
|
text = clipext.resolve(text)
|
||||||
|
elif STDIN_MODE == 'pipe':
|
||||||
|
text = clipext.resolve('!i')
|
||||||
|
|
||||||
|
if hasattr(args, 'content_args') and args.content_args is not None:
|
||||||
|
content_args = argparse_to_dict(args.content_args)
|
||||||
|
else:
|
||||||
|
content_args = None
|
||||||
|
|
||||||
|
return {
|
||||||
|
'yes_all': args.yes_all,
|
||||||
|
'yes_any': args.yes_any,
|
||||||
|
'not_all': args.not_all,
|
||||||
|
'not_any': args.not_any,
|
||||||
|
'case_sensitive': args.case_sensitive,
|
||||||
|
'content_args': content_args,
|
||||||
|
'do_expression': args.do_expression,
|
||||||
|
'do_glob': args.do_glob,
|
||||||
|
'do_regex': args.do_regex,
|
||||||
|
'local_only': args.local_only,
|
||||||
|
'line_numbers': args.line_numbers,
|
||||||
|
'text': text,
|
||||||
|
}
|
||||||
|
|
||||||
|
def search_argparse(args):
|
||||||
|
generator = search(**argparse_to_dict(args))
|
||||||
|
result_count = 0
|
||||||
|
for result in generator:
|
||||||
|
safeprint.safeprint(result)
|
||||||
|
result_count += 1
|
||||||
|
if args.show_count:
|
||||||
|
print('%d items.' % result_count)
|
||||||
|
|
||||||
|
def main(argv):
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
|
||||||
|
# The padding is inserted to guarantee that --content is not the first
|
||||||
|
# argument. Because if it were, we wouldn't know if we have
|
||||||
|
# [pre, '--content'] or ['--content', post], etc. and I don't want to
|
||||||
|
# actually check the values.
|
||||||
|
argv.insert(0, 'padding')
|
||||||
|
grouper = itertools.groupby(argv, lambda x: x == '--content')
|
||||||
|
halves = [list(group) for (key, group) in grouper]
|
||||||
|
# halves looks like [pre, '--content', post]
|
||||||
|
name_args = halves[0]
|
||||||
|
# Pop the padding
|
||||||
|
name_args.pop(0)
|
||||||
|
content_args = [item for chunk in halves[2:] for item in chunk]
|
||||||
|
|
||||||
|
parser.add_argument('yes_all', nargs='*', default=None)
|
||||||
|
parser.add_argument('--all', dest='yes_all', nargs='+')
|
||||||
|
parser.add_argument('--any', dest='yes_any', nargs='+')
|
||||||
|
parser.add_argument('--not_all', dest='not_all', nargs='+')
|
||||||
|
parser.add_argument('--not_any', dest='not_any', nargs='+')
|
||||||
|
|
||||||
|
parser.add_argument('--case', dest='case_sensitive', action='store_true')
|
||||||
|
parser.add_argument('--content', dest='do_content', action='store_true')
|
||||||
|
parser.add_argument('--count', dest='show_count', action='store_true')
|
||||||
|
parser.add_argument('--expression', dest='do_expression', action='store_true')
|
||||||
|
parser.add_argument('--glob', dest='do_glob', action='store_true')
|
||||||
|
parser.add_argument('--line_numbers', dest='line_numbers', action='store_true')
|
||||||
|
parser.add_argument('--local', dest='local_only', action='store_true')
|
||||||
|
parser.add_argument('--regex', dest='do_regex', action='store_true')
|
||||||
|
parser.add_argument('--text', dest='text', default=None)
|
||||||
|
parser.set_defaults(func=search_argparse)
|
||||||
|
|
||||||
|
args = parser.parse_args(name_args)
|
||||||
|
if content_args:
|
||||||
|
args.content_args = parser.parse_args(content_args)
|
||||||
|
else:
|
||||||
|
args.content_args = None
|
||||||
|
args.func(args)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
raise SystemExit(main(sys.argv[1:]))
|
6
sleep.py
Normal file
6
sleep.py
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import time
|
||||||
|
import sys
|
||||||
|
|
||||||
|
seconds = sys.argv[1]
|
||||||
|
seconds = float(seconds)
|
||||||
|
time.sleep(seconds)
|
19
sorted.py
Normal file
19
sorted.py
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
'''
|
||||||
|
Sort the lines coming from stdin and print them.
|
||||||
|
'''
|
||||||
|
from voussoirkit import clipext
|
||||||
|
import sys
|
||||||
|
|
||||||
|
if len(sys.argv) > 1:
|
||||||
|
text = clipext.resolve(sys.argv[1])
|
||||||
|
else:
|
||||||
|
text = clipext.resolve('!input')
|
||||||
|
|
||||||
|
text = text.split('\n')
|
||||||
|
if '-l' in sys.argv:
|
||||||
|
text.sort(key=lambda x: x.lower())
|
||||||
|
else:
|
||||||
|
text.sort()
|
||||||
|
|
||||||
|
new_text = '\n'.join(text)
|
||||||
|
print(new_text)
|
67
subtitle_shift.py
Normal file
67
subtitle_shift.py
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
'''
|
||||||
|
Usage:
|
||||||
|
|
||||||
|
Shift all subtitles 10 seconds forward:
|
||||||
|
> subtitle_shift file.srt +10
|
||||||
|
|
||||||
|
Shift all subtitles 10 seconds backward:
|
||||||
|
> subtitle_shift file.srt -10
|
||||||
|
|
||||||
|
This will produce "file_correct.srt" with the new timestamps.
|
||||||
|
'''
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
filename = sys.argv[1]
|
||||||
|
offset = float(sys.argv[2])
|
||||||
|
f = open(filename, 'r')
|
||||||
|
|
||||||
|
lines = [l.strip() for l in f.readlines()]
|
||||||
|
for (lineindex, line) in enumerate(lines):
|
||||||
|
changed = False
|
||||||
|
|
||||||
|
if '-->' not in line:
|
||||||
|
continue
|
||||||
|
|
||||||
|
words = line.split(' ')
|
||||||
|
for (wordindex, word) in enumerate(words):
|
||||||
|
word = word.replace('.', ',')
|
||||||
|
if not (':' in word and ',' in word):
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not word.replace(':', '').replace(',', '').isdigit():
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 1.) 01:23:45,678 --> 02:34:56,789 | our input
|
||||||
|
# 2.) 01:23:45:678 --> 02:34:56:789 | comma to colon
|
||||||
|
# 3.) 5025.678 --> 9296.789 | split by colon and sum
|
||||||
|
# 4.) 5035.678 --> 9306.789 | add offset
|
||||||
|
# 5.) 01:23:55.678 --> 02:35:06.789 | reformat
|
||||||
|
# 6.) 01:23:55,678 --> 02:35:06,789 | period to comma
|
||||||
|
word = word.replace(',', ':')
|
||||||
|
(hours, minutes, seconds, mili) = [int(x) for x in word.split(':')]
|
||||||
|
seconds = (3600 * hours) + (60 * minutes) + (seconds) + (mili / 1000)
|
||||||
|
|
||||||
|
seconds += offset
|
||||||
|
(hours, seconds) = divmod(seconds, 3600)
|
||||||
|
(minutes, seconds) = divmod(seconds, 60)
|
||||||
|
|
||||||
|
if hours < 0:
|
||||||
|
raise Exception('Negative time')
|
||||||
|
|
||||||
|
word = '%02d:%02d:%06.3f' % (hours, minutes, seconds)
|
||||||
|
word = word.replace('.', ',')
|
||||||
|
changed = True
|
||||||
|
words[wordindex] = word
|
||||||
|
|
||||||
|
if changed:
|
||||||
|
line = ' '.join(words)
|
||||||
|
print(line)
|
||||||
|
lines[lineindex] = line
|
||||||
|
|
||||||
|
lines = '\n'.join(lines)
|
||||||
|
(name, extension) = os.path.splitext(filename)
|
||||||
|
newname = name + '_correct' + extension
|
||||||
|
x = open(newname, 'w')
|
||||||
|
x.write(lines)
|
||||||
|
x.close()
|
197
subtitle_shift_pointsync.py
Normal file
197
subtitle_shift_pointsync.py
Normal file
|
@ -0,0 +1,197 @@
|
||||||
|
import argparse
|
||||||
|
import sys
|
||||||
|
|
||||||
|
inf = float('inf')
|
||||||
|
|
||||||
|
class Subtitles:
|
||||||
|
def __init__(self, lines):
|
||||||
|
self.lines = sorted(lines)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_text(cls, text):
|
||||||
|
text = text.strip()
|
||||||
|
while '\n\n\n' in text:
|
||||||
|
text = text.replace('\n\n\n', '\n\n')
|
||||||
|
lines = text.split('\n\n')
|
||||||
|
lines = [SubtitleLine.from_text(line) for line in lines]
|
||||||
|
return cls(lines)
|
||||||
|
|
||||||
|
def __getitem__(self, index):
|
||||||
|
return self.lines[index]
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(self.lines)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f'Subtitles with {len(self.lines)} lines.'
|
||||||
|
|
||||||
|
def as_srt(self):
|
||||||
|
lines = sorted(self.lines)
|
||||||
|
lines = [f'{index+1}\n{line.as_srt()}' for (index, line) in enumerate(lines)]
|
||||||
|
return '\n\n'.join(lines)
|
||||||
|
|
||||||
|
class SubtitleLine:
|
||||||
|
def __init__(self, start, end, text):
|
||||||
|
self.start = start
|
||||||
|
self.end = end
|
||||||
|
self.text = text
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_text(cls, text):
|
||||||
|
(index, timestamps, text) = text.split('\n', 2)
|
||||||
|
timestamps = timestamps.replace(',', '.')
|
||||||
|
(start, end) = timestamps.split('-->')
|
||||||
|
start = hms_to_seconds(start)
|
||||||
|
end = hms_to_seconds(end)
|
||||||
|
return cls(start, end, text)
|
||||||
|
|
||||||
|
def __lt__(self, other):
|
||||||
|
return (self.start, self.end) < (other.start, other.end)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return repr(self.as_srt())
|
||||||
|
|
||||||
|
def as_srt(self):
|
||||||
|
start = seconds_to_hms(self.start, force_hours=True, force_milliseconds=True)
|
||||||
|
end = seconds_to_hms(self.end, force_hours=True, force_milliseconds=True)
|
||||||
|
srt = f'{start} --> {end}'.replace('.', ',')
|
||||||
|
srt += '\n' + self.text
|
||||||
|
return srt
|
||||||
|
|
||||||
|
|
||||||
|
class Point:
|
||||||
|
def __init__(self, x, y=None):
|
||||||
|
self.x = x
|
||||||
|
self.y = y
|
||||||
|
|
||||||
|
def __lt__(self, other):
|
||||||
|
return (self.x, self.y) < (other.x, other.y)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f'Point({self.x}, {self.y})'
|
||||||
|
|
||||||
|
|
||||||
|
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.pop(0)) * 3600
|
||||||
|
if len(hms) == 2:
|
||||||
|
seconds += int(hms.pop(0)) * 60
|
||||||
|
if len(hms) == 1:
|
||||||
|
seconds += float(hms.pop(0).replace(',', '.'))
|
||||||
|
return seconds
|
||||||
|
|
||||||
|
def seconds_to_hms(seconds, force_hours=False, force_milliseconds=False):
|
||||||
|
milliseconds = seconds % 1
|
||||||
|
if milliseconds >= 0.999:
|
||||||
|
milliseconds = 0
|
||||||
|
seconds += 1
|
||||||
|
seconds = int(seconds)
|
||||||
|
(minutes, seconds) = divmod(seconds, 60)
|
||||||
|
(hours, minutes) = divmod(minutes, 60)
|
||||||
|
|
||||||
|
parts = []
|
||||||
|
if hours or force_hours:
|
||||||
|
parts.append(hours)
|
||||||
|
|
||||||
|
if minutes or force_hours:
|
||||||
|
parts.append(minutes)
|
||||||
|
|
||||||
|
parts.append(seconds)
|
||||||
|
|
||||||
|
hms = ':'.join(f'{part:02d}' for part in parts)
|
||||||
|
if milliseconds or force_milliseconds:
|
||||||
|
milliseconds = f'.{milliseconds:.03f}'.split('.')[-1]
|
||||||
|
hms += '.' + milliseconds
|
||||||
|
return hms
|
||||||
|
|
||||||
|
def linear(slope, intercept):
|
||||||
|
'''
|
||||||
|
Given slope m and intercept b, return a function f such that f(x) = mx + b.
|
||||||
|
'''
|
||||||
|
def f(x):
|
||||||
|
y = (slope * x) + intercept
|
||||||
|
print(x, y, f'{y:.03f}', seconds_to_hms(y))
|
||||||
|
return y
|
||||||
|
return f
|
||||||
|
|
||||||
|
def slope_intercept(p1, p2):
|
||||||
|
'''
|
||||||
|
Given two Points, return the slope and intercept describing a line
|
||||||
|
between them.
|
||||||
|
'''
|
||||||
|
slope = (p2.y - p1.y) / (p2.x - p1.x)
|
||||||
|
intercept = p1.y - (slope * p1.x)
|
||||||
|
return (slope, intercept)
|
||||||
|
|
||||||
|
def pointsync(input_filename, output_filename, input_landmarks):
|
||||||
|
landmarks = []
|
||||||
|
used_olds = set()
|
||||||
|
for landmark in input_landmarks:
|
||||||
|
(old, new) = landmark.split('=')
|
||||||
|
(old, new) = (hms_to_seconds(old), hms_to_seconds(new))
|
||||||
|
if old < 0 or new < 0:
|
||||||
|
raise ValueError('No negative numbers!')
|
||||||
|
if old in used_olds:
|
||||||
|
raise ValueError(f'Cant use the same old value {seconds_to_hms(old, force_hours=True)} twice.')
|
||||||
|
used_olds.add(old)
|
||||||
|
landmarks.append(Point(old, new))
|
||||||
|
landmarks.sort()
|
||||||
|
if landmarks[0].x != 0:
|
||||||
|
landmarks.insert(0, Point(0, 0))
|
||||||
|
|
||||||
|
# print(landmarks)
|
||||||
|
if len(landmarks) < 2:
|
||||||
|
raise ValueError('Not enough landmarks')
|
||||||
|
|
||||||
|
landmark_functions = []
|
||||||
|
for (land1, land2) in zip(landmarks, landmarks[1:]):
|
||||||
|
(slope, intercept) = slope_intercept(land1, land2)
|
||||||
|
if slope < 0:
|
||||||
|
raise ValueError(f'Negative slope between {land1} and {land2}.')
|
||||||
|
f = linear(slope, intercept)
|
||||||
|
landmark_functions.append((land1.x, f))
|
||||||
|
landmark_functions.append((inf, None))
|
||||||
|
old_srt = Subtitles.from_text(open(input_filename, encoding='utf-8').read())
|
||||||
|
|
||||||
|
pointer = 0
|
||||||
|
new_srt = Subtitles([])
|
||||||
|
for old_line in old_srt:
|
||||||
|
if old_line.start >= landmark_functions[pointer+1][0]:
|
||||||
|
pointer += 1
|
||||||
|
new_start = landmark_functions[pointer][1](old_line.start)
|
||||||
|
|
||||||
|
if old_line.end >= landmark_functions[pointer+1][0]:
|
||||||
|
pointer += 1
|
||||||
|
new_end = landmark_functions[pointer][1](old_line.end)
|
||||||
|
new_line = SubtitleLine(new_start, new_end, old_line.text)
|
||||||
|
new_srt.lines.append(new_line)
|
||||||
|
new_file = open(output_filename, 'w', encoding='utf-8')
|
||||||
|
new_file.write(new_srt.as_srt())
|
||||||
|
|
||||||
|
def pointsync_argparse(args):
|
||||||
|
input_filename = args.input_filename
|
||||||
|
output_filename = args.output_filename
|
||||||
|
if '.srt' not in output_filename:
|
||||||
|
raise ValueError('Output filename', output_filename)
|
||||||
|
|
||||||
|
input_landmarks = args.landmarks
|
||||||
|
return pointsync(input_filename, output_filename, input_landmarks)
|
||||||
|
|
||||||
|
def main(argv):
|
||||||
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
|
|
||||||
|
parser.add_argument('input_filename')
|
||||||
|
parser.add_argument('output_filename')
|
||||||
|
parser.add_argument('landmarks', nargs='*', default=None)
|
||||||
|
parser.set_defaults(func=pointsync_argparse)
|
||||||
|
|
||||||
|
args = parser.parse_args(argv)
|
||||||
|
args.func(args)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
raise SystemExit(main(sys.argv[1:]))
|
25
timestampfilename.pyw
Normal file
25
timestampfilename.pyw
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
'''
|
||||||
|
Drag a file on top of this .py file, and it will
|
||||||
|
be renamed to the current timestamp.
|
||||||
|
'''
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
STRFTIME = '%Y%m%d %H%M%S'
|
||||||
|
UTC = True
|
||||||
|
|
||||||
|
filename = sys.argv[1]
|
||||||
|
folder = os.path.dirname(filename)
|
||||||
|
if folder == '':
|
||||||
|
folder = os.getcwd()
|
||||||
|
basename = os.path.basename(filename)
|
||||||
|
extension = os.path.splitext(basename)[1]
|
||||||
|
|
||||||
|
now = datetime.datetime.now(datetime.timezone.utc if UTC else None)
|
||||||
|
newname = now.strftime(STRFTIME)
|
||||||
|
|
||||||
|
newname = '%s\\%s%s' % (folder, newname, extension)
|
||||||
|
print(filename, '-->', newname)
|
||||||
|
os.rename(filename, newname)
|
24
touch.py
Normal file
24
touch.py
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
'''
|
||||||
|
Create the file, or update the last modified timestamp.
|
||||||
|
'''
|
||||||
|
import glob
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from voussoirkit import clipext
|
||||||
|
from voussoirkit import safeprint
|
||||||
|
|
||||||
|
def touch(glob_pattern):
|
||||||
|
filenames = glob.glob(glob_pattern)
|
||||||
|
if len(filenames) == 0:
|
||||||
|
safeprint.safeprint(glob_pattern)
|
||||||
|
open(glob_pattern, 'a').close()
|
||||||
|
else:
|
||||||
|
for filename in filenames:
|
||||||
|
safeprint.safeprint(filename)
|
||||||
|
os.utime(filename)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
glob_patterns = [clipext.resolve(x).strip() for x in sys.argv[1:]]
|
||||||
|
for glob_pattern in glob_patterns:
|
||||||
|
touch(glob_pattern)
|
20
unique.py
Normal file
20
unique.py
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
'''
|
||||||
|
Keep the unique lines coming from stdin and print them.
|
||||||
|
'''
|
||||||
|
from voussoirkit import clipext
|
||||||
|
import sys
|
||||||
|
|
||||||
|
if len(sys.argv) > 1:
|
||||||
|
source = sys.argv[1]
|
||||||
|
else:
|
||||||
|
source = '!input'
|
||||||
|
lines = clipext.resolve(source, split_lines=True)
|
||||||
|
|
||||||
|
new_text = []
|
||||||
|
seen = set()
|
||||||
|
for line in lines:
|
||||||
|
if line not in seen:
|
||||||
|
#new_text.append(line)
|
||||||
|
seen.add(line)
|
||||||
|
print(line)
|
||||||
|
|
36
zerofile.py
Normal file
36
zerofile.py
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from voussoirkit import bytestring
|
||||||
|
|
||||||
|
|
||||||
|
filename = os.path.abspath(sys.argv[1])
|
||||||
|
|
||||||
|
|
||||||
|
def zerofile(filename, length):
|
||||||
|
if os.path.exists(filename):
|
||||||
|
raise ValueError(f'{filename} already exists.')
|
||||||
|
|
||||||
|
with open(filename, 'wb') as handle:
|
||||||
|
handle.seek(length - 1)
|
||||||
|
handle.write(bytes([0]))
|
||||||
|
|
||||||
|
def zerofile_argparse(args):
|
||||||
|
return zerofile(
|
||||||
|
filename=args.filename,
|
||||||
|
length=bytestring.parsebytes(args.length),
|
||||||
|
)
|
||||||
|
|
||||||
|
def main(argv):
|
||||||
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
|
|
||||||
|
parser.add_argument('filename')
|
||||||
|
parser.add_argument('length')
|
||||||
|
parser.set_defaults(func=zerofile_argparse)
|
||||||
|
|
||||||
|
args = parser.parse_args(argv)
|
||||||
|
args.func(args)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
raise SystemExit(main(sys.argv[1:]))
|
Loading…
Reference in a new issue