rembo10 / headphones

Automatic music downloader for SABnzbd
GNU General Public License v3.0
3.41k stars 600 forks source link

Request - AcoustId Fingerprinting #464

Closed sbuser closed 10 years ago

sbuser commented 12 years ago

Acoustid has come along to the point that most users should be able to use it with headphones. The easiest way to add support in headphones would be to use the pyacoustid library: https://github.com/sampsyo/pyacoustid

The pyacoustid library depends on some version of chromaprint being installed and available on the system. Lucas Lalinsky has made this very easy: http://acoustid.org/chromaprint

Windows users would need to get the static binaries and also set an environment variable (or have an options in Headphones for the path to fpcalc.exe). Most everyone else should be able to build the packages easily (he has some built for osx, linux, etc). Then adding the acoustic fingerprinting to Headphones is a matter of something like:

import acoustid

duration, fp = _fingerprint_file_fpcalc(path)
    response = lookup(apikey, fp, duration, meta)
    if parse:
        return parse_lookup_result(response)

A few more lines is all it takes to save the acoustid_id, fingerprint, and duration to the Mediafile. It would be great if headphones supported this for situations where the default autotag of beets doesn't come through - or, even when it does, for future reference with Acoustid.

I should perhaps also note that there is a plugin that does this for beets now - but it depends on the import event in beets and I don't think you'd be able to fire that event with the way autotag is being called. In any case it really is like 15 lines of code to get high quality audio fingerprints in every file.

avjui commented 12 years ago

+1

I would love to see this in headphones.

sbuser commented 12 years ago

Here is how I did this in Windows - you'd have to make allowances for the built libraries (see pyacoustid or the beets acoustid plugin for how) - but this should give you an idea:

def embedAcoustid(downloaded_track_list):

    for downloaded_track in downloaded_track_list:
        try:
            duration, fp = acoustid._fingerprint_file_fpcalc(downloaded_track)
            response = acoustid.lookup(API_KEY, fp, duration)
            if response['status'] != 'ok' or not response.get('results'):
                continue
            results = response.get('results')
            result = results[0]
            acoustid_id = result.get('id')
            logger.info("acoustidid: %s" % acoustid_id)
        except acoustid.AcoustidError, exc:
            logger.error("Could not generate Acoustid fingerprint for: %i" % exc.read())
            continue

        try:
            f = MediaFile(downloaded_track)
        except:
            logger.error('Could not read %s. Not embedding Acoustid data.' % downloaded_track)

        if f and duration:
            f.acoustid_id = acoustid_id
            f.acoustid_fingerprint = fp
            f.acoustid_duration = duration
            f.save()    

It requires the following changes in mediafile:


    acoustid_id = MediaField(
                mp3 = StorageStyle('TXXX', id3_desc=u'Acoustid Id'),
                mp4 = StorageStyle(
                    '----:com.apple.iTunes:Acoustid Id',
                    as_type=str),
                etc = StorageStyle('acoustid_id')
            )

    acoustid_fingerprint = MediaField(
                mp3 = StorageStyle('TXXX', id3_desc=u'Acoustid Fingerprint'),
                mp4 = StorageStyle(
                    '----:com.apple.iTunes:Acoustid Fingerprint',
                    as_type=str),
                etc = StorageStyle('acoustid_fingerprint')
            )

    acoustid_duration = MediaField(
                mp3 = StorageStyle('TXXX', id3_desc=u'Acoustid Fingerprint Duration'),
                mp4 = StorageStyle(
                    '----:com.apple.iTunes:Acoustid Fingerprint Duration',
                    as_type=str),
                etc = StorageStyle('acoustid_fingerprint_duration')
            )

Plus some variables and etc but you get the idea.

sbuser commented 12 years ago

Looking at it after copying it in I see that there is an additional check that would be required in case an acoustid_id didn't come back and etc but, again, you get the idea.