Files
picard/plugins/cuesheet.py
Lukáš Lalinský abdc026857 Added plugin API versioning.
Plugins can now define variables PLUGIN_VERSION, which defines version of the plugin. It is only informative field for the user. PLUGIN_API_VERSIONS is a list of supported Picard versions. The versions doesn't have to match exactly, though. For example API version "0.9.0" will match Picard version "0.9.0alpha11". Use as specific version numbers as needed.
2007-08-05 11:08:24 +02:00

195 lines
6.5 KiB
Python

# -*- 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.9.0alpha11"]
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))
if track.linked_file:
audio_filename = track.linked_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)