pyacoustid-1.0.0/0000755000076500000240000000000012121135400014625 5ustar asampsonstaff00000000000000pyacoustid-1.0.0/acoustid.py0000644000076500000240000003136412121135016017024 0ustar asampsonstaff00000000000000# This file is part of pyacoustid. # Copyright 2012, Adrian Sampson. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. import os import json import urllib import urllib2 import httplib import contextlib import errno try: import audioread have_audioread = True except ImportError: have_audioread = False try: import chromaprint have_chromaprint = True except ImportError: have_chromaprint = False import subprocess import threading import time import gzip from StringIO import StringIO API_BASE_URL = 'http://api.acoustid.org/v2/' DEFAULT_META = 'recordings' REQUEST_INTERVAL = 0.33 # 3 requests/second. MAX_AUDIO_LENGTH = 120 # Seconds. FPCALC_COMMAND = 'fpcalc' FPCALC_ENVVAR = 'FPCALC' class AcoustidError(Exception): """Base for exceptions in this module.""" class FingerprintGenerationError(AcoustidError): """The audio could not be fingerprinted.""" class NoBackendError(FingerprintGenerationError): """The audio could not be fingerprinted because neither the Chromaprint library nor the fpcalc command-line tool is installed. """ class FingerprintSubmissionError(AcoustidError): """Missing required data for a fingerprint submission.""" class WebServiceError(AcoustidError): """The Web service request failed. The field ``message`` contains a description of the error. If this is an error that was specifically sent by the acoustid server, then the ``code`` field contains the acoustid error code. """ def __init__(self, message, response=None): """Create an error for the given HTTP response body, if provided, with the ``message`` as a fallback. """ if response: # Try to parse the JSON error response. try: data = json.loads(response) except ValueError: pass else: if isinstance(data.get('error'), dict): error = data['error'] if 'message' in error: message = error['message'] if 'code' in error: self.code = error['code'] super(WebServiceError, self).__init__(message) self.message = message class _rate_limit(object): """A decorator that limits the rate at which the function may be called. The rate is controlled by the REQUEST_INTERVAL module-level constant; set the value to zero to disable rate limiting. The limiting is thread-safe; only one thread may be in the function at a time (acts like a monitor in this sense). """ def __init__(self, fun): self.fun = fun self.last_call = 0.0 self.lock = threading.Lock() def __call__(self, *args, **kwargs): with self.lock: # Wait until request_rate time has passed since last_call, # then update last_call. since_last_call = time.time() - self.last_call if since_last_call < REQUEST_INTERVAL: time.sleep(REQUEST_INTERVAL - since_last_call) self.last_call = time.time() # Call the original function. return self.fun(*args, **kwargs) def _compress(data): """Compress a string to a gzip archive.""" sio = StringIO() with contextlib.closing(gzip.GzipFile(fileobj=sio, mode='wb')) as f: f.write(data) return sio.getvalue() def _decompress(data): """Decompress a gzip archive contained in a string.""" sio = StringIO(data) with contextlib.closing(gzip.GzipFile(fileobj=sio)) as f: return f.read() def set_base_url(url): """Set the URL of the API server to query.""" if not url.endswith('/'): url += '/' global API_BASE_URL API_BASE_URL = url def _get_lookup_url(): """Get the URL of the lookup API endpoint.""" return API_BASE_URL + 'lookup' def _get_submit_url(): """Get the URL of the submission API endpoint.""" return API_BASE_URL + 'submit' @_rate_limit def _send_request(req): """Given a urllib2 Request object, make the request and return a tuple containing the response data and headers. """ try: with contextlib.closing(urllib2.urlopen(req)) as f: return f.read(), f.info() except urllib2.HTTPError as exc: raise WebServiceError('HTTP status %i' % exc.code, exc.read()) except httplib.BadStatusLine: raise WebServiceError('bad HTTP status line') except IOError: raise WebServiceError('connection failed') def _api_request(url, params): """Makes a POST request for the URL with the given form parameters, which are encoded as compressed form data, and returns a parsed JSON response. May raise a WebServiceError if the request fails. """ # Encode any Unicode values in parameters. (urllib.urlencode in # Python 2.x operates on bytestrings, so a Unicode error is raised # if non-ASCII characters are passed in a Unicode string.) byte_params = {} for key, value in params.iteritems(): if isinstance(key, unicode): key = key.encode('utf8') if isinstance(value, unicode): value = value.encode('utf8') byte_params[key] = value body = _compress(urllib.urlencode(byte_params)) req = urllib2.Request(url, body, { 'Content-Encoding': 'gzip', 'Accept-Encoding': 'gzip', }) data, headers = _send_request(req) if headers.get('Content-Encoding') == 'gzip': data = _decompress(data) try: return json.loads(data) except ValueError: raise WebServiceError('response is not valid JSON') def fingerprint(samplerate, channels, pcmiter, maxlength=MAX_AUDIO_LENGTH): """Fingerprint audio data given its sample rate and number of channels. pcmiter should be an iterable containing blocks of PCM data as byte strings. Raises a FingerprintGenerationError if anything goes wrong. """ # Maximum number of samples to decode. endposition = samplerate * channels * maxlength try: fper = chromaprint.Fingerprinter() fper.start(samplerate, channels) position = 0 # Samples of audio fed to the fingerprinter. for block in pcmiter: fper.feed(block) position += len(block) // 2 # 2 bytes/sample. if position >= endposition: break return fper.finish() except chromaprint.FingerprintError: raise FingerprintGenerationError("fingerprint calculation failed") def lookup(apikey, fingerprint, duration, meta=DEFAULT_META): """Look up a fingerprint with the Acoustid Web service. Returns the Python object reflecting the response JSON data. """ params = { 'format': 'json', 'client': apikey, 'duration': int(duration), 'fingerprint': fingerprint, 'meta': meta, } return _api_request(_get_lookup_url(), params) def parse_lookup_result(data): """Given a parsed JSON response, generate tuples containing the match score, the MusicBrainz recording ID, the title of the recording, and the name of the recording's first artist. (If an artist is not available, the last item is None.) If the response is incomplete, raises a WebServiceError. """ if data['status'] != 'ok': raise WebServiceError("status: %s" % data['status']) if 'results' not in data: raise WebServiceError("results not included") for result in data['results']: score = result['score'] if 'recordings' not in result: # No recording attached. This result is not very useful. continue for recording in result['recordings']: # Get the artist if available. if recording.get('artists'): names = [artist['name'] for artist in recording['artists']] artist_name = '; '.join(names) else: artist_name = None yield score, recording['id'], recording.get('title'), artist_name def _fingerprint_file_audioread(path, maxlength): """Fingerprint a file by using audioread and chromaprint.""" try: with audioread.audio_open(path) as f: duration = f.duration fp = fingerprint(f.samplerate, f.channels, iter(f), maxlength) except audioread.DecodeError: raise FingerprintGenerationError("audio could not be decoded") return duration, fp def _fingerprint_file_fpcalc(path, maxlength): """Fingerprint a file by calling the fpcalc application.""" fpcalc = os.environ.get(FPCALC_ENVVAR, FPCALC_COMMAND) command = [fpcalc, "-length", str(maxlength), path] try: with open(os.devnull, 'wb') as devnull: proc = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=devnull) output, _ = proc.communicate() except OSError as exc: if exc.errno == errno.ENOENT: raise NoBackendError("fpcalc not found") else: raise FingerprintGenerationError("fpcalc invocation failed: %s" % str(exc)) except UnicodeEncodeError: # Due to a bug in Python 2's subprocess on Windows, Unicode # filenames can fail to encode on that platform. See: # http://bugs.python.org/issue1759845 raise FingerprintGenerationError("argument encoding failed") retcode = proc.poll() if retcode: raise FingerprintGenerationError("fpcalc exited with status %i" % retcode) duration = fp = None for line in output.splitlines(): try: parts = line.split('=', 1) except ValueError: raise FingerprintGenerationError("malformed fpcalc output") if parts[0] == 'DURATION': try: duration = int(parts[1]) except ValueError: raise FingerprintGenerationError("fpcalc duration not numeric") elif parts[0] == 'FINGERPRINT': fp = parts[1] if duration is None or fp is None: raise FingerprintGenerationError("missing fpcalc output") return duration, fp def fingerprint_file(path, maxlength=MAX_AUDIO_LENGTH): """Fingerprint a file either using the Chromaprint dynamic library or the fpcalc command-line tool, whichever is available. Returns the duration and the fingerprint. """ path = os.path.abspath(os.path.expanduser(path)) if have_audioread and have_chromaprint: return _fingerprint_file_audioread(path, maxlength) else: return _fingerprint_file_fpcalc(path, maxlength) def match(apikey, path, meta=DEFAULT_META, parse=True): """Look up the metadata for an audio file. If ``parse`` is true, then ``parse_lookup_result`` is used to return an iterator over small tuple of relevant information; otherwise, the full parsed JSON response is returned. """ duration, fp = fingerprint_file(path) response = lookup(apikey, fp, duration, meta) if parse: return parse_lookup_result(response) else: return response def submit(apikey, userkey, data): """Submit a fingerprint to the acoustid server. The ``apikey`` and ``userkey`` parameters are API keys for the application and the submitting user, respectively. ``data`` may be either a single dictionary or a list of dictionaries. In either case, each dictionary must contain a ``fingerprint`` key and a ``duration`` key and may include the following: ``puid``, ``mbid``, ``track``, ``artist``, ``album``, ``albumartist``, ``year``, ``trackno``, ``discno``, ``fileformat``, ``bitrate`` If the required keys are not present in a dictionary, a FingerprintSubmissionError is raised. """ if isinstance(data, dict): data = [data] args = { 'format': 'json', 'client': apikey, 'user': userkey, } # Build up "field.#" parameters corresponding to the parameters # given in each dictionary. for i, d in enumerate(data): if "duration" not in d or "fingerprint" not in d: raise FingerprintSubmissionError("missing required parameters") for k, v in d.iteritems(): args["%s.%s" % (k, i)] = v response = _api_request(_get_submit_url(), args) if response['status'] != 'ok': raise WebServiceError("status: %s" % data['status']) pyacoustid-1.0.0/aidmatch.py0000644000076500000240000000330411722556654017001 0ustar asampsonstaff00000000000000# This file is part of pyacoustid. # Copyright 2011, Adrian Sampson. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. """Example script that identifies metadata for files specified on the command line. """ import acoustid import sys # API key for this demo script only. Get your own API key at the # Acoustid Web for your application. # http://acoustid.org/ API_KEY = 'cSpUJKpD' def aidmatch(filename): try: results = acoustid.match(API_KEY, filename) except acoustid.NoBackendError: print >>sys.stderr, "chromaprint library/tool not found" sys.exit(1) except acoustid.FingerprintGenerationError: print >>sys.stderr, "fingerprint could not be calculated" sys.exit(1) except acoustid.WebServiceError, exc: print >>sys.stderr, "web service request failed:", exc.message sys.exit(1) first = True for score, rid, title, artist in results: if first: first = False else: print print '%s - %s' % (artist, title) print 'http://musicbrainz.org/recording/%s' % rid print 'Score: %i%%' % (int(score * 100)) if __name__ == '__main__': aidmatch(sys.argv[1]) pyacoustid-1.0.0/chromaprint.py0000644000076500000240000001243711676672056017567 0ustar asampsonstaff00000000000000# Copyright (C) 2011 Lukas Lalinsky # (Minor modifications by Adrian Sampson.) # Distributed under the MIT license, see the LICENSE file for details. """Low-level ctypes wrapper from the chromaprint library.""" import sys import ctypes # Find the base library and declare prototypes. def _guess_lib_name(): if sys.platform == 'darwin': return ('libchromaprint.0.dylib',) elif sys.platform == 'win32': return ('chromaprint.dll', 'libchromaprint.dll') elif sys.platform == 'cygwin': return ('libchromaprint.dll.a', 'cygchromaprint-0.dll') return ('libchromaprint.so.0',) for name in _guess_lib_name(): try: _libchromaprint = ctypes.cdll.LoadLibrary(name) break except OSError: pass else: raise ImportError("couldn't find libchromaprint") _libchromaprint.chromaprint_get_version.argtypes = () _libchromaprint.chromaprint_get_version.restype = ctypes.c_char_p _libchromaprint.chromaprint_new.argtypes = (ctypes.c_int,) _libchromaprint.chromaprint_new.restype = ctypes.c_void_p _libchromaprint.chromaprint_free.argtypes = (ctypes.c_void_p,) _libchromaprint.chromaprint_free.restype = None _libchromaprint.chromaprint_start.argtypes = \ (ctypes.c_void_p, ctypes.c_int, ctypes.c_int) _libchromaprint.chromaprint_start.restype = ctypes.c_int _libchromaprint.chromaprint_feed.argtypes = \ (ctypes.c_void_p, ctypes.POINTER(ctypes.c_char), ctypes.c_int) _libchromaprint.chromaprint_feed.restype = ctypes.c_int _libchromaprint.chromaprint_finish.argtypes = (ctypes.c_void_p,) _libchromaprint.chromaprint_finish.restype = ctypes.c_int _libchromaprint.chromaprint_get_fingerprint.argtypes = \ (ctypes.c_void_p, ctypes.POINTER(ctypes.c_char_p)) _libchromaprint.chromaprint_get_fingerprint.restype = ctypes.c_int _libchromaprint.chromaprint_decode_fingerprint.argtypes = \ (ctypes.POINTER(ctypes.c_char), ctypes.c_int, ctypes.POINTER(ctypes.POINTER(ctypes.c_int32)), ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_int), ctypes.c_int) _libchromaprint.chromaprint_decode_fingerprint.restype = ctypes.c_int _libchromaprint.chromaprint_encode_fingerprint.argtypes = \ (ctypes.POINTER(ctypes.c_int32), ctypes.c_int, ctypes.c_int, ctypes.POINTER(ctypes.POINTER(ctypes.c_char)), ctypes.POINTER(ctypes.c_int), ctypes.c_int) _libchromaprint.chromaprint_encode_fingerprint.restype = ctypes.c_int _libchromaprint.chromaprint_dealloc.argtypes = (ctypes.c_void_p,) _libchromaprint.chromaprint_dealloc.restype = None # Main interface. class FingerprintError(Exception): """Raised when a call to the underlying library fails.""" def _check(res): """Check the result of a library call, raising an error if the call failed. """ if res != 1: raise FingerprintError() class Fingerprinter(object): ALGORITHM_TEST1 = 0 ALGORITHM_TEST2 = 1 ALGORITHM_TEST3 = 2 ALGORITHM_DEFAULT = ALGORITHM_TEST2 def __init__(self, algorithm=ALGORITHM_DEFAULT): self._ctx = _libchromaprint.chromaprint_new(algorithm) def __del__(self): _libchromaprint.chromaprint_free(self._ctx) del self._ctx def start(self, sample_rate, num_channels): """Initialize the fingerprinter with the given audio parameters. """ _check(_libchromaprint.chromaprint_start( self._ctx, sample_rate, num_channels )) def feed(self, data): """Send raw PCM audio data to the fingerprinter. Data may be either a bytestring or a buffer object. """ if isinstance(data, buffer): data = str(data) elif not isinstance(data, str): raise TypeError('data must be str or buffer') _check(_libchromaprint.chromaprint_feed( self._ctx, data, len(data) // 2 )) def finish(self): """Finish the fingerprint generation process and retrieve the resulting fignerprint as a bytestring. """ _check(_libchromaprint.chromaprint_finish(self._ctx)) fingerprint_ptr = ctypes.c_char_p() _check(_libchromaprint.chromaprint_get_fingerprint( self._ctx, ctypes.byref(fingerprint_ptr) )) fingerprint = fingerprint_ptr.value _libchromaprint.chromaprint_dealloc(fingerprint_ptr) return fingerprint def decode_fingerprint(data, base64=True): result_ptr = ctypes.POINTER(ctypes.c_int32)() result_size = ctypes.c_int() algorithm = ctypes.c_int() _check(_libchromaprint.chromaprint_decode_fingerprint( data, len(data), ctypes.byref(result_ptr), ctypes.byref(result_size), ctypes.byref(algorithm), 1 if base64 else 0 )) result = result_ptr[:result_size.value] _libchromaprint.chromaprint_dealloc(result_ptr) return result, algorithm.value def encode_fingerprint(fingerprint, algorithm, base64=True): fp_array = (ctypes.c_int * len(fingerprint))() for i in range(len(fingerprint)): fp_array[i] = fingerprint[i] result_ptr = ctypes.POINTER(ctypes.c_char)() result_size = ctypes.c_int() _check(_libchromaprint.chromaprint_encode_fingerprint( fp_array, len(fingerprint), algorithm, ctypes.byref(result_ptr), ctypes.byref(result_size), 1 if base64 else 0 )) result = result_ptr[:result_size.value] _libchromaprint.chromaprint_dealloc(result_ptr) return result pyacoustid-1.0.0/MANIFEST.in0000644000076500000240000000011211657337247016407 0ustar asampsonstaff00000000000000# Documentation. include README.rst # Example script. include aidmatch.py pyacoustid-1.0.0/PKG-INFO0000644000076500000240000001703612121135400015731 0ustar asampsonstaff00000000000000Metadata-Version: 1.1 Name: pyacoustid Version: 1.0.0 Summary: bindings for Chromaprint acoustic fingerprinting and the Acoustid API Home-page: https://github.com/sampsyo/pyacoustid Author: Adrian Sampson Author-email: adrian@radbox.org License: MIT Description: Chromaprint and Acoustid for Python =================================== `Chromaprint`_ and its associated `Acoustid`_ Web service make up a high-quality, open-source acoustic fingerprinting system. This package provides Python bindings for both the fingerprinting algorithm library, which is written in C but portable, and the Web service, which provides fingerprint lookups. .. _Chromaprint: http://acoustid.org/ .. _Acoustid: http://acoustid.org/chromaprint Installation ------------ First, install the `Chromaprint`_ fingerprinting library by `Lukas Lalinsky`__. (The library itself depends on an FFT library, but it's smart enough to use an algorithm from software you probably already have installed; see the Chromaprint page for details.) This module can use either the Chromaprint dynamic library or the ``fpcalc`` command-line tool, which itself depends on `libavcodec`_. If you use ``fpcalc``, either ensure that it is on your ``$PATH`` or set the ``FPCALC`` environment variable to its location. __ lukas_ .. _lukas: http://oxygene.sk/lukas/ .. _libavcodec: http://ffmpeg.org/ Then you can install this library from `PyPI`_ using `pip`_:: $ pip install pyacoustid This library uses `audioread`_ to do audio decoding (pip should automatically install this dependency), but it's not really necessary if you already have decoded audio. .. _pip: http://www.pip-installer.org/ .. _PyPI: http://pypi.python.org/ .. _audioread: https://github.com/sampsyo/audioread Running ------- You can run the included demonstration script, ``aidmatch.py``, to test your installation:: $ python aidmatch.py mysterious_music.mp3 This will show the top metadata match from Acoustid's database. The script uses `audioread`_ to decode music, so it should transparently use a media library available on your system (GStreamer, FFmpeg, MAD, or Core Audio). Using in Your Code ------------------ The simplest way to use pyacoustid to identify audio files is to call the ``match`` function:: >>> import acoustid >>> for score, recording_id, title, artist in acoustid.match(apikey, path): >>> ... This convenience function uses `audioread`_ to decode audio and parses the response for you, pulling out the most important track metadata. It returns in iterable over tuples of relevant information. Everything happens in one fell swoop. There are also a number of "smaller" functions you can use to perform parts of the process: - ``fingerprint(samplerate, channels, pcmiter)``: Generate a fingerprint for raw audio data. Specify the audio parameters and give an iterable containing blocks of PCM data. - ``fingerprint_file(path)``: Using either the Chromaprint dynamic library or the ``fpcalc`` command-line tool, fingerprint an audio file. Returns a pair consisting of the file's duration and its fingerprint. - ``lookup(apikey, fingerprint, duration)``: Make a request to the `Acoustid`_ API to look up the fingerprint returned by the previous function. An API key is required, as is the length, in seconds, of the source audio. Returns a parsed JSON response. - ``parse_lookup_result(data)``: Given a parsed JSON response, return an iterator over tuples containing the match score (a float between 0 and 1), the MusicBrainz recording ID, title, and artist name for each match. The module internally performs thread-safe API rate limiting to 3 queries per second whenever the Web API is called, in accordance with the `Web service documentation`_. If you're running your own Acoustid database server, you can set the base URL for all API calls with the ``set_base_url`` function. Calls to the library can raise ``AcoustidError`` exceptions of two subtypes: ``FingerprintGenerationError`` and ``WebServiceError``. Catch these exceptions if you want to proceed when audio can't be decoded or no match is found on the server. ``NoBackendError``, a subclass of ``FingerprintGenerationError``, is used when the Chromaprint library or fpcalc command-line tool cannot be found. .. _Web service documentation: http://acoustid.org/webservice Version History --------------- 1.0.0 Include ``fpcalc.py``, a script mimicking the ``fpcalc`` program from the Chromaprint package. Handle a ``UnicodeDecodeError`` raised when using the ``fpcalc`` backend on Windows with Unicode filenames. Standard error output from ``fpcalc`` is suppressed. 0.7 Properly encode Unicode parameters (resolves a ``UnicodeEncodeError`` in fingerprint submission). Parse all recordings for each Acoustid lookup result. 0.6 Add a new function, ``fingerprint_file``, that automatically selects a backend for fingerprinting a single file. 0.5 Fix response parsing when recording has no artists or title. Fix compatibility with Python < 2.7. Add specific ``NoBackendError`` exception. 0.4 Fingerprinting can now fall back to using the ``fpcalc`` command-line tool instead of the Chromaprint dynamic library so the library can be used with the binary distributions (thanks to Lukas Lalinsky). Fingerprint submission (thanks to Alastair Porter). Data chunks can now be buffers as well as bytestrings (fixes compatibility with pymad). 0.3 Configurable API base URL. Result parser now generates all results instead of returning just one. Find the chromaprint library on Cygwin. New module names: ``chromaprint`` and ``acoustid`` (no package). 0.2 Compress HTTP requests and responses. Limit audio decoding to 120 seconds. Return score from convenience function. 0.1 Initial release. Credits ------- This library is by Adrian Sampson. Chromaprint and Acoustid are by `Lukas Lalinsky`__. This package includes the original `ctypes`_-based bindings written by Lukas. The entire library is made available under the `MIT license`_. pyacoustid was written to be used with `beets`_, which you should probably check out. __ lukas_ .. _ctypes: http://docs.python.org/library/ctypes.html .. _beets: http://beets.radbox.org/ .. _MIT license: http://www.opensource.org/licenses/mit-license.php Platform: ALL Classifier: Topic :: Multimedia :: Sound/Audio :: Conversion Classifier: Intended Audience :: Developers pyacoustid-1.0.0/pyacoustid.egg-info/0000755000076500000240000000000012121135400020503 5ustar asampsonstaff00000000000000pyacoustid-1.0.0/pyacoustid.egg-info/dependency_links.txt0000644000076500000240000000000112121135400024551 0ustar asampsonstaff00000000000000 pyacoustid-1.0.0/pyacoustid.egg-info/PKG-INFO0000644000076500000240000001703612121135400021607 0ustar asampsonstaff00000000000000Metadata-Version: 1.1 Name: pyacoustid Version: 1.0.0 Summary: bindings for Chromaprint acoustic fingerprinting and the Acoustid API Home-page: https://github.com/sampsyo/pyacoustid Author: Adrian Sampson Author-email: adrian@radbox.org License: MIT Description: Chromaprint and Acoustid for Python =================================== `Chromaprint`_ and its associated `Acoustid`_ Web service make up a high-quality, open-source acoustic fingerprinting system. This package provides Python bindings for both the fingerprinting algorithm library, which is written in C but portable, and the Web service, which provides fingerprint lookups. .. _Chromaprint: http://acoustid.org/ .. _Acoustid: http://acoustid.org/chromaprint Installation ------------ First, install the `Chromaprint`_ fingerprinting library by `Lukas Lalinsky`__. (The library itself depends on an FFT library, but it's smart enough to use an algorithm from software you probably already have installed; see the Chromaprint page for details.) This module can use either the Chromaprint dynamic library or the ``fpcalc`` command-line tool, which itself depends on `libavcodec`_. If you use ``fpcalc``, either ensure that it is on your ``$PATH`` or set the ``FPCALC`` environment variable to its location. __ lukas_ .. _lukas: http://oxygene.sk/lukas/ .. _libavcodec: http://ffmpeg.org/ Then you can install this library from `PyPI`_ using `pip`_:: $ pip install pyacoustid This library uses `audioread`_ to do audio decoding (pip should automatically install this dependency), but it's not really necessary if you already have decoded audio. .. _pip: http://www.pip-installer.org/ .. _PyPI: http://pypi.python.org/ .. _audioread: https://github.com/sampsyo/audioread Running ------- You can run the included demonstration script, ``aidmatch.py``, to test your installation:: $ python aidmatch.py mysterious_music.mp3 This will show the top metadata match from Acoustid's database. The script uses `audioread`_ to decode music, so it should transparently use a media library available on your system (GStreamer, FFmpeg, MAD, or Core Audio). Using in Your Code ------------------ The simplest way to use pyacoustid to identify audio files is to call the ``match`` function:: >>> import acoustid >>> for score, recording_id, title, artist in acoustid.match(apikey, path): >>> ... This convenience function uses `audioread`_ to decode audio and parses the response for you, pulling out the most important track metadata. It returns in iterable over tuples of relevant information. Everything happens in one fell swoop. There are also a number of "smaller" functions you can use to perform parts of the process: - ``fingerprint(samplerate, channels, pcmiter)``: Generate a fingerprint for raw audio data. Specify the audio parameters and give an iterable containing blocks of PCM data. - ``fingerprint_file(path)``: Using either the Chromaprint dynamic library or the ``fpcalc`` command-line tool, fingerprint an audio file. Returns a pair consisting of the file's duration and its fingerprint. - ``lookup(apikey, fingerprint, duration)``: Make a request to the `Acoustid`_ API to look up the fingerprint returned by the previous function. An API key is required, as is the length, in seconds, of the source audio. Returns a parsed JSON response. - ``parse_lookup_result(data)``: Given a parsed JSON response, return an iterator over tuples containing the match score (a float between 0 and 1), the MusicBrainz recording ID, title, and artist name for each match. The module internally performs thread-safe API rate limiting to 3 queries per second whenever the Web API is called, in accordance with the `Web service documentation`_. If you're running your own Acoustid database server, you can set the base URL for all API calls with the ``set_base_url`` function. Calls to the library can raise ``AcoustidError`` exceptions of two subtypes: ``FingerprintGenerationError`` and ``WebServiceError``. Catch these exceptions if you want to proceed when audio can't be decoded or no match is found on the server. ``NoBackendError``, a subclass of ``FingerprintGenerationError``, is used when the Chromaprint library or fpcalc command-line tool cannot be found. .. _Web service documentation: http://acoustid.org/webservice Version History --------------- 1.0.0 Include ``fpcalc.py``, a script mimicking the ``fpcalc`` program from the Chromaprint package. Handle a ``UnicodeDecodeError`` raised when using the ``fpcalc`` backend on Windows with Unicode filenames. Standard error output from ``fpcalc`` is suppressed. 0.7 Properly encode Unicode parameters (resolves a ``UnicodeEncodeError`` in fingerprint submission). Parse all recordings for each Acoustid lookup result. 0.6 Add a new function, ``fingerprint_file``, that automatically selects a backend for fingerprinting a single file. 0.5 Fix response parsing when recording has no artists or title. Fix compatibility with Python < 2.7. Add specific ``NoBackendError`` exception. 0.4 Fingerprinting can now fall back to using the ``fpcalc`` command-line tool instead of the Chromaprint dynamic library so the library can be used with the binary distributions (thanks to Lukas Lalinsky). Fingerprint submission (thanks to Alastair Porter). Data chunks can now be buffers as well as bytestrings (fixes compatibility with pymad). 0.3 Configurable API base URL. Result parser now generates all results instead of returning just one. Find the chromaprint library on Cygwin. New module names: ``chromaprint`` and ``acoustid`` (no package). 0.2 Compress HTTP requests and responses. Limit audio decoding to 120 seconds. Return score from convenience function. 0.1 Initial release. Credits ------- This library is by Adrian Sampson. Chromaprint and Acoustid are by `Lukas Lalinsky`__. This package includes the original `ctypes`_-based bindings written by Lukas. The entire library is made available under the `MIT license`_. pyacoustid was written to be used with `beets`_, which you should probably check out. __ lukas_ .. _ctypes: http://docs.python.org/library/ctypes.html .. _beets: http://beets.radbox.org/ .. _MIT license: http://www.opensource.org/licenses/mit-license.php Platform: ALL Classifier: Topic :: Multimedia :: Sound/Audio :: Conversion Classifier: Intended Audience :: Developers pyacoustid-1.0.0/pyacoustid.egg-info/requires.txt0000644000076500000240000000001112121135400023073 0ustar asampsonstaff00000000000000audioreadpyacoustid-1.0.0/pyacoustid.egg-info/SOURCES.txt0000644000076500000240000000035712121135400022374 0ustar asampsonstaff00000000000000MANIFEST.in README.rst acoustid.py aidmatch.py chromaprint.py setup.py pyacoustid.egg-info/PKG-INFO pyacoustid.egg-info/SOURCES.txt pyacoustid.egg-info/dependency_links.txt pyacoustid.egg-info/requires.txt pyacoustid.egg-info/top_level.txtpyacoustid-1.0.0/pyacoustid.egg-info/top_level.txt0000644000076500000240000000002512121135400023232 0ustar asampsonstaff00000000000000chromaprint acoustid pyacoustid-1.0.0/README.rst0000644000076500000240000001366212121135206016330 0ustar asampsonstaff00000000000000Chromaprint and Acoustid for Python =================================== `Chromaprint`_ and its associated `Acoustid`_ Web service make up a high-quality, open-source acoustic fingerprinting system. This package provides Python bindings for both the fingerprinting algorithm library, which is written in C but portable, and the Web service, which provides fingerprint lookups. .. _Chromaprint: http://acoustid.org/ .. _Acoustid: http://acoustid.org/chromaprint Installation ------------ First, install the `Chromaprint`_ fingerprinting library by `Lukáš Lalinský`__. (The library itself depends on an FFT library, but it's smart enough to use an algorithm from software you probably already have installed; see the Chromaprint page for details.) This module can use either the Chromaprint dynamic library or the ``fpcalc`` command-line tool, which itself depends on `libavcodec`_. If you use ``fpcalc``, either ensure that it is on your ``$PATH`` or set the ``FPCALC`` environment variable to its location. __ lukas_ .. _lukas: http://oxygene.sk/lukas/ .. _libavcodec: http://ffmpeg.org/ Then you can install this library from `PyPI`_ using `pip`_:: $ pip install pyacoustid This library uses `audioread`_ to do audio decoding (pip should automatically install this dependency), but it's not really necessary if you already have decoded audio. .. _pip: http://www.pip-installer.org/ .. _PyPI: http://pypi.python.org/ .. _audioread: https://github.com/sampsyo/audioread Running ------- You can run the included demonstration script, ``aidmatch.py``, to test your installation:: $ python aidmatch.py mysterious_music.mp3 This will show the top metadata match from Acoustid's database. The script uses `audioread`_ to decode music, so it should transparently use a media library available on your system (GStreamer, FFmpeg, MAD, or Core Audio). Using in Your Code ------------------ The simplest way to use pyacoustid to identify audio files is to call the ``match`` function:: >>> import acoustid >>> for score, recording_id, title, artist in acoustid.match(apikey, path): >>> ... This convenience function uses `audioread`_ to decode audio and parses the response for you, pulling out the most important track metadata. It returns in iterable over tuples of relevant information. Everything happens in one fell swoop. There are also a number of "smaller" functions you can use to perform parts of the process: - ``fingerprint(samplerate, channels, pcmiter)``: Generate a fingerprint for raw audio data. Specify the audio parameters and give an iterable containing blocks of PCM data. - ``fingerprint_file(path)``: Using either the Chromaprint dynamic library or the ``fpcalc`` command-line tool, fingerprint an audio file. Returns a pair consisting of the file's duration and its fingerprint. - ``lookup(apikey, fingerprint, duration)``: Make a request to the `Acoustid`_ API to look up the fingerprint returned by the previous function. An API key is required, as is the length, in seconds, of the source audio. Returns a parsed JSON response. - ``parse_lookup_result(data)``: Given a parsed JSON response, return an iterator over tuples containing the match score (a float between 0 and 1), the MusicBrainz recording ID, title, and artist name for each match. The module internally performs thread-safe API rate limiting to 3 queries per second whenever the Web API is called, in accordance with the `Web service documentation`_. If you're running your own Acoustid database server, you can set the base URL for all API calls with the ``set_base_url`` function. Calls to the library can raise ``AcoustidError`` exceptions of two subtypes: ``FingerprintGenerationError`` and ``WebServiceError``. Catch these exceptions if you want to proceed when audio can't be decoded or no match is found on the server. ``NoBackendError``, a subclass of ``FingerprintGenerationError``, is used when the Chromaprint library or fpcalc command-line tool cannot be found. .. _Web service documentation: http://acoustid.org/webservice Version History --------------- 1.0.0 Include ``fpcalc.py``, a script mimicking the ``fpcalc`` program from the Chromaprint package. Handle a ``UnicodeDecodeError`` raised when using the ``fpcalc`` backend on Windows with Unicode filenames. Standard error output from ``fpcalc`` is suppressed. 0.7 Properly encode Unicode parameters (resolves a ``UnicodeEncodeError`` in fingerprint submission). Parse all recordings for each Acoustid lookup result. 0.6 Add a new function, ``fingerprint_file``, that automatically selects a backend for fingerprinting a single file. 0.5 Fix response parsing when recording has no artists or title. Fix compatibility with Python < 2.7. Add specific ``NoBackendError`` exception. 0.4 Fingerprinting can now fall back to using the ``fpcalc`` command-line tool instead of the Chromaprint dynamic library so the library can be used with the binary distributions (thanks to Lukáš Lalinský). Fingerprint submission (thanks to Alastair Porter). Data chunks can now be buffers as well as bytestrings (fixes compatibility with pymad). 0.3 Configurable API base URL. Result parser now generates all results instead of returning just one. Find the chromaprint library on Cygwin. New module names: ``chromaprint`` and ``acoustid`` (no package). 0.2 Compress HTTP requests and responses. Limit audio decoding to 120 seconds. Return score from convenience function. 0.1 Initial release. Credits ------- This library is by Adrian Sampson. Chromaprint and Acoustid are by `Lukáš Lalinský`__. This package includes the original `ctypes`_-based bindings written by Lukáš. The entire library is made available under the `MIT license`_. pyacoustid was written to be used with `beets`_, which you should probably check out. __ lukas_ .. _ctypes: http://docs.python.org/library/ctypes.html .. _beets: http://beets.radbox.org/ .. _MIT license: http://www.opensource.org/licenses/mit-license.php pyacoustid-1.0.0/setup.cfg0000644000076500000240000000007312121135400016446 0ustar asampsonstaff00000000000000[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 pyacoustid-1.0.0/setup.py0000644000076500000240000000315312113746505016360 0ustar asampsonstaff00000000000000# This file is part of pyacoustid. # Copyright 2011, Adrian Sampson. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. import os from setuptools import setup def _read(fn): path = os.path.join(os.path.dirname(__file__), fn) data = open(path).read().decode('utf8') # Special case some Unicode characters; PyPI seems to only like ASCII. data = data.replace(u'\xe1', u'a') data = data.replace(u'\u0161', u's') data = data.replace(u'\xfd', u'y') return data setup(name='pyacoustid', version='1.0.0', description= 'bindings for Chromaprint acoustic fingerprinting and the ' 'Acoustid API', author='Adrian Sampson', author_email='adrian@radbox.org', url='https://github.com/sampsyo/pyacoustid', license='MIT', platforms='ALL', long_description=_read('README.rst'), install_requires = ['audioread'], py_modules=[ 'chromaprint', 'acoustid', ], classifiers=[ 'Topic :: Multimedia :: Sound/Audio :: Conversion', 'Intended Audience :: Developers', ], )