enigma2 20130114 (master) -> 20130216 (master)
[enigma2.git] / usr / lib / enigma2 / python / Plugins / Extensions / DVDBurn / Bludisc.py
1 from Components.Task import Task, Job, DiskspacePrecondition, Condition
2 from Components.Harddisk import harddiskmanager
3 from Tools.Directories import SCOPE_HDD, resolveFilename, createDir
4 from time import strftime
5 from Process import CheckDiskspaceTask, getISOfilename, BurnTask, RemoveWorkspaceFolder
6 from Project import iso639language
7 import struct
8 import os
9 import re
10
11 zeros = bytearray(128)
12 VIDEO_TYPES     = { 'video/mpeg, mpegversion=(int)1': 0x01, 'video/mpeg, mpegversion=(int)2': 0x02, 'VC1': 0xEA, 'video/x-h264': 0x1B }
13 AUDIO_TYPES     = { 'audio/mpeg, mpegversion=(int)1': 0x03, 'audio/mpeg, mpegversion=(int)2': 0x04, 'audio/x-lpcm': 0x80, 'audio/x-ac3': 0x81, 'audio/x-dts': 0x82, 'TRUEHD': 0x83, 'AC3+': 0x84, 'DTSHD': 0x85, 'DTSHD Master': 0x86 }
14 VIDEO_FORMATS   = { 'i480': 1, 'i576': 2, 'p480': 3, 'i1080': 4, 'p720': 5, 'p1080': 6, 'p576': 7 }
15 VIDEO_RATES     = { 23976: 1, 24000: 2, 25000: 3, 29970: 4, 50000: 6, 59940: 7 }
16 AUDIO_CHANNELS  = { "reserved": 0, "mono": 1, "dual mono": 2, "stereo": 3, "multi": 6, "combo": 12 }
17 AUDIO_RATES     = { 48000: 1, 96000: 4, 192000: 5, 48/192: 12, 48/96: 14 }
18
19 class BludiscTitle(object):
20         def __init__(self, title):
21                 object.__setattr__(self, "_title", title)
22                 self.__streams = {}
23                 self.muxed_size = 0
24                 self.entrypoints = []   # [(source_packet_number, presentation_time_stamp)]
25
26         def __getattr__(self, attr):
27                 _title = object.__getattribute__(self, "_title")
28                 if hasattr(_title, attr):
29                         return getattr(_title, attr)
30                 else:
31                         return object.__getattribute__(self, attr)
32
33         def addStream(self, pid):
34                 self.__streams[pid] = BludiscStream(self, pid)
35
36         def getStreamByPID(self, pid):
37                 if pid in self.__streams:
38                         return self.__streams[pid]
39                 else: return None
40
41         def getVideoStreams(self):
42                 streams = []
43                 for stream in self.__streams.values():
44                         if stream.isVideo:
45                                 streams.append(stream)
46                 return streams
47         
48         VideoStreams = property(getVideoStreams)
49         
50         def getAudioStreams(self):
51                 streams = []
52                 for stream in self.__streams.values():
53                         if stream.isAudio:
54                                 streams.append(stream)
55                 return streams
56
57         AudioStreams = property(getAudioStreams)
58
59         def getInTimeBytes(self):
60                 in_time = self.entrypoints[0][1]        # first keyframe (in 90khz pts)
61                 return struct.pack('>L',in_time/2)      # start time (in 45khz ticks)
62
63         def getOutTimeBytes(self):
64               out_time = self.entrypoints[-1][1]        # last keyframe (in 90khz pts)
65               return struct.pack('>L',out_time/2)       # end time (in 45khz ticks)
66
67         InTime = property(getInTimeBytes)
68         OutTime = property(getOutTimeBytes)
69
70         def getNumSourcePackets(self):
71                 num_source_packets = self.muxed_size / 192
72                 return struct.pack('>L',num_source_packets) 
73
74         def getTsRecordingRate(self):
75                 clip_len_seconds = (self.entrypoints[-1][1] - self.entrypoints[0][1]) / 90000
76                 if self.length > clip_len_seconds:
77                         clip_len_seconds = self.length
78                 ts_recording_rate = self.muxed_size / clip_len_seconds  #! possible lack in accuracy 
79                 return struct.pack('>L',ts_recording_rate)
80
81         def getEPforOffsetPTS(self, requested_pts):
82                 best_pts = 0
83                 for spn, ep_pts in self.entrypoints:
84                         if abs(requested_pts-best_pts) > abs(requested_pts-ep_pts):
85                                 best_pts = ep_pts
86                         else:
87                                 break
88                 return best_pts / 2
89
90 class BludiscStream(object):
91         def __init__(self, parent, PID):
92                 self.__parent = parent
93                 self.__PID = PID
94                 self.__streamtype = 0x00
95                 self.__framerate = None
96                 self.__audiorate = 0
97                 self.__audiopresentation = 0
98                 self.languageCode = "und"
99                 self.isAudio = False
100                 self.isVideo = False
101
102         def setStreamtype(self, streamtype):
103                 if isinstance(streamtype, int):
104                         if streamtype in dict((VIDEO_TYPES[k], k) for k in VIDEO_TYPES):
105                                 self.__streamtype = streamtype
106                                 self.isVideo = True
107                                 self.isAudio = False
108                                 return True
109                         elif streamtype in dict((AUDIO_TYPES[k], k) for k in AUDIO_TYPES):
110                                 self.__streamtype = streamtype
111                                 self.isVideo = False
112                                 self.isAudio = True
113                                 return True
114                 if isinstance(streamtype, str):
115                         if streamtype in VIDEO_TYPES:
116                                 self.__streamtype = VIDEO_TYPES[streamtype]
117                                 self.isVideo = True
118                                 self.isAudio = False
119                                 return True
120                         elif streamtype in AUDIO_TYPES:
121                                 self.__streamtype = AUDIO_TYPES[streamtype]
122                                 self.isVideo = False
123                                 self.isAudio = True
124                                 return True
125                 self.__streamtype = 0x00
126                 self.isVideo = False
127                 self.isAudio = False
128                 return False
129
130         def getStreamtypeByte(self):
131                 return struct.pack('B',self.__streamtype)
132
133         streamType = property(getStreamtypeByte, setStreamtype)
134         
135         def getPIDBytes(self):
136                 return struct.pack('>H',self.__PID)
137         
138         pid = property(getPIDBytes)
139
140         def getFormatByte(self):
141                 val = 0
142                 if self.isVideo:
143                         yres = self.__parent.resolution[1]
144                         videoformat = 0
145                         frame_rate = 0
146
147                         if self.__parent.progressive > 0:
148                                 videoformatstring = "p"+str(yres)
149                         else:
150                                 videoformatstring = "i"+str(yres)
151                                 
152                         if videoformatstring in VIDEO_FORMATS:
153                                 videoformat = VIDEO_FORMATS[videoformatstring]
154                         else:
155                                 print "BludiscStream %s object warning... PID %i video stream format %s out of spec!" % (self.__parent.inputfile, self.__PID, videoformatstring)
156
157                         if self.__parent.framerate in VIDEO_RATES:
158                                 frame_rate = VIDEO_RATES[self.__parent.framerate]
159                         else:
160                                 print "BludiscStream %s object warning... PID %i video frame rate %s out of spec!" % (self.__parent.inputfile, self.__PID, self.__parent.framerate)
161
162                         byteval = (videoformat << 4) + frame_rate
163
164                 if self.isAudio: 
165                         byteval = (self.__audiopresentation << 4) + self.__audiorate
166
167                 return struct.pack('B',byteval)
168         
169         formatByte = property(getFormatByte)
170
171         def setAudioPresentation(self, channels):
172                 presentation = "reserved"
173                 if channels in [1, 2]:
174                         presentation = {1: "mono", 2: "stereo"}[channels]
175                 if channels > 2:
176                         presentation = "multi"
177                 self.__audiopresentation = AUDIO_CHANNELS[presentation]
178
179         def setAudioRate(self, samplerate):
180                 if samplerate in AUDIO_RATES:
181                         self.__audiorate = AUDIO_RATES[samplerate]
182
183         def getAspectByte(self):
184                 aspect = self.__parent.properties.aspect.value
185                 if self.isVideo:
186                       if aspect == "16:9":
187                             return struct.pack('B',0x30)
188                       elif aspect == "4:3":
189                             return struct.pack('B',0x20)
190
191         aspect = property(getAspectByte)
192
193 class RemuxTask(Task):
194         def __init__(self, job, title, title_no):
195                 Task.__init__(self, job, "Remultiplex Movie")
196                 self.global_preconditions.append(DiskspacePrecondition(title.estimatedDiskspace))
197                 self.postconditions.append(GenericPostcondition())
198                 self.setTool("bdremux")
199                 self.title = title
200                 self.title_no = title_no
201                 self.job = job
202                 inputfile = title.inputfile
203                 self.outputfile = self.job.workspace+'BDMV/STREAM/%05d.m2ts' % self.title_no
204                 self.args += [inputfile, self.outputfile, "--entrypoints", "--cutlist"]
205                 self.args += self.getPIDs()
206                 self.end = ( self.title.filesize / 188 )
207                 self.weighting = 1000
208
209         def getPIDs(self):
210                 dvbpids = [self.title.VideoPID]
211                 for audiotrack in self.title.properties.audiotracks:
212                         if audiotrack.active.getValue():
213                                 if audiotrack.format.value == "AC3": #! only consider ac3 streams at the moment
214                                         dvbpids.append(int(audiotrack.pid.getValue()))
215                 sourcepids = "--source-pids=" + ",".join(["0x%04x" % pid for pid in dvbpids])
216                 self.bdmvpids = [0x1011]+range(0x1100,0x1107)[:len(dvbpids)-1]
217                 resultpids = "--result-pids=" + ",".join(["0x%04x" % pid for pid in self.bdmvpids])
218                 return [sourcepids, resultpids]
219
220         def processOutputLine(self, line):
221                 if line.startswith("entrypoint:"):
222                         values = line[:-1].split(' ')
223                         (spn, pts) = (int(values[1]), int(values[2]))
224                         if spn > 0 and pts > 0:
225                                 self.title.entrypoints.append((spn, pts))
226                                 print "[bdremux] added new entrypoint", self.title.entrypoints[-1]
227                         self.progress = spn
228                 elif line.startswith("linked:"):        
229                         words = line[:-1].split(' ')
230                         pid = int(words[5].split('_')[1])
231                         self.title.addStream(pid)
232                         print "[bdremux] added stream with pid", pid
233                 elif line.find("has CAPS:") > 0:
234                         words = line[:-1].split(' ')
235                         pid = int(words[0].split('_')[1])
236                         stype = words[3][:-1]
237                         if words[3].find("mpeg") > 0:
238                                 stype = words[3]+' '+words[4][:-1]
239
240                         stream = self.title.getStreamByPID(pid)
241                         if stream == None:
242                                 print "[bdremux] invalid stream!"
243                                 return
244
245                         sdict = {}
246                         if stype.startswith("audio"):
247                                 sdict = AUDIO_TYPES
248                         elif stype.startswith("video"):
249                                 sdict = VIDEO_TYPES
250                         if stype in sdict:
251                                 stream.streamType = sdict[stype]
252
253                         for field in words[4:]:
254                                 key, val = field.split('=')
255                                 m = re.search('\(int\)(\d*).*', val)
256                                 if m and m.groups() > 1:
257                                         v = int(m.group(1))
258                                         if key == "rate":
259                                                 stream.setAudioRate(v)
260                                         elif key == "channels":
261                                                 stream.setAudioPresentation(v)
262                         print "[bdremux] discovered caps for pid %i (%s)" % (pid, stype)
263                 elif line.startswith("ERROR:"):
264                         self.error_text = line[7:-1]
265                         print "[bdremux] error:", self.error_text
266                         Task.processFinished(self, 1)
267                 else:
268                         print "[bdremux]", line[:-1]
269
270         def cleanup(self, failed):
271                 if not failed:
272                         self.title.muxed_size = os.path.getsize(self.outputfile)
273
274 class GenericPostcondition(Condition):
275         def check(self, task):
276                 return task.returncode == 0
277
278         def getErrorMessage(self, task):
279                 if hasattr(task, "error_text"):
280                         error_text = task.error_text
281                 else:
282                         error_text = _("An unknown error occured!")
283                 return '%s (%s)' % (task.name, error_text)
284
285 class CreateStructureTask(Task):
286         def __init__(self, job):
287                 Task.__init__(self, job, "Creating BDMV Directory Structure")
288                 self.job = job
289                 self.weighting = 10
290                 self.postconditions.append(GenericPostcondition())
291
292         def run(self, callback):
293                 self.callback = callback
294                 self.conduct()
295                 Task.processFinished(self, 0)
296
297         def conduct(self):
298                 for directory in ['BDMV','BDMV/AUXDATA','BDMV/BACKUP','BDMV/BACKUP/BDJO','BDMV/BACKUP/CLIPINF','BDMV/BACKUP/JAR','BDMV/BACKUP/PLAYLIST','BDMV/BDJO','BDMV/CLIPINF','BDMV/JAR','BDMV/META','BDMV/META/DL','BDMV/PLAYLIST','BDMV/STREAM']:
299                         if not createDir(self.job.workspace+directory):
300                                 Task.processFinished(self, 1)
301
302 class CreateIndexTask(Task):
303         def __init__(self, job):
304                 Task.__init__(self, job, "Create BDMV Index File")
305                 self.job = job
306                 self.weighting = 10
307                 self.postconditions.append(GenericPostcondition())
308
309         def run(self, callback):
310                 self.callback = callback
311                 self.conduct()
312                 Task.processFinished(self, 0)
313                 #try:
314                         #self.conduct()
315                         #Task.processFinished(self, 0)
316                 #except:
317                         #Task.processFinished(self, 1)
318
319         def conduct(self):
320                 indexbuffer = bytearray("INDX0200")
321                 indexbuffer += '\x00\x00\x00\x4E'       # index_start
322                 indexbuffer += zeros[0:4]               # extension_data_start
323                 indexbuffer += zeros[0:24]              # reserved
324                 indexbuffer += '\x00\x00\x00\x22'       # app_info length
325                 indexbuffer += '\x00'                   # 1 bit reserved, 1 bit initial_output_mode_preference, 1 bit content_exist_flag, 5 bits reserved
326
327                 num_titles = len(self.job.titles)
328                 if (num_titles == 1 and len(self.job.titles[0].VideoStreams) == 1):
329                         indexbuffer += self.job.titles[0].VideoStreams[0].formatByte    # video_format & frame_rate
330                 else:
331                         indexbuffer += '\x00'           # video_format & frame_rate
332                 indexbuffer += 'Provider Name: Dream Multimedia ' # 32 byte user data
333
334                 INDEXES = bytearray(4)                  # length of indexes
335                 INDEXES += '\x40'                       # object_type (HDMV = 0x40)
336                 INDEXES += zeros[0:3]                   # first playback:
337                 INDEXES += '\x00\x00'                   # playback_type (Movie = 0x00)
338                 INDEXES += '\x00\x00'                   # id_ref
339                 INDEXES += zeros[0:4]                   # skip
340                 INDEXES += '\x40'
341                 INDEXES += zeros[0:3]                   # top menu:
342                 INDEXES += '\x40\x00'                   # playback_type (Interactive = 0x40)
343                 INDEXES += '\xFF\xFF'                   # id_ref 
344                 INDEXES += zeros[0:4]
345
346                 INDEXES += struct.pack('>H',num_titles)
347                 for i in range(num_titles):
348                         HDMV_OBJ = bytearray('\x40')    # object_type & access_type
349                         HDMV_OBJ += zeros[0:3]          # skip 3 bytes
350                         HDMV_OBJ += zeros[0:2]
351                         HDMV_OBJ += struct.pack('>H',i) # index 2 bytes
352                         HDMV_OBJ += zeros[0:4]          # skip 4 bytes
353                         INDEXES += HDMV_OBJ
354                 INDEXES[0:4] = struct.pack('>L',len(INDEXES)-4)
355                 indexbuffer += INDEXES
356
357                 f = open(self.job.workspace+"BDMV/index.bdmv", 'w')
358                 f.write(buffer(indexbuffer))
359                 f.close()
360                 f = open(self.job.workspace+"BDMV/BACKUP/index.bdmv", 'w')
361                 f.write(buffer(indexbuffer))
362                 f.close()
363
364 class CreateMobjTask(Task):
365         def __init__(self, job):
366                 Task.__init__(self, job, "Create BDMV Movie Objects File")
367                 self.job = job
368                 self.weighting = 10
369                 self.postconditions.append(GenericPostcondition())
370
371         def run(self, callback):
372                 self.callback = callback
373                 self.conduct()
374                 Task.processFinished(self, 0)
375                 #try:
376                         #self.conduct()
377                         #Task.processFinished(self, 0)
378                 #except:
379                         #Task.processFinished(self, 1)
380
381         def conduct(self):
382                 mob = bytearray("MOBJ0200")
383                 mob += zeros[0:4] #extension_data_start
384                 mob += zeros[0:28] #reserved?
385
386                 instructions = []
387
388                 for i in range(len(self.job.titles)):
389                         instructions.append(    # load title number into register0 \
390                                  [['\x50\x40\x00\x01','\x00\x00\x00\x00',struct.pack('>L',i)],\
391                                                 # PLAY_PL
392                                   ['\x22\x00\x00\x00','\x00\x00\x00\x00','\x00\x00\x00\x00']])
393                         if i < len(self.job.titles)-1: # on all except last title JUMP_TITLE i+2 (JUMP_TITLE is one-based)
394                                 instructions[-1].append(['\x21\x81\x00\x00',struct.pack('>L',i+2),'\x00\x00\x00\x00'])
395
396                 #SETSTREAM (first audio stream as default track) ['\x51\xC0\x00\x01','\x00\x00\x00\x00','\x80\x01\x00\x00'] #!
397
398                 num_objects = len(instructions)
399                 OBJECTS = bytearray(4) #length of objects
400                 OBJECTS += zeros[0:4] #reserved
401                 OBJECTS += struct.pack('>H',num_objects)
402                 for i in range(num_objects):
403                         MOBJ = bytearray()
404                         MOBJ += '\x80\x00' # resume_intention_flag, menu_call_mask, title_search_maskplayback_type, 13 reserved
405                         num_commands = len(instructions[i])
406                         MOBJ += struct.pack('>H',num_commands)
407                         for c in range(num_commands):
408                                 CMD = bytearray()
409                                 CMD += instructions[i][c][0] #options
410                                 CMD += instructions[i][c][1] #destination
411                                 CMD += instructions[i][c][2] #source
412                                 MOBJ += CMD
413                         OBJECTS += MOBJ
414                 OBJECTS[0:4] = struct.pack('>L',len(OBJECTS)-4)
415
416                 mob += OBJECTS
417
418                 f = open(self.job.workspace+"BDMV/MovieObject.bdmv", 'w')
419                 f.write(buffer(mob))
420                 f.close()
421                 f = open(self.job.workspace+"BDMV/BACKUP/MovieObject.bdmv", 'w')
422                 f.write(buffer(mob))
423                 f.close()
424
425 class CreateMplsTask(Task):
426         def __init__(self, job, title, mpls_num):
427                 Task.__init__(self, job, "Create BDMV Playlist File")
428                 self.title = title
429                 self.mpls_num = mpls_num
430                 self.job = job
431                 self.weighting = 10
432                 self.postconditions.append(GenericPostcondition())
433
434         def run(self, callback):
435                 self.callback = callback
436                 try:
437                         self.conduct()
438                         Task.processFinished(self, 0)
439                 except Exception:
440                         Task.processFinished(self, 1)
441
442         def conduct(self):
443                 mplsbuffer = bytearray("MPLS0200")
444
445                 mplsbuffer += '\x00\x00\x00\x3a'        #playlist_start_address #Position of PlayList, from beginning of file
446
447                 mplsbuffer += zeros[0:4]        #playlist_mark_start_address Position of PlayListMark, from beginning of file
448                 mplsbuffer += zeros[0:4]        #extension_data_start_address= bytearray(4)
449                 mplsbuffer += zeros[0:20]       #reserved
450
451                 AppInfoPlayList = bytearray()   #length of AppInfoPlayList (4 bytes)
452
453                 AppInfoPlayList += '\x00'       #reserved 1 byte
454                 AppInfoPlayList += '\x01'       #playlist_playback_type
455                 AppInfoPlayList += zeros[0:2]   #reserved 2 bytes
456                 AppInfoPlayList += zeros[0:8]   #UO_mask_table
457
458                 AppInfoPlayList += '\x40\x00'   #playlist_random_access_flag, audio_mix_app_flag, lossless_may_bypass_mixer_flag, 13 bit reserved_for_word_align
459
460                 mplsbuffer += bytearray(struct.pack('>L',len(AppInfoPlayList)))
461                 mplsbuffer += AppInfoPlayList
462
463                 PlayList = bytearray()          #length of PlayList (4 bytes)
464
465                 PlayList += zeros[0:2]          #reserved 2 bytes
466
467                 num_of_playitems = 1
468                 PlayList += struct.pack('>H',num_of_playitems)
469
470                 num_of_subpaths = 0
471                 PlayList += struct.pack('>H',num_of_subpaths)
472
473                 num_primary_video = len(self.title.VideoStreams)
474                 
475                 if num_primary_video == 0:
476                         self.error_text = "Title %05d has no valid video streams!" % self.mpls_num
477                         raise Exception, self.error_text
478
479                 num_primary_audio = len(self.title.AudioStreams)
480
481                 num_pg = 0                      # (presentation graphics, subtitle)
482                 num_ig = 0                      # (interactive graphics)
483                 num_secondary_audio = 0
484                 num_secondary_video = 0
485                 num_PIP_PG = 0
486
487                 for item_i in range(num_of_playitems):
488                         PlayItem = bytearray()
489                         clip_no = "%05d" % self.mpls_num
490                         PlayItem += bytearray(clip_no)
491                         PlayItem += "M2TS"
492                         PlayItem += '\x00\x01'                  # reserved 11 bits & 1 bit is_multi_angle & connection_condition
493                         PlayItem += '\x00'                      # stc_id
494                         PlayItem += self.title.InTime           # start time (in 45khz ticks)
495                         PlayItem += self.title.OutTime          # end time (in 45khz ticks)
496                         PlayItem += zeros[0:8]                  # UO_mask_table
497                         PlayItem += '\x00'                      # random_access_flag (uppermost bit, 0=permit) & reserved 7 bits
498                         PlayItem += '\x01\x00\x02'              # still_mode & still_time (in s)
499
500                         StnTable = bytearray()  # len 4 bytes
501                         StnTable += zeros[0:2]  # reserved
502                         StnTable += struct.pack('B',num_primary_video)
503                         StnTable += struct.pack('B',num_primary_audio)
504                         StnTable += struct.pack('B',num_pg)
505                         StnTable += struct.pack('B',num_ig)
506                         StnTable += struct.pack('B',num_secondary_audio)
507                         StnTable += struct.pack('B',num_secondary_video)
508                         StnTable += struct.pack('B',num_PIP_PG)
509                         StnTable += zeros[0:5]  # reserved
510
511                         for vid in self.title.VideoStreams:
512                                 print "adding vid", vid, type(vid)
513                                 VideoEntry = bytearray(1)       # len
514                                 VideoEntry += '\x01'            # type 01 = elementary stream of the clip used by the PlayItem
515
516                                 VideoEntry += vid.pid           # stream_pid
517                                 VideoEntry += zeros[0:6]        # reserved
518                                 VideoEntry[0] = struct.pack('B',len(VideoEntry)-1)
519
520                                 VideoAttr = bytearray(1)        # len
521                                 VideoAttr += vid.streamType     # Video type
522                                 VideoAttr += vid.formatByte     # Format & Framerate
523                                 VideoAttr += zeros[0:3]         # reserved
524                                 VideoAttr[0] = struct.pack('B',len(VideoAttr)-1)
525                                 
526                                 StnTable += VideoEntry
527                                 StnTable += VideoAttr
528
529                         for aud in self.title.AudioStreams:
530                                 AudioEntry = bytearray(1)       # len
531                                 AudioEntry += '\x01'            # type 01 = elementary stream of the clip used by the PlayItem
532                                 AudioEntry += aud.pid           # stream_pid
533                                 AudioEntry += zeros[0:6]        # reserved
534                                 AudioEntry[0] = struct.pack('B',len(AudioEntry)-1)
535
536                                 AudioAttr = bytearray(1)        # len
537                                 AudioAttr += aud.streamType     # stream_coding_type
538                                 AudioAttr += aud.formatByte     # Audio Format & Samplerate
539                                 AudioAttr += aud.languageCode   # Audio Language Code
540                                 AudioAttr[0] = struct.pack('B',len(AudioAttr)-1)
541
542                                 StnTable += AudioEntry
543                                 StnTable += AudioAttr
544
545                         PlayItem += struct.pack('>H',len(StnTable))
546                         PlayItem += StnTable
547                         
548                         PlayList += struct.pack('>H',len(PlayItem))
549                         PlayList += PlayItem
550
551                 mplsbuffer += struct.pack('>L',len(PlayList))
552                 mplsbuffer += PlayList
553
554                 PlayListMarkStartAdress = bytearray(struct.pack('>L',len(mplsbuffer)))
555                 mplsbuffer[0x0C:0x10] = PlayListMarkStartAdress
556                 
557                 if len(self.title.entrypoints) == 0:
558                         print "no entry points found for this title!"
559                         self.title.entrypoints.append(0)
560
561                 #playlist mark list [(id, type, timestamp, skip duration)]
562                 #! implement cutlist / skip marks
563                 markslist = [(0, 1, self.title.entrypoints[0][1]/2, 0)]
564                 mark_id = 1
565                 try:
566                         for chapter_pts in self.title.chaptermarks:
567                                 if (chapter_pts):
568                                         ep_pts = self.title.getEPforOffsetPTS(chapter_pts)
569                                         if ( ep_pts > markslist[0][2] ):
570                                                 markslist.append((mark_id, 1, ep_pts, 0))
571                                                 mark_id += 1
572                 except AttributeError:
573                         print "title has no chaptermarks"
574                 print "**** final markslist", markslist
575
576                 num_marks = len(markslist)
577                 PlayListMark = bytearray()                      # len 4 bytes
578                 PlayListMark += struct.pack('>H',num_marks)
579                 for mark_id, mark_type, mark_ts, skip_dur in markslist:
580                         MarkEntry = bytearray()
581                         MarkEntry += struct.pack('B',mark_id)   # mark_id
582                         MarkEntry += struct.pack('B',mark_type) # mark_type 00=resume, 01=bookmark, 02=skip mark
583                         MarkEntry += struct.pack('>H',item_i)   # play_item_ref (number of PlayItem that the mark is for
584                         MarkEntry += struct.pack('>L',mark_ts)  # (in 45khz time ticks)
585                         MarkEntry += '\xFF\xFF'                 # entry_ES_PID
586                         MarkEntry += struct.pack('>L',skip_dur) # for skip marks: skip duration
587                         PlayListMark += MarkEntry
588
589                 mplsbuffer += struct.pack('>L',len(PlayListMark))
590                 mplsbuffer += PlayListMark
591
592                 f = open(self.job.workspace+"BDMV/PLAYLIST/%05d.mpls" % self.mpls_num, 'w')
593                 f.write(buffer(mplsbuffer))
594                 f.close()
595                 f = open(self.job.workspace+"BDMV/BACKUP/PLAYLIST/%05d.mpls" % self.mpls_num, 'w')
596                 f.write(buffer(mplsbuffer))
597                 f.close()
598
599 class CreateClpiTask(Task):
600         def __init__(self, job, title, clip_num):
601                 Task.__init__(self, job, "Create BDMV Clip Info File")
602                 self.title = title
603                 self.clip_num = clip_num
604                 self.job = job
605                 self.weighting = 10
606                 self.postconditions.append(GenericPostcondition())
607
608         def run(self, callback):
609                 self.callback = callback
610                 self.conduct()
611                 Task.processFinished(self, 0)
612                 #try:
613                         #self.conduct()
614                         #Task.processFinished(self, 0)
615                 #except:
616                         #Task.processFinished(self, 1)
617
618         def conduct(self):
619                 clpibuffer = bytearray("HDMV0200")              #type_indicator
620
621                 clpibuffer += '\x00\x00\x00\xdc'                #sequence_info_start_address
622                 clpibuffer += '\x00\x00\x00\xf6'                #program_info_start_address
623                 clpibuffer += zeros[0:4]                        #cpi_start_address
624                 clpibuffer += zeros[0:4]                        #clip_mark_start_address
625                 clpibuffer += zeros[0:4]                        #ext_data_start_address
626                 clpibuffer += zeros[0:12]                       #reserved
627
628                 ClipInfo = bytearray(4)                         # len 4 bytes
629                 ClipInfo += zeros[0:2]                          # reserved
630                 ClipInfo += '\x01'                              # clip_stream_type
631                 ClipInfo += '\x01'                              # application_type
632                 ClipInfo += '\x00\x00\x00\x00'                  # 31 bit reserved + 1 bit is_cc5 (seamless connection condition)
633                 ClipInfo += self.title.getTsRecordingRate()     # transport stream bitrate
634                 ClipInfo += self.title.getNumSourcePackets()    # number_source_packets
635                 ClipInfo += zeros[0:128]
636
637                 TS_type_info_block = bytearray('\x00\x1E')      # len 2 bytes
638                 TS_type_info_block += '\x80'                    # validity flags
639                 TS_type_info_block += 'HDMV'                    # format_id
640                 TS_type_info_block += zeros[0:25]               # nit/stream_format_name?
641                 ClipInfo += TS_type_info_block
642
643                 ClipInfo[0:4] = bytearray(struct.pack('>L',len(ClipInfo)-4))
644
645                 num_stc_sequences = 1
646                 SequenceInfo = bytearray(4)                     # len 4 bytes
647                 SequenceInfo += '\x00'                          # reserved
648                 SequenceInfo += '\x01'                          # num_atc_sequences
649                 SequenceInfo += '\x00\x00\x00\x00'              # spn_atc_start
650                 SequenceInfo += struct.pack('B',num_stc_sequences)
651                 SequenceInfo += '\x00'                          # offset_stc_id
652                 num_of_playitems = 1
653                 for pi in range(num_of_playitems):
654                         STCEntry = bytearray()
655                         STCEntry += '\x10\x01'                  # pcr_pid #!
656                         STCEntry += '\x00\x00\x00\x00'          # spn_stc_start
657                         STCEntry += self.title.InTime           # presentation_start_time (in 45khz)
658                         STCEntry += self.title.OutTime          # presentation_end_time (in 45khz)
659                         SequenceInfo += STCEntry
660                 SequenceInfo[0:4] = struct.pack('>L',len(SequenceInfo)-4)
661
662                 num_program_sequences = 1
663                 num_streams_in_ps = len(self.title.VideoStreams)+len(self.title.AudioStreams)
664
665                 ProgramInfo = bytearray(4)                      # len 4 bytes
666                 ProgramInfo += '\x00'                           # reserved align
667                 ProgramInfo += struct.pack('B',num_program_sequences)
668                 for psi in range(num_program_sequences):
669                         ProgramEntry = bytearray()
670                         ProgramEntry += '\x00\x00\x00\x00'      # spn_program_sequence_start
671                         ProgramEntry += '\x01\x00'              # program_map_pid
672                         ProgramEntry += struct.pack('B',num_streams_in_ps)
673                         ProgramEntry += '\x00'                  # num_groups
674                         for stream in self.title.VideoStreams+self.title.AudioStreams:
675                                 StreamEntry = bytearray()
676                                 StreamEntry += stream.pid       # stream_pid
677                                 StreamCodingInfo = bytearray('\x15')            # len 1 byte
678                                 StreamCodingInfo += stream.streamType
679                                 if stream.isVideo:
680                                         StreamCodingInfo += stream.formatByte   # video_format & framerate
681                                         StreamCodingInfo += stream.aspect       # aspect (4 bit) & 2 reserved & 1 oc_flag & 1 reserved
682                                         StreamCodingInfo += zeros[0:2]          #reserved
683                                 elif stream.isAudio:
684                                         StreamCodingInfo += stream.formatByte   # audio_presentation_type & samplerate
685                                         StreamCodingInfo += stream.languageCode # audio language code
686                                 for i in range(12):
687                                         StreamCodingInfo += '\x30'      # 12 byte padding with ascii char '0'
688                                 StreamCodingInfo += zeros[0:4]          # 4 byte reserved
689                                 StreamEntry += StreamCodingInfo
690                                 ProgramEntry += StreamEntry
691                         ProgramInfo += ProgramEntry
692                 ProgramInfo[0:4] = struct.pack('>L',len(ProgramInfo)-4)
693
694                 coarse_entrypoints = [] # [(presentation_time_stamp, source_packet_number, ref_ep_fine_id)]
695                 fine_entrypoints = []   # [(presentation_time_stamp, source_packet_number)]
696
697                 prev_coarse = -1
698                 prev_spn = -1
699                 fine_ref = -1
700                 for entrypoint in self.title.entrypoints:
701                         fine_entrypoints.append((entrypoint[1],entrypoint[0]))
702                         coarse_pts = entrypoint[1] & 0x1FFF80000
703                         high_spn = ( entrypoint[0] & 0xFFFE0000 )
704                         fine_ref += 1
705
706                         if coarse_pts > prev_coarse or high_spn > prev_spn:
707                                 coarse_entrypoints.append((entrypoint[1],entrypoint[0],fine_ref))
708                         prev_coarse = coarse_pts
709                         prev_spn = high_spn
710
711                 CPI = bytearray(4)              # len 4 bytes
712                 CPI += '\x00\x01'               # reserved_align & cpi_type = ep_map
713                 EP_MAP = bytearray('\x00')      # reserved_align
714                 num_stream_pid = len(self.title.VideoStreams)
715                 EP_MAP += struct.pack('B',num_stream_pid)
716
717                 for stream in self.title.VideoStreams:
718                         EP_STREAMS = bytearray()
719                         EP_STREAMS += stream.pid
720
721                         ap_stream_type = 1
722                         num_ep_coarse = len(coarse_entrypoints)
723                         num_ep_fine = len(fine_entrypoints)
724                         ep_bits = ((num_ep_fine & 0x3FFFF) + ((num_ep_coarse & 0xFFFF) << 0x12) + ((ap_stream_type & 0xF) << 0x22))
725                         # 10 bits align, 4 bits ap_stream_type, 16 bits number_ep_coarse, 18 bits number_ep_fine
726                         EP_STREAMS += struct.pack('>3H',((ep_bits&0xFFFF00000000)>>0x20),((ep_bits&0xFFFF0000)>>0x10),ep_bits&0xFFFF)
727                         EP_STREAMS += '\x00\x00\x00\x0e'        # ep_map_stream_start_addr
728                         EP_MAP += EP_STREAMS
729
730                 EP_MAP_STREAM = ""
731                 for stream in self.title.VideoStreams:
732                         EP_MAP_STREAM = bytearray(4)    # len
733                         for ep in coarse_entrypoints:
734                                 EP_MAP_STREAM_COARSE = bytearray()
735
736                                 pts_ep_coarse = ( ep[0] & 0x1FFF80000 ) >> 19
737                                 spn_ep_coarse = ep[1]
738                                 ref_ep_fine_id = ep[2]
739
740                                 coarse_bits = (pts_ep_coarse & 0x3FFF) + ((ref_ep_fine_id & 0x3FFFF) << 0xE)
741
742                                 EP_MAP_STREAM_COARSE += struct.pack('>L',coarse_bits)
743                                 EP_MAP_STREAM_COARSE += struct.pack('>L',spn_ep_coarse)
744
745                                 EP_MAP_STREAM += EP_MAP_STREAM_COARSE
746
747                         EP_MAP_STREAM[0:4] = struct.pack('>L',len(EP_MAP_STREAM))
748
749                         for ep in fine_entrypoints:
750                                 EP_MAP_STREAM_FINE = bytearray()
751
752                                 is_angle_change_point = 0
753                                 i_end_position_offset = 1
754
755                                 pts_ep_fine = ( ep[0] & 0xFFE00 ) >> 0x09
756                                 spn_ep_fine = ( ep[1] & 0x1FFFF )
757
758                                 fine_bits = (spn_ep_fine & 0x1FFFF) + ((pts_ep_fine & 0x7FF) << 0x11) + (i_end_position_offset << 0x1C) + (is_angle_change_point << 0x1F)
759
760                                 EP_MAP_STREAM_FINE += struct.pack('>L',fine_bits)
761
762                                 EP_MAP_STREAM += EP_MAP_STREAM_FINE
763
764                 EP_MAP += EP_MAP_STREAM
765                 CPI += EP_MAP
766                 CPI[0:4] = struct.pack('>L',len(CPI)-4)
767
768                 clpibuffer += ClipInfo
769                 while len(clpibuffer) < 0xDC:
770                         clpibuffer += '\x30'    # insert padding
771                 clpibuffer += SequenceInfo
772                 while len(clpibuffer) < 0xF6:
773                         clpibuffer += '\x30'    # insert padding
774                 clpibuffer += ProgramInfo
775                 while len(clpibuffer) < 0x134:
776                         clpibuffer += '\x30'    # insert padding
777                 clpibuffer[0x10:0x14] = struct.pack('>L',len(clpibuffer)) #cpi_start_address
778
779                 clpibuffer += CPI
780                 clpibuffer[0x14:0x18] = struct.pack('>L',len(clpibuffer)) #clip_mark_start_address
781                 clpibuffer += zeros[0:4]
782
783                 f = open(self.job.workspace+"BDMV/CLIPINF/%05d.clpi" % self.clip_num, 'w')
784                 f.write(buffer(clpibuffer))
785                 f.close()
786                 f = open(self.job.workspace+"BDMV/BACKUP/CLIPINF/%05d.clpi" % self.clip_num, 'w')
787                 f.write(buffer(clpibuffer))
788                 f.close()
789
790 class CopyThumbTask(Task):
791         def __init__(self, job, sourcefile, title_no):
792                 Task.__init__(self, job, "Copy thumbnail")
793                 self.setTool("cp")
794                 source = sourcefile.rsplit('.',1)[0] + ".png"
795                 dest = self.job.workspace+'BDMV/META/DL/thumb_%05d.png' % title_no
796                 self.args += [source, dest]
797                 self.weighting = 10
798
799 class CreateMetaTask(Task):
800         def __init__(self, job, project):
801                 Task.__init__(self, job, "Create BDMV Meta Info Files")
802                 self.project = project
803                 self.job = job
804                 self.weighting = 10
805                 self.postconditions.append(GenericPostcondition())
806                 self.languageCode = iso639language.get_dvd_id(self.project.menutemplate.settings.menulang.getValue())
807
808         def run(self, callback):
809                 self.callback = callback
810                 self.conduct()
811
812         def conduct(self):
813                 from Tools.XMLTools import stringToXML
814                 dl = ['<?xml version="1.0" encoding="utf-8" ?>']
815                 dl.append('<disclib xmlns="urn:BDA:bdmv;disclib" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:BDA:bdmv;disclib disclib.xsd">')
816                 dl.append('\t<di:discinfo xmlns:di="urn:BDA:bdmv;discinfo">')
817                 dl.append(strftime("\t<di:date>%Y-%m-%d</di:date>"))
818                 dl.append('\t\t<di:creator>Dream Multimedia Enigma2</di:creator>')
819                 dl.append('\t\t<di:title>')
820                 dl.append('\t\t\t<di:name>'+stringToXML(self.project.settings.name.value)+'</di:name>')
821                 dl.append('\t\t\t<di:numSets>1</di:numSets>')
822                 dl.append('\t\t\t<di:setNumber>1</di:setNumber>')
823                 dl.append('\t\t</di:title>')
824                 dl.append('\t\t<di:description>')
825                 dl.append('\t\t\t<di:tableOfContents>')
826                 for title_no, title in enumerate(self.job.titles):
827                         dl.append('\t\t\t\t<di:titleName titleNumber="%d">%s</di:titleName>' % (title_no, stringToXML(title.properties.menutitle.value)))
828                 dl.append('\t\t\t</di:tableOfContents>')
829                 for title_no in range(len(self.job.titles)):
830                         dl.append('\t\t\t<di:thumbnail href="thumb_%05d.png" />' % title_no)
831                 dl.append('\t\t</di:description>')
832                 dl.append('\t\t<di:language>'+stringToXML(self.languageCode)+'</di:language>')
833                 dl.append('\t</di:discinfo>')
834                 dl.append('</disclib>')
835
836                 filename = self.job.workspace+'BDMV/META/DL/bdmt_%s.xml' % self.languageCode
837                 try:    
838                         file = open(filename, "w")
839                         for line in dl:
840                                 file.write(line+'\n')
841                         file.close()
842                 except:
843                         Task.processFinished(self, 1)
844                 Task.processFinished(self, 0)
845                 self.project.finished_burning = True
846
847 class BDMVJob(Job):
848         def __init__(self, project):
849                 Job.__init__(self, "Bludisc Burn")
850                 self.project = project
851                 new_workspace = resolveFilename(SCOPE_HDD) + "tmp/" + strftime("bludisc_%Y%m%d%H%M/")
852                 createDir(new_workspace, True)
853                 self.workspace = new_workspace
854                 self.project.workspace = self.workspace
855                 self.titles = []
856                 for title in self.project.titles:
857                         self.titles.append(BludiscTitle(title)) # wrap original DVD-Title into new BludiscTitle objects
858                 self.conduct()
859
860         def conduct(self):
861                 if self.project.settings.output.getValue() == "iso":
862                         CheckDiskspaceTask(self)
863                 CreateStructureTask(self)
864                 for i, title in enumerate(self.titles):
865                         RemuxTask(self, title, i)
866                 CreateIndexTask(self)
867                 CreateMobjTask(self)
868                 for i, title in enumerate(self.titles):
869                         CreateMplsTask(self, title, i)
870                         CreateClpiTask(self, title, i)
871                         CopyThumbTask(self, title.inputfile, i)
872                 CreateMetaTask(self, self.project)
873                 output = self.project.settings.output.getValue()
874                 volName = self.project.settings.name.getValue()
875                 tool = "growisofs"
876                 if output == "medium":
877                         self.name = _("Burn Bludisc")
878                         burnargs = [ "-Z", "/dev/" + harddiskmanager.getCD(), "-dvd-compat", "-use-the-force-luke=tty"]
879                 elif output == "iso":
880                         tool = "genisoimage"
881                         self.name = _("Create Bludisc ISO file")
882                         isopathfile = getISOfilename(self.project.settings.isopath.getValue(), volName)
883                         burnargs = [ "-o", isopathfile ]
884                 burnargs += [ "-udf", "-allow-limited-size", "-publisher", "Dreambox", "-V", volName, self.workspace ]
885                 BurnTask(self, burnargs, tool)
886                 RemoveWorkspaceFolder(self)