-
Notifications
You must be signed in to change notification settings - Fork 17
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
0 parents
commit 4f922b7
Showing
4 changed files
with
191 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,58 @@ | ||
# Simple tool to compare audio files | ||
|
||
NOTE: I haven't written this, merely found it on the internet and ported to | ||
python 3. | ||
|
||
* https://shivama205.medium.com/audio-signals-comparison-23e431ed2207 "Audio signals: Comparison" | ||
* https://gist.github.com/shivama205/5578f999a9c88112f5d042ebb83e54d5 scripts from the article | ||
|
||
Related projects: | ||
|
||
* https://acoustid.org/chromaprint fpcalc | ||
* numpy | ||
|
||
Usage: | ||
|
||
Sample files captured from a streaming source without exact start, duration but | ||
are the same song: | ||
|
||
$ ./compare.py -i file1.mp3 -o file2.mp3 | ||
Calculating fingerprint by fpcalc for file1.mp3 | ||
Calculating fingerprint by fpcalc for file2.mp3 | ||
File A: file1.mp3 | ||
File B: file2.mp3 | ||
Match with correlation of 63.74% at offset 55 | ||
|
||
$ ./compare.py -i file2.mp3 -o file1.mp3 | ||
Calculating fingerprint by fpcalc for file2.mp3 | ||
Calculating fingerprint by fpcalc for file1.mp3 | ||
File A: file2.mp3 | ||
File B: file2.mp3 | ||
Match with correlation of 63.74% at offset -5 | ||
|
||
For some files the swapped order may not lead to the same results due to offset | ||
or the way the `fpcalc` fingerprint is generated (see help). | ||
|
||
$ ./compare.py -i file2.mp3 -o file3.mp3 | ||
Calculating fingerprint by fpcalc for file2.mp3 | ||
Calculating fingerprint by fpcalc for file3.mp3 | ||
File A: file2.mp3 | ||
File B: file3.mp3 | ||
Match with correlation of 93.01% at offset -24 | ||
|
||
$ ./compare.py | ||
Calculating fingerprint by fpcalc for file1.mp3 | ||
Calculating fingerprint by fpcalc for file3.mp3 | ||
File A: file1.mp3 | ||
File B: file3.mp3 | ||
Match with correlation of 63.96% at offset 31 | ||
|
||
Internally the fingerprint is generated by `fpcalc -length 500`, cached | ||
versions can be produced by `fpcalc-gen`. | ||
|
||
Changes: | ||
|
||
- port to python3 | ||
- print the similary as percents | ||
- print input files on separate lines | ||
- support precalculated fingerprint in `file.mp3.fpcalc` |
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 @@ | ||
#!/usr/bin/python3 | ||
# compare.py | ||
import argparse | ||
from correlation import correlate | ||
|
||
def initialize(): | ||
parser = argparse.ArgumentParser() | ||
parser.add_argument("-i ", "--source-file", help="source file") | ||
parser.add_argument("-o ", "--target-file", help="target file") | ||
args = parser.parse_args() | ||
|
||
SOURCE_FILE = args.source_file if args.source_file else None | ||
TARGET_FILE = args.target_file if args.target_file else None | ||
if not SOURCE_FILE or not TARGET_FILE: | ||
raise Exception("Source or Target files not specified.") | ||
return SOURCE_FILE, TARGET_FILE | ||
|
||
if __name__ == "__main__": | ||
SOURCE_FILE, TARGET_FILE = initialize() | ||
correlate(SOURCE_FILE, TARGET_FILE) |
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,111 @@ | ||
#!/usr/bin/python3 | ||
|
||
# correlation.py | ||
import subprocess | ||
import numpy | ||
import os | ||
|
||
# seconds to sample audio file for | ||
sample_time = 500 | ||
# number of points to scan cross correlation over | ||
span = 150 | ||
# step size (in points) of cross correlation | ||
step = 1 | ||
# minimum number of points that must overlap in cross correlation | ||
# exception is raised if this cannot be met | ||
min_overlap = 20 | ||
# report match when cross correlation has a peak exceeding threshold | ||
threshold = 0.5 | ||
|
||
# calculate fingerprint | ||
# Generate file.mp3.fpcalc by "fpcalc -raw -length 500 file.mp3" | ||
def calculate_fingerprints(filename): | ||
if os.path.exists(filename + '.fpcalc'): | ||
print("Found precalculated fingerprint for %s" % (filename)) | ||
f = open(filename + '.fpcalc', "r") | ||
fpcalc_out = ''.join(f.readlines()) | ||
f.close() | ||
else: | ||
print("Calculating fingerprint by fpcalc for %s" % (filename)) | ||
fpcalc_out = str(subprocess.check_output(['fpcalc', '-raw', '-length', str(sample_time), filename])).strip().replace('\\n', '').replace("'", "") | ||
|
||
fingerprint_index = fpcalc_out.find('FINGERPRINT=') + 12 | ||
# convert fingerprint to list of integers | ||
fingerprints = list(map(int, fpcalc_out[fingerprint_index:].split(','))) | ||
|
||
return fingerprints | ||
|
||
# returns correlation between lists | ||
def correlation(listx, listy): | ||
if len(listx) == 0 or len(listy) == 0: | ||
# Error checking in main program should prevent us from ever being | ||
# able to get here. | ||
raise Exception('Empty lists cannot be correlated.') | ||
if len(listx) > len(listy): | ||
listx = listx[:len(listy)] | ||
elif len(listx) < len(listy): | ||
listy = listy[:len(listx)] | ||
|
||
covariance = 0 | ||
for i in range(len(listx)): | ||
covariance += 32 - bin(listx[i] ^ listy[i]).count("1") | ||
covariance = covariance / float(len(listx)) | ||
|
||
return covariance/32 | ||
|
||
# return cross correlation, with listy offset from listx | ||
def cross_correlation(listx, listy, offset): | ||
if offset > 0: | ||
listx = listx[offset:] | ||
listy = listy[:len(listx)] | ||
elif offset < 0: | ||
offset = -offset | ||
listy = listy[offset:] | ||
listx = listx[:len(listy)] | ||
if min(len(listx), len(listy)) < min_overlap: | ||
# Error checking in main program should prevent us from ever being | ||
# able to get here. | ||
return | ||
#raise Exception('Overlap too small: %i' % min(len(listx), len(listy))) | ||
return correlation(listx, listy) | ||
|
||
# cross correlate listx and listy with offsets from -span to span | ||
def compare(listx, listy, span, step): | ||
if span > min(len(listx), len(listy)): | ||
# Error checking in main program should prevent us from ever being | ||
# able to get here. | ||
raise Exception('span >= sample size: %i >= %i\n' | ||
% (span, min(len(listx), len(listy))) | ||
+ 'Reduce span, reduce crop or increase sample_time.') | ||
corr_xy = [] | ||
for offset in numpy.arange(-span, span + 1, step): | ||
corr_xy.append(cross_correlation(listx, listy, offset)) | ||
return corr_xy | ||
|
||
# return index of maximum value in list | ||
def max_index(listx): | ||
max_index = 0 | ||
max_value = listx[0] | ||
for i, value in enumerate(listx): | ||
if value > max_value: | ||
max_value = value | ||
max_index = i | ||
return max_index | ||
|
||
def get_max_corr(corr, source, target): | ||
max_corr_index = max_index(corr) | ||
max_corr_offset = -span + max_corr_index * step | ||
#print("max_corr_index = ", max_corr_index, "max_corr_offset = ", max_corr_offset) | ||
# report matches | ||
if corr[max_corr_index] > threshold: | ||
print("File A: %s" % (source)) | ||
print("File B: %s" % (target)) | ||
print('Match with correlation of %.2f%% at offset %i' | ||
% (corr[max_corr_index] * 100.0, max_corr_offset)) | ||
|
||
def correlate(source, target): | ||
fingerprint_source = calculate_fingerprints(source) | ||
fingerprint_target = calculate_fingerprints(target) | ||
|
||
corr = compare(fingerprint_source, fingerprint_target, span, step) | ||
max_corr_offset = get_max_corr(corr, source, target) |
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 @@ | ||
#!/bin/sh | ||
fpcalc -raw -length 500 "$1" > "$1".fpcalc |