Skip to content

Instantly share code, notes, and snippets.

@nycki93
Created July 24, 2024 23:59
Show Gist options
  • Select an option

  • Save nycki93/18514a1545ee2e5d43dc5cd2d75fa794 to your computer and use it in GitHub Desktop.

Select an option

Save nycki93/18514a1545ee2e5d43dc5cd2d75fa794 to your computer and use it in GitHub Desktop.
#!/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