From: Felix Domke Date: Wed, 11 Jun 2008 00:07:28 +0000 (+0000) Subject: add early version of DVDBurn plugin. Basic functionality is working, but it needs... X-Git-Tag: 2.6.0~1137 X-Git-Url: https://git.cweiske.de/enigma2.git/commitdiff_plain/53f199c5a82bcd460d3153814f2e057c747e2a3a?ds=sidebyside add early version of DVDBurn plugin. Basic functionality is working, but it needs a major GUI redesign. Required tools: mplex, projectx, growisofs, mkisofs/genisoimage, dvdauthor --- diff --git a/lib/python/Plugins/Extensions/DVDBurn/DVDProject.py b/lib/python/Plugins/Extensions/DVDBurn/DVDProject.py new file mode 100644 index 00000000..e7876e41 --- /dev/null +++ b/lib/python/Plugins/Extensions/DVDBurn/DVDProject.py @@ -0,0 +1,20 @@ +class DVDProject: + def __init__(self): + self.titles = [ ] + self.target = None + self.name = _("New DVD") + + def addService(self, service): + import DVDTitle + t = DVDTitle.DVDTitle() + t.source = service + + from enigma import eServiceCenter, iServiceInformation + serviceHandler = eServiceCenter.getInstance() + + info = serviceHandler.info(service) + descr = info and " " + info.getInfoString(service, iServiceInformation.sDescription) or "" + t.name = info and info.getName(service) or "Title" + descr + + self.titles.append(t) + return t diff --git a/lib/python/Plugins/Extensions/DVDBurn/DVDTitle.py b/lib/python/Plugins/Extensions/DVDBurn/DVDTitle.py new file mode 100644 index 00000000..bc343e78 --- /dev/null +++ b/lib/python/Plugins/Extensions/DVDBurn/DVDTitle.py @@ -0,0 +1,9 @@ + +class DVDTitle: + def __init__(self): + self.cutlist = [ ] + self.source = None + self.name = "" + + def estimateDiskspace(self): + return 0 diff --git a/lib/python/Plugins/Extensions/DVDBurn/LICENSE b/lib/python/Plugins/Extensions/DVDBurn/LICENSE new file mode 100644 index 00000000..99700593 --- /dev/null +++ b/lib/python/Plugins/Extensions/DVDBurn/LICENSE @@ -0,0 +1,12 @@ +This plugin is licensed under the Creative Commons +Attribution-NonCommercial-ShareAlike 3.0 Unported +License. To view a copy of this license, visit +http://creativecommons.org/licenses/by-nc-sa/3.0/ or send a letter to Creative +Commons, 559 Nathan Abbott Way, Stanford, California 94305, USA. + +Alternatively, this plugin may be distributed and executed on hardware which +is licensed by Dream Multimedia GmbH. + +This plugin is NOT free software. It is open source, you are allowed to +modify it (if you keep the license), but it may not be commercially +distributed other than under the conditions noted above. diff --git a/lib/python/Plugins/Extensions/DVDBurn/Makefile.am b/lib/python/Plugins/Extensions/DVDBurn/Makefile.am new file mode 100644 index 00000000..04659770 --- /dev/null +++ b/lib/python/Plugins/Extensions/DVDBurn/Makefile.am @@ -0,0 +1,8 @@ +installdir = $(LIBDIR)/enigma2/python/Plugins/Extensions/DVDBurn + +install_PYTHON = \ + __init__.py \ + plugin.py \ + DVDProject.py DVDTitle.py TitleCutter.py TitleList.py Process.py + +install_DATA = keymap.xml diff --git a/lib/python/Plugins/Extensions/DVDBurn/Process.py b/lib/python/Plugins/Extensions/DVDBurn/Process.py new file mode 100644 index 00000000..9163b8ae --- /dev/null +++ b/lib/python/Plugins/Extensions/DVDBurn/Process.py @@ -0,0 +1,248 @@ +from Components.Task import Task, Job, job_manager, DiskspacePrecondition, Condition + +class DemuxTask(Task): + def __init__(self, job, inputfile, cutlist): + Task.__init__(self, job, "Demux video into ES") + + self.global_preconditions.append(DiskspacePrecondition(4*1024*1024)) + self.setTool("/opt/bin/projectx") + self.cutfile = self.job.workspace + "/cut.Xcl" + self.generated_files = [ ] + self.cutlist = cutlist + + self.end = 300 + self.prog_state = 0 + self.weighting = 1000 + + self.args += [inputfile, "-demux", "-out", self.job.workspace, "-cut", self.job.workspace + "/" + self.cutfile ] + + def prepare(self): + self.writeCutfile() + + def processOutputLine(self, line): + line = line[:-1] + MSG_NEW_FILE = "---> new File: " + MSG_PROGRESS = "[PROGRESS] " + + if line.startswith(MSG_NEW_FILE): + file = line[len(MSG_NEW_FILE):] + if file[0] == "'": + file = file[1:-1] + self.haveNewFile(file) + elif line.startswith(MSG_PROGRESS): + progress = line[len(MSG_PROGRESS):] + self.haveProgress(progress) + + def haveNewFile(self, file): + print "PRODUCED FILE [%s]" % file + self.generated_files.append(file) + + def haveProgress(self, progress): + print "PROGRESS [%s]" % progress + MSG_CHECK = "check & synchronize audio file" + MSG_DONE = "done..." + if progress == "preparing collection(s)...": + self.prog_state = 0 + elif progress[:len(MSG_CHECK)] == MSG_CHECK: + self.prog_state += 1 + else: + try: + print "have progress:", progress + p = int(progress) + p = p - 1 + self.prog_state * 100 + if p > self.progress: + self.progress = p + except ValueError: + print "val error" + pass + + def writeCutfile(self): + f = open(self.cutfile, "w") + f.write("CollectionPanel.CutMode=4\n") + for p in self.cutlist: + s = p / 90000 + m = s / 60 + h = m / 60 + + m %= 60 + s %= 60 + + f.write("%02d:%02d:%02d\n" % (h, m, s)) + f.close() + + def cleanup(self, failed): + if failed: + import os + for f in self.generated_files: + os.remove(f) + +class MplexTask(Task): + def __init__(self, job, outputfile, demux_task): + Task.__init__(self, job, "Mux ES into PS") + + self.weighting = 500 + self.demux_task = demux_task + self.setTool("/usr/bin/mplex") + self.args += ["-f8", "-o", self.job.workspace + "/" + outputfile, "-v1"] + + def prepare(self): + self.args += self.demux_task.generated_files + +class RemoveESFiles(Task): + def __init__(self, job, demux_task): + Task.__init__(self, job, "Remove temp. files") + self.demux_task = demux_task + self.setTool("/bin/rm") + + def prepare(self): + self.args += ["-f"] + self.args += self.demux_task.generated_files + self.args += [self.demux_task.cutfile] + +class DVDAuthorTask(Task): + def __init__(self, job, inputfiles, chapterlist): + Task.__init__(self, job, "dvdauthor") + + self.weighting = 300 + self.setTool("/usr/bin/dvdauthor") + chapterargs = "--chapters=" + ','.join(["%d:%02d:%02d.%03d" % (p / (90000 * 3600), p % (90000 * 3600) / (90000 * 60), p % (90000 * 60) / 90000, (p % 90000) / 90) for p in chapterlist]) + self.args += ["-t", chapterargs, "-o", self.job.workspace + "/dvd", "-f"] + inputfiles + +class RemovePSFile(Task): + def __init__(self, job, psfile): + Task.__init__(self, job, "Remove temp. files") + self.setTool("/bin/rm") + self.args += ["-f", psfile] + +class DVDAuthorFinalTask(Task): + def __init__(self, job): + Task.__init__(self, job, "dvdauthor finalize") + self.setTool("/usr/bin/dvdauthor") + self.args += ["-T", "-o", self.job.workspace + "/dvd"] + +class BurnTaskPostcondition(Condition): + def check(self, task): + return task.error is None + + def getErrorMessage(self, task): + return { + task.ERROR_MEDIA: ("Medium is not a writeable DVD!"), + task.ERROR_SIZE: ("Content does not fit on DVD!"), + task.ERROR_WRITE_FAILED: ("Write failed!"), + task.ERROR_DVDROM: ("No (supported) DVDROM found!"), + task.ERROR_UNKNOWN: ("An unknown error occured!") + }[task.error] + +class BurnTask(Task): + ERROR_MEDIA, ERROR_SIZE, ERROR_WRITE_FAILED, ERROR_DVDROM, ERROR_UNKNOWN = range(5) + def __init__(self, job): + Task.__init__(self, job, "burn") + + self.weighting = 500 + self.end = 120 # 100 for writing, 10 for buffer flush, 10 for closing disc + self.postconditions.append(BurnTaskPostcondition()) + self.setTool("/bin/growisofs") + self.args += ["-dvd-video", "-dvd-compat", "-Z", "/dev/cdroms/cdrom0", "-V", "Dreambox_DVD", "-use-the-force-luke=dummy", self.job.workspace + "/dvd"] + + def prepare(self): + self.error = None + + def processOutputLine(self, line): + line = line[:-1] + print "[GROWISOFS] %s" % line + if line[8:14] == "done, ": + self.progress = float(line[:6]) + print "progress:", self.progress + elif line.find("flushing cache") != -1: + self.progress = 100 + elif line.find("closing disc") != -1: + self.progress = 110 + elif line.startswith(":-["): + if line.find("ASC=30h") != -1: + self.error = self.ERROR_MEDIA + else: + self.error = self.ERROR_UNKNOWN + print "BurnTask: unknown error %s" % line + elif line.startswith(":-("): + if line.find("No space left on device") != -1: + self.error = self.ERROR_SIZE + elif line.find("write failed") != -1: + self.error = self.ERROR_WRITE_FAILED + elif line.find("unable to open64(\"/dev/cdroms/cdrom0\",O_RDONLY): No such file or directory") != -1: # fixme + self.error = self.ERROR_DVDROM + elif line.find("media is not recognized as recordable DVD") != -1: + self.error = self.ERROR_MEDIA + else: + self.error = self.ERROR_UNKNOWN + print "BurnTask: unknown error %s" % line + +class RemoveDVDFolder(Task): + def __init__(self, job): + Task.__init__(self, job, "Remove temp. files") + self.setTool("/bin/rm") + self.args += ["-rf", self.job.workspace + "/dvd"] + +class DVDJob(Job): + def __init__(self, cue): + Job.__init__(self, "DVD Burn") + self.cue = cue + self.workspace = "/media/hdd/tmp" + self.fromDescription(self.createDescription()) + + def fromDescription(self, description): + nr_titles = int(description["nr_titles"]) + + for i in range(nr_titles): + inputfile = description["inputfile%d" % i] + cutlist_entries = description["cutlist%d_entries" % i] + cutlist = [ ] + for j in range(cutlist_entries): + cutlist.append(int(description["cutlist%d_%d" % (i, j)])) + + chapterlist_entries = description["chapterlist%d_entries" % i] + chapterlist = [ ] + for j in range(chapterlist_entries): + chapterlist.append(int(description["chapterlist%d_%d" % (i, j)])) + + demux = DemuxTask(self, inputfile = inputfile, cutlist = cutlist) + + title_filename = self.workspace + "/dvd_title_%d.mpg" % i + + MplexTask(self, "dvd_title_%d.mpg" % i, demux) + RemoveESFiles(self, demux) + DVDAuthorTask(self, [title_filename], chapterlist = chapterlist) + RemovePSFile(self, title_filename) + DVDAuthorFinalTask(self) + BurnTask(self) + RemoveDVDFolder(self) + + def createDescription(self): + # self.cue is a list of titles, with + # each title being a tuple of + # inputfile, + # a list of cutpoints (in,out) + # a list of chaptermarks + # we turn this into a flat dict with + # nr_titles = the number of titles, + # cutlist%d_entries = the number of cutlist entries for title i, + # cutlist%d_%d = cutlist entry j for title i, + # chapterlist%d_entries = the number of chapters for title i, + # chapterlist%d_%d = chapter j for title i + res = { "nr_titles": len(self.cue) } + for i in range(len(self.cue)): + c = self.cue[i] + res["inputfile%d" % i] = c[0] + res["cutlist%d_entries" % i] = len(c[1]) + for j in range(len(c[1])): + res["cutlist%d_%d" % (i,j)] = c[1][j] + + res["chapterlist%d_entries" % i] = len(c[2]) + for j in range(len(c[2])): + res["chapterlist%d_%d" % (i,j)] = c[2][j] + return res + +def Burn(session, cue): + print "burning cuesheet!" + j = DVDJob(cue) + job_manager.AddJob(j) + return j diff --git a/lib/python/Plugins/Extensions/DVDBurn/TitleCutter.py b/lib/python/Plugins/Extensions/DVDBurn/TitleCutter.py new file mode 100644 index 00000000..3cba54af --- /dev/null +++ b/lib/python/Plugins/Extensions/DVDBurn/TitleCutter.py @@ -0,0 +1,10 @@ +from Plugins.Extensions.CutListEditor.plugin import CutListEditor + +class TitleCutter(CutListEditor): + def __init__(self, session, title): + CutListEditor.__init__(self, session, title.source) + #, title.cutlist) + + def exit(self): + self.session.nav.stopService() + self.close(self.cut_list[:]) diff --git a/lib/python/Plugins/Extensions/DVDBurn/TitleList.py b/lib/python/Plugins/Extensions/DVDBurn/TitleList.py new file mode 100644 index 00000000..2bc51722 --- /dev/null +++ b/lib/python/Plugins/Extensions/DVDBurn/TitleList.py @@ -0,0 +1,160 @@ +import DVDProject, DVDTitle, TitleList, TitleCutter + +from Screens.Screen import Screen +from Components.ActionMap import HelpableActionMap, ActionMap +from Components.Sources.List import List +from enigma import eListboxPythonMultiContent, gFont, RT_HALIGN_LEFT + +class TitleList(Screen): + + skin = """ + + + + + """ + + def __init__(self, session, project = None): + Screen.__init__(self, session) + + if project is not None: + self.project = project + else: + self.newProject() + + self["titleactions"] = HelpableActionMap(self, "DVDTitleList", + { + "addTitle": (self.addTitle, _("Add a new title"), _("Add title...")), + "editTitle": (self.editTitle, _("Edit current title"), _("Edit title...")), + "removeCurrentTitle": (self.removeCurrentTitle, _("Remove currently selected title"), _("Remove title")), + "saveProject": (self.saveProject, _("Save current project to disk"), _("Save...")), + "burnProject": (self.burnProject, _("Burn DVD"), _("Burn")), + }) + + self["actions"] = ActionMap(["OkCancelActions"], + { + "cancel": self.leave + }) + + #Action("addTitle", self.addTitle) + + self["titles"] = List(list = [ ], enableWrapAround = True, item_height=50, fonts = [gFont("Regular", 20)]) + self.updateTitleList() + + #self["addTitle"] = ActionButton("titleactions", "addTitle") + #self["editTitle"] = ActionButton("titleactions", "editTitle") + #self["removeCurrentTitle"] = ActionButton("titleactions", "removeCurrentTitle") + #self["saveProject"] = ActionButton("titleactions", "saveProject") + #self["burnProject"] = ActionButton("titleactions", "burnProject") + + def newProject(self): + self.project = DVDProject.DVDProject() + self.project.titles = [ ] + + def addTitle(self): + from Screens.MovieSelection import MovieSelection + self.session.openWithCallback(self.selectedSource, MovieSelection) + + def selectedSource(self, source): + if source is None: + return None + t = self.project.addService(source) + self.updateTitleList() + + self.editTitle(t) + + def removeCurrentTitle(self): + title = self.getCurrentTitle() + if title is not None: + self.project.titles.remove(title) + self.updateTitleList() + + def saveProject(self): + pass + + def burnProject(self): + print "producing final cue sheet:" + cue = self.produceFinalCuesheet() + import Process + job = Process.Burn(self.session, cue) + print cue + from Screens.TaskView import JobView + self.session.open(JobView, job) + + def updateTitleList(self): + res = [ ] + for title in self.project.titles: + a = [ title, (eListboxPythonMultiContent.TYPE_TEXT, 0, 10, 400, 50, 0, RT_HALIGN_LEFT, title.name) ] + res.append(a) + + self["titles"].list = res + + def getCurrentTitle(self): + t = self["titles"].getCurrent() + return t and t[0] + + def editTitle(self, title = None): + t = title or self.getCurrentTitle() + if t is not None: + self.current_edit_title = t + self.session.openWithCallback(self.titleEditDone, TitleCutter.TitleCutter, t) + + def titleEditDone(self, cutlist): + t = self.current_edit_title + t.cutlist = cutlist + print "title edit of %s done, resulting cutlist:" % (t.source.toString()), t.cutlist + + def leave(self): + self.close() + + def produceFinalCuesheet(self): + res = [ ] + for title in self.project.titles: + path = title.source.getPath() + print ">>> path:", path + cutlist = title.cutlist + + # our demuxer expects *stricly* IN,OUT lists. + first = True + currently_in = False + CUT_TYPE_IN = 0 + CUT_TYPE_OUT = 1 + CUT_TYPE_MARK = 2 + CUT_TYPE_LAST = 3 + + accumulated_in = 0 + accumulated_at = 0 + last_in = 0 + + res_cutlist = [ ] + + res_chaptermarks = [0] + + for (pts, type) in cutlist: + if first and type == CUT_TYPE_OUT: # first mark is "out" + res_cutlist.append(0) # emulate "in" at first + currently_in = True + + first = False + + if type == CUT_TYPE_IN and not currently_in: + res_cutlist.append(pts) + last_in = pts + currently_in = True + + if type == CUT_TYPE_OUT and currently_in: + res_cutlist.append(pts) + + # accumulate the segment + accumulated_in += pts - last_in + accumulated_at = pts + currently_in = False + + if type == CUT_TYPE_MARK and currently_in: + # relocate chaptermark against "in" time. This is not 100% accurate, + # as the in/out points are not. + res_chaptermarks.append(pts - accumulated_at + accumulated_in) + + res.append( (path, res_cutlist, res_chaptermarks) ) + + return res diff --git a/lib/python/Plugins/Extensions/DVDBurn/__init__.py b/lib/python/Plugins/Extensions/DVDBurn/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/lib/python/Plugins/Extensions/DVDBurn/keymap.xml b/lib/python/Plugins/Extensions/DVDBurn/keymap.xml new file mode 100644 index 00000000..6c57ba41 --- /dev/null +++ b/lib/python/Plugins/Extensions/DVDBurn/keymap.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/lib/python/Plugins/Extensions/DVDBurn/plugin.py b/lib/python/Plugins/Extensions/DVDBurn/plugin.py new file mode 100644 index 00000000..66fe96d5 --- /dev/null +++ b/lib/python/Plugins/Extensions/DVDBurn/plugin.py @@ -0,0 +1,11 @@ +from Plugins.Plugin import PluginDescriptor + +def main(session, service, **kwargs): + import TitleList + import DVDProject + project = DVDProject.DVDProject() + project.addService(service) + session.open(TitleList.TitleList, project) + +def Plugins(**kwargs): + return PluginDescriptor(name="DVD Tool", description=_("Burn To DVD..."), where = PluginDescriptor.WHERE_MOVIELIST, fnc=main)