--- /dev/null
+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
--- /dev/null
+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 = """
+ <screen position="100,100" size="550,400" title="DVD Tool" >
+ <widget source="titles" render="Listbox" scrollbarMode="showOnDemand" position="0,0" size="400,400">
+ <convert type="StaticMultiList" />
+ </widget>
+ </screen>"""
+
+ 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