# -*- 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"] 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)