Commits

htgoebel committed 4c5d3cd

Initial checking of compare-tracks.py

Comments (0)

Files changed (1)

compare-tracks.py

+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2011 by Hartmut Goebel <h.goebel@goebel-consult.de>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+__author__ = "Hartmut Goebel <h.goebel@goebel-consult.de>"
+__copyright__ = "Copyright 2011 by Hartmut Goebel <h.goebel@goebel-consult.de>"
+__licence__ = "GNU General Public License version 3 (GPL v3)"
+
+# pip install --install-option="--home=~" discogs-client
+import unicodedata
+import difflib
+import pprint
+import urlparse
+import webbrowser
+import tempfile
+import time
+import os
+
+import discogs_client as discogs
+import musicbrainz as mb
+
+MUSICBRAINZ = '=== musicbrainz ==='
+DISCOGS = '=== discogs ==='
+
+discogs.user_agent = 'TrackCompare/0.1 +http://crazy-compilers.com'
+mb.set_client('TrackCompare/0.1 +http://crazy-compilers.com')
+
+WAIT_FOR_DELETE = 10
+
+class NotFoundError(Exception): pass
+
+def _canonic_catalogno(catno):
+    return ''.join(c for c in catno if c.isalnum())
+
+def _strip_accents(s):
+   return ''.join((c
+                   for c in unicodedata.normalize('NFD', s)
+                   if unicodedata.category(c) != 'Mn'))
+
+def dFindRelease(label, catno):
+    catno = _canonic_catalogno(catno)
+    try:
+        dLabel = discogs.Label(label)
+        for dRelease in dLabel.releases:
+            if _canonic_catalogno(dRelease['catno']) == catno:
+                return discogs.Release(dRelease['id'])
+        raise NotFoundError('label %r, %r' % (label, catno))
+    except discogs.DiscogsAPIError:
+        raise
+
+def dGetRelease(rel_id):
+    return discogs.Release(rel_id)
+
+def dGetLabelString(release):
+    entries = [filter(None, (l.get('name'), l.get('catno')))
+               for l in release.data['labels']]
+    return ', '.join(' - '.join(e) for e in entries)
+
+def dTracklist(release):
+    tracks = []
+    for track in release.tracklist:
+        position = track['position'].strip('.')
+        position = position.replace('.', '-')
+        if not '-' in position:
+            position = '1-' + position
+        artist = ' '.join(isinstance(a, discogs.Artist) and a.name or a
+                          for a in track['artists'])
+        if artist:
+            artist = ' -- ' + artist
+        entry = ('\t'.join((position,
+                            track['title'],
+                            artist,
+                            track['duration'])).lower())
+        tracks.append(_strip_accents(entry))
+    return tracks
+
+
+mbRELEASE_INCLUDES = ["labels", "recordings", "release-groups", "media",
+                     "discids", "artist-credits"]
+
+def find_discogs_release(releaseId):
+    mbRel = mb.get_release_by_id(releaseId, includes=mbRELEASE_INCLUDES)
+    labels = mbRel['label-info-list']
+    if len(labels) == 0:
+        raise Exception('No label')
+    elif len(labels) > 1:
+        raise Exception('More than one labels')
+    label = labels[0]['label']
+
+    rel = dFindRelease(*desc)
+
+def mbTrackList(release):
+    tracks = []
+    is_various_artist = release['artist-credit-phrase'] == 'Various Artists'
+    release['medium-list'].sort(key=lambda x: int(x['position'] or '0'))
+    for medium in release['medium-list']:
+        medium['track-list'].sort(key=lambda x: int(x['position'] or '0'))
+        for track in medium['track-list']:
+            position = u'%s-%02i' % (medium['position'], int(track['position']))
+            recording = track['recording']
+            duration = (u'%i:%02i'
+                        % divmod(int(recording.get('length',0))/1000, 60))
+            if is_various_artist:
+                artist = ' -- ' + recording['artist-credit-phrase']
+            else:
+                artist = u''
+            entry = ('\t'.join((position,
+                                unicode(recording['title']),
+                                artist,
+                                duration)).lower())
+            tracks.append(_strip_accents(entry))
+    return tracks
+
+
+def mbGetLabelString(release):
+    entries = [filter(None, (l.get('label', {}).get('name'), l.get('catalog-number')))
+               for l in release['label-info-list']]
+    return u', '.join(u' - '.join(e) for e in entries)
+
+
+def IS_CHARACTER_JUNK(ch, ws="- \t"):
+    return ch in ws
+
+
+def compare_tracks(dId, *mbIds):
+    dRel = dGetRelease(dId)
+    dInfo = [
+        u'Label: ' + _strip_accents(dGetLabelString(dRel).lower()),
+        u'Date: ' + dRel.data.get('released', u''),
+        u'Country: ' + dRel.data.get('country', u''),
+        '', # empty line
+        ]
+    dInfo.extend(dTracklist(dRel))
+
+    mbInfo = []
+    for mbId in mbIds:
+        mbRel = mb.get_release_by_id(mbId, includes=mbRELEASE_INCLUDES)['release']
+        mbInfo.extend([
+            u'Label: ' + _strip_accents(mbGetLabelString(mbRel).lower()),
+            u'Date: ' + mbRel.get('date', u''),
+            u'Country: ' + mbRel.get('country', u''),
+            '', # empty line
+            ])
+        mbInfo.extend(mbTrackList(mbRel))
+
+    diff = difflib.HtmlDiff(charjunk=IS_CHARACTER_JUNK).make_file(mbInfo, dInfo, MUSICBRAINZ, DISCOGS,
+                                        )
+    outname = tempfile.mktemp(prefix='mb-compare-', suffix='.tmp.html')
+    with open(outname, 'wu') as of:
+        diff = diff.replace('charset=ISO-8859-1', 'charset=UTF-8')
+        of.write(diff.encode('utf-8'))
+    url = urlparse.urlunparse(('file', '', outname, '','',''))
+    webbrowser.open_new_tab(url)
+    if WAIT_FOR_DELETE >= 0:
+        time.sleep(WAIT_FOR_DELETE)
+        os.remove(outname)
+
+def _get_releases(parser, text):
+    id_url = urlparse.urlparse(text)
+    id = id_url.path
+    if 'discogs' in id_url.netloc:
+        type = DISCOGS
+    elif 'musicbrainz' in id_url.netloc:
+        type = MUSICBRAINZ
+    elif '-' in id_url.path:
+        type = MUSICBRAINZ
+    else:
+        type = DISCOGS
+    # :todo: handle ?rel=xxx format
+    if '/' in id:
+        id = id.split('/')
+        try:
+            id = id[id.index('release')+1]
+        except (ValueError, IndexError):
+            parser.error('Does not look like a valid %s ids.' % type)
+    return type, id
+
+
+if __name__ == '__main__':
+    import argparse
+    parser = argparse.ArgumentParser()
+    parser.add_argument('ids', nargs="*")
+    args = parser.parse_args()
+
+    ids = {}
+    for i in args.ids:
+        k, v = _get_releases(parser, i)
+        ids.setdefault(k,[]).append(v)
+    if len(ids) < 2:
+        parser.error('Need both discogs and musicbrainz ids.')
+    compare_tracks(ids[DISCOGS][0], *ids[MUSICBRAINZ])