bic 8 months ago
commit
7c42241337
9 changed files with 620 additions and 0 deletions
  1. 156 0
      .gitignore
  2. 213 0
      poetry.lock
  3. 21 0
      pyproject.toml
  4. 9 0
      readme.md
  5. 44 0
      scripts/prober.liq
  6. BIN
      spectrogram.png
  7. 35 0
      vibroscopio/cli.py
  8. 142 0
      vibroscopio/fingerprint.py
  9. 0 0
      vibroscopio/stream.py

+ 156 - 0
.gitignore

@@ -0,0 +1,156 @@
+
+stage
+
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+pip-wheel-metadata/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+#  Usually these files are written by a python script from a template
+#  before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+*.py,cover
+.hypothesis/
+.pytest_cache/
+cover/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+db.sqlite3-journal
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+#   For a library or package, you might want to ignore these files since the code is
+#   intended to run in multiple environments; otherwise, check them in:
+# .python-version
+
+# pipenv
+#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+#   However, in case of collaboration, if having platform-specific dependencies or dependencies
+#   having no cross-platform support, pipenv may install dependencies that don't work, or not
+#   install all needed dependencies.
+#Pipfile.lock
+
+# PEP 582; used by e.g. github.com/David-OConnor/pyflow
+__pypackages__/
+
+# Celery stuff
+celerybeat-schedule
+celerybeat.pid
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+# pytype static type analyzer
+.pytype/
+
+# Ignore qmlc files
+*.qmlc
+
+.idea
+
+# Do not commit VPN certs.
+dome.conf
+
+main.pyproject.user
+*.autosave
+builds
+hosts
+.python-version
+host_vars/
+.vscode
+
+assets/

+ 213 - 0
poetry.lock

@@ -0,0 +1,213 @@
+[[package]]
+name = "audioread"
+version = "2.1.9"
+description = "multi-library, cross-platform audio decoding"
+category = "main"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "certifi"
+version = "2021.10.8"
+description = "Python package for providing Mozilla's CA Bundle."
+category = "main"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "charset-normalizer"
+version = "2.0.12"
+description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
+category = "main"
+optional = false
+python-versions = ">=3.5.0"
+
+[package.extras]
+unicode_backport = ["unicodedata2"]
+
+[[package]]
+name = "click"
+version = "8.0.4"
+description = "Composable command line interface toolkit"
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+colorama = {version = "*", markers = "platform_system == \"Windows\""}
+
+[[package]]
+name = "colorama"
+version = "0.4.4"
+description = "Cross-platform colored terminal text."
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+
+[[package]]
+name = "commonmark"
+version = "0.9.1"
+description = "Python parser for the CommonMark Markdown spec"
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.extras]
+test = ["flake8 (==3.7.8)", "hypothesis (==3.55.3)"]
+
+[[package]]
+name = "idna"
+version = "3.3"
+description = "Internationalized Domain Names in Applications (IDNA)"
+category = "main"
+optional = false
+python-versions = ">=3.5"
+
+[[package]]
+name = "numpy"
+version = "1.22.2"
+description = "NumPy is the fundamental package for array computing with Python."
+category = "main"
+optional = false
+python-versions = ">=3.8"
+
+[[package]]
+name = "pyacoustid"
+version = "1.2.2"
+description = "bindings for Chromaprint acoustic fingerprinting and the Acoustid API"
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+audioread = "*"
+requests = "*"
+
+[[package]]
+name = "pygments"
+version = "2.11.2"
+description = "Pygments is a syntax highlighting package written in Python."
+category = "main"
+optional = false
+python-versions = ">=3.5"
+
+[[package]]
+name = "requests"
+version = "2.27.1"
+description = "Python HTTP for Humans."
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
+
+[package.dependencies]
+certifi = ">=2017.4.17"
+charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""}
+idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""}
+urllib3 = ">=1.21.1,<1.27"
+
+[package.extras]
+socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"]
+use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"]
+
+[[package]]
+name = "rich"
+version = "11.2.0"
+description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
+category = "main"
+optional = false
+python-versions = ">=3.6.2,<4.0.0"
+
+[package.dependencies]
+colorama = ">=0.4.0,<0.5.0"
+commonmark = ">=0.9.0,<0.10.0"
+pygments = ">=2.6.0,<3.0.0"
+
+[package.extras]
+jupyter = ["ipywidgets (>=7.5.1,<8.0.0)"]
+
+[[package]]
+name = "urllib3"
+version = "1.26.8"
+description = "HTTP library with thread-safe connection pooling, file post, and more."
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
+
+[package.extras]
+brotli = ["brotlipy (>=0.6.0)"]
+secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"]
+socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
+
+[metadata]
+lock-version = "1.1"
+python-versions = "^3.10"
+content-hash = "8c25537d733e8f48e66f2612d9e0af3f695d256b280661d9f2db2c13ca0b8a4a"
+
+[metadata.files]
+audioread = [
+    {file = "audioread-2.1.9.tar.gz", hash = "sha256:a3480e42056c8e80a8192a54f6729a280ef66d27782ee11cbd63e9d4d1523089"},
+]
+certifi = [
+    {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"},
+    {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"},
+]
+charset-normalizer = [
+    {file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"},
+    {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"},
+]
+click = [
+    {file = "click-8.0.4-py3-none-any.whl", hash = "sha256:6a7a62563bbfabfda3a38f3023a1db4a35978c0abd76f6c9605ecd6554d6d9b1"},
+    {file = "click-8.0.4.tar.gz", hash = "sha256:8458d7b1287c5fb128c90e23381cf99dcde74beaf6c7ff6384ce84d6fe090adb"},
+]
+colorama = [
+    {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
+    {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"},
+]
+commonmark = [
+    {file = "commonmark-0.9.1-py2.py3-none-any.whl", hash = "sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9"},
+    {file = "commonmark-0.9.1.tar.gz", hash = "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60"},
+]
+idna = [
+    {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"},
+    {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"},
+]
+numpy = [
+    {file = "numpy-1.22.2-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:515a8b6edbb904594685da6e176ac9fbea8f73a5ebae947281de6613e27f1956"},
+    {file = "numpy-1.22.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:76a4f9bce0278becc2da7da3b8ef854bed41a991f4226911a24a9711baad672c"},
+    {file = "numpy-1.22.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:168259b1b184aa83a514f307352c25c56af111c269ffc109d9704e81f72e764b"},
+    {file = "numpy-1.22.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3556c5550de40027d3121ebbb170f61bbe19eb639c7ad0c7b482cd9b560cd23b"},
+    {file = "numpy-1.22.2-cp310-cp310-win_amd64.whl", hash = "sha256:aafa46b5a39a27aca566198d3312fb3bde95ce9677085efd02c86f7ef6be4ec7"},
+    {file = "numpy-1.22.2-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:55535c7c2f61e2b2fc817c5cbe1af7cb907c7f011e46ae0a52caa4be1f19afe2"},
+    {file = "numpy-1.22.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:60cb8e5933193a3cc2912ee29ca331e9c15b2da034f76159b7abc520b3d1233a"},
+    {file = "numpy-1.22.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b536b6840e84c1c6a410f3a5aa727821e6108f3454d81a5cd5900999ef04f89"},
+    {file = "numpy-1.22.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2638389562bda1635b564490d76713695ff497242a83d9b684d27bb4a6cc9d7a"},
+    {file = "numpy-1.22.2-cp38-cp38-win32.whl", hash = "sha256:6767ad399e9327bfdbaa40871be4254d1995f4a3ca3806127f10cec778bd9896"},
+    {file = "numpy-1.22.2-cp38-cp38-win_amd64.whl", hash = "sha256:03ae5850619abb34a879d5f2d4bb4dcd025d6d8fb72f5e461dae84edccfe129f"},
+    {file = "numpy-1.22.2-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:d76a26c5118c4d96e264acc9e3242d72e1a2b92e739807b3b69d8d47684b6677"},
+    {file = "numpy-1.22.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:15efb7b93806d438e3bc590ca8ef2f953b0ce4f86f337ef4559d31ec6cf9d7dd"},
+    {file = "numpy-1.22.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:badca914580eb46385e7f7e4e426fea6de0a37b9e06bec252e481ae7ec287082"},
+    {file = "numpy-1.22.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94dd11d9f13ea1be17bac39c1942f527cbf7065f94953cf62dfe805653da2f8f"},
+    {file = "numpy-1.22.2-cp39-cp39-win32.whl", hash = "sha256:8cf33634b60c9cef346663a222d9841d3bbbc0a2f00221d6bcfd0d993d5543f6"},
+    {file = "numpy-1.22.2-cp39-cp39-win_amd64.whl", hash = "sha256:59153979d60f5bfe9e4c00e401e24dfe0469ef8da6d68247439d3278f30a180f"},
+    {file = "numpy-1.22.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a176959b6e7e00b5a0d6f549a479f869829bfd8150282c590deee6d099bbb6e"},
+    {file = "numpy-1.22.2.zip", hash = "sha256:076aee5a3763d41da6bef9565fdf3cb987606f567cd8b104aded2b38b7b47abf"},
+]
+pyacoustid = [
+    {file = "pyacoustid-1.2.2.tar.gz", hash = "sha256:c279d9c30a7f481f1420fc37db65833b5f9816cd364dc2acaa93a11c482d4141"},
+]
+pygments = [
+    {file = "Pygments-2.11.2-py3-none-any.whl", hash = "sha256:44238f1b60a76d78fc8ca0528ee429702aae011c265fe6a8dd8b63049ae41c65"},
+    {file = "Pygments-2.11.2.tar.gz", hash = "sha256:4e426f72023d88d03b2fa258de560726ce890ff3b630f88c21cbb8b2503b8c6a"},
+]
+requests = [
+    {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"},
+    {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"},
+]
+rich = [
+    {file = "rich-11.2.0-py3-none-any.whl", hash = "sha256:d5f49ad91fb343efcae45a2b2df04a9755e863e50413623ab8c9e74f05aee52b"},
+    {file = "rich-11.2.0.tar.gz", hash = "sha256:1a6266a5738115017bb64a66c59c717e7aa047b3ae49a011ede4abdeffc6536e"},
+]
+urllib3 = [
+    {file = "urllib3-1.26.8-py2.py3-none-any.whl", hash = "sha256:000ca7f471a233c2251c6c7023ee85305721bfdf18621ebff4fd17a8653427ed"},
+    {file = "urllib3-1.26.8.tar.gz", hash = "sha256:0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c"},
+]

+ 21 - 0
pyproject.toml

@@ -0,0 +1,21 @@
+[tool.poetry]
+name = "vibroscopio"
+version = "0.1.0"
+description = ""
+authors = ["gibix <bicno@autistici.org>"]
+
+[tool.poetry.dependencies]
+python = "^3.10"
+pyacoustid = "^1.2.2"
+rich = "^11.2.0"
+click = "^8.0.4"
+numpy = "^1.22.2"
+
+[tool.poetry.dev-dependencies]
+
+[build-system]
+requires = ["poetry-core>=1.0.0"]
+build-backend = "poetry.core.masonry.api"
+
+[tool.poetry.scripts]
+vibroscopio = 'vibroscopio.cli:main'

+ 9 - 0
readme.md

@@ -0,0 +1,9 @@
+
+http://coding-geek.com/how-shazam-works/
+
+https://link.springer.com/article/10.1007/s11042-020-09912-4
+https://github.com/swesterfeld/audiowmark
+https://github.com/ebu/awesome-broadcasting#network--storage-testing
+https://medium.com/intrasonics/a-fingerprint-for-audio-3b337551a671
+
+https://github.com/kdave/audio-compare/blob/master/correlation.py

+ 44 - 0
scripts/prober.liq

@@ -0,0 +1,44 @@
+#!/usr/bin/liquidsoap
+
+in = mksafe(single("/srv/assets/sample.mp3"))
+
+def vibroscopio(path) =
+	# out = process.read.lines(
+    	# "/home/me/path/to/beet random -f '$path' #{arg}"
+	# )
+
+	# log(out)
+  # )
+  log(path)
+end
+
+output.file(
+        %wav(
+			mono=true,
+			channels=1,
+			duration=30.
+		),
+        "/srv/stage/source/%H.%M.%S.wav",
+        reopen_when = {0s or 30s},
+		reopen_delay = 20.,
+		on_close = vibroscopio,
+        in
+)
+
+output.file(
+        %wav(
+			mono=true,
+			channels=1,
+			duration=60.
+		),
+        "/srv/stage/target/%H.%M.%S.wav",
+        reopen_when = {0s},
+		reopen_delay = 10.,
+		on_close = vibroscopio,
+        in
+)
+
+# thread.run(every=3600.,
+#     fun () -> (),
+#         process.read.lines("find /srv/stage/* -type f -mmin +2 -delete")
+# )

BIN
spectrogram.png


+ 35 - 0
vibroscopio/cli.py

@@ -0,0 +1,35 @@
+
+import click
+import logging
+from rich.logging import RichHandler
+from vibroscopio.fingerprint import correlate, signature
+
+@click.group()
+@click.option('--verbosity', '-v', default=2, count=True)
+def cli(verbosity):
+    FORMAT = "%(message)s"
+    logging.basicConfig(
+            level=verbosity * 10,
+            format=FORMAT,
+            datefmt="[%X]",
+            handlers=[RichHandler()])
+
+@click.command()
+@click.argument('file')
+def fingerprint(path):
+    s = signature(path)
+    print(s)
+
+@click.command()
+@click.option('--source', '-s', help='source is expected to be bigger than target', type=click.Path(exists=True))
+@click.option('--target', '-t', help='target is supposed to be smaller than source', type=click.Path(exists=True))
+@click.option('--length', '-l', default=120, help='seconds of source length')
+@click.option('--span', '-s', default=60, help='allowd span allignment in seconds')
+def compare(source, target, length, span):
+    correlate(source, target, length, length * 7)
+
+cli.add_command(fingerprint)
+cli.add_command(compare)
+
+def main():
+    cli()

+ 142 - 0
vibroscopio/fingerprint.py

@@ -0,0 +1,142 @@
+
+from typing import final
+import acoustid
+
+def signature(path):
+    return acoustid.fingerprint_file(path)
+
+import subprocess
+import numpy
+import os
+
+# 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: str, length: int):
+    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()
+
+    try:
+        fpcalc_out = str(subprocess.check_output(['fpcalc', '-raw', filename]))
+    except subprocess.CalledProcessError as e:
+        fpcalc_out = str(e.output)
+
+    fpcalc_out = fpcalc_out.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))
+
+    correlation = covariance / 32
+
+    if correlation is None:
+        return
+
+    return correlation
+
+
+# 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)))
+    corr = correlation(listx, listy)
+    return corr
+
+# cross correlate listx and listy with offsets from 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, 1, step):
+    for offset in numpy.arange(-span, span + 1, step):
+        tmp_corr = cross_correlation(listx, listy, offset)
+        corr_xy.append(tmp_corr)
+
+    corr_xy = list(filter(None, corr_xy))
+
+    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, span):
+    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, length, span):
+    fingerprint_source = calculate_fingerprints(target, length)
+    # print(len(fingerprint_source))
+
+    fingerprint_target = calculate_fingerprints(source, 59)
+    # print(len(fingerprint_target))
+
+    corr = compare(fingerprint_source, fingerprint_target, span, step)
+
+    max_corr_offset = get_max_corr(corr, source, target, span)

+ 0 - 0
vibroscopio/stream.py