├── README ├── bzip2-all.sh ├── sox.sh ├── fix-broken-encoding.py ├── youtube-download.sh ├── announce-current-song.sh ├── spokepov-mirror.py ├── udp-relay.py ├── sha1compare.py └── vcard-dedup.py /README: -------------------------------------------------------------------------------- 1 | Odds and ends. Consider this a grab-bag of little scripts written over time. 2 | -------------------------------------------------------------------------------- /bzip2-all.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Usage: 4 | # bash bzip2-sll.sh dir1 dir2 dir3 ... 5 | # For every file in the given directories, make a .bzip2 version of it next to it. 6 | # Useful for Source Engine servers who wish to have compressed .bz2 files ready. 7 | 8 | IFS="`echo -en "\n\b"`" 9 | for arg; do 10 | for file in `find "$arg" -type f -name '*.bz2'`; do 11 | rm "$file" 12 | done 13 | for file in `find "$arg" -type f`; do 14 | echo "$file" 15 | bzip2 -k9 "$file" 16 | done 17 | done 18 | -------------------------------------------------------------------------------- /sox.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | pwd=`pwd` 4 | 5 | for i in "$@"; do 6 | filedir=$(dirname "$i") 7 | filename=$(basename "$i") 8 | InfoFormat=$(soxi -e "$i") 9 | InfoDepth=$(soxi -b "$i") 10 | InfoFreq=$(soxi -r "$i") 11 | if [ ${filedir:0:1} == "/" ]; then 12 | outdir="$filedir/spectrums" 13 | else 14 | outdir="$pwd/$filedir/spectrums" 15 | fi 16 | mkdir -p "$outdir" 17 | sox "$i" -n spectrogram -h -p 6 -x 2000 -y 800 -c "$InfoFormat $InfoDepth bits @ $InfoFreq Hz" -t "$filename" -o "$outdir/$filename.png" 18 | if [ $? -eq 0 ]; then 19 | echo "$filename.png" created successfully 20 | else 21 | echo "Error creating $filename.png - Stopping." 22 | exit -1 23 | fi 24 | done 25 | -------------------------------------------------------------------------------- /fix-broken-encoding.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Dirty script running some plain text replacements for broken encodings. 4 | # Usage: fix=broken-encoding.py > output.txt 5 | 6 | import sys 7 | 8 | contents = open(sys.argv[1], 'r', encoding=sys.argv[2]).read() 9 | 10 | replacements = { 11 | u'à': u'à', 12 | u'â': u'â', 13 | u'À': u'À', 14 | u'ç': u'ç', 15 | u'Ç': u'Ç', 16 | u'é': u'é', 17 | u'è': u'è', 18 | u'ê': u'ê', 19 | u'É': u'É', 20 | u'È': u'È', 21 | u'î': u'î', 22 | u'ï': u'ï', 23 | u'ñ': u'ñ', 24 | u'ô': u'ô', 25 | u'ù': u'ù', 26 | u'û': u'û', 27 | } 28 | 29 | for target, replacement in replacements.items(): 30 | contents = contents.replace(target, replacement) 31 | 32 | print(contents) 33 | -------------------------------------------------------------------------------- /youtube-download.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Dirty script to download a bunch of youtube videos 4 | # Supports format selection and redownloading when the connection breaks 5 | # Usage: 6 | # $ ./youtube-download.sh url1 url2 ... 7 | 8 | faveFormats=(fmt46_1080p fmt37_1080p fmt45_720p fmt22_720p) 9 | outputDirectory="$HOME/yt" 10 | 11 | set -e 12 | 13 | download() { 14 | if ! clive -F "$1" &> /dev/null; then 15 | echo ">>> Cannot list formats for video: '$1'" 16 | return 1 17 | fi 18 | formats="$(clive -F "$1" | grep fmt | head -1 | cut -d ' ' -f 1 | sed -r 's/\|/\n/g')" 19 | chosenFormat='' 20 | for f in "${faveFormats[@]}"; do 21 | if echo "$formats" | grep -q "$f"; then 22 | chosenFormat="-f '$f'" 23 | break 24 | fi 25 | done 26 | echo ">>> Downloading: '$1'" 27 | cd "$outputDirectory" 28 | while true; do 29 | if clive $chosenFormat "$1"; then 30 | break 31 | fi 32 | sleep 3 33 | done 34 | echo ">>> Done downloading: '$1'" 35 | } 36 | 37 | failedVideos=() 38 | for arg; do 39 | echo "> Processing: '$arg'" 40 | if ! download "$arg"; then 41 | failedVideos+=("$arg") 42 | fi 43 | done 44 | if [ "${#failedVideos[@]}" -ne 0 ]; then 45 | echo "> Failed videos:" 46 | for failedVideo in "${failedVideos[@]}"; do 47 | echo ">>> Failed: '$failedVideo'" 48 | done 49 | fi 50 | -------------------------------------------------------------------------------- /announce-current-song.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euxo pipefail 4 | 5 | lastTrack='' 6 | fifo='/tmp/announce-current-song.fifo.wav' 7 | rm -f "$fifo" || true 8 | mkfifo "$fifo" 9 | while true; do 10 | sleep 10 11 | # Get title and artist of currently-playing track. 12 | meta="$(qdbus org.kde.amarok /Player org.freedesktop.MediaPlayer.GetMetadata || true)" 13 | if ! echo "$meta" | grep -qP '^artist:'; then 14 | lastTrack='' 15 | continue # Most likely not playing audio right now. 16 | fi 17 | artist="$(echo "$meta" | grep -P '^artist:' | cut -d' ' -f2-)" 18 | title="$(echo "$meta" | grep -P '^title:' | cut -d' ' -f2-)" 19 | currentTrack="$title by $artist" 20 | if [ "$currentTrack" == "$lastTrack" ]; then 21 | continue # Already announced. 22 | fi 23 | # Fade out current music volume so that the track name can be announced. 24 | for i in $(seq 100 -5 30); do 25 | qdbus org.kde.amarok /Player VolumeSet "$i" 26 | sleep 0.075 27 | done 28 | # Write text-to-speech data to FIFO. 29 | pico2wave --wave="$fifo" "Current track is $currentTrack." & 30 | # Play text-to-speech data from FIFO to speakers. 31 | paplay "$fifo" 32 | # Raise music volume back up. 33 | for i in $(seq 30 5 100); do 34 | qdbus org.kde.amarok /Player VolumeSet "$i" 35 | sleep 0.075 36 | done 37 | lastTrack="$currentTrack" 38 | done 39 | -------------------------------------------------------------------------------- /spokepov-mirror.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Horizontally mirrors a SpokePOV .dat file. 3 | # Useful if you want to use the "mirror" option on the SpokePOV software but SpokePOV units 4 | # are installed "backwards" because they fit your spokes better that way. 5 | # http://www.ladyada.net/make/spokepov/software.html 6 | # SpokePOV .dat file format info from https://github.com/adafruit/SpokePOV/blob/d4acac8cdfe3663d2b01fa4280da7d41ca80fedb/wheelpanel.cpp#L132 7 | 8 | import struct, sys 9 | 10 | if len(sys.argv) != 2: 11 | print('Usage: spokepov-mirror.py filename.dat') 12 | print('Writes result to filename_flipped.dat') 13 | sys.exit(1) 14 | 15 | contents = open(sys.argv[1], 'rb').read(-1) 16 | newcontents = b'' 17 | 18 | # Magic header 19 | if contents[:12] != b'\x08\x00\x00\x00SpokePOV': 20 | print('Invalid SpokePOV file.') 21 | sys.exit(1) 22 | newcontents += contents[:12] 23 | contents = contents[12:] 24 | 25 | # Format version 26 | if contents[:2] != b'\x01\x02': 27 | print('Invalid SpokePOV file version.') 28 | sys.exit(1) 29 | newcontents += contents[:2] 30 | contents = contents[2:] 31 | 32 | # Radial image dimensions 33 | # leds = Number of LEDs on each SpokePOV unit 34 | # pixels = Number of pixels in one rotation per LED 35 | leds, pixels = struct.unpack('=BH', contents[:3]) 36 | print('LEDs: %d / Pixels: %d' % (leds, pixels)) 37 | newcontents += contents[:3] 38 | contents = contents[3:] 39 | 40 | # Do the actual mirroring 41 | for led in range(leds): 42 | led_offset = led * pixels 43 | newcontents += contents[led_offset + pixels:led_offset:-1] 44 | 45 | output = sys.argv[1][:-4] + '_flipped.dat' 46 | open(output, 'wb').write(newcontents) 47 | -------------------------------------------------------------------------------- /udp-relay.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Super simple script that listens to a local UDP port and relays all packets to an arbitrary remote host. 3 | # Packets that the host sends back will also be relayed to the local UDP client. 4 | # Works with Python 2 and 3 5 | 6 | import sys, socket 7 | 8 | # Whether or not to print the IP address and port of each packet received 9 | debug=False 10 | 11 | def fail(reason): 12 | sys.stderr.write(reason + '\n') 13 | sys.exit(1) 14 | 15 | if len(sys.argv) != 2 or len(sys.argv[1].split(':')) != 3: 16 | fail('Usage: udp-relay.py localPort:remoteHost:remotePort') 17 | 18 | localPort, remoteHost, remotePort = sys.argv[1].split(':') 19 | 20 | try: 21 | localPort = int(localPort) 22 | except: 23 | fail('Invalid port number: ' + str(localPort)) 24 | try: 25 | remotePort = int(remotePort) 26 | except: 27 | fail('Invalid port number: ' + str(remotePort)) 28 | 29 | try: 30 | s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 31 | s.bind(('', localPort)) 32 | except: 33 | fail('Failed to bind on port ' + str(localPort)) 34 | 35 | knownClient = None 36 | knownServer = (remoteHost, remotePort) 37 | sys.stdout.write('All set, listening on '+str(localPort)+'.\n') 38 | while True: 39 | data, addr = s.recvfrom(32768) 40 | if knownClient is None or addr != knownServer: 41 | if debug: 42 | print("") 43 | knownClient = addr 44 | 45 | if debug: 46 | print("Packet received from "+str(addr)) 47 | 48 | if addr == knownClient: 49 | if debug: 50 | print("\tforwording tO "+str(knownServer)) 51 | 52 | s.sendto(data, knownServer) 53 | else: 54 | if debug: 55 | print("\tforwarding to "+str(knownClient)) 56 | s.sendto(data, knownClient) 57 | -------------------------------------------------------------------------------- /sha1compare.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Compares two files containing a bunch of sha1sums. 4 | # Usage: file1 file1Prefix file2 file2Prefix 5 | # Output is of the form "MESSAGETYPE:messageid: Message goes here", to allow for easy grepping. 6 | 7 | import sys 8 | import re 9 | import shlex 10 | 11 | def parseFile(f, prefix): 12 | prefix = bytes(prefix, 'utf8') 13 | sha1sums = {} 14 | for lineNumber, line in enumerate(open(f, 'rb').read().split(b'\n')): 15 | l = line.strip() 16 | if not l: 17 | continue 18 | l = l.split(b' ', 1) 19 | if len(l) != 2 or len(l[0]) != 40 or any(c not in b'0123456789abcdef' for c in l[0]): 20 | print('WARNING:badsyntax: Invalid line %d of file %r: %r (ignoring)' % (lineNumber, f, line)) 21 | continue 22 | if not l[1].startswith(prefix): 23 | print('WARNING:noprefix: File for line %d of file %r does not start with prefix %r (ignoring)' % (lineNumber, f, prefix)) 24 | continue 25 | sha1sum = str(l[0], 'ascii') 26 | hashedFile = l[1][len(prefix):] 27 | try: 28 | utf8File = str(hashedFile, 'utf8') 29 | except UnicodeDecodeError: 30 | print('WARNING:notutf8: Filename on line %d of file %r cannot be decoded with UTF-8: %r (ignoring)' % (lineNumber, f, hashedFile)) 31 | continue 32 | if utf8File in sha1sums: 33 | if sha1sums[utf8File] != sha1sum: 34 | print('ERROR:inconsistentsums: Hashed file %r of file %r is present more than once and with different hash values.' % (utf8File, f)) 35 | sys.exit(1) 36 | print('INFO:dupline: Hashed file %r of file %r is present more than once, though with the same hash value. Ignoring.' % (utf8File, f)) 37 | else: 38 | sha1sums[utf8File] = sha1sum 39 | return sha1sums 40 | 41 | def cp(messageid, f1, f2): 42 | print('CMD:%s: %s' % (messageid, ' '.join(map(shlex.quote, ('cp', '-av', f1, f2))))) 43 | 44 | f1, f2 = sys.argv[1], sys.argv[3] 45 | prefix1, prefix2 = sys.argv[2], sys.argv[4] 46 | sha1sums1 = parseFile(f1, prefix1) 47 | sha1sums2 = parseFile(f2, prefix2) 48 | for f in sorted(sha1sums1): 49 | if f not in sha1sums2: 50 | print('DIFF:in1not2: File %r is in %r but not %r' % (f, f1, f2)) 51 | cp('in1not2', prefix1 + f, prefix2 + f) 52 | elif sha1sums1[f] != sha1sums2[f]: 53 | print('DIFF:mismatch: File %r is in both files but has different hash value' % (f,)) 54 | cp('mismatch', prefix1 + f, prefix2 + f) 55 | for f in sorted(sha1sums2): 56 | if f not in sha1sums1: 57 | print('DIFF:in2not1: File %r is in %r but not %r' % (f, f2, f1)) 58 | cp('in2not1', prefix2 + f, prefix1 + f) 59 | -------------------------------------------------------------------------------- /vcard-dedup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # 4 | # Crudely and inefficiently deduplicate vcard files 5 | # Not suitable for serious business, this is just a one-off scripts 6 | # 7 | # Usage: 8 | # $ cat vcard1.vcf vcard2.vcf ... | python2 vcard-dedup.py > result.vcf 9 | # 10 | 11 | import re 12 | import sys 13 | 14 | vcard = re.compile(u'^BEGIN:VCARD([\\s\\S]+?)^END:VCARD', re.MULTILINE) 15 | fieldReg = re.compile(u'^([^:;\\s]+)((?:;[^:;\\s]+)*):(.*(?:[\r\n]+ +.*)*)', re.MULTILINE) 16 | def getFields(v): 17 | fields = [] 18 | for f in fieldReg.findall(v): 19 | value = [] 20 | for val in u''.join(map(lambda x: x.strip(), f[2].split(u'\n'))).split(u';'): 21 | if val.strip(): 22 | value.append(val.replace(u'\\:', u':')) 23 | fields.append((f[0], f[1], value)) 24 | return fields 25 | 26 | def printFields(fields): 27 | lines = [u'BEGIN:VCARD'] 28 | for f in fields: 29 | lines.append(f[0] + f[1] + u':' + u';'.join(f[2])) 30 | while len(lines[-1]) > 76: 31 | lines = lines[:-1] + [lines[-1][:76], u' ' + lines[-1][76:]] 32 | lines.append(u'END:VCARD') 33 | return u'\n'.join(lines) 34 | 35 | def getFieldsOf(fields, fieldName, asTuple): 36 | matchingFields = [] 37 | for f in fields: 38 | if f[0].lower() == fieldName.lower(): 39 | if asTuple: 40 | matchingFields.append(f) 41 | else: 42 | matchingFields.extend(f[2]) 43 | return matchingFields 44 | 45 | def m(v): 46 | f = getFields(v) 47 | email = getFieldsOf(f, 'email', False) 48 | FN = getFieldsOf(f, 'FN', False) 49 | N = getFieldsOf(f, 'N', False) 50 | if len(FN) == 0 and len(N) > 0: 51 | FN = [u' '.join(N)] 52 | return set(email), set(FN) 53 | vcards = vcard.findall(sys.stdin.read().decode('utf8')) 54 | vFields = map(m, vcards) 55 | toMerge = [] 56 | taken = set() 57 | for i, vf1 in enumerate(vFields): 58 | if i in taken: 59 | continue 60 | taken.add(i) 61 | matches = [vcards[i]] 62 | for j, vf2 in enumerate(vFields): 63 | if j not in taken and (len(vf1[0] & vf2[0]) or len(vf1[1] & vf2[1])): 64 | taken.add(j) 65 | matches.append(vcards[j]) 66 | if len(matches) > 1: 67 | toMerge.append(matches) 68 | output = u'' 69 | valueSplit = re.compile(u'[-;:,._*(){}[\\]\\s]+') 70 | for m in toMerge: 71 | allFields = [] 72 | for v in m: 73 | allFields.extend(getFields(v)) 74 | fieldNames = list(set(map(lambda x: x[0], allFields))) 75 | fieldNames.sort() 76 | if u'VERSION' in fieldNames: # Move VERSION to first field 77 | fieldNames.remove(u'VERSION') 78 | fieldNames.insert(0, u'VERSION') 79 | finalFields = [] 80 | for fn in fieldNames: 81 | fs = getFieldsOf(allFields, fn, True) 82 | uniqueValues = set(map(lambda x: u';'.join(x[2]), fs)) 83 | # Dedup values 84 | filteredValues = [] 85 | for v1 in uniqueValues: 86 | v1Split = valueSplit.split(v1.lower()) 87 | isUnique = True 88 | for v2 in filteredValues[:]: 89 | v2Split = valueSplit.split(v2.lower()) 90 | isUnique = False 91 | fullCoverage = True 92 | isSuperset = True 93 | for c1 in v1Split: 94 | if c1 not in v2Split: 95 | fullCoverage = False 96 | isUnique = True 97 | break 98 | if not fullCoverage: 99 | for c2 in v2Split: 100 | if c2 not in v1Split: 101 | isSuperset = False 102 | break 103 | if isSuperset: 104 | filteredValues.remove(v2) 105 | filteredValues.append(v1) 106 | if isUnique: 107 | filteredValues.append(v1) 108 | for v in filteredValues: 109 | if v.strip(): 110 | matchingFields = filter(lambda x: u';'.join(x[2]) == v, fs) 111 | matchingTypes = list(set(map(lambda x: x[1], matchingFields))) 112 | matchingTypes.sort(key=lambda x: len(x)) 113 | chosenType = matchingTypes[-1] 114 | chosenField = filter(lambda x: x[1] == chosenType, matchingFields)[0] 115 | finalFields.append((fn, chosenType, chosenField[2])) 116 | output += printFields(finalFields) + u'\n' 117 | print output.encode('utf8') 118 | --------------------------------------------------------------------------------