diff --git a/collect-fingerprints-of-songs.py b/collect-fingerprints-of-songs.py index bf1c062..15a08c3 100644 --- a/collect-fingerprints-of-songs.py +++ b/collect-fingerprints-of-songs.py @@ -30,33 +30,33 @@ colored('channels=%d', 'white', attrs=['dark']), # channels colored('%s', 'white', attrs=['bold']) # filename ) - print msg % (song_id, len(audio['channels']), filename) + print(msg % (song_id, len(audio['channels']), filename)) if song: hash_count = db.get_song_hashes_count(song_id) if hash_count > 0: msg = ' already exists (%d hashes), skip' % hash_count - print colored(msg, 'red') + print(colored(msg, 'red')) continue - print colored(' new song, going to analyze..', 'green') + print(colored(' new song, going to analyze..', 'green')) hashes = set() channel_amount = len(audio['channels']) for channeln, channel in enumerate(audio['channels']): msg = ' fingerprinting channel %d/%d' - print colored(msg, attrs=['dark']) % (channeln+1, channel_amount) + print(colored(msg, attrs=['dark']) % (channeln+1, channel_amount)) channel_hashes = fingerprint.fingerprint(channel, Fs=audio['Fs'], plots=config['fingerprint.show_plots']) channel_hashes = set(channel_hashes) msg = ' finished channel %d/%d, got %d hashes' - print colored(msg, attrs=['dark']) % ( + print(colored(msg, attrs=['dark']) % ( channeln+1, channel_amount, len(channel_hashes) - ) + )) hashes |= channel_hashes @@ -67,7 +67,7 @@ values.append((song_id, hash, offset)) msg = ' storing %d hashes in db' % len(values) - print colored(msg, 'green') + print(colored(msg, 'green')) db.store_fingerprints(values) diff --git a/libs/db_sqlite.py b/libs/db_sqlite.py index eeee5d7..a116e55 100644 --- a/libs/db_sqlite.py +++ b/libs/db_sqlite.py @@ -1,8 +1,8 @@ -from db import Database -from config import get_config -import sqlite3 import sys -from itertools import izip_longest +import sqlite3 +from libs.db import Database +from libs.config import get_config +from itertools import zip_longest from termcolor import colored class SqliteDatabase(Database): @@ -66,11 +66,11 @@ def findAll(self, table, params): def insert(self, table, params): keys = ', '.join(params.keys()) - values = params.values() + values = list(params.values()) - query = "INSERT INTO songs (%s) VALUES (?, ?)" % (keys); + query = "INSERT INTO songs (%s) VALUES ('%s','%s')" % (keys, values[0], values[1]); - self.cur.execute(query, values) + self.cur.execute(query) self.conn.commit() return self.cur.lastrowid @@ -79,7 +79,7 @@ def insertMany(self, table, columns, values): def grouper(iterable, n, fillvalue=None): args = [iter(iterable)] * n return (filter(None, values) for values - in izip_longest(fillvalue=fillvalue, *args)) + in zip_longest(fillvalue=fillvalue, *args)) for split_values in grouper(values, 1000): query = "INSERT OR IGNORE INTO %s (%s) VALUES (?, ?, ?)" % (table, ", ".join(columns)) diff --git a/libs/fingerprint.py b/libs/fingerprint.py index 3c0b5e4..5c63eb0 100644 --- a/libs/fingerprint.py +++ b/libs/fingerprint.py @@ -1,6 +1,8 @@ import hashlib import numpy as np import matplotlib.mlab as mlab +# import matplotlib +# matplotlib.use('TKAgg') import matplotlib.pyplot as plt from termcolor import colored @@ -95,7 +97,8 @@ def fingerprint(channel_samples, Fs=DEFAULT_FS, local_maxima = get_2D_peaks(arr2D, plot=plots, amp_min=amp_min) msg = ' local_maxima: %d of frequency & time pairs' - print colored(msg, attrs=['dark']) % len(local_maxima) + local_maxima = list(local_maxima) + print(colored(msg, attrs=['dark']) % len(local_maxima)) # return hashes return generate_hashes(local_maxima, fan_value=fan_value) @@ -164,5 +167,6 @@ def generate_hashes(peaks, fan_value=DEFAULT_FAN_VALUE): # check if delta is between min & max if t_delta >= MIN_HASH_TIME_DELTA and t_delta <= MAX_HASH_TIME_DELTA: - h = hashlib.sha1("%s|%s|%s" % (str(freq1), str(freq2), str(t_delta))) + full_code = "%s|%s|%s" %(str(freq1), str(freq2), str(t_delta)) + h = hashlib.sha1(full_code.encode('utf-8')) yield (h.hexdigest()[0:FINGERPRINT_REDUCTION], t1) diff --git a/libs/reader_file.py b/libs/reader_file.py index 0a49124..7f80ee1 100644 --- a/libs/reader_file.py +++ b/libs/reader_file.py @@ -1,11 +1,11 @@ -from reader import BaseReader import os +import numpy as np +from reader import Reader from pydub import AudioSegment from pydub.utils import audioop -import numpy as np from hashlib import sha1 -class FileReader(BaseReader): +class FileReader(Reader): def __init__(self, filename): # super(FileReader, self).__init__(a) self.filename = filename @@ -37,7 +37,7 @@ def parse_audio(self): data = np.fromstring(audiofile._data, np.int16) channels = [] - for chn in xrange(audiofile.channels): + for chn in range(audiofile.channels): channels.append(data[chn::audiofile.channels]) fs = audiofile.frame_rate diff --git a/libs/reader_microphone.py b/libs/reader_microphone.py index 4d565c3..11ad94a 100644 --- a/libs/reader_microphone.py +++ b/libs/reader_microphone.py @@ -1,9 +1,14 @@ import pyaudio import numpy import wave -from reader import BaseReader +from reader import Reader +from reader._search import Search +from reader._storage import Storage +from reader._parser import Parser +from reader._types import NameScheme -class MicrophoneReader(BaseReader): + +class MicrophoneReader(Reader): default_chunksize = 8192 default_format = pyaudio.paInt16 default_channels = 2 @@ -12,7 +17,7 @@ class MicrophoneReader(BaseReader): # set default def __init__(self, a): - super(MicrophoneReader, self).__init__(a) + super(MicrophoneReader, self).__init__(a, Storage, Search, Parser, NameScheme) self.audio = pyaudio.PyAudio() self.stream = None self.data = [] diff --git a/recognize-from-microphone.py b/recognize-from-microphone.py index 255ceaf..b0983ed 100644 --- a/recognize-from-microphone.py +++ b/recognize-from-microphone.py @@ -6,7 +6,7 @@ import argparse from argparse import RawTextHelpFormatter -from itertools import izip_longest +from itertools import zip_longest from termcolor import colored from libs.config import get_config from libs.reader_microphone import MicrophoneReader @@ -44,7 +44,7 @@ channels=channels) msg = ' * started recording..' - print colored(msg, attrs=['dark']) + print(colored(msg, attrs=['dark'])) while True: bufferSize = int(reader.rate / reader.chunksize * seconds) @@ -54,10 +54,10 @@ if visualise_console: msg = colored(' %05d', attrs=['dark']) + colored(' %s', 'green') - print msg % visual_peak.calc(nums) + print(msg % visual_peak.calc(nums)) else: msg = ' processing %d of %d..' % (i, bufferSize) - print colored(msg, attrs=['dark']) + print(colored(msg, attrs=['dark'])) if not record_forever: break @@ -68,19 +68,19 @@ reader.stop_recording() msg = ' * recording has been stopped' - print colored(msg, attrs=['dark']) + print(colored(msg, attrs=['dark'])) def grouper(iterable, n, fillvalue=None): args = [iter(iterable)] * n - return (filter(None, values) for values - in izip_longest(fillvalue=fillvalue, *args)) + return (list(filter(None, values)) for values + in zip_longest(fillvalue=fillvalue, *args)) data = reader.get_recorded_data() msg = ' * recorded %d samples' - print colored(msg, attrs=['dark']) % len(data[0]) + print(colored(msg, attrs=['dark']) % len(data[0])) # reader.save_recorded('test.wav') @@ -108,40 +108,41 @@ def return_matches(hashes): FROM fingerprints WHERE upper(hash) IN (%s) """ - query = query % ', '.join('?' * len(split_values)) + list_leng = len(list(split_values)) + query = query % ', '.join('?' * list_leng) x = db.executeAll(query, split_values) matches_found = len(x) if matches_found > 0: msg = ' ** found %d hash matches (step %d/%d)' - print colored(msg, 'green') % ( + print(colored(msg, 'green') % ( matches_found, len(split_values), len(values) - ) + )) else: msg = ' ** not matches found (step %d/%d)' - print colored(msg, 'red') % ( + print(colored(msg, 'red') % ( len(split_values), len(values) - ) + )) for hash, sid, offset in x: # (sid, db_offset - song_sampled_offset) - yield (sid, offset - mapper[hash]) + yield (sid, mapper[hash]) for channeln, channel in enumerate(data): # TODO: Remove prints or change them into optional logging. msg = ' fingerprinting channel %d/%d' - print colored(msg, attrs=['dark']) % (channeln+1, channel_amount) + print(colored(msg, attrs=['dark']) % (channeln+1, channel_amount)) matches.extend(find_matches(channel)) msg = ' finished channel %d/%d, got %d hashes' - print colored(msg, attrs=['dark']) % ( + print(colored(msg, attrs=['dark']) % ( channeln+1, channel_amount, len(matches) - ) + )) def align_matches(matches): diff_counter = {} @@ -181,11 +182,11 @@ def align_matches(matches): total_matches_found = len(matches) - print '' + print('') if total_matches_found > 0: msg = ' ** totally found %d hash matches' - print colored(msg, 'green') % total_matches_found + print(colored(msg, 'green') % total_matches_found) song = align_matches(matches) @@ -193,11 +194,11 @@ def align_matches(matches): msg += ' offset: %d (%d secs)\n' msg += ' confidence: %d' - print colored(msg, 'green') % ( + print(colored(msg, 'green') % ( song['SONG_NAME'], song['SONG_ID'], song['OFFSET'], song['OFFSET_SECS'], song['CONFIDENCE'] - ) + )) else: msg = ' ** not matches found at all' - print colored(msg, 'red') + print(colored(msg, 'red')) diff --git a/recognizer.py b/recognizer.py new file mode 100644 index 0000000..5718d92 --- /dev/null +++ b/recognizer.py @@ -0,0 +1,211 @@ +#!/usr/bin/python +import os +import sys +import libs +import libs.fingerprint as fingerprint +import argparse + +from argparse import RawTextHelpFormatter +from itertools import zip_longest +from termcolor import colored +from libs.config import get_config +from libs.reader_microphone import MicrophoneReader +from libs.visualiser_console import VisualiserConsole as visual_peak +from libs.visualiser_plot import VisualiserPlot as visual_plot +from libs.db_sqlite import SqliteDatabase +# from libs.db_mongo import MongoDatabase + + +def grouper(iterable, n, fillvalue=None): + args = [iter(iterable)] * n + return (list(filter(None, values)) for values + in zip_longest(fillvalue=fillvalue, *args)) + +def find_matches(samples,db, Fs=fingerprint.DEFAULT_FS): + hashes = fingerprint.fingerprint(samples, Fs=Fs) + return return_matches(hashes, db) + + +def align_matches(matches, db): + diff_counter = {} + largest = 0 + largest_count = 0 + song_id = -1 + + for tup in matches: + sid, diff = tup + + if diff not in diff_counter: + diff_counter[diff] = {} + + if sid not in diff_counter[diff]: + diff_counter[diff][sid] = 0 + + diff_counter[diff][sid] += 1 + + if diff_counter[diff][sid] > largest_count: + largest = diff + largest_count = diff_counter[diff][sid] + song_id = sid + + songM = db.get_song_by_id(song_id) + nseconds = round(float(largest) / fingerprint.DEFAULT_FS * + fingerprint.DEFAULT_WINDOW_SIZE * + fingerprint.DEFAULT_OVERLAP_RATIO, 5) + + return { + "SONG_ID" : song_id, + "SONG_NAME" : songM[1], + "CONFIDENCE" : largest_count, + "OFFSET" : int(largest), + "OFFSET_SECS" : nseconds + } + + +def return_matches(hashes, db): + mapper = {} + for hash, offset in hashes: + mapper[hash.upper()] = offset + values = mapper.keys() + + for split_values in grouper(values, 1000): + # @todo move to db related files + query = """ + SELECT upper(hash), song_fk, offset + FROM fingerprints + WHERE upper(hash) IN (%s) + """ + list_leng = len(list(split_values)) + query = query % ', '.join('?' * list_leng) + + x = db.executeAll(query, split_values) + matches_found = len(x) + + if matches_found > 0: + msg = ' ** found %d hash matches (step %d/%d)' + print(colored(msg, 'green') % ( + matches_found, + len(split_values), + len(values) + )) + else: + msg = ' ** not matches found (step %d/%d)' + print(colored(msg, 'red') % ( + len(split_values), + len(values) + )) + + for hash, sid, offset in x: + # (sid, db_offset - song_sampled_offset) + yield (sid, mapper[hash]) + +def recognize(seconds): + config = get_config() + + db = SqliteDatabase() + + seconds = int(seconds) + + chunksize = 2**12 # 4096 + channels = 2 #int(config['channels']) # 1=mono, 2=stereo + + record_forever = False + visualise_console = bool(config['mic.visualise_console']) + visualise_plot = bool(config['mic.visualise_plot']) + + reader = MicrophoneReader(None) + + reader.start_recording(seconds=seconds, + chunksize=chunksize, + channels=channels) + + msg = ' * started recording..' + print(colored(msg, attrs=['dark'])) + + while True: + bufferSize = int(reader.rate / reader.chunksize * seconds) + + for i in range(0, bufferSize): + nums = reader.process_recording() + + if visualise_console: + msg = colored(' %05d', attrs=['dark']) + colored(' %s', 'green') + print(msg % visual_peak.calc(nums)) + else: + msg = ' processing %d of %d..' % (i, bufferSize) + print(colored(msg, attrs=['dark'])) + + if not record_forever: break + + if visualise_plot: + data = reader.get_recorded_data()[0] + visual_plot.show(data) + + reader.stop_recording() + + msg = ' * recording has been stopped' + print(colored(msg, attrs=['dark'])) + + data = reader.get_recorded_data() + + msg = ' * recorded %d samples' + print(colored(msg, attrs=['dark']) % len(data[0])) + + # reader.save_recorded('test.wav') + + + Fs = fingerprint.DEFAULT_FS + channel_amount = len(data) + + result = set() + matches = [] + + for channeln, channel in enumerate(data): + # TODO: Remove prints or change them into optional logging. + msg = ' fingerprinting channel %d/%d' + print(colored(msg, attrs=['dark']) % (channeln+1, channel_amount)) + + matches.extend(find_matches(channel, db)) + + msg = ' finished channel %d/%d, got %d hashes' + print(colored(msg, attrs=['dark']) % ( + channeln+1, channel_amount, len(matches) + )) + + total_matches_found = len(matches) + + print('') + + if total_matches_found > 0: + msg = ' ** totally found %d hash matches' + print(colored(msg, 'green') % total_matches_found) + + song = align_matches(matches, db) + + msg = ' => song: %s (id=%d)\n' + msg += ' offset: %d (%d secs)\n' + msg += ' confidence: %d' + + print(colored(msg, 'green') % ( + song['SONG_NAME'], song['SONG_ID'], + song['OFFSET'], song['OFFSET_SECS'], + song['CONFIDENCE'] + )) + + result = { + "match_found": True, + "song_name": song['SONG_ID'], + "accuracy": song['CONFIDENCE'] + } + return result + else: + msg = ' ** not matches found at all' + print(colored(msg, 'red')) + result = { + "match_found": False + } + return result + +# Entry point +if __name__ == '__main__': + recognizer() diff --git a/requirements.txt b/requirements.txt index 39ec3ce..c8ecf1c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,8 @@ numpy>=1 +matplotlib termcolor pyaudio wave pydub +reader +scipy diff --git a/tests/sqlite.py b/tests/sqlite.py index 9029e43..634afe1 100644 --- a/tests/sqlite.py +++ b/tests/sqlite.py @@ -11,4 +11,4 @@ row = db.executeOne("SELECT 2+3 as x;") assert row[0] == 5, "failed simple sql execution" - print ' * %s' % colored('ok', 'green') + print(' * %s' % colored('ok', 'green'))