-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
RandomUsername96
committed
Oct 1, 2016
0 parents
commit d8a21f2
Showing
12 changed files
with
530 additions
and
0 deletions.
There are no files selected for viewing
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
# such-wav-very-wow | ||
|
||
**Design doc**: | ||
https://docs.google.com/document/d/1wTqI9ibEF-vHbr2aUqO6_ZJvHAKXhTdXMFTS0o6OocA/edit | ||
|
||
**img_convert.py** - pomoćna skripta koja konvertuje slike u b&w mod | ||
**audiolib.py** - pomoćna biblioteka | ||
|
||
**morse.py** - morse code enkoder/dekoder za prvi deo zadatka | ||
**bfsk.py** - bfsk ascii enkoder/dekoder za drugi deo zadatka | ||
**sstv.py** - mrrobot b&w n8 SSTV enkoder/dekoder za treći deo zadatka | ||
|
||
**gen_tekst.sh** - generiše tekst zadatka u tekst.wav (morse) i odmah ga dekodira radi provere | ||
**gen_2.sh** - generiše 2.wav (bfsk) i odmah dekodira radi provere | ||
**gen_3.sh** - generiše 3.wav (sstv) i odmah dekodira radi provere | ||
|
||
**qr.jpg** - ulaz za treći deo zadatka | ||
**qr_sol.jpg** - izlaz sstv.py nakon encode+decode | ||
|
||
**such-wav-very-wow.zip** - Zip arhiva (da, zip u git repou, stvarno) koja sadrži {tekst.wav, 2.wav, 3.wav} tj. sve što treba da bude shipovano kao zadatak pored naslova. |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
""" | ||
Osnovne audio funkcije i promenljive koje su manje vise potrebne u svakom fajlu. | ||
Dodate jos neke funkcije koje nisu striktno u vezi sa zvukom ali su potrebne na | ||
vise mesta. Pravljenje jos jedne biblioteke zbog dve male fje bi bilo previse bahato. | ||
""" | ||
import math | ||
import struct | ||
import wave | ||
|
||
# global consts, no real reason to ever change these | ||
nchannels = 1 # number of channels | ||
bitdepth = 2 # bits per sample | ||
framerate = 44100.0 # samples/frames per second | ||
comptype = "NONE" # compression type | ||
compname = "not compressed" # compression name | ||
amp_range = 64000.0 # multiplier for amplitude, amplitude range | ||
|
||
# splits list L into chunks of size sz and returns a list of lists | ||
def chunks(L, sz): | ||
return [L[start:start+sz] for start in range(0, len(L), sz)] | ||
|
||
# usual flatten operation, converts nested list to a regular list | ||
def flatten(L): | ||
return [elem for inner in L for elem in inner] | ||
|
||
# helper function that converts length in ms to frames | ||
def s2f(ms): | ||
return round(framerate * ms / 1000) | ||
|
||
# noob approach to extracting frequencies from an array of frames | ||
def extract_freqs_noob(frames): | ||
# monitors every time waveform crosses X axis while going upwards | ||
# and calculates frequencies for each period | ||
freqs_frames = [] # (frequency, number of frames) tuple | ||
last_frame = curr_num_frames = 0 | ||
for frame in frames: | ||
if last_frame < 0 and frame >= 0: | ||
# one period is over | ||
freqs_frames.append((round(framerate / curr_num_frames), curr_num_frames)) | ||
curr_num_frames = 0 | ||
curr_num_frames += 1 | ||
last_frame = frame | ||
return freqs_frames | ||
|
||
# uses provided starting phase and frequency to generate num_frames audio frames | ||
# returns a tuple (list of frames, phase for the next part of the waveform) | ||
def create_frames(freq, num_frames, phase): | ||
ret = [] | ||
delta = 0 if freq == 0 else (framerate / freq) * (phase / (2 * math.pi)) | ||
for x in range(num_frames): | ||
phase = (2*math.pi*freq*((x+delta)/framerate)) % (2*math.pi) | ||
ret.append(math.sin(phase)) # [-1, 1], will be multiplied by amp | ||
out_phase = (2*math.pi*freq*((num_frames+delta)/framerate)) % (2*math.pi) | ||
return (ret, out_phase) | ||
|
||
# writes input frames to the specified wav file | ||
def write_wav(frames, wav_filename): | ||
# opens output file and sets params | ||
wav_file = wave.open(wav_filename, "w") | ||
wav_file.setparams((nchannels, bitdepth, int(framerate), len(frames), | ||
comptype, compname)) | ||
|
||
# writes audio frames to file and closes it afterwards | ||
for frame in frames: | ||
wav_file.writeframes(struct.pack('h', int(frame*amp_range/2))) | ||
wav_file.close() | ||
|
||
# returns frames from the specified wav file | ||
def read_wav(wav_filename): | ||
wav_file = wave.open(wav_filename, "r") | ||
nframes = wav_file.getnframes() | ||
if wav_file.getparams() != (nchannels, bitdepth, int(framerate), nframes, comptype, compname): | ||
raise Exception('input file has different params from what we use to generate them') | ||
wav_frames = wav_file.readframes(wav_file.getnframes()) | ||
frames = list(struct.unpack_from("%dh" % nframes, wav_frames)) | ||
return frames |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
""" | ||
BFSK ASCII kodiranje i dekodiranje | ||
python3 bfsk.py enc "Bonsoir Elliot :)" 0.1 output.wav | ||
enkodira "Bonsoir Elliot :)" koristeci 0.1s za trajanje bita i upisuje rezultat u output.wav | ||
python3 bfsk.py dec input.wav | ||
dekodira sadrzaj fajla input.wav i ispisuje rezultat | ||
""" | ||
import audiolib as a | ||
import sys | ||
import itertools | ||
|
||
# top level params, can be tuned | ||
freq_lo = 300.0 # frequency that represents 0 | ||
freq_hi = 1000.0 # frequency that represents 1 | ||
|
||
# Binary String -> ASCII Char conversion | ||
def binstr2asciichar(str): | ||
return chr(int(str, 2)) | ||
|
||
# Binary String <- ASCII Char conversion | ||
def asciichar2binstr(ch): | ||
return bin(ord(ch))[2:].zfill(8) | ||
|
||
# encodes the message using bit_len as the bit length in seconds and writes it to file | ||
def encode(message, bit_len, wav_filename): | ||
print('Message:\"' + message + '\"') | ||
# converts the message to binary ascii | ||
encoded_message = ''.join([asciichar2binstr(ch) for ch in message]) | ||
print('Binary ASCII:\"' + encoded_message + '\"') | ||
# creates frames | ||
frames_per_bit = int(a.framerate * bit_len) | ||
frames = [] | ||
phase = 0 | ||
# the main ecnoding loop | ||
for bit in encoded_message: | ||
# encodes one bit | ||
freq = freq_hi if bit == '1' else freq_lo | ||
new_frames, phase = a.create_frames(freq, frames_per_bit, phase) | ||
frames.extend(new_frames) | ||
# writes to file | ||
a.write_wav(frames, wav_filename) | ||
print('Wrote to file:\"' + wav_filename + '\"') | ||
|
||
# this is where the magic happens, takes an array of frames and returns a message string | ||
def extract_bits(frames): | ||
# gets frequencies from frames | ||
freqs_frames = a.extract_freqs_noob(frames) | ||
|
||
# converts frequencies to bits, uses the average of all frequencies as a threshold | ||
sum_all_freqs = sum([freq * frames for (freq, frames) in freqs_frames]) | ||
total_num_frames = sum([frames for (freq, frames) in freqs_frames]) | ||
freq_thresh = sum_all_freqs / total_num_frames | ||
bits_frames = [( (freq > freq_thresh), frames) for freq, frames in freqs_frames] | ||
|
||
# tuples get expanded and now we get an array of bits, one bit for each frame | ||
bits_expanded = a.flatten([[freq] * frames for (freq, frames) in bits_frames]) | ||
# groups consecutive blocks of same values and filters out "really short blocks" that might | ||
# be a result of noise (magic constant 10) | ||
bits_grouped = [list(g) for k, g in itertools.groupby(bits_expanded)] | ||
bits_filtered = [ (group[0], len(group)) for group in bits_grouped if len(group) > 10] | ||
|
||
# doesn't know frames_per_bit, assumes that it's equal to the length | ||
# of the shortest block of same bits (relies on the assumption that input has 101/010 somewhere) | ||
maybe_frames_per_bit = min( [num_frames for bit, num_frames in bits_filtered] ) | ||
# adds bits to the return string | ||
bits = a.flatten([[str(int(bit))] * round(num_frames / maybe_frames_per_bit) for bit, num_frames in bits_filtered]) | ||
return ''.join(bits) | ||
|
||
# decodes wav_filename.wav | ||
# > ugly hacks that work should get full marks | ||
def decode(wav_filename): | ||
# opens input file and gets frames | ||
frames = a.read_wav(wav_filename) | ||
print('Read from file:\"' + wav_filename + '\"') | ||
encoded_message = extract_bits(frames) | ||
print('Binary ASCII:\"' + encoded_message + '\"') | ||
bin_strings = [''.join(bin_list) for bin_list in a.chunks(encoded_message, 8)] | ||
message = ''.join([binstr2asciichar(bin_str) for bin_str in bin_strings]) | ||
print('Message:\"' + message + '\"') # solution | ||
|
||
# main function, takes care of parsing the command line arguments | ||
def main(): | ||
if len(sys.argv) < 2: | ||
raise Exception('no arguments') | ||
if sys.argv[1] == 'enc': | ||
if len(sys.argv) != 5: | ||
raise Exception('enc expects exactly three arguments') | ||
encode(sys.argv[2], float(sys.argv[3]), sys.argv[4]) | ||
elif sys.argv[1] == 'dec': | ||
if len(sys.argv) != 3: | ||
raise Exception('dec expects exactly one argument') | ||
decode(sys.argv[2]) | ||
else: | ||
raise Exception('enc/dec are only allowed commands') | ||
|
||
# entry point | ||
if __name__ == "__main__": | ||
main() |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
python3 bfsk.py enc "#Rand0m joined as R #Micro joined as M #Rand0m joined as R M: Oj R: Oj M: Imas li i dalje onu referencu za SSTV MrRobot B&W n8? R: Imam, cek sekund R: http://pastebin.com/raw/nsaByFUF R: Sta ce ti? M: Hocu da enkriptujem ono u treci fajl R: Aaa kul moze EOT" 0.1 2.wav | ||
python3 bfsk.py dec 2.wav |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
python3 sstv.py enc qr.jpg 3.wav | ||
python3 sstv.py dec 3.wav qr_sol.jpg |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
python3 morse.py enc "STX U VREME KADA NE RASPRAVLJAJU O VALIDNOSTI POLITICKIH OPCIJA KOJE ZASTUPAJU, RANDOM I MAJKRO OBOZAVAJU DA SVOJE RAZGOVORE PRENOSE NA NAJNEKONVENCIONALNIJE NACINE. PRED VAMA SU 3 ZVUCNA FAJLA. VAS ZADATAK JE DA RAZBIJETE OVE NSA PROOF METODE MODULACIJE I ENKODIRANJA I OTKRIJETE STA SE KRIJE SA DRUGE STRANE. AKO CITATE OVAJ TEKST, A NISTE VARALI, ONDA VAM CESTITAMO, JER STE PRVI DEO ZADATKA USPESNO URADILI. OD VAS SE DALJE OCEKUJE DA DEKODIRATE PREOSTALA DVA FAJLA I DOSTAVITE REZULTAT ZAJEDNO SA IZVORNIM KODOVIMA PROGRAMA KOJE STE NAPISALI KAKO BI DO REZULTATA DOSLI, UKLJUCUJUCI I KOD KOJIM STE DOSLI DO OVOG TEKSTA. RESENJA ZASNOVANA NA DOBROM SLUHU, TUDJIM KODOVIMA I VLASKOJ MAGIJI CE BITI ZNATNO MANJE BODOVANA. JOS JEDNA SITNICA: DRUGI FAJL JE BFSK MODULISAN ASCII TEKST SA 8 BITA PO KARAKTERU, A IZA TRECEG FAJLA SE KRIJE NAGRADA ZA NAJBRZEG. SRECNO ETX EOF" 0.06 tekst.wav | ||
python3 morse.py dec tekst.wav |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
from PIL import Image | ||
import sys | ||
|
||
img = Image.open("qr.jpg") | ||
print(img.format, img.mode, img.size) | ||
img = img.convert("1") | ||
img.save("qr.jpg") |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
""" | ||
Morse kodiranje i dekodiranje | ||
python3 morse.py enc "HELLO FRIEND" 0.06 output.wav | ||
enkodira "HELLO FRIEND" koristeci 0.06s za trajanje tacke i upisuje rezultat u output.wav | ||
python3 morse.py dec input.wav | ||
dekodira sadrzaj fajla input.wav i ispisuje rezultat | ||
""" | ||
import audiolib as a | ||
import sys | ||
|
||
# top level params, can be tuned | ||
freq = 300.0 # frequency | ||
|
||
# morse code dictionary, space maps to space for convenience | ||
morse = {'A': '.-', 'B': '-...', 'C': '-.-.', | ||
'D': '-..', 'E': '.', 'F': '..-.', | ||
'G': '--.', 'H': '....', 'I': '..', | ||
'J': '.---', 'K': '-.-', 'L': '.-..', | ||
'M': '--', 'N': '-.', 'O': '---', | ||
'P': '.--.', 'Q': '--.-', 'R': '.-.', | ||
'S': '...', 'T': '-', 'U': '..-', | ||
'V': '...-', 'W': '.--', 'X': '-..-', | ||
'Y': '-.--', 'Z': '--..', | ||
'0': '-----', '1': '.----', '2': '..---', | ||
'3': '...--', '4': '....-', '5': '.....', | ||
'6': '-....', '7': '--...', '8': '---..', | ||
'9': '----.', | ||
' ': ' ', '.': '.-.-.-', ',': '--..--', ':': '---...' | ||
} | ||
|
||
# encodes the message using tick_len as the unit length and creates wav_filename.wav | ||
def encode(message, tick_len, wav_filename): | ||
print('Message:\"' + message + '\"') | ||
# converts the message to text morse | ||
# after encoding we get 1 space between letters and 3 between words | ||
encoded_message = ''.join([symbol for letter in ' '.join(message) for symbol in morse[letter]]) | ||
|
||
print('Text morse:\"' + encoded_message + '\"') | ||
|
||
# builds the array of frames | ||
frames_per_tick = int(a.framerate * tick_len) | ||
frames = [] | ||
phase = 0 | ||
# the main ecnoding loop | ||
for symbol in encoded_message: | ||
if symbol == ' ': | ||
new_frames, phase = a.create_frames(0, 2*frames_per_tick, phase) | ||
frames.extend(new_frames) # letter break = 3, word break = 7 | ||
if symbol == '.': | ||
new_frames, phase = a.create_frames(0, 1*frames_per_tick, phase) | ||
frames.extend(new_frames) # symbol break = 1 | ||
new_frames, phase = a.create_frames(freq, 1*frames_per_tick, phase) | ||
frames.extend(new_frames) # dit = 1 | ||
elif symbol == '-': | ||
new_frames, phase = a.create_frames(0, 1*frames_per_tick, phase) | ||
frames.extend(new_frames) # symbol break = 1 | ||
new_frames, phase = a.create_frames(freq, 3*frames_per_tick, phase) | ||
frames.extend(new_frames) # dah = 3 | ||
|
||
# writes to file | ||
a.write_wav(frames, wav_filename) | ||
print('Wrote to file:\"' + wav_filename + '\"') | ||
|
||
# adds one block to the array of blocks | ||
def add_block(blocks, curr_block): | ||
blocks.append(curr_block) | ||
# if there is a really short useless block, removes it and merges two surrounding ones | ||
if len(blocks) >= 3 and abs(blocks[-2]) == 1: | ||
blocks.pop() | ||
blocks.pop() | ||
blocks[-1] += curr_block+1 | ||
|
||
# goes through frames and extracts blocks of sines/silences | ||
def extract_blocks(frames): | ||
# tries to adapt to various tick_lenghts | ||
# kicks really short blocks out (probably noise) | ||
blocks = [] | ||
curr_block = 0 # silent blocks are represented by negative values | ||
last_frame = 0 | ||
for frame in frames: | ||
if frame == 0: # silent frame | ||
# registers the end of the sine block | ||
if curr_block > 0: | ||
add_block(blocks, curr_block) | ||
curr_block = 0; | ||
curr_block -= 1 | ||
else: # sine frame | ||
# registers the end of the silent block | ||
if curr_block < 0: | ||
add_block(blocks, curr_block) | ||
curr_block = 0; | ||
curr_block += 1 | ||
# last block | ||
add_block(blocks, curr_block) | ||
# if last blocks is +-1, delete it | ||
if len(blocks) and abs(blocks[-1]) == 1: | ||
blocks.pop() | ||
return blocks | ||
|
||
# decodes wav_filename.wav | ||
# > ugly hacks that work should get full marks | ||
def decode(wav_filename): | ||
# opens input file and gets frames | ||
frames = a.read_wav(wav_filename) | ||
print('Read from file:\"' + wav_filename + '\"') | ||
# goes through frames and extracts blocks of sines/silences | ||
blocks = extract_blocks(frames) | ||
# determines the lengths of 5 possible atoms: word_break, letter_break, symbol_break, dit, dah | ||
block_lengths = sorted(set(blocks)) | ||
if len(block_lengths) != 5: | ||
print(block_lengths) | ||
raise Exception('more or less than 5 different block lengths, oops') | ||
# yes, I know what you're thinking, another crazy assumption | ||
# we can be clever with less than 5 different block lengths but let's | ||
# just leave it for now and assume that all 5 block types will be present | ||
# we have 5 sorted block lengths | ||
len_to_atom = { | ||
block_lengths[0]: '# #', # word_break | ||
block_lengths[1]: '#', # letter_break | ||
block_lengths[2]: '', # symbol_break, not needed anymore | ||
block_lengths[3]: '.', # dit | ||
block_lengths[4]: '-' # dah | ||
} | ||
encoded_message = ''.join([len_to_atom[block] for block in blocks]) | ||
print('Text morse:\"' + encoded_message.replace('#', ' ') + '\"') # debug print | ||
# makes an inverse mapping and decodes text morse to plaintext | ||
inv_morse = {v: k for k, v in morse.items()} | ||
message = ''.join([inv_morse[letter] for letter in encoded_message.split('#')]) | ||
print('Message:\"' + message + '\"') # solution | ||
|
||
# main function, takes care of parsing the command line arguments | ||
def main(): | ||
if len(sys.argv) < 2: | ||
raise Exception('no arguments') | ||
if sys.argv[1] == 'enc': | ||
if len(sys.argv) != 5: | ||
raise Exception('enc expects exactly three arguments') | ||
encode(sys.argv[2], float(sys.argv[3]), sys.argv[4]) | ||
elif sys.argv[1] == 'dec': | ||
if len(sys.argv) != 3: | ||
raise Exception('dec expects exactly one argument') | ||
decode(sys.argv[2]) | ||
else: | ||
raise Exception('enc/dec are only allowed commands') | ||
|
||
# entry point | ||
if __name__ == "__main__": | ||
main() |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Oops, something went wrong.