Blob Blame History Raw
# -*- coding: utf-8 -*-

PLUGIN_NAME = u"Generate Cuesheet"
PLUGIN_AUTHOR = u"Lukáš Lalinský"
PLUGIN_DESCRIPTION = "Generate cuesheet (.cue file) from an album."
PLUGIN_VERSION = "0.1"
PLUGIN_API_VERSIONS = ["0.10", "0.15"]


import os.path
import re
from PyQt4 import QtCore, QtGui
from picard.util import find_existing_path, encode_filename
from picard.ui.itemviews import BaseAction, register_album_action


_whitespace_re = re.compile('\s', re.UNICODE)
_split_re = re.compile('\s*("[^"]*"|[^ ]+)\s*', re.UNICODE)


def msfToMs(msf):
    msf = msf.split(":")
    return ((int(msf[0]) * 60 + int(msf[1])) * 75 + int(msf[2])) * 1000 / 75   


class CuesheetTrack(list):

    def __init__(self, cuesheet, index):
        list.__init__(self)
        self.cuesheet = cuesheet
        self.index = index

    def set(self, *args):
        self.append(args)

    def find(self, prefix):
        return [i for i in self if tuple(i[:len(prefix)]) == tuple(prefix)]

    def getTrackNumber(self):
        return self.index

    def getLength(self):
        try:
            nextTrack = self.cuesheet.tracks[self.index+1]
            index0 = self.find((u"INDEX",u"01"))
            index1 = nextTrack.find((u"INDEX",u"01"))
            return msfToMs(index1[0][2]) - msfToMs(index0[0][2]) 
        except IndexError:
            return 0

    def getField(self, prefix):
        try:
            return self.find(prefix)[0][len(prefix)]
        except IndexError:
            return u""

    def getArtist(self):
        return self.getField((u"PERFORMER",))

    def getTitle(self):
        return self.getField((u"TITLE",))

    def setArtist(self, artist):
        found = False
        for item in self:
            if item[0] == u"PERFORMER":
                if not found:
                    item[1] = artist
                    found = True
                else:
                    del item
        if not found:
            self.append((u"PERFORMER", artist))

    artist = property(getArtist, setArtist)


class Cuesheet(object):

    def __init__(self, filename):
        self.filename = filename
        self.tracks = []

    def read(self):
        f = open(encode_filename(self.filename))
        self.parse(f.readlines())
        f.close()

    def unquote(self, string):
        if string.startswith('"'):
            if string.endswith('"'):
                return string[1:-1]
            else:
                return string[1:]
        return string

    def quote(self, string):
        if _whitespace_re.search(string):
            return '"' + string.replace('"', '\'') + '"'
        return string

    def parse(self, lines):
        track = CuesheetTrack(self, 0)
        self.tracks = [track]
        isUnicode = False
        for line in lines:
            # remove BOM
            if line.startswith('\xfe\xff'):
                isUnicode = True
                line = line[1:]
            # decode to unicode string
            line = line.strip()
            if isUnicode:
                line = line.decode('UTF-8', 'replace')
            else:
                line = line.decode('ISO-8859-1', 'replace')
            # parse the line
            split = [self.unquote(s) for s in _split_re.findall(line)]
            keyword = split[0].upper()
            if keyword == 'TRACK':
                trackNum = int(split[1])
                track = CuesheetTrack(self, trackNum)
                self.tracks.append(track)
            track.append(split)

    def write(self):
        lines = []
        for track in self.tracks:
            num = track.index
            for line in track:
                indent = 0
                if num > 0:
                    if line[0] == "TRACK":
                        indent = 2
                    elif line[0] != "FILE":
                        indent = 4
                line2 = u" ".join([self.quote(s) for s in line])
                lines.append(" " * indent + line2.encode("UTF-8") + "\n")
        f = open(encode_filename(self.filename), "wt")
        f.writelines(lines)
        f.close()


class GenerateCuesheet(BaseAction):
    NAME = "Generate &Cuesheet..."

    def callback(self, objs):
        album = objs[0]
        current_directory = self.config.persist["current_directory"] or QtCore.QDir.homePath()
        current_directory = find_existing_path(unicode(current_directory))
        selected_format = QtCore.QString()
        filename = QtGui.QFileDialog.getSaveFileName(None, "", current_directory, "Cuesheet (*.cue)", selected_format)
        if filename:
            filename = unicode(filename)
            cuesheet = Cuesheet(filename)
            #try: cuesheet.read()
            #except IOError: pass
            while len(cuesheet.tracks) <= len(album.tracks):
                track = CuesheetTrack(cuesheet, len(cuesheet.tracks))
                cuesheet.tracks.append(track)
            #if len(cuesheet.tracks) > len(album.tracks) - 1:
            #    cuesheet.tracks = cuesheet.tracks[0:len(album.tracks)+1]

            t = cuesheet.tracks[0]
            t.set("PERFORMER", album.metadata["albumartist"])
            t.set("TITLE", album.metadata["album"])
            t.set("REM", "MUSICBRAINZ_ALBUM_ID", album.metadata["musicbrainz_albumid"])
            t.set("REM", "MUSICBRAINZ_ALBUM_ARTIST_ID", album.metadata["musicbrainz_albumartistid"])
            if "date" in album.metadata:
                t.set("REM", "DATE", album.metadata["date"])
            index = 0.0
            for i, track in enumerate(album.tracks):
                mm = index / 60.0
                ss = (mm - int(mm)) * 60.0
                ff = (ss - int(ss)) * 75.0
                index += track.metadata.length / 1000.0
                t = cuesheet.tracks[i + 1]
                t.set("TRACK", "%02d" % (i + 1), "AUDIO")
                t.set("PERFORMER", track.metadata["artist"])
                t.set("TITLE", track.metadata["title"])
                t.set("REM", "MUSICBRAINZ_TRACK_ID", track.metadata["musicbrainz_trackid"])
                t.set("REM", "MUSICBRAINZ_ARTIST_ID", track.metadata["musicbrainz_artistid"])
                t.set("INDEX", "01", "%02d:%02d:%02d" % (mm, ss, ff))
                for file in track.linked_files:
                    audio_filename = file.filename
                    if os.path.dirname(filename) == os.path.dirname(audio_filename):
                        audio_filename = os.path.basename(audio_filename)
                    cuesheet.tracks[i].set("FILE", audio_filename, "MP3")

            cuesheet.write()


action = GenerateCuesheet()
register_album_action(action)