allow burning DVDs with multiple titles where playback automatically jumps to the...
[vuplus_dvbapp] / lib / python / Plugins / Extensions / DVDBurn / Process.py
1 from Components.Task import Task, Job, job_manager, DiskspacePrecondition, Condition
2
3 class MakeFifoNode(Task):
4         def __init__(self, job, number):
5                 Task.__init__(self, job, "Make FIFO Nodes")
6                 self.setTool("/bin/mknod")
7                 nodename = self.job.workspace + "/dvd_title_%d" % number + ".mpg"
8                 self.args += [nodename, "p"]
9
10 class LinkTS(Task):
11         def __init__(self, job, sourcefile, link_name):
12                 Task.__init__(self, job, "Creating Symlink for source titles")
13                 self.setTool("/bin/ln")
14                 self.args += ["-s", sourcefile, link_name]
15
16 class DemuxTask(Task):
17         def __init__(self, job, inputfile, cutlist):
18                 Task.__init__(self, job, "Demux video into ES")
19
20                 self.global_preconditions.append(DiskspacePrecondition(4*1024*1024))
21                 self.setTool("/usr/bin/projectx")
22                 self.cutfile = self.job.workspace + "/cut.Xcl"
23                 self.generated_files = [ ]
24                 self.cutlist = cutlist
25
26                 self.end = 300
27                 self.prog_state = 0
28                 self.weighting = 1000
29
30                 self.args += [inputfile, "-demux", "-out", self.job.workspace, "-cut", self.job.workspace + "/" + self.cutfile ]
31
32         def prepare(self):
33                 self.writeCutfile()
34
35         def processOutputLine(self, line):
36                 line = line[:-1]
37                 MSG_NEW_FILE = "---> new File: "
38                 MSG_PROGRESS = "[PROGRESS] "
39
40                 if line.startswith(MSG_NEW_FILE):
41                         file = line[len(MSG_NEW_FILE):]
42                         if file[0] == "'":
43                                 file = file[1:-1]
44                         self.haveNewFile(file)
45                 elif line.startswith(MSG_PROGRESS):
46                         progress = line[len(MSG_PROGRESS):]
47                         self.haveProgress(progress)
48
49         def haveNewFile(self, file):
50                 print "PRODUCED FILE [%s]" % file
51                 self.generated_files.append(file)
52
53         def haveProgress(self, progress):
54                 print "PROGRESS [%s]" % progress
55                 MSG_CHECK = "check & synchronize audio file"
56                 MSG_DONE = "done..."
57                 if progress == "preparing collection(s)...":
58                         self.prog_state = 0
59                 elif progress[:len(MSG_CHECK)] == MSG_CHECK:
60                         self.prog_state += 1
61                 else:
62                         try:
63                                 p = int(progress)
64                                 p = p - 1 + self.prog_state * 100
65                                 if p > self.progress:
66                                         self.progress = p
67                         except ValueError:
68                                 print "val error"
69                                 pass
70
71         def writeCutfile(self):
72                 f = open(self.cutfile, "w")
73                 f.write("CollectionPanel.CutMode=4\n")
74                 for p in self.cutlist:
75                         s = p / 90000
76                         m = s / 60
77                         h = m / 60
78
79                         m %= 60
80                         s %= 60
81
82                         f.write("%02d:%02d:%02d\n" % (h, m, s))
83                 f.close()
84
85         def cleanup(self, failed):
86                 if failed:
87                         import os
88                         for f in self.generated_files:
89                                 os.remove(f)
90
91 class MplexTask(Task):
92         def __init__(self, job, outputfile, demux_task):
93                 Task.__init__(self, job, "Mux ES into PS")
94
95                 self.weighting = 500
96                 self.demux_task = demux_task
97                 self.setTool("/usr/bin/mplex")
98                 self.args += ["-f8", "-o", outputfile, "-v1"]
99
100         def prepare(self):
101                 self.args += self.demux_task.generated_files
102
103         def processOutputLine(self, line):
104                 print "[MplexTask] processOutputLine=", line
105
106 class RemoveESFiles(Task):
107         def __init__(self, job, demux_task):
108                 Task.__init__(self, job, "Remove temp. files")
109                 self.demux_task = demux_task
110                 self.setTool("/bin/rm")
111
112         def prepare(self):
113                 self.args += ["-f"]
114                 self.args += self.demux_task.generated_files
115                 self.args += [self.demux_task.cutfile]
116
117 class DVDAuthorTask(Task):
118         def __init__(self, job):
119                 Task.__init__(self, job, "Authoring DVD")
120
121                 self.weighting = 300
122                 self.setTool("/usr/bin/dvdauthor")
123                 self.CWD = self.job.workspace
124                 self.args += ["-x", self.job.workspace+"/dvdauthor.xml"]
125
126         def processOutputLine(self, line):
127                 print "[DVDAuthorTask] processOutputLine=", line
128                 if line.startswith("STAT: Processing"):
129                         self.callback(self, [], stay_resident=True)
130
131 class DVDAuthorFinalTask(Task):
132         def __init__(self, job):
133                 Task.__init__(self, job, "dvdauthor finalize")
134                 self.setTool("/usr/bin/dvdauthor")
135                 self.args += ["-T", "-o", self.job.workspace + "/dvd"]
136
137 class WaitForResidentTasks(Task):
138         def __init__(self, job):
139                 Task.__init__(self, job, "waiting for dvdauthor to finalize")
140                 
141         def run(self, callback, task_progress_changed):
142                 print "waiting for %d resident task(s) %s to finish..." % (len(self.job.resident_tasks),str(self.job.resident_tasks))
143                 if self.job.resident_tasks == 0:
144                         callback(self, [])
145
146 class BurnTaskPostcondition(Condition):
147         def check(self, task):
148                 return task.error is None
149
150         def getErrorMessage(self, task):
151                 return {
152                         task.ERROR_MEDIA: ("Medium is not a writeable DVD!"),
153                         task.ERROR_SIZE: ("Content does not fit on DVD!"),
154                         task.ERROR_WRITE_FAILED: ("Write failed!"),
155                         task.ERROR_DVDROM: ("No (supported) DVDROM found!"),
156                         task.ERROR_UNKNOWN: ("An unknown error occured!")
157                 }[task.error]
158
159 class BurnTask(Task):
160         ERROR_MEDIA, ERROR_SIZE, ERROR_WRITE_FAILED, ERROR_DVDROM, ERROR_UNKNOWN = range(5)
161         def __init__(self, job):
162                 Task.__init__(self, job, "burn")
163
164                 self.weighting = 500
165                 self.end = 120 # 100 for writing, 10 for buffer flush, 10 for closing disc
166                 self.postconditions.append(BurnTaskPostcondition())
167                 self.setTool("/bin/growisofs")
168                 self.args += ["-dvd-video", "-dvd-compat", "-Z", "/dev/cdroms/cdrom0", "-V", "Dreambox_DVD", "-use-the-force-luke=dummy", self.job.workspace + "/dvd"]
169
170         def prepare(self):
171                 self.error = None
172
173         def processOutputLine(self, line):
174                 line = line[:-1]
175                 print "[GROWISOFS] %s" % line
176                 if line[8:14] == "done, ":
177                         self.progress = float(line[:6])
178                         print "progress:", self.progress
179                 elif line.find("flushing cache") != -1:
180                         self.progress = 100
181                 elif line.find("closing disc") != -1:
182                         self.progress = 110
183                 elif line.startswith(":-["):
184                         if line.find("ASC=30h") != -1:
185                                 self.error = self.ERROR_MEDIA
186                         else:
187                                 self.error = self.ERROR_UNKNOWN
188                                 print "BurnTask: unknown error %s" % line
189                 elif line.startswith(":-("):
190                         if line.find("No space left on device") != -1:
191                                 self.error = self.ERROR_SIZE
192                         elif line.find("write failed") != -1:
193                                 self.error = self.ERROR_WRITE_FAILED
194                         elif line.find("unable to open64(\"/dev/cdroms/cdrom0\",O_RDONLY): No such file or directory") != -1: # fixme
195                                 self.error = self.ERROR_DVDROM
196                         elif line.find("media is not recognized as recordable DVD") != -1:
197                                 self.error = self.ERROR_MEDIA
198                         else:
199                                 self.error = self.ERROR_UNKNOWN
200                                 print "BurnTask: unknown error %s" % line
201
202 class RemoveDVDFolder(Task):
203         def __init__(self, job):
204                 Task.__init__(self, job, "Remove temp. files")
205                 self.setTool("/bin/rm")
206                 self.args += ["-rf", self.job.workspace]
207
208 class DVDJob(Job):
209         def __init__(self, cue):
210                 Job.__init__(self, "DVD Burn")
211                 self.cue = cue
212                 from time import strftime
213                 from Tools.Directories import SCOPE_HDD, resolveFilename, createDir
214                 new_workspace = resolveFilename(SCOPE_HDD) + "tmp/" + strftime("%Y%m%d%H%M%S")
215                 createDir(new_workspace)
216                 self.workspace = new_workspace
217                 self.fromDescription(self.createDescription())
218
219         def fromDescription(self, description):
220                 nr_titles = int(description["nr_titles"])
221                 authorxml = """
222 <dvdauthor dest="%s">
223    <vmgm>
224       <menus>
225          <pgc>
226             <post> jump title 1; </post>
227          </pgc>
228       </menus>
229    </vmgm>
230    <titleset>
231       <titles>""" % (self.workspace+"/dvd")
232                 for i in range(nr_titles):
233                         chapterlist_entries = description["chapterlist%d_entries" % i]
234                         chapterlist = [ ]
235                         print str(chapterlist_entries)
236                         for j in range(chapterlist_entries):
237                                 chapterlist.append(int(description["chapterlist%d_%d" % (i, j)]))
238                         print str(chapterlist)
239                         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])
240
241                         title_no = i+1
242                         MakeFifoNode(self, title_no)
243                         vob_tag = """file="%s/dvd_title_%d.mpg" chapters="%s" />""" % (self.workspace, title_no, chapters)
244                                                 
245                         if title_no < nr_titles:
246                                 post_tag = "> jump title %d;</post>" % ( title_no+1 )
247                         else:
248                                 post_tag = " />"
249                         authorxml += """
250          <pgc>
251             <vob %s
252             <post%s
253          </pgc>""" % (vob_tag, post_tag)
254                 authorxml += """
255       </titles>
256    </titleset>
257 </dvdauthor>
258 """
259                 f = open(self.workspace+"/dvdauthor.xml", "w")
260                 f.write(authorxml)
261                 f.close()
262
263                 DVDAuthorTask(self)
264
265                 for i in range(nr_titles):
266                         inputfile = description["inputfile%d" % i]
267                         cutlist_entries = description["cutlist%d_entries" % i]
268                         cutlist = [ ]
269                         for j in range(cutlist_entries):
270                                 cutlist.append(int(description["cutlist%d_%d" % (i, j)]))
271
272                         link_name =  self.workspace + "/source_title_%d.ts" % (i+1)
273                         LinkTS(self, inputfile, link_name)
274                         demux = DemuxTask(self, inputfile = link_name, cutlist = cutlist)
275                         title_filename =  self.workspace + "/dvd_title_%d.mpg" % (i+1)
276                         MplexTask(self, title_filename, demux)
277                         RemoveESFiles(self, demux)
278                         
279                         #RemovePSFile(self, title_filename)
280                 #DVDAuthorFinalTask(self)
281                 WaitForResidentTasks(self)
282                 BurnTask(self)
283                 #RemoveDVDFolder(self)
284
285         def createDescription(self):
286                 # self.cue is a list of titles, with 
287                 #   each title being a tuple of 
288                 #     inputfile,
289                 #     a list of cutpoints (in,out)
290                 #     a list of chaptermarks
291                 # we turn this into a flat dict with
292                 # nr_titles = the number of titles,
293                 # cutlist%d_entries = the number of cutlist entries for title i,
294                 # cutlist%d_%d = cutlist entry j for title i,
295                 # chapterlist%d_entries = the number of chapters for title i,
296                 # chapterlist%d_%d = chapter j for title i
297                 res = { "nr_titles": len(self.cue) }
298                 for i in range(len(self.cue)):
299                         c = self.cue[i]
300                         res["inputfile%d" % i] = c[0]
301                         res["cutlist%d_entries" % i] = len(c[1])
302                         for j in range(len(c[1])):
303                                 res["cutlist%d_%d" % (i,j)] = c[1][j]
304
305                         res["chapterlist%d_entries" % i] = len(c[2])
306                         for j in range(len(c[2])):
307                                 res["chapterlist%d_%d" % (i,j)] = c[2][j]
308                 return res
309
310 def Burn(session, cue):
311         print "burning cuesheet!"
312         j = DVDJob(cue)
313         job_manager.AddJob(j)
314         return j