mirror of
https://github.com/fergalmoran/picard.git
synced 2026-02-02 13:53:59 +00:00
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.
195 lines
6.5 KiB
Python
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)
|