bddf06
# -*- coding: utf-8 -*-
bddf06
bddf06
PLUGIN_NAME = u"Generate Cuesheet"
bddf06
PLUGIN_AUTHOR = u"Lukáš Lalinský"
bddf06
PLUGIN_DESCRIPTION = "Generate cuesheet (.cue file) from an album."
bddf06
PLUGIN_VERSION = "0.1"
bddf06
PLUGIN_API_VERSIONS = ["0.10", "0.15"]
bddf06
bddf06
bddf06
import os.path
bddf06
import re
bddf06
from PyQt4 import QtCore, QtGui
bddf06
from picard.util import find_existing_path, encode_filename
bddf06
from picard.ui.itemviews import BaseAction, register_album_action
bddf06
bddf06
bddf06
_whitespace_re = re.compile('\s', re.UNICODE)
bddf06
_split_re = re.compile('\s*("[^"]*"|[^ ]+)\s*', re.UNICODE)
bddf06
bddf06
bddf06
def msfToMs(msf):
bddf06
    msf = msf.split(":")
bddf06
    return ((int(msf[0]) * 60 + int(msf[1])) * 75 + int(msf[2])) * 1000 / 75   
bddf06
bddf06
bddf06
class CuesheetTrack(list):
bddf06
bddf06
    def __init__(self, cuesheet, index):
bddf06
        list.__init__(self)
bddf06
        self.cuesheet = cuesheet
bddf06
        self.index = index
bddf06
bddf06
    def set(self, *args):
bddf06
        self.append(args)
bddf06
bddf06
    def find(self, prefix):
bddf06
        return [i for i in self if tuple(i[:len(prefix)]) == tuple(prefix)]
bddf06
bddf06
    def getTrackNumber(self):
bddf06
        return self.index
bddf06
bddf06
    def getLength(self):
bddf06
        try:
bddf06
            nextTrack = self.cuesheet.tracks[self.index+1]
bddf06
            index0 = self.find((u"INDEX",u"01"))
bddf06
            index1 = nextTrack.find((u"INDEX",u"01"))
bddf06
            return msfToMs(index1[0][2]) - msfToMs(index0[0][2]) 
bddf06
        except IndexError:
bddf06
            return 0
bddf06
bddf06
    def getField(self, prefix):
bddf06
        try:
bddf06
            return self.find(prefix)[0][len(prefix)]
bddf06
        except IndexError:
bddf06
            return u""
bddf06
bddf06
    def getArtist(self):
bddf06
        return self.getField((u"PERFORMER",))
bddf06
bddf06
    def getTitle(self):
bddf06
        return self.getField((u"TITLE",))
bddf06
bddf06
    def setArtist(self, artist):
bddf06
        found = False
bddf06
        for item in self:
bddf06
            if item[0] == u"PERFORMER":
bddf06
                if not found:
bddf06
                    item[1] = artist
bddf06
                    found = True
bddf06
                else:
bddf06
                    del item
bddf06
        if not found:
bddf06
            self.append((u"PERFORMER", artist))
bddf06
bddf06
    artist = property(getArtist, setArtist)
bddf06
bddf06
bddf06
class Cuesheet(object):
bddf06
bddf06
    def __init__(self, filename):
bddf06
        self.filename = filename
bddf06
        self.tracks = []
bddf06
bddf06
    def read(self):
bddf06
        f = open(encode_filename(self.filename))
bddf06
        self.parse(f.readlines())
bddf06
        f.close()
bddf06
bddf06
    def unquote(self, string):
bddf06
        if string.startswith('"'):
bddf06
            if string.endswith('"'):
bddf06
                return string[1:-1]
bddf06
            else:
bddf06
                return string[1:]
bddf06
        return string
bddf06
bddf06
    def quote(self, string):
bddf06
        if _whitespace_re.search(string):
bddf06
            return '"' + string.replace('"', '\'') + '"'
bddf06
        return string
bddf06
bddf06
    def parse(self, lines):
bddf06
        track = CuesheetTrack(self, 0)
bddf06
        self.tracks = [track]
bddf06
        isUnicode = False
bddf06
        for line in lines:
bddf06
            # remove BOM
bddf06
            if line.startswith('\xfe\xff'):
bddf06
                isUnicode = True
bddf06
                line = line[1:]
bddf06
            # decode to unicode string
bddf06
            line = line.strip()
bddf06
            if isUnicode:
bddf06
                line = line.decode('UTF-8', 'replace')
bddf06
            else:
bddf06
                line = line.decode('ISO-8859-1', 'replace')
bddf06
            # parse the line
bddf06
            split = [self.unquote(s) for s in _split_re.findall(line)]
bddf06
            keyword = split[0].upper()
bddf06
            if keyword == 'TRACK':
bddf06
                trackNum = int(split[1])
bddf06
                track = CuesheetTrack(self, trackNum)
bddf06
                self.tracks.append(track)
bddf06
            track.append(split)
bddf06
bddf06
    def write(self):
bddf06
        lines = []
bddf06
        for track in self.tracks:
bddf06
            num = track.index
bddf06
            for line in track:
bddf06
                indent = 0
bddf06
                if num > 0:
bddf06
                    if line[0] == "TRACK":
bddf06
                        indent = 2
bddf06
                    elif line[0] != "FILE":
bddf06
                        indent = 4
bddf06
                line2 = u" ".join([self.quote(s) for s in line])
bddf06
                lines.append(" " * indent + line2.encode("UTF-8") + "\n")
bddf06
        f = open(encode_filename(self.filename), "wt")
bddf06
        f.writelines(lines)
bddf06
        f.close()
bddf06
bddf06
bddf06
class GenerateCuesheet(BaseAction):
bddf06
    NAME = "Generate &Cuesheet..."
bddf06
bddf06
    def callback(self, objs):
bddf06
        album = objs[0]
bddf06
        current_directory = self.config.persist["current_directory"] or QtCore.QDir.homePath()
bddf06
        current_directory = find_existing_path(unicode(current_directory))
bddf06
        selected_format = QtCore.QString()
bddf06
        filename = QtGui.QFileDialog.getSaveFileName(None, "", current_directory, "Cuesheet (*.cue)", selected_format)
bddf06
        if filename:
bddf06
            filename = unicode(filename)
bddf06
            cuesheet = Cuesheet(filename)
bddf06
            #try: cuesheet.read()
bddf06
            #except IOError: pass
bddf06
            while len(cuesheet.tracks) <= len(album.tracks):
bddf06
                track = CuesheetTrack(cuesheet, len(cuesheet.tracks))
bddf06
                cuesheet.tracks.append(track)
bddf06
            #if len(cuesheet.tracks) > len(album.tracks) - 1:
bddf06
            #    cuesheet.tracks = cuesheet.tracks[0:len(album.tracks)+1]
bddf06
bddf06
            t = cuesheet.tracks[0]
bddf06
            t.set("PERFORMER", album.metadata["albumartist"])
bddf06
            t.set("TITLE", album.metadata["album"])
bddf06
            t.set("REM", "MUSICBRAINZ_ALBUM_ID", album.metadata["musicbrainz_albumid"])
bddf06
            t.set("REM", "MUSICBRAINZ_ALBUM_ARTIST_ID", album.metadata["musicbrainz_albumartistid"])
bddf06
            if "date" in album.metadata:
bddf06
                t.set("REM", "DATE", album.metadata["date"])
bddf06
            index = 0.0
bddf06
            for i, track in enumerate(album.tracks):
bddf06
                mm = index / 60.0
bddf06
                ss = (mm - int(mm)) * 60.0
bddf06
                ff = (ss - int(ss)) * 75.0
bddf06
                index += track.metadata.length / 1000.0
bddf06
                t = cuesheet.tracks[i + 1]
bddf06
                t.set("TRACK", "%02d" % (i + 1), "AUDIO")
bddf06
                t.set("PERFORMER", track.metadata["artist"])
bddf06
                t.set("TITLE", track.metadata["title"])
bddf06
                t.set("REM", "MUSICBRAINZ_TRACK_ID", track.metadata["musicbrainz_trackid"])
bddf06
                t.set("REM", "MUSICBRAINZ_ARTIST_ID", track.metadata["musicbrainz_artistid"])
bddf06
                t.set("INDEX", "01", "%02d:%02d:%02d" % (mm, ss, ff))
bddf06
                for file in track.linked_files:
bddf06
                    audio_filename = file.filename
bddf06
                    if os.path.dirname(filename) == os.path.dirname(audio_filename):
bddf06
                        audio_filename = os.path.basename(audio_filename)
bddf06
                    cuesheet.tracks[i].set("FILE", audio_filename, "MP3")
bddf06
bddf06
            cuesheet.write()
bddf06
bddf06
bddf06
action = GenerateCuesheet()
bddf06
register_album_action(action)