Created
July 24, 2024 23:59
-
-
Save nycki93/18514a1545ee2e5d43dc5cd2d75fa794 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| # kcs_decode.py | |
| # | |
| # Author : David Beazley (http://www.dabeaz.com) | |
| # Copyright (C) 2010 | |
| # | |
| # Modified by : Nick "Nycki" Lamicela (http://nycki.net) | |
| # 2024 | |
| # | |
| # Requires Python 3.1.2 or newer | |
| """ | |
| Converts a WAV file containing Kansas City Standard data and | |
| extracts text data from it. See: | |
| http://en.wikipedia.org/wiki/Kansas_City_standard | |
| """ | |
| from collections import deque | |
| from itertools import islice | |
| # Base frequency (representing a 1) | |
| BASE_FREQ = 2400 | |
| def get_wave_widths(wavefile): | |
| samplewidth = wavefile.getsampwidth() | |
| nchannels = wavefile.getnchannels() | |
| sign_prev = 0 | |
| width = 0 | |
| while True: | |
| # get most significant bits | |
| frames = wavefile.readframes(8192) | |
| if not frames: break | |
| msbytes = bytearray(frames[samplewidth-1::samplewidth*nchannels]) | |
| # watch for sign changes | |
| for byte in msbytes: | |
| width += 1 | |
| sign = byte > 127 and 1 or 0 | |
| if (sign != sign_prev): | |
| sign_prev = sign | |
| # print(width, end=',') | |
| yield width | |
| width = 0 | |
| def get_bits(widths, frames_per_bit): | |
| crossings = 0 | |
| frames_total = 0 | |
| while True: | |
| width = next(widths) | |
| crossings += 1 | |
| frames_total += width | |
| # flexible to allow slight under/over speed | |
| if frames_total > frames_per_bit * 31 // 32: | |
| if crossings > 12: | |
| yield 1 | |
| else: | |
| yield 0 | |
| crossings = 0 | |
| frames_total = 0 | |
| def get_bytes(bit_stream): | |
| mode = 'idle' | |
| bits = [] | |
| while True: | |
| try: bit = next(bit_stream) | |
| except: break | |
| if (mode == 'idle' and bit == 0): | |
| mode = 'reading' | |
| continue | |
| if (mode == 'reading' and len(bits) < 8): | |
| bits.append(bit) | |
| if (len(bits) == 8): | |
| byte = 0 | |
| for b in reversed(bits): | |
| byte = byte * 2 + b | |
| yield byte | |
| mode = 'idle' | |
| bits = [] | |
| if __name__ == '__main__': | |
| import wave | |
| import sys | |
| import optparse | |
| p = optparse.OptionParser() | |
| p.add_option("-b",action="store_true",dest="binary") | |
| p.add_option("--binary",action="store_true",dest="binary") | |
| p.set_defaults(binary=False) | |
| opts, args = p.parse_args() | |
| if len(args) != 1: | |
| print("Usage: %s [-b] infile" % sys.argv[0],file=sys.stderr) | |
| raise SystemExit(1) | |
| wf = wave.open(args[0]) | |
| # Compute the number of audio frames used to encode a single data bit | |
| frames_per_bit = int(round(float(wf.getframerate())*8/BASE_FREQ)) | |
| # print(f'frames per bit = {frames_per_bit}') | |
| wave_widths = get_wave_widths(wf) | |
| bit_stream = get_bits(wave_widths, frames_per_bit) | |
| byte_stream = get_bytes(bit_stream) | |
| if opts.binary: | |
| # Output the byte stream in 80-byte chunks with NULL stripping | |
| outf = sys.stdout.buffer.raw | |
| while True: | |
| buffer = bytes(islice(byte_stream,80)) | |
| if not buffer: | |
| break | |
| outf.write(buffer) | |
| else: | |
| buffer = bytearray() | |
| while True: | |
| linebreak = buffer.find(b'\n') | |
| if linebreak >= 0: | |
| line = buffer[:linebreak+1].replace(b'\r\n',b'\n') | |
| sys.stdout.write(line.decode('utf-8')) | |
| del buffer[:linebreak+1] | |
| else: | |
| fragment = bytes(byte for byte in islice(byte_stream,80) if byte > 0) | |
| if not fragment: | |
| sys.stdout.write(buffer.decode('utf-8')) | |
| break | |
| buffer.extend(fragment) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment