2013-12-20 17:16:35 +00:00
|
|
|
from dejavu.database import get_database
|
2013-12-18 23:54:17 +00:00
|
|
|
import dejavu.decoder as decoder
|
2013-12-17 22:12:21 +00:00
|
|
|
import fingerprint
|
2013-12-23 13:59:08 +00:00
|
|
|
import multiprocessing
|
2013-12-18 23:54:17 +00:00
|
|
|
import os
|
2013-12-16 23:38:58 +00:00
|
|
|
|
2013-12-17 23:31:57 +00:00
|
|
|
class Dejavu(object):
|
2014-07-03 03:49:38 +00:00
|
|
|
|
|
|
|
SONG_ID = "song_id"
|
|
|
|
SONG_NAME = 'song_name'
|
|
|
|
CONFIDENCE = 'confidence'
|
|
|
|
MATCH_TIME = 'match_time'
|
|
|
|
OFFSET = 'offset'
|
|
|
|
|
2013-12-16 23:38:58 +00:00
|
|
|
def __init__(self, config):
|
2013-12-18 23:54:17 +00:00
|
|
|
super(Dejavu, self).__init__()
|
2013-12-17 23:31:57 +00:00
|
|
|
|
2013-12-16 23:38:58 +00:00
|
|
|
self.config = config
|
2013-12-17 23:31:57 +00:00
|
|
|
|
2013-12-16 23:38:58 +00:00
|
|
|
# initialize db
|
2013-12-20 17:16:35 +00:00
|
|
|
db_cls = get_database(config.get("database_type", None))
|
2013-12-17 23:31:57 +00:00
|
|
|
|
2013-12-20 17:16:35 +00:00
|
|
|
self.db = db_cls(**config.get("database", {}))
|
2013-12-16 23:38:58 +00:00
|
|
|
self.db.setup()
|
2014-01-20 19:53:25 +00:00
|
|
|
|
|
|
|
# if we should limit seconds fingerprinted,
|
2014-01-08 04:25:55 +00:00
|
|
|
# None|-1 means use entire track
|
|
|
|
self.limit = self.config.get("fingerprint_limit", None)
|
|
|
|
if self.limit == -1: # for JSON compatibility
|
|
|
|
self.limit = None
|
2014-02-20 21:30:15 +00:00
|
|
|
self.get_fingerprinted_songs()
|
2013-12-16 23:38:58 +00:00
|
|
|
|
2014-02-20 21:30:15 +00:00
|
|
|
def get_fingerprinted_songs(self):
|
2013-12-16 23:38:58 +00:00
|
|
|
# get songs previously indexed
|
2013-12-25 08:56:43 +00:00
|
|
|
# TODO: should probably use a checksum of the file instead of filename
|
2013-12-16 23:38:58 +00:00
|
|
|
self.songs = self.db.get_songs()
|
2013-12-18 23:54:17 +00:00
|
|
|
self.songnames_set = set() # to know which ones we've computed before
|
2013-12-17 23:31:57 +00:00
|
|
|
for song in self.songs:
|
|
|
|
song_name = song[self.db.FIELD_SONGNAME]
|
|
|
|
self.songnames_set.add(song_name)
|
2013-12-16 23:38:58 +00:00
|
|
|
|
2013-12-18 23:54:17 +00:00
|
|
|
def fingerprint_directory(self, path, extensions, nprocesses=None):
|
|
|
|
# Try to use the maximum amount of processes if not given.
|
2013-12-23 13:59:08 +00:00
|
|
|
try:
|
|
|
|
nprocesses = nprocesses or multiprocessing.cpu_count()
|
|
|
|
except NotImplementedError:
|
|
|
|
nprocesses = 1
|
|
|
|
else:
|
|
|
|
nprocesses = 1 if nprocesses <= 0 else nprocesses
|
|
|
|
|
|
|
|
pool = multiprocessing.Pool(nprocesses)
|
|
|
|
|
2014-01-20 19:53:25 +00:00
|
|
|
filenames_to_fingerprint = []
|
2013-12-23 13:59:08 +00:00
|
|
|
for filename, _ in decoder.find_files(path, extensions):
|
2013-12-25 08:56:43 +00:00
|
|
|
|
|
|
|
# don't refingerprint already fingerprinted files
|
|
|
|
if decoder.path_to_songname(filename) in self.songnames_set:
|
|
|
|
print "%s already fingerprinted, continuing..." % filename
|
|
|
|
continue
|
|
|
|
|
2014-01-20 19:53:25 +00:00
|
|
|
filenames_to_fingerprint.append(filename)
|
|
|
|
|
|
|
|
# Prepare _fingerprint_worker input
|
|
|
|
worker_input = zip(filenames_to_fingerprint,
|
|
|
|
[self.limit] * len(filenames_to_fingerprint))
|
|
|
|
|
|
|
|
# Send off our tasks
|
|
|
|
iterator = pool.imap_unordered(_fingerprint_worker,
|
|
|
|
worker_input)
|
|
|
|
|
|
|
|
# Loop till we have all of them
|
|
|
|
while True:
|
|
|
|
try:
|
|
|
|
song_name, hashes = iterator.next()
|
|
|
|
except multiprocessing.TimeoutError:
|
|
|
|
continue
|
|
|
|
except StopIteration:
|
|
|
|
break
|
|
|
|
except:
|
|
|
|
print("Failed fingerprinting")
|
|
|
|
|
|
|
|
# Print traceback because we can't reraise it here
|
|
|
|
import traceback, sys
|
|
|
|
traceback.print_exc(file=sys.stdout)
|
|
|
|
else:
|
|
|
|
sid = self.db.insert_song(song_name)
|
|
|
|
|
|
|
|
self.db.insert_hashes(sid, hashes)
|
2014-02-20 19:43:42 +00:00
|
|
|
self.db.set_song_fingerprinted(sid)
|
2014-02-20 21:30:15 +00:00
|
|
|
self.get_fingerprinted_songs()
|
2013-12-23 13:59:08 +00:00
|
|
|
|
|
|
|
pool.close()
|
|
|
|
pool.join()
|
2013-12-17 23:31:57 +00:00
|
|
|
|
2013-12-17 22:12:21 +00:00
|
|
|
def fingerprint_file(self, filepath, song_name=None):
|
2014-02-20 21:30:15 +00:00
|
|
|
|
|
|
|
songname = decoder.path_to_songname(filepath)
|
|
|
|
song_name = song_name or songname
|
|
|
|
# don't refingerprint already fingerprinted files
|
|
|
|
if song_name in self.songnames_set:
|
|
|
|
print "%s already fingerprinted, continuing..." % song_name
|
|
|
|
else:
|
|
|
|
song_name, hashes = _fingerprint_worker(filepath,
|
|
|
|
self.limit,
|
|
|
|
song_name=song_name)
|
|
|
|
|
|
|
|
sid = self.db.insert_song(song_name)
|
|
|
|
|
|
|
|
self.db.insert_hashes(sid, hashes)
|
|
|
|
self.db.set_song_fingerprinted(sid)
|
|
|
|
self.get_fingerprinted_songs()
|
2013-12-17 23:31:57 +00:00
|
|
|
|
2013-12-17 20:18:55 +00:00
|
|
|
def find_matches(self, samples, Fs=fingerprint.DEFAULT_FS):
|
2013-12-16 23:38:58 +00:00
|
|
|
hashes = fingerprint.fingerprint(samples, Fs=Fs)
|
|
|
|
return self.db.return_matches(hashes)
|
2013-12-17 23:31:57 +00:00
|
|
|
|
2013-12-17 20:18:55 +00:00
|
|
|
def align_matches(self, matches):
|
2013-12-16 23:38:58 +00:00
|
|
|
"""
|
|
|
|
Finds hash matches that align in time with other matches and finds
|
|
|
|
consensus about which hashes are "true" signal from the audio.
|
2013-12-17 23:31:57 +00:00
|
|
|
|
2013-12-16 23:38:58 +00:00
|
|
|
Returns a dictionary with match information.
|
|
|
|
"""
|
|
|
|
# align by diffs
|
|
|
|
diff_counter = {}
|
|
|
|
largest = 0
|
|
|
|
largest_count = 0
|
|
|
|
song_id = -1
|
|
|
|
for tup in matches:
|
|
|
|
sid, diff = tup
|
|
|
|
if not diff in diff_counter:
|
|
|
|
diff_counter[diff] = {}
|
|
|
|
if not sid 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
|
|
|
|
|
2013-12-17 23:31:57 +00:00
|
|
|
# extract idenfication
|
2013-12-16 23:38:58 +00:00
|
|
|
song = self.db.get_song_by_id(song_id)
|
|
|
|
if song:
|
2014-07-03 03:49:38 +00:00
|
|
|
# TODO: Clarify what `get_song_by_id` should return.
|
|
|
|
songname = song.get(Dejavu.SONG_NAME, None)
|
2013-12-16 23:38:58 +00:00
|
|
|
else:
|
|
|
|
return None
|
2013-12-17 23:31:57 +00:00
|
|
|
|
2013-12-16 23:38:58 +00:00
|
|
|
# return match info
|
|
|
|
song = {
|
2014-07-03 03:49:38 +00:00
|
|
|
Dejavu.SONG_ID : song_id,
|
|
|
|
Dejavu.SONG_NAME : songname,
|
|
|
|
Dejavu.CONFIDENCE : largest_count,
|
|
|
|
Dejavu.OFFSET : largest }
|
2013-12-17 23:31:57 +00:00
|
|
|
|
|
|
|
return song
|
2013-12-18 23:54:17 +00:00
|
|
|
|
|
|
|
def recognize(self, recognizer, *options, **kwoptions):
|
|
|
|
r = recognizer(self)
|
|
|
|
return r.recognize(*options, **kwoptions)
|
|
|
|
|
|
|
|
|
2014-01-20 19:53:25 +00:00
|
|
|
def _fingerprint_worker(filename, limit=None, song_name=None):
|
|
|
|
# Pool.imap sends arguments as tuples so we have to unpack
|
|
|
|
# them ourself.
|
|
|
|
try:
|
|
|
|
filename, limit = filename
|
|
|
|
except ValueError:
|
|
|
|
pass
|
|
|
|
|
|
|
|
songname, extension = os.path.splitext(os.path.basename(filename))
|
|
|
|
|
|
|
|
song_name = song_name or songname
|
2013-12-23 13:59:08 +00:00
|
|
|
|
2014-01-08 04:25:55 +00:00
|
|
|
channels, Fs = decoder.read(filename, limit)
|
2013-12-23 13:59:08 +00:00
|
|
|
|
2014-01-20 19:53:25 +00:00
|
|
|
result = set()
|
2013-12-23 13:59:08 +00:00
|
|
|
|
|
|
|
channel_amount = len(channels)
|
|
|
|
for channeln, channel in enumerate(channels):
|
|
|
|
# TODO: Remove prints or change them into optional logging.
|
|
|
|
print("Fingerprinting channel %d/%d for %s" % (channeln + 1,
|
|
|
|
channel_amount,
|
|
|
|
filename))
|
|
|
|
hashes = fingerprint.fingerprint(channel, Fs=Fs)
|
|
|
|
print("Finished channel %d/%d for %s" % (channeln + 1, channel_amount,
|
|
|
|
filename))
|
|
|
|
|
2014-01-20 19:53:25 +00:00
|
|
|
result |= set(hashes)
|
2013-12-23 13:59:08 +00:00
|
|
|
|
2014-01-20 19:53:25 +00:00
|
|
|
return song_name, result
|
2013-12-23 13:59:08 +00:00
|
|
|
|
|
|
|
|
2013-12-18 23:54:17 +00:00
|
|
|
def chunkify(lst, n):
|
|
|
|
"""
|
|
|
|
Splits a list into roughly n equal parts.
|
|
|
|
http://stackoverflow.com/questions/2130016/splitting-a-list-of-arbitrary-size-into-only-roughly-n-equal-parts
|
|
|
|
"""
|
|
|
|
return [lst[i::n] for i in xrange(n)]
|