MythTV  master
mythburn.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 
4 # python 3 doesn't have a unicode type
5 try:
6  unicode
7 except:
8  unicode = str
9 
10 # python 3 doesn't have a long type
11 try:
12  long
13 except:
14  long = int
15 
16 
17 # mythburn.py
18 # The ported MythBurn scripts which feature:
19 
20 # Burning of recordings (including HDTV) and videos
21 # of ANY format to DVDR. Menus are created using themes
22 # and are easily customised.
23 
24 # See mydata.xml for format of input file
25 
26 # spit2k1
27 # 11 January 2006
28 # 6 Feb 2006 - Added into CVS for the first time
29 
30 # paulh
31 # 4 May 2006 - Added into mythtv svn
32 
33 # For this script to work you need to have...
34 # Python - v2.6 or later
35 # mythtv python bindings installed
36 # python-imaging (PIL) or Pillow the fork of PIL
37 # dvdauthor - v0.6.14
38 # dvd+rw-tools - v7.1
39 # cdrtools - v3.01
40 
41 # Optional for shrink-to-fit requantisation
42 # M2VRequantiser (from flexion, based on the newer code from Metakine)
43 
44 # Optional (for Right To Left languages)
45 # pyfribidi
46 
47 # Optional (alternate demuxer)
48 # ProjectX - >=0.91
49 
50 # Optional (to lower the ionice level)
51 # psutil
52 
53 #******************************************************************************
54 #******************************************************************************
55 #******************************************************************************
56 
57 
58 # All strings in this file should be unicode, not byte string!! They get converted to utf-8 only
59 
60 
61 
62 
63 # version of script - change after each update
64 VERSION="0.2.20200122-1"
65 
66 # keep all temporary files for debugging purposes
67 # set this to True before a first run through when testing
68 # out new themes (see below)
69 debug_keeptempfiles = False
70 
71 
77 debug_secondrunthrough = False
78 
79 # default encoding profile to use
80 defaultEncodingProfile = "SP"
81 
82 # add audio sync offset when re-muxing
83 useSyncOffset = True
84 
85 # if the theme doesn't have a chapter menu and this is set to true then the
86 # chapter marks will be set to the cut point end marks
87 addCutlistChapters = False
88 
89 # change this to True to always convert any audio tracks to ac3 for better compatibility
90 encodetoac3 = False
91 
92 #*********************************************************************************
93 #Dont change the stuff below!!
94 #*********************************************************************************
95 import os
96 import sys
97 import string
98 import codecs
99 import getopt
100 import traceback
101 import signal
102 import xml.dom.minidom
103 from PIL import Image
104 from PIL import ImageDraw
105 from PIL import ImageFont
106 from PIL import ImageColor
107 import unicodedata
108 import time
109 import tempfile
110 from fcntl import ioctl
111 
112 try:
113  import CDROM
114 except:
115  # Some hardcoded values for ioctl calls,
116  # not available on python > 3.5, see include/linux/cdrom.h
117  class CDROM(object):
118  CDS_NO_INFO = 0
119  CDS_NO_DISC = 1
120  CDS_TRAY_OPEN = 2
121  CDS_DRIVE_NOT_READY = 3
122  CDS_DISC_OK = 4
123  CDROMEJECT = 0x5309
124  CDROMRESET = 0x5312
125  CDROM_DRIVE_STATUS = 0x5326
126  CDROM_LOCKDOOR = 0x5329
127 
128 from shutil import copy
129 
130 import MythTV
131 from MythTV import datetime
132 from MythTV.altdict import OrdDict
133 
134 # media types (should match the enum in mytharchivewizard.h)
135 DVD_SL = 0
136 DVD_DL = 1
137 DVD_RW = 2
138 FILE = 3
139 
140 dvdPAL=(720,576)
141 dvdNTSC=(720,480)
142 dvdPALdpi=(75,80)
143 dvdNTSCdpi=(81,72)
144 
145 dvdPALHalfD1="352x576"
146 dvdNTSCHalfD1="352x480"
147 dvdPALD1="%sx%s" % (dvdPAL[0],dvdPAL[1])
148 dvdNTSCD1="%sx%s" % (dvdNTSC[0],dvdNTSC[1])
149 
150 #Single and dual layer recordable DVD free space in MBytes
151 dvdrsize=(4482,8106)
152 
153 frameratePAL=25
154 framerateNTSC=29.97
155 
156 #any aspect ratio above this value is assumed to be 16:9
157 aspectRatioThreshold = 1.4
158 
159 #Just blank globals at startup
160 temppath=""
161 logpath=""
162 scriptpath=""
163 sharepath=""
164 videopath=""
165 defaultsettings=""
166 videomode=""
167 gallerypath=""
168 musicpath=""
169 dateformat=""
170 timeformat=""
171 dbVersion=""
172 preferredlang1=""
173 preferredlang2=""
174 useFIFO = True
175 alwaysRunMythtranscode = False
176 copyremoteFiles = False
177 thumboffset = 10
178 usebookmark = True
179 clearArchiveTable = True
180 nicelevel = 17;
181 drivespeed = 0;
182 
183 #main menu aspect ratio (4:3 or 16:9)
184 mainmenuAspectRatio = "16:9"
185 
186 #chapter menu aspect ratio (4:3, 16:9 or Video)
187 #video means same aspect ratio as the video title
188 chaptermenuAspectRatio = "Video"
189 
190 #default chapter length in seconds
191 chapterLength = 5 * 60;
192 
193 #name of the default job file
194 jobfile="mydata.xml"
195 
196 #progress log filename and file object
197 progresslog = ""
198 progressfile = codecs.open("/dev/null", 'w', 'utf-8')
199 
200 #default location of DVD drive
201 dvddrivepath = "/dev/dvd"
202 
203 #default option settings
204 docreateiso = False
205 doburn = True
206 erasedvdrw = False
207 mediatype = DVD_SL
208 savefilename = ''
209 
210 installPrefix = ""
211 
212 # job xml file
213 jobDOM = None
214 
215 # theme xml file
216 themeDOM = None
217 themeName = ''
218 
219 #dictionary of font definitions used in theme
220 themeFonts = {}
221 
222 # no. of processors we have access to
223 cpuCount = 1
224 
225 DB = MythTV.MythDB()
226 MVID = MythTV.MythVideo(db=DB)
227 Video = MythTV.Video
228 
229 configHostname = DB.gethostname()
230 
231 
232 
233 # mytharchivehelper needes this locale to work correctly
234 try:
235  oldlocale = os.environ["LC_ALL"]
236 except:
237  oldlocale = ""
238 os.putenv("LC_ALL", "en_US.UTF-8")
239 
240 
241 # fix rtl text where pyfribidi is not available
242 # should write a simple algorithm, meanwhile just return the original string
243 def simple_fix_rtl(str):
244  return str
245 
246 # Bind the name fix_rtl to the appropriate function
247 try:
248  import pyfribidi
249 except ImportError:
250  sys.stdout.write("Using simple_fix_rtl\n")
251  fix_rtl = simple_fix_rtl
252 else:
253  sys.stdout.write("Using pyfribidi.log2vis\n")
254  fix_rtl = pyfribidi.log2vis
255 
256 
258 
259 class FontDef(object):
260  def __init__(self, name=None, fontFile=None, size=19, color="white", effect="normal", shadowColor="black", shadowSize=1):
261  self.name = name
262  self.fontFile = fontFile
263  self.size = size
264  self.color = color
265  self.effect = effect
266  self.shadowColor = shadowColor
267  self.shadowSize = shadowSize
268  self.font = None
269 
270  def getFont(self):
271  if self.font is None:
272  self.font = ImageFont.truetype(self.fontFile, int(self.size))
273 
274  return self.font
275 
276  def drawText(self, text, color=None):
277  if self.font is None:
278  self.font = ImageFont.truetype(self.fontFile, int(self.size))
279 
280  if color is None:
281  color = self.color
282 
283  textwidth, textheight = self.font.getsize(text)
284 
285  image = Image.new("RGBA", (textwidth + (self.shadowSize * 2), textheight), (0,0,0,0))
286  draw = ImageDraw.ImageDraw(image)
287 
288  if self.effect == "shadow":
289  draw.text((self.shadowSize,self.shadowSize), text, font=self.font, fill=self.shadowColor)
290  draw.text((0,0), text, font=self.font, fill=color)
291  elif self.effect == "outline":
292  for x in range(0, self.shadowSize * 2 + 1):
293  for y in range(0, self.shadowSize * 2 + 1):
294  draw.text((x, y), text, font=self.font, fill=self.shadowColor)
295 
296  draw.text((self.shadowSize,self.shadowSize), text, font=self.font, fill=color)
297  else:
298  draw.text((0,0), text, font=self.font, fill=color)
299 
300  bbox = image.getbbox()
301  image = image.crop(bbox)
302  return image
303 
304 
306 
307 def write(text, progress=True):
308  """Simple place to channel all text output through"""
309 
310  if sys.version_info == 2:
311  sys.stdout.write((text + "\n").encode("utf-8", "replace"))
312  else:
313  sys.stdout.write(text + "\n")
314  sys.stdout.flush()
315 
316  if progress == True and progresslog != "":
317  progressfile.write(time.strftime("%Y-%m-%d %H:%M:%S ") + text + "\n")
318  progressfile.flush()
319 
320 
322 
323 def fatalError(msg):
324  """Display an error message and exit app"""
325  write("*"*60)
326  write("ERROR: " + msg)
327  write("See mythburn.log for more information.")
328  write("*"*60)
329  write("")
330  saveSetting("MythArchiveLastRunResult", "Failed: " + quoteString(msg));
331  saveSetting("MythArchiveLastRunEnd", time.strftime("%Y-%m-%d %H:%M:%S "))
332  sys.exit(0)
333 
334 # ###########################################################
335 # Display a warning message
336 
337 def nonfatalError(msg):
338  """Display a warning message"""
339  write("*"*60)
340  write("WARNING: " + msg)
341  write("*"*60)
342  write("")
343 
344 
346 
347 def quoteString(str):
348  """Return the input string with single quotes escaped."""
349  return str.replace("'", "'\"'\"'")
350 
351 
353 
355  """This is the folder where all temporary files will be created."""
356  return temppath
357 
358 
360 
362  """return the number of CPUs"""
363  # /proc/cpuinfo
364  cpustat = codecs.open("/proc/cpuinfo", 'r', 'utf-8')
365  cpudata = cpustat.readlines()
366  cpustat.close()
367 
368  cpucount = 0
369  for line in cpudata:
370  tokens = line.split()
371  if len(tokens) > 0:
372  if tokens[0] == "processor":
373  cpucount += 1
374 
375  if cpucount == 0:
376  cpucount = 1
377 
378  write("Found %d CPUs" % cpucount)
379 
380  return cpucount
381 
382 
384 
386  """This is the folder where all encoder profile files are located."""
387  return os.path.join(sharepath, "mytharchive", "encoder_profiles")
388 
389 
391 
392 def doesFileExist(file):
393  """Returns true/false if a given file or path exists."""
394  return os.path.exists( file )
395 
396 
398 
399 def quoteCmdArg(arg):
400  arg = arg.replace('"', '\\"')
401  arg = arg.replace('`', '\\`')
402  return '"%s"' % arg
403 
404 
406 
407 def getText(node):
408  """Returns the text contents from a given XML element."""
409  if node.childNodes.length>0:
410  return node.childNodes[0].data
411  else:
412  return ""
413 
414 
416 
417 def getThemeFile(theme,file):
418  """Find a theme file - first look in the specified theme directory then look in the
419  shared music and image directories"""
420  if os.path.exists(os.path.join(sharepath, "mytharchive", "themes", theme, file)):
421  return os.path.join(sharepath, "mytharchive", "themes", theme, file)
422 
423  if os.path.exists(os.path.join(sharepath, "mytharchive", "images", file)):
424  return os.path.join(sharepath, "mytharchive", "images", file)
425 
426  if os.path.exists(os.path.join(sharepath, "mytharchive", "intro", file)):
427  return os.path.join(sharepath, "mytharchive", "intro", file)
428 
429  if os.path.exists(os.path.join(sharepath, "mytharchive", "music", file)):
430  return os.path.join(sharepath, "mytharchive", "music", file)
431 
432  fatalError("Cannot find theme file '%s' in theme '%s'" % (file, theme))
433 
434 
436 
437 def getFontPathName(fontname):
438  return os.path.join(sharepath, "fonts", fontname)
439 
440 
442 
443 def getItemTempPath(itemnumber):
444  return os.path.join(getTempPath(),"%s" % itemnumber)
445 
446 
448 
449 def validateTheme(theme):
450  #write( "Checking theme", theme
451  file = getThemeFile(theme,"theme.xml")
452  write("Looking for: " + file)
453  return doesFileExist( getThemeFile(theme,"theme.xml") )
454 
455 
457 
458 def isResolutionOkayForDVD(videoresolution):
459  if videomode=="ntsc":
460  return videoresolution==(720,480) or videoresolution==(704,480) or videoresolution==(352,480) or videoresolution==(352,240)
461  else:
462  return videoresolution==(720,576) or videoresolution==(704,576) or videoresolution==(352,576) or videoresolution==(352,288)
463 
464 
466 
468  """Does what it says on the tin!."""
469  for root, dirs, deletefiles in os.walk(folder, topdown=False):
470  for name in deletefiles:
471  os.remove(os.path.join(root, name))
472 
473 
475 
477  for root, dirs, files in os.walk(folder, topdown=False):
478  for name in files:
479  os.remove(os.path.join(root, name))
480  for name in dirs:
481  if os.path.islink(os.path.join(root, name)):
482  os.remove(os.path.join(root, name))
483  else:
484  os.rmdir(os.path.join(root, name))
485 
486 
488 
490  """Checks to see if the user has cancelled this run"""
491  if os.path.exists(os.path.join(logpath, "mythburncancel.lck")):
492  os.remove(os.path.join(logpath, "mythburncancel.lck"))
493  write('*'*60)
494  write("Job has been cancelled at users request")
495  write('*'*60)
496  sys.exit(1)
497 
498 
501 
502 def runCommand(command):
504 
505  result = os.system(command.encode('utf-8'))
506 
507  if os.WIFEXITED(result):
508  result = os.WEXITSTATUS(result)
510  return result
511 
512 
514 
515 def secondsToFrames(seconds):
516  """Convert a time in seconds to a frame position"""
517  if videomode=="pal":
518  framespersecond=frameratePAL
519  else:
520  framespersecond=framerateNTSC
521 
522  frames=int(seconds * framespersecond)
523  return frames
524 
525 
527 
528 def encodeMenu(background, tempvideo, music, musiclength, tempmovie, xmlfile, finaloutput, aspectratio):
529  if videomode=="pal":
530  framespersecond=frameratePAL
531  else:
532  framespersecond=framerateNTSC
533 
534  totalframes=int(musiclength * framespersecond)
535 
536  command = quoteCmdArg(path_jpeg2yuv[0]) + " -n %s -v0 -I p -f %s -j %s | %s -b 5000 -a %s -v 1 -f 8 -o %s" \
537  % (totalframes, framespersecond, quoteCmdArg(background), quoteCmdArg(path_mpeg2enc[0]), aspectratio, quoteCmdArg(tempvideo))
538  result = runCommand(command)
539  if result!=0:
540  fatalError("Failed while running jpeg2yuv - %s" % command)
541 
542  command = quoteCmdArg(path_mplex[0]) + " -f 8 -v 0 -o %s %s %s" % (quoteCmdArg(tempmovie), quoteCmdArg(tempvideo), quoteCmdArg(music))
543  result = runCommand(command)
544  if result!=0:
545  fatalError("Failed while running mplex - %s" % command)
546 
547  if xmlfile != "":
548  command = quoteCmdArg(path_spumux[0]) + " -m dvd -s 0 %s < %s > %s" % (quoteCmdArg(xmlfile), quoteCmdArg(tempmovie), quoteCmdArg(finaloutput))
549  result = runCommand(command)
550  if result!=0:
551  fatalError("Failed while running spumux - %s" % command)
552  else:
553  os.rename(tempmovie, finaloutput)
554 
555  if os.path.exists(tempvideo):
556  os.remove(tempvideo)
557  if os.path.exists(tempmovie):
558  os.remove(tempmovie)
559 
560 
563 
564 def findEncodingProfile(profile):
565  """Returns the XML node for the given encoding profile"""
566 
567  # which encoding file do we need
568 
569  # first look for a custom profile file in ~/.mythtv/MythArchive/
570  if videomode == "ntsc":
571  filename = os.path.expanduser("~/.mythtv/MythArchive/ffmpeg_dvd_ntsc.xml")
572  else:
573  filename = os.path.expanduser("~/.mythtv/MythArchive/ffmpeg_dvd_pal.xml")
574 
575  if not os.path.exists(filename):
576  # not found so use the default profiles
577  if videomode == "ntsc":
578  filename = getEncodingProfilePath() + "/ffmpeg_dvd_ntsc.xml"
579  else:
580  filename = getEncodingProfilePath() + "/ffmpeg_dvd_pal.xml"
581 
582  write("Using encoder profiles from %s" % filename)
583 
584  DOM = xml.dom.minidom.parse(filename)
585 
586  #Error out if its the wrong XML
587  if DOM.documentElement.tagName != "encoderprofiles":
588  fatalError("Profile xml file doesn't look right (%s)" % filename)
589 
590  profiles = DOM.getElementsByTagName("profile")
591  for node in profiles:
592  if getText(node.getElementsByTagName("name")[0]) == profile:
593  write("Encoding profile (%s) found" % profile)
594  return node
595 
596  write('WARNING: Encoding profile "' + profile + '" not found.')
597  write('Using default profile "' + defaultEncodingProfile + '".')
598  for node in profiles:
599  if getText(node.getElementsByTagName("name")[0]) == defaultEncodingProfile:
600  write("Encoding profile (%s) found" % defaultEncodingProfile)
601  return node
602 
603  fatalError('Neither encoding profile "' + profile + '" nor default enocding profile "' + defaultEncodingProfile + '" found. Giving up.')
604  return None
605 
606 
608 
610  """Loads the XML file from disk for a specific theme"""
611 
612  #Load XML input file from disk
613  themeDOM = xml.dom.minidom.parse( getThemeFile(theme,"theme.xml") )
614  #Error out if its the wrong XML
615  if themeDOM.documentElement.tagName != "mythburntheme":
616  fatalError("Theme xml file doesn't look right (%s)" % theme)
617  return themeDOM
618 
619 
621 
622 def getLengthOfVideo(index):
623  """Returns the length of a video file (in seconds)"""
624 
625  #open the XML containing information about this file
626  infoDOM = xml.dom.minidom.parse(os.path.join(getItemTempPath(index), 'streaminfo.xml'))
627 
628  #error out if its the wrong XML
629  if infoDOM.documentElement.tagName != "file":
630  fatalError("Stream info file doesn't look right (%s)" % os.path.join(getItemTempPath(index), 'streaminfo.xml'))
631  file = infoDOM.getElementsByTagName("file")[0]
632  if file.attributes["cutduration"].value != 'N/A':
633  duration = int(file.attributes["cutduration"].value)
634  else:
635  duration = 0;
636 
637  return duration
638 
639 
642 
643 def getAudioParams(folder):
644  """Returns the audio bitrate and no of channels for a file from its streaminfo.xml"""
645 
646  #open the XML containing information about this file
647  infoDOM = xml.dom.minidom.parse(os.path.join(folder, 'streaminfo.xml'))
648 
649  #error out if its the wrong XML
650  if infoDOM.documentElement.tagName != "file":
651  fatalError("Stream info file doesn't look right (%s)" % os.path.join(folder, 'streaminfo.xml'))
652  audio = infoDOM.getElementsByTagName("file")[0].getElementsByTagName("streams")[0].getElementsByTagName("audio")[0]
653 
654  samplerate = audio.attributes["samplerate"].value
655  channels = audio.attributes["channels"].value
656 
657  return (samplerate, channels)
658 
659 
662 
663 def getVideoParams(folder):
664  """Returns the video resolution, fps and aspect ratio for the video file from the streaminfo.xml file"""
665 
666  #open the XML containing information about this file
667  infoDOM = xml.dom.minidom.parse(os.path.join(folder, 'streaminfo.xml'))
668 
669  #error out if its the wrong XML
670  if infoDOM.documentElement.tagName != "file":
671  fatalError("Stream info file doesn't look right (%s)" % os.path.join(getItemTempPath(index), 'streaminfo.xml'))
672  video = infoDOM.getElementsByTagName("file")[0].getElementsByTagName("streams")[0].getElementsByTagName("video")[0]
673 
674  if video.attributes["aspectratio"].value != 'N/A':
675  aspect_ratio = video.attributes["aspectratio"].value
676  else:
677  aspect_ratio = "1.77778"
678 
679  videores = video.attributes["width"].value + 'x' + video.attributes["height"].value
680  fps = video.attributes["fps"].value
681 
682  #sanity check the fps
683  if videomode=="pal":
684  fr=frameratePAL
685  else:
686  fr=framerateNTSC
687 
688  if float(fr) != float(fps):
689  write("WARNING: frames rates do not match")
690  write("The frame rate for %s should be %s but the stream info file "
691  "report a fps of %s" % (videomode, fr, fps))
692  fps = fr
693 
694  return (videores, fps, aspect_ratio)
695 
696 
698 
700  """Returns the aspect ratio of the video file (1.333, 1.778, etc)"""
701 
702  #open the XML containing information about this file
703  infoDOM = xml.dom.minidom.parse(os.path.join(getItemTempPath(index), 'streaminfo.xml'))
704 
705  #error out if its the wrong XML
706  if infoDOM.documentElement.tagName != "file":
707  fatalError("Stream info file doesn't look right (%s)" % os.path.join(getItemTempPath(index), 'streaminfo.xml'))
708  video = infoDOM.getElementsByTagName("file")[0].getElementsByTagName("streams")[0].getElementsByTagName("video")[0]
709  if video.attributes["aspectratio"].value != 'N/A':
710  aspect_ratio = float(video.attributes["aspectratio"].value)
711  else:
712  aspect_ratio = 1.77778; # default
713  write("aspect ratio is: %s" % aspect_ratio)
714  return aspect_ratio
715 
716 
718 
719 def calcSyncOffset(index):
720  """Returns the sync offset between the video and first audio stream"""
721 
722  #open the XML containing information about this file
723  #infoDOM = xml.dom.minidom.parse(os.path.join(getItemTempPath(index), 'streaminfo_orig.xml'))
724  infoDOM = xml.dom.minidom.parse(os.path.join(getItemTempPath(index), 'streaminfo.xml'))
725 
726  #error out if its the wrong XML
727  if infoDOM.documentElement.tagName != "file":
728  fatalError("Stream info file doesn't look right (%s)" % os.path.join(getItemTempPath(index), 'streaminfo_orig.xml'))
729 
730  video = infoDOM.getElementsByTagName("file")[0].getElementsByTagName("streams")[0].getElementsByTagName("video")[0]
731  video_start = float(video.attributes["start_time"].value)
732 
733  audio = infoDOM.getElementsByTagName("file")[0].getElementsByTagName("streams")[0].getElementsByTagName("audio")[0]
734  audio_start = float(audio.attributes["start_time"].value)
735 
736 # write("Video start time is: %s" % video_start)
737 # write("Audio start time is: %s" % audio_start)
738 
739  sync_offset = int((video_start - audio_start) * 1000)
740 
741 # write("Sync offset is: %s" % sync_offset)
742  return sync_offset
743 
744 
746 
748  duration = getLengthOfVideo(index)
749 
750  minutes = int(duration / 60)
751  seconds = duration % 60
752  hours = int(minutes / 60)
753  minutes %= 60
754 
755  return '%02d:%02d:%02d' % (hours, minutes, seconds)
756 
757 
759 
760 def frameToTime(frame, fps):
761  sec = int(frame / fps)
762  frame = frame - int(sec * fps)
763  mins = sec // 60
764  sec %= 60
765  hour = mins // 60
766  mins %= 60
767 
768  return '%02d:%02d:%02d' % (hour, mins, sec)
769 
770 
772 
773 def timeStringToSeconds(formatedtime):
774  parts = formatedtime.split(':')
775  if len(parts) != 3:
776  return 0
777 
778  sec = int(parts[2])
779  mins = int(parts[1])
780  hour = int(parts[0])
781 
782  return sec + (mins * 60) + (hour * 60 * 60)
783 
784 
787 
788 def createVideoChapters(itemnum, numofchapters, lengthofvideo, getthumbnails):
789  """Returns numofchapters chapter marks even spaced through a certain time period"""
790 
791  # if there are user defined thumb images already available use them
792  infoDOM = xml.dom.minidom.parse(os.path.join(getItemTempPath(itemnum),"info.xml"))
793  thumblistNode = infoDOM.getElementsByTagName("thumblist")
794  if thumblistNode.length > 0:
795  thumblist = getText(thumblistNode[0])
796  write("Using user defined thumb images - %s" % thumblist)
797  return thumblist
798 
799  # no user defined thumbs so create them
800  segment=int(lengthofvideo / numofchapters)
801 
802  write( "Video length is %s seconds. Each chapter will be %s seconds" % (lengthofvideo,segment))
803 
804  chapters=[]
805 
806  thumbList=[]
807  starttime=0
808  count=1
809  while count<=numofchapters:
810  chapters.append(time.strftime("%H:%M:%S",time.gmtime(starttime)))
811 
812  if starttime==0:
813  if thumboffset < segment:
814  thumbList.append(str(thumboffset))
815  else:
816  thumbList.append(str(starttime))
817  else:
818  thumbList.append(str(starttime))
819 
820  starttime+=segment
821  count+=1
822 
823  chapters = ','.join(chapters)
824  thumbList = ','.join(thumbList)
825 
826  if getthumbnails==True:
827  extractVideoFrames( os.path.join(getItemTempPath(itemnum),"stream.mv2"),
828  os.path.join(getItemTempPath(itemnum),"chapter-%1.jpg"), thumbList)
829 
830  return chapters
831 
832 
834 
835 def createVideoChaptersFixedLength(itemnum, segment, lengthofvideo):
836  """Returns chapter marks at cut list ends,
837  or evenly spaced chapters 'segment' seconds through the file"""
838 
839 
840  if addCutlistChapters == True:
841  # we've been asked to use the cut list as chapter marks
842  # so if there is a cut list available, use it
843 
844  infoDOM = xml.dom.minidom.parse(os.path.join(getItemTempPath(itemnum),"info.xml"))
845  chapterlistNode = infoDOM.getElementsByTagName("chapterlist")
846  if chapterlistNode.length > 0:
847  chapterlist = getText(chapterlistNode[0])
848  write("Using commercial end marks - %s" % chapterlist)
849  return chapterlist
850 
851  if lengthofvideo < segment:
852  return "00:00:00"
853 
854  numofchapters = lengthofvideo // segment + 1;
855  chapters = "00:00:00"
856  starttime = 0
857  count = 2
858  while count <= numofchapters:
859  starttime += segment
860  chapters += "," + time.strftime("%H:%M:%S", time.gmtime(starttime))
861  count += 1
862 
863  write("Fixed length chapters: %s" % chapters)
864 
865  return chapters
866 
867 
869 
871  """Reads settings from MythTV database"""
872 
873  write( "Obtaining MythTV settings from MySQL database for hostname " + configHostname)
874 
875  #DBSchemaVer, ISO639Language0 ISO639Language1 are not dependant upon the hostname.
876  sqlstatement="""SELECT value, data FROM settings WHERE value IN(
877  'DBSchemaVer',
878  'ISO639Language0',
879  'ISO639Language1')
880  OR (hostname=%s AND value IN(
881  'VideoStartupDir',
882  'GalleryDir',
883  'MusicLocation',
884  'MythArchiveVideoFormat',
885  'MythArchiveTempDir',
886  'MythArchiveMplexCmd',
887  'MythArchiveDvdauthorCmd',
888  'MythArchiveMkisofsCmd',
889  'MythArchiveM2VRequantiserCmd',
890  'MythArchiveMpg123Cmd',
891  'MythArchiveProjectXCmd',
892  'MythArchiveDVDLocation',
893  'MythArchiveGrowisofsCmd',
894  'MythArchiveJpeg2yuvCmd',
895  'MythArchiveSpumuxCmd',
896  'MythArchiveMpeg2encCmd',
897  'MythArchiveCopyRemoteFiles',
898  'MythArchiveAlwaysUseMythTranscode',
899  'MythArchiveUseProjectX',
900  'MythArchiveAddSubtitles',
901  'MythArchiveUseFIFO',
902  'MythArchiveMainMenuAR',
903  'MythArchiveChapterMenuAR',
904  'MythArchiveDateFormat',
905  'MythArchiveTimeFormat',
906  'MythArchiveClearArchiveTable',
907  'MythArchiveDriveSpeed',
908  'JobQueueCPU'
909  )) ORDER BY value"""
910 
911  # create a cursor
912  cursor = DB.cursor()
913  # execute SQL statement
914  cursor.execute(sqlstatement, (configHostname,))
915  # get the resultset as a tuple
916  result = cursor.fetchall()
917 
918  cfg = {}
919  for i in range(len(result)):
920  cfg[result[i][0]] = result[i][1]
921 
922  #bail out if we can't find the temp dir setting
923  if not "MythArchiveTempDir" in cfg:
924  fatalError("Can't find the setting for the temp directory. \nHave you run setup in the frontend?")
925  return cfg
926 
927 
929 
930 def saveSetting(name, data):
931  host = DB.gethostname()
932  DB.settings[host][name] = data
933 
934 
936 
938  ''' Remove all archive items from the archiveitems DB table'''
939 
940  write("Removing all archive items from the archiveitems DB table")
941  with DB as cursor:
942  cursor.execute("DELETE FROM archiveitems")
943 
944 
946 
947 def getOptions(options):
948  global doburn
949  global docreateiso
950  global erasedvdrw
951  global mediatype
952  global savefilename
953 
954  if options.length == 0:
955  fatalError("Trying to read the options from the job file but none found?")
956  options = options[0]
957 
958  doburn = options.attributes["doburn"].value != '0'
959  docreateiso = options.attributes["createiso"].value != '0'
960  erasedvdrw = options.attributes["erasedvdrw"].value != '0'
961  mediatype = int(options.attributes["mediatype"].value)
962  savefilename = options.attributes["savefilename"].value
963 
964  write("Options - mediatype = %d, doburn = %d, createiso = %d, erasedvdrw = %d" \
965  % (mediatype, doburn, docreateiso, erasedvdrw))
966  write(" savefilename = '%s'" % savefilename)
967 
968 
970 
971 def expandItemText(infoDOM, text, itemnumber, pagenumber, keynumber,chapternumber, chapterlist ):
972  """Replaces keywords in a string with variables from the XML and filesystem"""
973  text=text.replace("%page","%s" % pagenumber)
974 
975  #See if we can use the thumbnail/cover file for videos if there is one.
976  if getText( infoDOM.getElementsByTagName("coverfile")[0]) =="":
977  text=text.replace("%thumbnail", os.path.join( getItemTempPath(itemnumber), "title.jpg"))
978  else:
979  text=text.replace("%thumbnail", getText( infoDOM.getElementsByTagName("coverfile")[0]) )
980 
981  text=text.replace("%itemnumber","%s" % itemnumber )
982  text=text.replace("%keynumber","%s" % keynumber )
983 
984  text=text.replace("%title",getText( infoDOM.getElementsByTagName("title")[0]) )
985  text=text.replace("%subtitle",getText( infoDOM.getElementsByTagName("subtitle")[0]) )
986  text=text.replace("%description",getText( infoDOM.getElementsByTagName("description")[0]) )
987  text=text.replace("%type",getText( infoDOM.getElementsByTagName("type")[0]) )
988 
989  text=text.replace("%recordingdate",getText( infoDOM.getElementsByTagName("recordingdate")[0]) )
990  text=text.replace("%recordingtime",getText( infoDOM.getElementsByTagName("recordingtime")[0]) )
991 
992  text=text.replace("%duration", getFormatedLengthOfVideo(itemnumber))
993 
994  text=text.replace("%myfolder",getThemeFile(themeName,""))
995 
996  if chapternumber>0:
997  text=text.replace("%chapternumber","%s" % chapternumber )
998  text=text.replace("%chaptertime","%s" % chapterlist[chapternumber - 1] )
999  text=text.replace("%chapterthumbnail", os.path.join( getItemTempPath(itemnumber), "chapter-%s.jpg" % chapternumber))
1000 
1001  return text
1002 
1003 
1005 
1006 def getScaledAttribute(node, attribute):
1007  """ Returns a value taken from attribute in node scaled for the current video mode"""
1008 
1009  if videomode == "pal" or attribute == "x" or attribute == "w":
1010  return int(node.attributes[attribute].value)
1011  else:
1012  return int(float(node.attributes[attribute].value) / 1.2)
1013 
1014 
1016 
1017 def intelliDraw(drawer, text, font, containerWidth):
1018  """Based on http://mail.python.org/pipermail/image-sig/2004-December/003064.html"""
1019  #Args:
1020  # drawer: Instance of "ImageDraw.Draw()"
1021  # text: string of long text to be wrapped
1022  # font: instance of ImageFont (I use .truetype)
1023  # containerWidth: number of pixels text lines have to fit into.
1024 
1025  #write("containerWidth: %s" % containerWidth)
1026  words = text.split()
1027  lines = [] # prepare a return argument
1028  lines.append(words)
1029  finished = False
1030  line = 0
1031  while not finished:
1032  thistext = lines[line]
1033  newline = []
1034  innerFinished = False
1035  while not innerFinished:
1036  #write( 'thistext: '+str(thistext))
1037  #write("textWidth: %s" % drawer.textsize(' '.join(thistext),font)[0])
1038 
1039  if drawer.textsize(' '.join(thistext),font.getFont())[0] > containerWidth:
1040  # this is the heart of the algorithm: we pop words off the current
1041  # sentence until the width is ok, then in the next outer loop
1042  # we move on to the next sentence.
1043  if str(thistext).find(' ') != -1:
1044  newline.insert(0,thistext.pop(-1))
1045  else:
1046  # FIXME should truncate the string here
1047  innerFinished = True
1048  else:
1049  innerFinished = True
1050  if len(newline) > 0:
1051  lines.append(newline)
1052  line = line + 1
1053  else:
1054  finished = True
1055  tmp = []
1056  for i in lines:
1057  tmp.append( fix_rtl( ' '.join(i) ) )
1058  lines = tmp
1059  return lines
1060 
1061 
1063 
1064 def paintBackground(image, node):
1065  if node.hasAttribute("bgcolor"):
1066  bgcolor = node.attributes["bgcolor"].value
1067  x = getScaledAttribute(node, "x")
1068  y = getScaledAttribute(node, "y")
1069  w = getScaledAttribute(node, "w")
1070  h = getScaledAttribute(node, "h")
1071  r,g,b = ImageColor.getrgb(bgcolor)
1072 
1073  if node.hasAttribute("bgalpha"):
1074  a = int(node.attributes["bgalpha"].value)
1075  else:
1076  a = 255
1077 
1078  image.paste((r, g, b, a), (x, y, x + w, y + h))
1079 
1080 
1081 
1083 
1084 def paintButton(draw, bgimage, bgimagemask, node, infoDOM, itemnum, page,
1085  itemsonthispage, chapternumber, chapterlist):
1086 
1087  imagefilename = getThemeFile(themeName, node.attributes["filename"].value)
1088  if not doesFileExist(imagefilename):
1089  fatalError("Cannot find image for menu button (%s)." % imagefilename)
1090  maskimagefilename = getThemeFile(themeName, node.attributes["mask"].value)
1091  if not doesFileExist(maskimagefilename):
1092  fatalError("Cannot find mask image for menu button (%s)." % maskimagefilename)
1093 
1094  picture = Image.open(imagefilename,"r").resize(
1095  (getScaledAttribute(node, "w"), getScaledAttribute(node, "h")))
1096  picture = picture.convert("RGBA")
1097  bgimage.paste(picture, (getScaledAttribute(node, "x"),
1098  getScaledAttribute(node, "y")), picture)
1099  del picture
1100 
1101  # if we have some text paint that over the image
1102  textnode = node.getElementsByTagName("textnormal")
1103  if textnode.length > 0:
1104  textnode = textnode[0]
1105  text = expandItemText(infoDOM,textnode.attributes["value"].value,
1106  itemnum, page, itemsonthispage,
1107  chapternumber,chapterlist)
1108 
1109  if text > "":
1110  paintText(draw, bgimage, text, textnode)
1111 
1112  del text
1113 
1114  write( "Added button image %s" % imagefilename)
1115 
1116  picture = Image.open(maskimagefilename,"r").resize(
1117  (getScaledAttribute(node, "w"), getScaledAttribute(node, "h")))
1118  picture = picture.convert("RGBA")
1119  bgimagemask.paste(picture, (getScaledAttribute(node, "x"),
1120  getScaledAttribute(node, "y")),picture)
1121  #del picture
1122 
1123  # if we have some text paint that over the image
1124  textnode = node.getElementsByTagName("textselected")
1125  if textnode.length > 0:
1126  textnode = textnode[0]
1127  text = expandItemText(infoDOM, textnode.attributes["value"].value,
1128  itemnum, page, itemsonthispage,
1129  chapternumber, chapterlist)
1130  textImage = Image.new("RGBA",picture.size)
1131  textDraw = ImageDraw.Draw(textImage)
1132 
1133  if text > "":
1134  paintText(textDraw, textImage, text, textnode, "white",
1135  getScaledAttribute(node, "x") - getScaledAttribute(textnode, "x"),
1136  getScaledAttribute(node, "y") - getScaledAttribute(textnode, "y"),
1137  getScaledAttribute(textnode, "w"),
1138  getScaledAttribute(textnode, "h"))
1139 
1140  #convert the RGB image to a 1 bit image
1141  (width, height) = textImage.size
1142  for y in range(height):
1143  for x in range(width):
1144  if textImage.getpixel((x,y)) < (100, 100, 100, 255):
1145  textImage.putpixel((x,y), (0, 0, 0, 0))
1146  else:
1147  textImage.putpixel((x,y), (255, 255, 255, 255))
1148 
1149  if textnode.hasAttribute("colour"):
1150  color = textnode.attributes["colour"].value
1151  elif textnode.hasAttribute("color"):
1152  color = textnode.attributes["color"].value
1153  else:
1154  color = "white"
1155 
1156  bgimagemask.paste(color,
1157  (getScaledAttribute(textnode, "x"),
1158  getScaledAttribute(textnode, "y")),
1159  textImage)
1160 
1161  del text, textImage, textDraw
1162  del picture
1163 
1164 
1166 
1167 def paintText(draw, image, text, node, color = None,
1168  x = None, y = None, width = None, height = None):
1169  """Takes a piece of text and draws it onto an image inside a bounding box."""
1170  #The text is wider than the width of the bounding box
1171 
1172  if x is None:
1173  x = getScaledAttribute(node, "x")
1174  y = getScaledAttribute(node, "y")
1175  width = getScaledAttribute(node, "w")
1176  height = getScaledAttribute(node, "h")
1177 
1178  font = themeFonts[node.attributes["font"].value]
1179 
1180  if color is None:
1181  if node.hasAttribute("colour"):
1182  color = node.attributes["colour"].value
1183  elif node.hasAttribute("color"):
1184  color = node.attributes["color"].value
1185  else:
1186  color = None
1187 
1188  if node.hasAttribute("halign"):
1189  halign = node.attributes["halign"].value
1190  elif node.hasAttribute("align"):
1191  halign = node.attributes["align"].value
1192  else:
1193  halign = "left"
1194 
1195  if node.hasAttribute("valign"):
1196  valign = node.attributes["valign"].value
1197  else:
1198  valign = "top"
1199 
1200  if node.hasAttribute("vindent"):
1201  vindent = int(node.attributes["vindent"].value)
1202  else:
1203  vindent = 0
1204 
1205  if node.hasAttribute("hindent"):
1206  hindent = int(node.attributes["hindent"].value)
1207  else:
1208  hindent = 0
1209 
1210  lines = intelliDraw(draw, text, font, width - (hindent * 2))
1211  j = 0
1212 
1213  # work out what the line spacing should be
1214  textImage = font.drawText(lines[0])
1215  h = int(textImage.size[1] * 1.1)
1216 
1217  for i in lines:
1218  if (j * h) < (height - (vindent * 2) - h):
1219  textImage = font.drawText(i, color)
1220  write( "Wrapped text = " + i ) # encoding is done within 'write'
1221 
1222  if halign == "left":
1223  xoffset = hindent
1224  elif halign == "center" or halign == "centre":
1225  xoffset = (width // 2) - (textImage.size[0] // 2)
1226  elif halign == "right":
1227  xoffset = width - textImage.size[0] - hindent
1228  else:
1229  xoffset = hindent
1230 
1231  if valign == "top":
1232  yoffset = vindent
1233  elif valign == "center" or halign == "centre":
1234  yoffset = (height // 2) - (textImage.size[1] // 2)
1235  elif valign == "bottom":
1236  yoffset = height - textImage.size[1] - vindent
1237  else:
1238  yoffset = vindent
1239 
1240  image.paste(textImage, (x + xoffset,y + yoffset + j * h), textImage)
1241  else:
1242  write( "Wrapped text = " + i ) # encoding is done within 'write'
1243  #Move to next line
1244  j = j + 1
1245 
1246 
1248 
1249 def paintImage(filename, maskfilename, imageDom, destimage, stretch=True):
1250  """Paste the image specified in the filename into the specified image"""
1251 
1252  if not doesFileExist(filename):
1253  write("Image file (%s) does not exist" % filename)
1254  return False
1255 
1256  picture = Image.open(filename, "r")
1257  xpos = getScaledAttribute(imageDom, "x")
1258  ypos = getScaledAttribute(imageDom, "y")
1259  w = getScaledAttribute(imageDom, "w")
1260  h = getScaledAttribute(imageDom, "h")
1261  (imgw, imgh) = picture.size
1262  write("Image (%s, %s) into space of (%s, %s) at (%s, %s)" % (imgw, imgh, w, h, xpos, ypos), False)
1263 
1264  # the theme can override the default stretch behaviour
1265  if imageDom.hasAttribute("stretch"):
1266  if imageDom.attributes["stretch"].value == "True":
1267  stretch = True
1268  else:
1269  stretch = False
1270 
1271  if stretch == True:
1272  imgw = w;
1273  imgh = h;
1274  else:
1275  if float(w)/imgw < float(h)/imgh:
1276  # Width is the constraining dimension
1277  imgh = imgh*w//imgw
1278  imgw = w
1279  if imageDom.hasAttribute("valign"):
1280  valign = imageDom.attributes["valign"].value
1281  else:
1282  valign = "center"
1283 
1284  if valign == "bottom":
1285  ypos += h - imgh
1286  if valign == "center":
1287  ypos += (h - imgh)//2
1288  else:
1289  # Height is the constraining dimension
1290  imgw = imgw*h//imgh
1291  imgh = h
1292  if imageDom.hasAttribute("halign"):
1293  halign = imageDom.attributes["halign"].value
1294  else:
1295  halign = "center"
1296 
1297  if halign == "right":
1298  xpos += w - imgw
1299  if halign == "center":
1300  xpos += (w - imgw)//2
1301 
1302  write("Image resized to (%s, %s) at (%s, %s)" % (imgw, imgh, xpos, ypos), False)
1303  picture = picture.resize((imgw, imgh))
1304  picture = picture.convert("RGBA")
1305 
1306  if maskfilename != None and doesFileExist(maskfilename):
1307  maskpicture = Image.open(maskfilename, "r").resize((imgw, imgh))
1308  maskpicture = maskpicture.convert("RGBA")
1309  else:
1310  maskpicture = picture
1311 
1312  destimage.paste(picture, (xpos, ypos), maskpicture)
1313  del picture
1314  if maskfilename != None and doesFileExist(maskfilename):
1315  del maskpicture
1316 
1317  write ("Added image %s" % filename)
1318 
1319  return True
1320 
1321 
1322 
1324 
1325 def checkBoundaryBox(boundarybox, node):
1326  # We work out how much space all of our graphics and text are taking up
1327  # in a bounding rectangle so that we can use this as an automatic highlight
1328  # on the DVD menu
1329  if getText(node.attributes["static"]) == "False":
1330  if getScaledAttribute(node, "x") < boundarybox[0]:
1331  boundarybox = getScaledAttribute(node, "x"), boundarybox[1], boundarybox[2], boundarybox[3]
1332 
1333  if getScaledAttribute(node, "y") < boundarybox[1]:
1334  boundarybox = boundarybox[0], getScaledAttribute(node, "y"), boundarybox[2], boundarybox[3]
1335 
1336  if (getScaledAttribute(node, "x") + getScaledAttribute(node, "w")) > boundarybox[2]:
1337  boundarybox = boundarybox[0], boundarybox[1], getScaledAttribute(node, "x") + \
1338  getScaledAttribute(node, "w"), boundarybox[3]
1339 
1340  if (getScaledAttribute(node, "y") + getScaledAttribute(node, "h")) > boundarybox[3]:
1341  boundarybox = boundarybox[0], boundarybox[1], boundarybox[2], \
1342  getScaledAttribute(node, "y") + getScaledAttribute(node, "h")
1343 
1344  return boundarybox
1345 
1346 
1348 
1349 def loadFonts(themeDOM):
1350  global themeFonts
1351 
1352  #Find all the fonts
1353  nodelistfonts = themeDOM.getElementsByTagName("font")
1354 
1355  fontnumber = 0
1356  for node in nodelistfonts:
1357  filename = getText(node)
1358 
1359  if node.hasAttribute("name"):
1360  name = node.attributes["name"].value
1361  else:
1362  name = str(fontnumber)
1363 
1364  fontsize = getScaledAttribute(node, "size")
1365 
1366  if node.hasAttribute("color"):
1367  color = node.attributes["color"].value
1368  else:
1369  color = "white"
1370 
1371  if node.hasAttribute("effect"):
1372  effect = node.attributes["effect"].value
1373  else:
1374  effect = "normal"
1375 
1376  if node.hasAttribute("shadowsize"):
1377  shadowsize = int(node.attributes["shadowsize"].value)
1378  else:
1379  shadowsize = 0
1380 
1381  if node.hasAttribute("shadowcolor"):
1382  shadowcolor = node.attributes["shadowcolor"].value
1383  else:
1384  shadowcolor = "black"
1385 
1386  themeFonts[name] = FontDef(name, getFontPathName(filename),
1387  fontsize, color, effect, shadowcolor, shadowsize)
1388 
1389  write( "Loading font %s, %s size %s" % (fontnumber,getFontPathName(filename),fontsize) )
1390  fontnumber+=1
1391 
1392 
1394 
1395 def getFileInformation(file, folder):
1396  outputfile = os.path.join(folder, "info.xml")
1397  impl = xml.dom.minidom.getDOMImplementation()
1398  infoDOM = impl.createDocument(None, "fileinfo", None)
1399  top_element = infoDOM.documentElement
1400 
1401  data = OrdDict((('chanid',''),
1402  ('type',''), ('filename',''),
1403  ('title',''), ('recordingdate',''),
1404  ('recordingtime',''), ('subtitle',''),
1405  ('description',''), ('rating',''),
1406  ('coverfile',''), ('cutlist','')))
1407 
1408  # if the jobfile has amended file details use them
1409  details = file.getElementsByTagName("details")
1410  if details.length > 0:
1411  data.type = file.attributes["type"].value
1412  data.filename = file.attributes["filename"].value
1413  data.title = details[0].attributes["title"].value
1414  data.recordingdate = details[0].attributes["startdate"].value
1415  data.recordingtime = details[0].attributes["starttime"].value
1416  data.subtitle = details[0].attributes["subtitle"].value
1417  data.description = getText(details[0])
1418 
1419  # if this a myth recording we still need to find the chanid, starttime and hascutlist
1420  if file.attributes["type"].value=="recording":
1421  filename = file.attributes["filename"].value
1422  try:
1423  rec = next(DB.searchRecorded(basename=os.path.basename(filename)))
1424  except StopIteration:
1425  fatalError("Failed to get recording details from the DB for %s" % filename)
1426 
1427  data.chanid = rec.chanid
1428  data.recordingtime = rec.starttime.isoformat()
1429  data.recordingdate = rec.starttime.isoformat()
1430 
1431  cutlist = rec.markup.getcutlist()
1432  if len(cutlist):
1433  data.hascutlist = 'yes'
1434  if file.attributes["usecutlist"].value == "0" and addCutlistChapters == True:
1435  chapterlist = ['00:00:00']
1436  res, fps, ar = getVideoParams(folder)
1437  for s,e in cutlist:
1438  chapterlist.append(frameToTime(s, float(fps)))
1439  data.chapterlist = ','.join(chapterlist)
1440  else:
1441  data.hascutlist = 'no'
1442 
1443  elif file.attributes["type"].value=="recording":
1444  filename = file.attributes["filename"].value
1445  try:
1446  rec = next(DB.searchRecorded(basename=os.path.basename(filename)))
1447  except StopIteration:
1448  fatalError("Failed to get recording details from the DB for %s" % filename)
1449 
1450  write(" " + rec.title)
1451  data.type = file.attributes["type"].value
1452  data.filename = filename
1453  data.title = rec.title
1454  data.recordingdate = rec.progstart.strftime(dateformat)
1455  data.recordingtime = rec.progstart.strftime(timeformat)
1456  data.subtitle = rec.subtitle
1457  data.description = rec.description
1458  data.rating = str(rec.stars)
1459  data.chanid = rec.chanid
1460  data.starttime = rec.starttime.utcisoformat()
1461 
1462  cutlist = rec.markup.getcutlist()
1463  if len(cutlist):
1464  data.hascutlist = 'yes'
1465  if file.attributes["usecutlist"].value == "0" and addCutlistChapters == True:
1466  chapterlist = ['00:00:00']
1467  res, fps, ar = getVideoParams(folder)
1468  for s,e in cutlist:
1469  chapterlist.append(frameToTime(s, float(fps)))
1470  data.chapterlist = ','.join(chapterlist)
1471  else:
1472  data.hascutlist = 'no'
1473 
1474  elif file.attributes["type"].value=="video":
1475  filename = file.attributes["filename"].value
1476  try:
1477  vid = next(MVID.searchVideos(file=filename))
1478  except StopIteration:
1479  vid = Video.fromFilename(filename)
1480 
1481  data.type = file.attributes["type"].value
1482  data.filename = filename
1483  data.title = vid.title
1484 
1485  if vid.year != 1895:
1486  data.recordingdate = unicode(vid.year)
1487 
1488  data.subtitle = vid.subtitle
1489 
1490  if (vid.plot is not None) and (vid.plot != 'None'):
1491  data.description = vid.plot
1492 
1493  data.rating = str(vid.userrating)
1494 
1495  if doesFileExist(unicode(vid.coverfile)):
1496  data.coverfile = vid.coverfile
1497 
1498  elif file.attributes["type"].value=="file":
1499  data.type = file.attributes["type"].value
1500  data.filename = file.attributes["filename"].value
1501  data.title = file.attributes["filename"].value
1502 
1503  # if the jobfile has thumb image details copy the images to the work dir
1504  thumbs = file.getElementsByTagName("thumbimages")
1505  if thumbs.length > 0:
1506  thumbs = thumbs[0]
1507  thumbs = file.getElementsByTagName("thumb")
1508  thumblist = []
1509  res, fps, ar = getVideoParams(folder)
1510 
1511  for thumb in thumbs:
1512  caption = thumb.attributes["caption"].value
1513  frame = thumb.attributes["frame"].value
1514  filename = thumb.attributes["filename"].value
1515  if caption != "Title":
1516  thumblist.append(frameToTime(int(frame), float(fps)))
1517 
1518  # copy thumb file to work dir
1519  copy(filename, folder)
1520 
1521  data.thumblist = ','.join(thumblist)
1522 
1523  for k,v in list(data.items()):
1524  write( "Node = %s, Data = %s" % (k, v))
1525  node = infoDOM.createElement(k)
1526  # v may be either an integer. Therefore we have to
1527  # convert it to an unicode-string
1528  # If it is already a string it is not encoded as all
1529  # strings in this script are unicode. As no
1530  # encoding-argument is supplied to unicode() it does
1531  # nothing in this case.
1532  node.appendChild(infoDOM.createTextNode(unicode(v)))
1533  top_element.appendChild(node)
1534 
1535  WriteXMLToFile (infoDOM, outputfile)
1536 
1537 
1539 
1540 def WriteXMLToFile(myDOM, filename):
1541 
1542  #Save the XML file to disk for use later on
1543  f=open(filename, 'w')
1544 
1545  if sys.hexversion >= 0x03000000:
1546  f.write(myDOM.toprettyxml(indent=" ", encoding="UTF-8").decode())
1547  elif sys.hexversion >= 0x020703F0:
1548  f.write(myDOM.toprettyxml(indent=" ", encoding="UTF-8"))
1549  else:
1550  f.write(myDOM.toxml(encoding="UTF-8"))
1551 
1552  f.close()
1553 
1554 
1555 
1557 
1558 def preProcessFile(file, folder, count):
1559  """Pre-process a single video/recording file."""
1560 
1561  write( "Pre-processing %s %d: '%s'" % (file.attributes["type"].value, count, file.attributes["filename"].value))
1562 
1563  #As part of this routine we need to pre-process the video:
1564  #1. check the file actually exists
1565  #2. extract information from mythtv for this file in xml file
1566  #3. Extract a single frame from the video to use as a thumbnail and resolution check
1567  mediafile=""
1568 
1569  if file.attributes["type"].value == "recording":
1570  mediafile = file.attributes["filename"].value
1571  elif file.attributes["type"].value == "video":
1572  mediafile = os.path.join(videopath, file.attributes["filename"].value)
1573  elif file.attributes["type"].value == "file":
1574  mediafile = file.attributes["filename"].value
1575  else:
1576  fatalError("Unknown type of video file it must be 'recording', 'video' or 'file'.")
1577 
1578  if file.hasAttribute("localfilename"):
1579  mediafile = file.attributes["localfilename"].value
1580 
1581  if doesFileExist(mediafile) == False:
1582  fatalError("Source file does not exist: " + mediafile)
1583 
1584  getStreamInformation(mediafile, os.path.join(folder, "streaminfo.xml"), 0)
1585  copy(os.path.join(folder, "streaminfo.xml"), os.path.join(folder, "streaminfo_orig.xml"))
1586 
1587  getFileInformation(file, folder)
1588 
1589  videosize = getVideoSize(os.path.join(folder, "streaminfo.xml"))
1590 
1591  write( "Video resolution is %s by %s" % (videosize[0], videosize[1]))
1592 
1593 
1595 
1596 def encodeAudio(format, sourcefile, destinationfile, deletesourceafterencode):
1597  write( "Encoding audio to "+format)
1598  if format == "ac3":
1599  cmd = "mythffmpeg -v 0 -y "
1600 
1601  if cpuCount > 1:
1602  cmd += "-threads %d " % cpuCount
1603 
1604  cmd += "-i %s -f ac3 -ab 192k -ar 48000 %s" % (quoteCmdArg(sourcefile), quoteCmdArg(destinationfile))
1605  result = runCommand(cmd)
1606 
1607  if result != 0:
1608  fatalError("Failed while running mythffmpeg to re-encode the audio to ac3\n"
1609  "Command was %s" % cmd)
1610  else:
1611  fatalError("Unknown encodeAudio format " + format)
1612 
1613  if deletesourceafterencode==True:
1614  os.remove(sourcefile)
1615 
1616 
1619 
1620 def multiplexMPEGStream(video, audio1, audio2, destination, syncOffset):
1621  """multiplex one video and one or two audio streams together"""
1622 
1623  write("Multiplexing MPEG stream to %s" % destination)
1624 
1625  # no need to use a sync offset if projectx was used to demux the streams
1626  if useprojectx:
1627  syncOffset = 0
1628  else:
1629  if useSyncOffset == True:
1630  write("Adding sync offset of %dms" % syncOffset)
1631  else:
1632  write("Using sync offset is disabled - it would be %dms" % syncOffset)
1633  syncOffset = 0
1634 
1635  if doesFileExist(destination)==True:
1636  os.remove(destination)
1637 
1638  # figure out what audio files to use
1639  if doesFileExist(audio1 + ".ac3"):
1640  audio1 = audio1 + ".ac3"
1641  elif doesFileExist(audio1 + ".mp2"):
1642  audio1 = audio1 + ".mp2"
1643  else:
1644  fatalError("No audio stream available!")
1645 
1646  if doesFileExist(audio2 + ".ac3"):
1647  audio2 = audio2 + ".ac3"
1648  elif doesFileExist(audio2 + ".mp2"):
1649  audio2 = audio2 + ".mp2"
1650 
1651  # if subtitles exist, we need to run sequentially, so they can be
1652  # multiplexed to the final file
1653  if os.path.exists(os.path.dirname(destination) + "/stream.d/spumux.xml"):
1654  localUseFIFO=False
1655  else:
1656  localUseFIFO=useFIFO
1657 
1658  if localUseFIFO==True:
1659  os.mkfifo(destination)
1660  mode=os.P_NOWAIT
1661  else:
1662  mode=os.P_WAIT
1663 
1664  checkCancelFlag()
1665 
1666  if not doesFileExist(audio2):
1667  write("Available streams - video and one audio stream")
1668  write("running %s -M -f 8 -v 0 --sync-offset %sms -o %s %s %s" %(path_mplex[0], syncOffset, destination, video, audio1))
1669  result=os.spawnlp(mode, path_mplex[0], path_mplex[1],
1670  '-M',
1671  '-f', '8',
1672  '-v', '0',
1673  '--sync-offset', '%sms' % syncOffset,
1674  '-o', destination,
1675  video,
1676  audio1)
1677  else:
1678  write("Available streams - video and two audio streams")
1679  result=os.spawnlp(mode, path_mplex[0], path_mplex[1],
1680  '-M',
1681  '-f', '8',
1682  '-v', '0',
1683  '--sync-offset', '%sms' % syncOffset,
1684  '-o', destination,
1685  video,
1686  audio1,
1687  audio2)
1688 
1689  if localUseFIFO == True:
1690  write( "Multiplex started PID=%s" % result)
1691  return result
1692  else:
1693  if result != 0:
1694  fatalError("mplex failed with result %d" % result)
1695 
1696  # run spumux to add subtitles if they exist
1697  if os.path.exists(os.path.dirname(destination) + "/stream.d/spumux.xml"):
1698  write("Checking integrity of subtitle pngs")
1699  command = quoteCmdArg(os.path.join(scriptpath, "testsubtitlepngs.sh")) + " " + quoteCmdArg(os.path.dirname(destination) + "/stream.d/spumux.xml")
1700  result = runCommand(command)
1701  if result!=0:
1702  fatalError("Failed while running testsubtitlepngs.sh - %s" % command)
1703 
1704  write("Running spumux to add subtitles")
1705  command = quoteCmdArg(path_spumux[0]) + " -P %s <%s >%s" % (quoteCmdArg(os.path.dirname(destination) + "/stream.d/spumux.xml"), quoteCmdArg(destination), quoteCmdArg(os.path.splitext(destination)[0] + "-sub.mpg"))
1706  result = runCommand(command)
1707  if result!=0:
1708  nonfatalError("Failed while running spumux.\n"
1709  "Command was - %s.\n"
1710  "Look in the full log to see why it failed" % command)
1711  os.remove(os.path.splitext(destination)[0] + "-sub.mpg")
1712  else:
1713  os.rename(os.path.splitext(destination)[0] + "-sub.mpg", destination)
1714 
1715  return True
1716 
1717 
1718 
1720 
1721 def getStreamInformation(filename, xmlFilename, lenMethod):
1722  """create a stream.xml file for filename"""
1723 
1724  command = "mytharchivehelper -q -q --getfileinfo --infile %s --outfile %s --method %d" % (quoteCmdArg(filename), quoteCmdArg(xmlFilename), lenMethod)
1725 
1726 
1727  result = runCommand(command)
1728 
1729  if result != 0:
1730  fatalError("Failed while running mytharchivehelper to get stream information.\n"
1731  "Result: %d, Command was %s" % (result, command))
1732 
1733  # print out the streaminfo.xml file to the log
1734  infoDOM = xml.dom.minidom.parse(xmlFilename)
1735  write(xmlFilename + ":-\n" + infoDOM.toprettyxml(" ", ""), False)
1736 
1737 
1739 
1740 def getVideoSize(xmlFilename):
1741  """Get video width and height from stream.xml file"""
1742 
1743  #open the XML containing information about this file
1744  infoDOM = xml.dom.minidom.parse(xmlFilename)
1745  #error out if its the wrong XML
1746 
1747  if infoDOM.documentElement.tagName != "file":
1748  fatalError("This info file doesn't look right (%s)." % xmlFilename)
1749  nodes = infoDOM.getElementsByTagName("video")
1750  if nodes.length == 0:
1751  fatalError("Didn't find any video elements in stream info file. (%s)" % xmlFilename)
1752 
1753  if nodes.length > 1:
1754  write("Found more than one video element in stream info file.!!!")
1755  node = nodes[0]
1756  width = int(node.attributes["width"].value)
1757  height = int(node.attributes["height"].value)
1758 
1759  return (width, height)
1760 
1761 
1763 
1764 def runMythtranscode(chanid, starttime, destination, usecutlist, localfile):
1765  """Use mythtranscode to cut commercials and/or clean up an mpeg2 file"""
1766 
1767  try:
1768  rec = next(DB.searchRecorded(chanid=chanid, starttime=starttime))
1769  cutlist = rec.markup.getcutlist()
1770  except StopIteration:
1771  cutlist = []
1772 
1773  cutlist_s = ""
1774  if usecutlist and len(cutlist):
1775  cutlist_s = "'"
1776  for cut in cutlist:
1777  cutlist_s += ' %d-%d ' % cut
1778  cutlist_s += "'"
1779  write("Using cutlist: %s" % cutlist_s)
1780 
1781  if (localfile != ""):
1782  if usecutlist == True:
1783  command = "mythtranscode --mpeg2 --honorcutlist %s --infile %s --outfile %s" % (cutlist_s, quoteCmdArg(localfile), quoteCmdArg(destination))
1784  else:
1785  command = "mythtranscode --mpeg2 --infile %s --outfile %s" % (quoteCmdArg(localfile), quoteCmdArg(destination))
1786  else:
1787  if usecutlist == True:
1788  command = "mythtranscode --mpeg2 --honorcutlist --chanid %s --starttime %s --outfile %s" % (chanid, starttime, quoteCmdArg(destination))
1789  else:
1790  command = "mythtranscode --mpeg2 --chanid %s --starttime %s --outfile %s" % (chanid, starttime, quoteCmdArg(destination))
1791 
1792  result = runCommand(command)
1793 
1794  if (result != 0):
1795  write("Failed while running mythtranscode to cut commercials and/or clean up an mpeg2 file.\n"
1796  "Result: %d, Command was %s" % (result, command))
1797  return False;
1798 
1799  return True
1800 
1801 
1802 
1804 
1805 def generateProjectXCutlist(chanid, starttime, folder):
1806  """generate cutlist_x.txt for ProjectX"""
1807 
1808  rec = next(DB.searchRecorded(chanid=chanid, starttime=starttime))
1809  starttime = rec.starttime.utcisoformat()
1810  cutlist = rec.markup.getcutlist()
1811 
1812  if len(cutlist):
1813  with codecs.open(os.path.join(folder, "cutlist_x.txt"), 'w', 'utf-8') as cutlist_f:
1814  cutlist_f.write("CollectionPanel.CutMode=2\n")
1815  i = 0
1816  for cut in cutlist:
1817  # we need to reverse the cutlist because ProjectX wants to know
1818  # the bits to keep not what to cut
1819 
1820  if i == 0:
1821  if cut[0] != 0:
1822  cutlist_f.write('0\n%d\n' % cut[0])
1823  cutlist_f.write('%d\n' % cut[1])
1824  elif i == len(cutlist) - 1:
1825  cutlist_f.write('%d\n' % cut[0])
1826  if cut[1] != 9999999:
1827  cutlist_f.write('%d\n9999999\n' % cut[1])
1828  else:
1829  cutlist_f.write('%d\n%d\n' % cut)
1830 
1831  i+=1
1832  return True
1833  else:
1834  write("No cutlist in the DB for chanid %s, starttime %s" % chanid, starttime)
1835  return False
1836 
1837 
1839 
1840 def runProjectX(chanid, starttime, folder, usecutlist, file):
1841  """Use Project-X to cut commercials and demux an mpeg2 file"""
1842 
1843  if usecutlist:
1844  if generateProjectXCutlist(chanid, starttime, folder) == False:
1845  write("Failed to generate Project-X cutlist.")
1846  return False
1847 
1848  if os.path.exists(file) != True:
1849  write("Error: input file doesn't exist on local filesystem")
1850  return False
1851 
1852  command = quoteCmdArg(path_projectx[0]) + " %s -id '%s' -set ExternPanel.appendPidToFileName=1 -out %s -name stream" % (quoteCmdArg(file), getStreamList(folder), quoteCmdArg(folder))
1853  if usecutlist == True:
1854  command += " -cut %s" % quoteCmdArg(os.path.join(folder, "cutlist_x.txt"))
1855  write(command)
1856  result = runCommand(command)
1857 
1858  if (result != 0):
1859  write("Failed while running Project-X to cut commercials and/or demux an mpeg2 file.\n"
1860  "Result: %d, Command was %s" % (result, command))
1861  return False;
1862 
1863 
1864  # workout which files we need and rename them
1865  video, audio1, audio2 = selectStreams(folder)
1866  if addSubtitles:
1867  subtitles = selectSubtitleStream(folder)
1868 
1869  videoID_hex = "0x%x" % video[VIDEO_ID]
1870  if audio1[AUDIO_ID] != -1:
1871  audio1ID_hex = "0x%x" % audio1[AUDIO_ID]
1872  else:
1873  audio1ID_hex = ""
1874  if audio2[AUDIO_ID] != -1:
1875  audio2ID_hex = "0x%x" % audio2[AUDIO_ID]
1876  else:
1877  audio2ID_hex = ""
1878  if addSubtitles and subtitles[SUBTITLE_ID] != -1:
1879  subtitlesID_hex = "0x%x" % subtitles[SUBTITLE_ID]
1880  else:
1881  subtitlesID_hex = ""
1882 
1883 
1884  files = os.listdir(folder)
1885  for file in files:
1886  if file[0:9] == "stream{0x": # don't rename files that have already been renamed
1887  PID = file[7:13]
1888  SubID = file[19:23]
1889  if PID == videoID_hex or SubID == videoID_hex:
1890  os.rename(os.path.join(folder, file), os.path.join(folder, "stream.mv2"))
1891  elif PID == audio1ID_hex or SubID == audio1ID_hex:
1892  os.rename(os.path.join(folder, file), os.path.join(folder, "stream0." + file[-3:]))
1893  elif PID == audio2ID_hex or SubID == audio2ID_hex:
1894  os.rename(os.path.join(folder, file), os.path.join(folder, "stream1." + file[-3:]))
1895  elif PID == subtitlesID_hex or SubID == subtitlesID_hex:
1896  if file[-3:] == "sup":
1897  os.rename(os.path.join(folder, file), os.path.join(folder, "stream.sup"))
1898  else:
1899  os.rename(os.path.join(folder, file), os.path.join(folder, "stream.sup.IFO"))
1900 
1901 
1902  # Fallback if assignment and renaming by ID failed
1903 
1904  files = os.listdir(folder)
1905  for file in files:
1906  if file[0:9] == "stream{0x": # don't rename files that have already been renamed
1907  if not os.path.exists(os.path.join(folder, "stream.mv2")) and file[-3:] == "m2v":
1908  os.rename(os.path.join(folder, file), os.path.join(folder, "stream.mv2"))
1909  elif not (os.path.exists(os.path.join(folder, "stream0.ac3")) or os.path.exists(os.path.join(folder, "stream0.mp2"))) and file[-3:] == "ac3":
1910  os.rename(os.path.join(folder, file), os.path.join(folder, "stream0.ac3"))
1911  elif not (os.path.exists(os.path.join(folder, "stream0.ac3")) or os.path.exists(os.path.join(folder, "stream0.mp2"))) and file[-3:] == "mp2":
1912  os.rename(os.path.join(folder, file), os.path.join(folder, "stream0.mp2"))
1913  elif not (os.path.exists(os.path.join(folder, "stream1.ac3")) or os.path.exists(os.path.join(folder, "stream1.mp2"))) and file[-3:] == "ac3":
1914  os.rename(os.path.join(folder, file), os.path.join(folder, "stream1.ac3"))
1915  elif not (os.path.exists(os.path.join(folder, "stream1.ac3")) or os.path.exists(os.path.join(folder, "stream1.mp2"))) and file[-3:] == "mp2":
1916  os.rename(os.path.join(folder, file), os.path.join(folder, "stream1.mp2"))
1917  elif not os.path.exists(os.path.join(folder, "stream.sup")) and file[-3:] == "sup":
1918  os.rename(os.path.join(folder, file), os.path.join(folder, "stream.sup"))
1919  elif not os.path.exists(os.path.join(folder, "stream.sup.IFO")) and file[-3:] == "IFO":
1920  os.rename(os.path.join(folder, file), os.path.join(folder, "stream.sup.IFO"))
1921 
1922 
1923  # if we have some dvb subtitles and the user wants to add them to the DVD
1924  # convert them to pngs and create the spumux xml file
1925  if addSubtitles:
1926  if (os.path.exists(os.path.join(folder, "stream.sup")) and
1927  os.path.exists(os.path.join(folder, "stream.sup.IFO"))):
1928  write("Found DVB subtitles converting to DVD subtitles")
1929  command = "mytharchivehelper -q -q --sup2dast "
1930  command += " --infile %s --ifofile %s --delay 0" % (quoteCmdArg(os.path.join(folder, "stream.sup")), quoteCmdArg(os.path.join(folder, "stream.sup.IFO")))
1931 
1932  result = runCommand(command)
1933 
1934  if result != 0:
1935  write("Failed while running mytharchivehelper to convert DVB subtitles to DVD subtitles.\n"
1936  "Result: %d, Command was %s" % (result, command))
1937  return False
1938 
1939  # sanity check the created spumux.xml
1940  checkSubtitles(os.path.join(folder, "stream.d", "spumux.xml"))
1941 
1942  return True
1943 
1944 
1946 
1947 def ts2pts(time):
1948  h = int(time[0:2]) * 3600 * 90000
1949  m = int(time[3:5]) * 60 * 90000
1950  s = int(time[6:8]) * 90000
1951  ms = int(time[9:11]) * 90
1952 
1953  return h + m + s + ms
1954 
1955 
1957 
1958 def checkSubtitles(spumuxFile):
1959 
1960  #open the XML containing information about this file
1961  subDOM = xml.dom.minidom.parse(spumuxFile)
1962 
1963  #error out if its the wrong XML
1964  if subDOM.documentElement.tagName != "subpictures":
1965  fatalError("This does not look like a spumux.xml file (%s)" % spumuxFile)
1966 
1967  streamNodes = subDOM.getElementsByTagName("stream")
1968  if streamNodes.length == 0:
1969  write("Didn't find any stream elements in file.!!!")
1970  return
1971  streamNode = streamNodes[0]
1972 
1973  nodes = subDOM.getElementsByTagName("spu")
1974  if nodes.length == 0:
1975  write("Didn't find any spu elements in file.!!!")
1976  return
1977 
1978  lastStart = -1
1979  lastEnd = -1
1980  for node in nodes:
1981  errored = False
1982  start = ts2pts(node.attributes["start"].value)
1983  end = ts2pts(node.attributes["end"].value)
1984  image = node.attributes["image"].value
1985 
1986  if end <= start:
1987  errored = True
1988  if start <= lastEnd:
1989  errored = True
1990 
1991  if errored:
1992  write("removing subtitle: %s to %s - (%d - %d (%d))" % (node.attributes["start"].value, node.attributes["end"].value, start, end, lastEnd), False)
1993  streamNode.removeChild(node)
1994  node.unlink()
1995 
1996  lastStart = start
1997  lastEnd = end
1998 
1999  WriteXMLToFile(subDOM, spumuxFile)
2000 
2001 
2003 
2004 def extractVideoFrame(source, destination, seconds):
2005  write("Extracting thumbnail image from %s at position %s" % (source, seconds))
2006  write("Destination file %s" % destination)
2007 
2008  if doesFileExist(destination) == False:
2009 
2010  if videomode=="pal":
2011  fr=frameratePAL
2012  else:
2013  fr=framerateNTSC
2014 
2015  command = "mytharchivehelper -q -q --createthumbnail --infile %s --thumblist '%s' --outfile %s" % (quoteCmdArg(source), seconds, quoteCmdArg(destination))
2016  result = runCommand(command)
2017  if result != 0:
2018  fatalError("Failed while running mytharchivehelper to get thumbnails.\n"
2019  "Result: %d, Command was %s" % (result, command))
2020  try:
2021  myimage=Image.open(destination,"r")
2022 
2023  if myimage.format != "JPEG":
2024  write( "Something went wrong with thumbnail capture - " + myimage.format)
2025  return (long(0),long(0))
2026  else:
2027  return myimage.size
2028  except IOError:
2029  return (long(0),long(0))
2030 
2031 
2033 
2034 def extractVideoFrames(source, destination, thumbList):
2035  write("Extracting thumbnail images from: %s - at %s" % (source, thumbList))
2036  write("Destination file %s" % destination)
2037 
2038  command = "mytharchivehelper -q -q --createthumbnail --infile %s --thumblist '%s' --outfile %s" % (quoteCmdArg(source), thumbList, quoteCmdArg(destination))
2039  write(command)
2040  result = runCommand(command)
2041  if result != 0:
2042  fatalError("Failed while running mytharchivehelper to get thumbnails.\n"
2043  "Result: %d, Command was %s" % (result, command))
2044 
2045 
2047 
2048 def encodeVideoToMPEG2(source, destvideofile, video, audio1, audio2, aspectratio, profile):
2049  """Encodes an unknown video source file eg. AVI to MPEG2 video and AC3 audio, use mythffmpeg"""
2050 
2051  profileNode = findEncodingProfile(profile)
2052 
2053  passes = int(getText(profileNode.getElementsByTagName("passes")[0]))
2054 
2055  command = "mythffmpeg"
2056 
2057  if cpuCount > 1:
2058  command += " -threads %d" % cpuCount
2059 
2060  parameters = profileNode.getElementsByTagName("parameter")
2061 
2062  for param in parameters:
2063  name = param.attributes["name"].value
2064  value = param.attributes["value"].value
2065 
2066  # do some parameter substitution
2067  if value == "%inputfile":
2068  value = quoteCmdArg(source)
2069  if value == "%outputfile":
2070  value = quoteCmdArg(destvideofile)
2071  if value == "%aspect":
2072  value = aspectratio
2073 
2074  # only re-encode the audio if it is not already in AC3 format
2075  if audio1[AUDIO_CODEC] == "AC3":
2076  if name == "-acodec":
2077  value = "copy"
2078  if name == "-ar" or name == "-b:a" or name == "-ac":
2079  name = ""
2080  value = ""
2081 
2082  if name != "":
2083  command += " " + name
2084 
2085  if value != "":
2086  command += " " + value
2087 
2088 
2089  #add second audio track if required
2090  if audio2[AUDIO_ID] != -1:
2091  for param in parameters:
2092  name = param.attributes["name"].value
2093  value = param.attributes["value"].value
2094 
2095  # only re-encode the audio if it is not already in AC3 format
2096  if audio1[AUDIO_CODEC] == "AC3":
2097  if name == "-acodec":
2098  value = "copy"
2099  if name == "-ar" or name == "-b:a" or name == "-ac":
2100  name = ""
2101  value = ""
2102 
2103  if name == "-acodec" or name == "-ar" or name == "-b:a" or name == "-ac":
2104  command += " " + name + " " + value
2105 
2106  #make sure we get the correct stream(s) that we want
2107  command += " -map 0:%d -map 0:%d " % (video[VIDEO_INDEX], audio1[AUDIO_INDEX])
2108  if audio2[AUDIO_ID] != -1:
2109  command += "-map 0:%d" % (audio2[AUDIO_INDEX])
2110 
2111  if passes == 1:
2112  write(command)
2113  result = runCommand(command)
2114  if result!=0:
2115  fatalError("Failed while running mythffmpeg to re-encode video.\n"
2116  "Command was %s" % command)
2117 
2118  else:
2119  passLog = os.path.join(getTempPath(), 'pass')
2120 
2121  pass1 = command.replace("%passno","1")
2122  pass1 = pass1.replace("%passlogfile", quoteCmdArg(passLog))
2123  write("Pass 1 - " + pass1)
2124  result = runCommand(pass1)
2125 
2126  if result!=0:
2127  fatalError("Failed while running mythffmpeg (Pass 1) to re-encode video.\n"
2128  "Command was %s" % command)
2129 
2130  if os.path.exists(destvideofile):
2131  os.remove(destvideofile)
2132 
2133  pass2 = command.replace("%passno","2")
2134  pass2 = pass2.replace("%passlogfile", passLog)
2135  write("Pass 2 - " + pass2)
2136  result = runCommand(pass2)
2137 
2138  if result!=0:
2139  fatalError("Failed while running mythffmpeg (Pass 2) to re-encode video.\n"
2140  "Command was %s" % command)
2141 
2143 
2144 def encodeNuvToMPEG2(chanid, starttime, mediafile, destvideofile, folder, profile, usecutlist):
2145  """Encodes a nuv video source file to MPEG2 video and AC3 audio, using mythtranscode & mythffmpeg"""
2146 
2147  # make sure mythtranscode hasn't left some stale fifos hanging around
2148  if ((doesFileExist(os.path.join(folder, "audout")) or doesFileExist(os.path.join(folder, "vidout")))):
2149  fatalError("Something is wrong! Found one or more stale fifo's from mythtranscode\n"
2150  "Delete the fifos in '%s' and start again" % folder)
2151 
2152  profileNode = findEncodingProfile(profile)
2153  parameters = profileNode.getElementsByTagName("parameter")
2154 
2155  # default values - will be overriden by values from the profile
2156  outvideobitrate = "5000k"
2157  if videomode == "ntsc":
2158  outvideores = "720x480"
2159  else:
2160  outvideores = "720x576"
2161 
2162  outaudiochannels = 2
2163  outaudiobitrate = 384
2164  outaudiosamplerate = 48000
2165  outaudiocodec = "ac3"
2166  deinterlace = 0
2167  filter = ""
2168  qmin = 5
2169  qmax = 31
2170  qdiff = 31
2171 
2172  for param in parameters:
2173  name = param.attributes["name"].value
2174  value = param.attributes["value"].value
2175 
2176  # we only support a subset of the parameter for the moment
2177  if name == "-acodec":
2178  outaudiocodec = value
2179  if name == "-ac":
2180  outaudiochannels = value
2181  if name == "-ab":
2182  outaudiobitrate = value
2183  if name == "-ar":
2184  outaudiosamplerate = value
2185  if name == "-b":
2186  outvideobitrate = value
2187  if name == "-s":
2188  outvideores = value
2189  if name == "-deinterlace":
2190  deinterlace = 1
2191  if name == "-filter:v":
2192  filter = " -filter:v " + quoteCmdArg(value) + " "
2193  if name == "-qmin":
2194  qmin = value
2195  if name == "-qmax":
2196  qmax = value
2197  if name == "-qdiff":
2198  qdiff = value
2199 
2200  if chanid != -1:
2201  utcstarttime = datetime.duck(starttime).utcisoformat()
2202  if (usecutlist == True):
2203  PID=os.spawnlp(os.P_NOWAIT, "mythtranscode", "mythtranscode",
2204  '--profile', '27',
2205  '--chanid', chanid,
2206  '--starttime', utcstarttime,
2207  '--honorcutlist',
2208  '--fifodir', folder)
2209  write("mythtranscode started (using cut list) PID = %s" % PID)
2210  else:
2211  PID=os.spawnlp(os.P_NOWAIT, "mythtranscode", "mythtranscode",
2212  '--profile', '27',
2213  '--chanid', chanid,
2214  '--starttime', utcstarttime,
2215  '--fifodir', folder)
2216 
2217  write("mythtranscode started PID = %s" % PID)
2218  elif mediafile != -1:
2219  PID=os.spawnlp(os.P_NOWAIT, "mythtranscode", "mythtranscode",
2220  '--profile', '27',
2221  '--infile', mediafile,
2222  '--fifodir', folder)
2223  write("mythtranscode started (using file) PID = %s" % PID)
2224  else:
2225  fatalError("no video source passed to encodeNuvToMPEG2.\n")
2226 
2227 
2228  samplerate, channels = getAudioParams(folder)
2229  videores, fps, aspectratio = getVideoParams(folder)
2230 
2231  command = "mythffmpeg -y "
2232 
2233  if cpuCount > 1:
2234  command += "-threads %d " % cpuCount
2235 
2236  command += "-f s16le -ar %s -ac %s -i %s " % (samplerate, channels, quoteCmdArg(os.path.join(folder, "audout")))
2237  command += "-f rawvideo -pix_fmt yuv420p -s %s -aspect %s -r %s " % (videores, aspectratio, fps)
2238  command += "-i %s " % quoteCmdArg(os.path.join(folder, "vidout"))
2239  command += "-aspect %s -r %s " % (aspectratio, fps)
2240  if (deinterlace == 1):
2241  command += "-deinterlace "
2242  command += "%s" % filter
2243  command += "-s %s -b %s -vcodec mpeg2video " % (outvideores, outvideobitrate)
2244  command += "-qmin %s -qmax %s -qdiff %s " % (qmin, qmax, qdiff)
2245  command += "-ab %s -ar %s -acodec %s " % (outaudiobitrate, outaudiosamplerate, outaudiocodec)
2246  command += "-f dvd %s" % quoteCmdArg(destvideofile)
2247 
2248  #wait for mythtranscode to create the fifos
2249  tries = 30
2250  while (tries and not(doesFileExist(os.path.join(folder, "audout")) and
2251  doesFileExist(os.path.join(folder, "vidout")))):
2252  tries -= 1
2253  write("Waiting for mythtranscode to create the fifos")
2254  time.sleep(1)
2255 
2256  if (not(doesFileExist(os.path.join(folder, "audout")) and doesFileExist(os.path.join(folder, "vidout")))):
2257  fatalError("Waited too long for mythtranscode to create the fifos - giving up!!")
2258 
2259  write("Running mythffmpeg")
2260  result = runCommand(command)
2261  if result != 0:
2262  os.kill(PID, signal.SIGKILL)
2263  fatalError("Failed while running mythffmpeg to re-encode video.\n"
2264  "Command was %s" % command)
2265 
2266 
2268 
2270  write( "Starting dvdauthor")
2271  checkCancelFlag()
2272  result=os.spawnlp(os.P_WAIT, path_dvdauthor[0],path_dvdauthor[1],'-x',os.path.join(getTempPath(),'dvdauthor.xml'))
2273  if result!=0:
2274  fatalError("Failed while running dvdauthor. Result: %d" % result)
2275  write( "Finished dvdauthor")
2276 
2277 
2279 
2280 def CreateDVDISO(title):
2281  write("Creating ISO image")
2282  checkCancelFlag()
2283  command = quoteCmdArg(path_mkisofs[0]) + ' -dvd-video '
2284  command += ' -V ' + quoteCmdArg(title)
2285  command += ' -o ' + quoteCmdArg(os.path.join(getTempPath(), 'mythburn.iso'))
2286  command += " " + quoteCmdArg(os.path.join(getTempPath(),'dvd'))
2287 
2288  result = runCommand(command)
2289 
2290  if result!=0:
2291  fatalError("Failed while running mkisofs.\n"
2292  "Command was %s" % command)
2293 
2294  write("Finished creating ISO image")
2295 
2296 
2298 
2299 
2300 def BurnDVDISO(title):
2301  write( "Burning ISO image to %s" % dvddrivepath)
2302 
2303 
2305  def drivestatus():
2306  f = os.open(dvddrivepath, os.O_RDONLY | os.O_NONBLOCK)
2307  status = ioctl(f,CDROM.CDROM_DRIVE_STATUS, 0)
2308  os.close(f)
2309  return status
2310  def displayneededdisktype():
2311  if mediatype == DVD_SL:
2312  write("Please insert an empty single-layer disc (DVD+R or DVD-R).")
2313  if mediatype == DVD_DL:
2314  write("Please insert an empty double-layer disc (DVD+R DL or DVD-R DL).")
2315  if mediatype == DVD_RW:
2316  write("Please insert a rewritable disc (DVD+RW or DVD-RW).")
2317  def drive(action, value=0):
2318  waitForDrive()
2319  # workaround as some distros have problems to eject the DVD
2320  # 'sudo sysctl -w dev.cdrom.autoclose=0' should help, but we don't have the privliges to do this
2321  if action == CDROM.CDROMEJECT:
2322  counter = 0
2323  while drivestatus() != CDROM.CDS_TRAY_OPEN and counter < 15:
2324  counter = counter + 1
2325  drive(CDROM.CDROM_LOCKDOOR, 0)
2326  if counter % 2: # if counter is 1,3,5,7,... use ioctl
2327  f = os.open(dvddrivepath, os.O_RDONLY | os.O_NONBLOCK)
2328  try:
2329  ioctl(f,action, value)
2330  except:
2331  write("Sending command '0x%x' to drive failed" %action, False)
2332  os.close(f)
2333  else: # try eject-command
2334  if runCommand("eject " + quoteCmdArg(dvddrivepath)) == 32512:
2335  write('"eject" is probably not installed.', False)
2336  waitForDrive()
2337  time.sleep(3)
2338  if drivestatus() == CDROM.CDS_TRAY_OPEN:
2339  res = True
2340  else:
2341  res = False
2342  write("Failed to eject the disc! Probably drive is blocked by another program.")
2343  # normal
2344  else:
2345  f = os.open(dvddrivepath, os.O_RDONLY | os.O_NONBLOCK)
2346  try:
2347  ioctl(f,action, value)
2348  res = True
2349  except:
2350  write("Sending command '0x%x' to drive failed" %action, False)
2351  res = False
2352  os.close(f)
2353  return res
2354  def waitForDrive():
2355  tries = 0
2356  while drivestatus() == CDROM.CDS_DRIVE_NOT_READY:
2357  checkCancelFlag()
2358  write("Waiting for drive")
2359  time.sleep(5)
2360  runCommand("pumount " + quoteCmdArg(dvddrivepath))
2361  tries += 1
2362  if tries > 10:
2363  # Try a hard reset if the device is still not ready
2364  write("Try a hard-reset of the device")
2365  drive(CDROM.CDROMRESET)
2366  tries = 0
2367 
2368 
2369 
2370 
2372 
2373 
2374  finished = False
2375  while not finished:
2376  # Maybe the user has no appropriate medium or something alike. Give her the chance to cancel.
2377  checkCancelFlag()
2378 
2379  # If drive needs some time (for example to close the tray) give it to it
2380  waitForDrive()
2381 
2382  if drivestatus() == CDROM.CDS_DISC_OK or drivestatus() == CDROM.CDS_NO_INFO:
2383 
2384  # If the frontend has a previously burnt DVD+RW mounted,
2385  # growisofs will fail to burn it, so try to pumount it first...
2386  runCommand("pumount " + quoteCmdArg(dvddrivepath));
2387 
2388 
2389  command = quoteCmdArg(path_growisofs[0]) + " -input-charset=UTF-8 -dvd-compat"
2390  if drivespeed != 0:
2391  command += " -speed=%d" % drivespeed
2392  if mediatype == DVD_RW and erasedvdrw == True:
2393  command += " -use-the-force-luke"
2394  command += " -Z " + quoteCmdArg(dvddrivepath) + " -dvd-video -V " + quoteCmdArg(title) + " " + quoteCmdArg(os.path.join(getTempPath(),'dvd'))
2395  write(command)
2396  write("Running growisofs to burn DVD")
2397 
2398  result = runCommand(command)
2399  if result == 0:
2400  finished = True
2401 
2402  # Wait till the drive is not busy any longer
2403  f = os.open(dvddrivepath, os.O_RDONLY | os.O_NONBLOCK)
2404  busy = True
2405  tries = 0
2406  while busy and tries < 10:
2407  tries += 1
2408  try:
2409  ioctl(f, CDROM.CDROM_LOCKDOOR, 0)
2410  busy = False
2411  except:
2412  write("Drive is still busy")
2413  time.sleep(5)
2414  waitForDrive()
2415  os.close(f)
2416  else:
2417  if result == 252:
2418  write("-"*60)
2419  write("You probably inserted a medium of wrong type.")
2420  elif (result == 156):
2421  write("-"*60)
2422  write("You probably inserted a non-empty, corrupt or too small medium.")
2423  elif (result == 144):
2424  write("-"*60)
2425  write("You inserted a non-empty medium.")
2426  else:
2427  write("-"*60)
2428  write("ERROR: Failed while running growisofs.")
2429  write("Result %d, Command was: %s" % (result, command))
2430  write("Please check mythburn.log for further information")
2431  write("-"*60)
2432  write("")
2433  write("Going to try it again until canceled by user:")
2434  write("-"*60)
2435  write("")
2436  displayneededdisktype()
2437 
2438  # eject the disc
2439  drive(CDROM.CDROMEJECT)
2440 
2441 
2442  elif drivestatus() == CDROM.CDS_TRAY_OPEN:
2443  displayneededdisktype()
2444  write("Waiting for tray to close.")
2445  # Wait until user closes tray or cancels
2446  while drivestatus() == CDROM.CDS_TRAY_OPEN:
2447  checkCancelFlag()
2448  time.sleep(5)
2449  elif drivestatus() == CDROM.CDS_NO_DISC:
2450  drive(CDROM.CDROMEJECT)
2451  displayneededdisktype()
2452 
2453  write("Finished burning ISO image")
2454 
2455 
2458 
2459 def deMultiplexMPEG2File(folder, mediafile, video, audio1, audio2):
2460 
2461  if getFileType(folder) == "mpegts":
2462  command = "mythreplex --demux --fix_sync -t TS -o %s " % quoteCmdArg(folder + "/stream")
2463  command += "-v %d " % (video[VIDEO_ID])
2464 
2465  if audio1[AUDIO_ID] != -1:
2466  if audio1[AUDIO_CODEC] == 'MP2':
2467  command += "-a %d " % (audio1[AUDIO_ID])
2468  elif audio1[AUDIO_CODEC] == 'AC3':
2469  command += "-c %d " % (audio1[AUDIO_ID])
2470  elif audio1[AUDIO_CODEC] == 'EAC3':
2471  command += "-c %d " % (audio1[AUDIO_ID])
2472 
2473  if audio2[AUDIO_ID] != -1:
2474  if audio2[AUDIO_CODEC] == 'MP2':
2475  command += "-a %d " % (audio2[AUDIO_ID])
2476  elif audio2[AUDIO_CODEC] == 'AC3':
2477  command += "-c %d " % (audio2[AUDIO_ID])
2478  elif audio2[AUDIO_CODEC] == 'EAC3':
2479  command += "-c %d " % (audio2[AUDIO_ID])
2480 
2481  else:
2482  command = "mythreplex --demux --fix_sync -o %s " % quoteCmdArg(folder + "/stream")
2483  command += "-v %d " % (video[VIDEO_ID] & 255)
2484 
2485  if audio1[AUDIO_ID] != -1:
2486  if audio1[AUDIO_CODEC] == 'MP2':
2487  command += "-a %d " % (audio1[AUDIO_ID] & 255)
2488  elif audio1[AUDIO_CODEC] == 'AC3':
2489  command += "-c %d " % (audio1[AUDIO_ID] & 255)
2490  elif audio1[AUDIO_CODEC] == 'EAC3':
2491  command += "-c %d " % (audio1[AUDIO_ID] & 255)
2492 
2493 
2494  if audio2[AUDIO_ID] != -1:
2495  if audio2[AUDIO_CODEC] == 'MP2':
2496  command += "-a %d " % (audio2[AUDIO_ID] & 255)
2497  elif audio2[AUDIO_CODEC] == 'AC3':
2498  command += "-c %d " % (audio2[AUDIO_ID] & 255)
2499  elif audio2[AUDIO_CODEC] == 'EAC3':
2500  command += "-c %d " % (audio2[AUDIO_ID] & 255)
2501 
2502  mediafile = quoteCmdArg(mediafile)
2503  command += mediafile
2504  write("Running: " + command)
2505 
2506  result = runCommand(command)
2507 
2508  if result!=0:
2509  fatalError("Failed while running mythreplex. Command was %s" % command)
2510 
2511 
2513 
2514 def runM2VRequantiser(source,destination,factor):
2515  mega=1024.0*1024.0
2516  M2Vsize0 = os.path.getsize(source)
2517  write("Initial M2Vsize is %.2f Mb , target is %.2f Mb" % ( (float(M2Vsize0)/mega), (float(M2Vsize0)/(factor*mega)) ))
2518 
2519  command = quoteCmdArg(path_M2VRequantiser[0])
2520  command += " %.5f " % factor
2521  command += " %s " % M2Vsize0
2522  command += " < %s " % quoteCmdArg(source)
2523  command += " > %s " % quoteCmdArg(destination)
2524 
2525  write("Running: " + command)
2526  result = runCommand(command)
2527  if result!=0:
2528  fatalError("Failed while running M2VRequantiser. Command was %s" % command)
2529 
2530  M2Vsize1 = os.path.getsize(destination)
2531 
2532  write("M2Vsize after requant is %.2f Mb " % (float(M2Vsize1)/mega))
2533  fac1=float(M2Vsize0) / float(M2Vsize1)
2534  write("Factor demanded %.5f, achieved %.5f, ratio %.5f " % ( factor, fac1, fac1/factor))
2535 
2536 
2538 
2540  """ Returns the sizes of all video, audio and menu files"""
2541  filecount=0
2542  totalvideosize=0
2543  totalaudiosize=0
2544  totalmenusize=0
2545 
2546  for node in files:
2547  filecount+=1
2548  #Generate a temp folder name for this file
2549  folder=getItemTempPath(filecount)
2550  #Process this file
2551  file=os.path.join(folder,"stream.mv2")
2552  #Get size of vobfile in MBytes
2553  totalvideosize+=os.path.getsize(file)
2554 
2555  #Get size of audio track 1
2556  if doesFileExist(os.path.join(folder,"stream0.ac3")):
2557  totalaudiosize+=os.path.getsize(os.path.join(folder,"stream0.ac3"))
2558  if doesFileExist(os.path.join(folder,"stream0.mp2")):
2559  totalaudiosize+=os.path.getsize(os.path.join(folder,"stream0.mp2"))
2560 
2561  #Get size of audio track 2 if available
2562  if doesFileExist(os.path.join(folder,"stream1.ac3")):
2563  totalaudiosize+=os.path.getsize(os.path.join(folder,"stream1.ac3"))
2564  if doesFileExist(os.path.join(folder,"stream1.mp2")):
2565  totalaudiosize+=os.path.getsize(os.path.join(folder,"stream1.mp2"))
2566 
2567  # add chapter menu if available
2568  if doesFileExist(os.path.join(getTempPath(),"chaptermenu-%s.mpg" % filecount)):
2569  totalmenusize+=os.path.getsize(os.path.join(getTempPath(),"chaptermenu-%s.mpg" % filecount))
2570 
2571  # add details page if available
2572  if doesFileExist(os.path.join(getTempPath(),"details-%s.mpg" % filecount)):
2573  totalmenusize+=os.path.getsize(os.path.join(getTempPath(),"details-%s.mpg" % filecount))
2574 
2575  filecount=1
2576  while doesFileExist(os.path.join(getTempPath(),"menu-%s.mpg" % filecount)):
2577  totalmenusize+=os.path.getsize(os.path.join(getTempPath(),"menu-%s.mpg" % filecount))
2578  filecount+=1
2579 
2580  return totalvideosize,totalaudiosize,totalmenusize
2581 
2582 
2584 
2585 def total_mv2_brl(files,rate):
2586  tvsize=0
2587  filecount=0
2588  for node in files:
2589  filecount+=1
2590  folder=getItemTempPath(filecount)
2591  progduration=getLengthOfVideo(filecount)
2592  file=os.path.join(folder,"stream.mv2")
2593  progvsize=os.path.getsize(file)
2594  progvbitrate=progvsize/progduration
2595  if progvbitrate>rate :
2596  tvsize+=progduration*rate
2597  else:
2598  tvsize+=progvsize
2599 
2600  return tvsize
2601 
2602 
2605 
2606 def performMPEG2Shrink(files,dvdrsize):
2607  checkCancelFlag()
2608  mega=1024.0*1024.0
2609  fudge_pack=1.04 # for mpeg packing
2610  fudge_requant=1.05 # for requant shrinkage uncertainty
2611 
2612  totalvideosize,totalaudiosize,totalmenusize=calculateFileSizes(files)
2613  allfiles=totalvideosize+totalaudiosize+totalmenusize
2614 
2615  #Report findings
2616  write( "Total video %.2f Mb, audio %.2f Mb, menus %.2f Mb." % (totalvideosize/mega,totalaudiosize/mega,totalmenusize/mega))
2617 
2618  #Subtract the audio, menus and packaging overhead from the size of the disk (we cannot shrink this further)
2619  mv2space=((dvdrsize*mega-totalmenusize)/fudge_pack)-totalaudiosize
2620 
2621  if mv2space<0:
2622  fatalError("Audio and menu files are too big. No room for video. Giving up!")
2623 
2624  if totalvideosize>mv2space:
2625  write( "Video files are %.1f Mb too big. Need to shrink." % ((totalvideosize - mv2space)/mega) )
2626 
2627  if path_M2VRequantiser[0] == "":
2628  fatalError("M2VRequantiser is not available to resize the files. Giving up!")
2629 
2630  vsize=0
2631  duration=0
2632  filecount=0
2633  for node in files:
2634  filecount+=1
2635  folder=getItemTempPath(filecount)
2636  file=os.path.join(folder,"stream.mv2")
2637  vsize+=os.path.getsize(file)
2638  duration+=getLengthOfVideo(filecount)
2639 
2640  #We need to shrink the video files to fit into the space available. It seems sensible
2641  #to do this by imposing a common upper limit on the mean video bit-rate of each recording;
2642  #this will not further reduce the visual quality of any that were transmitted at lower bit-rates.
2643 
2644  #Now find the bit-rate limit by iteration between initially defined upper and lower bounds.
2645  #The code is based on 'rtbis' from Numerical Recipes by W H Press et al., CUP.
2646 
2647  #A small multiple of the average input bit-rate should be ok as the initial upper bound,
2648  #(although a fixed value or one related to the max value could be used), and zero as the lower bound.
2649  #The function relating bit-rate upper limit to total file size is smooth and monotonic,
2650  #so there should be no convergence problem.
2651 
2652  vrLo=0.0
2653  vrHi=3.0*float(vsize)/duration
2654 
2655  vrate=vrLo
2656  vrinc=vrHi-vrLo
2657  count=0
2658 
2659  while count<30 :
2660  count+=1
2661  vrinc=vrinc*0.5
2662  vrtest=vrate+vrinc
2663  testsize=total_mv2_brl(files,vrtest)
2664  if (testsize<mv2space):
2665  vrate=vrtest
2666 
2667  write("vrate %.3f kb/s, testsize %.4f , mv2space %.4f Mb " % ((vrate)/1000.0, (testsize)/mega, (mv2space)/mega) )
2668  filecount=0
2669  for node in files:
2670  filecount+=1
2671  folder=getItemTempPath(filecount)
2672  file=os.path.join(folder,"stream.mv2")
2673  progvsize=os.path.getsize(file)
2674  progduration=getLengthOfVideo(filecount)
2675  progvbitrate=progvsize/progduration
2676  write( "File %s, size %.2f Mb, rate %.2f, limit %.2f kb/s " %( filecount, float(progvsize)/mega, progvbitrate/1000.0, vrate/1000.0 ))
2677  if progvbitrate>vrate :
2678  scalefactor=1.0+(fudge_requant*float(progvbitrate-vrate)/float(vrate))
2679  if scalefactor>3.0 :
2680  write( "Large shrink factor. You may not like the result! ")
2681  runM2VRequantiser(os.path.join(getItemTempPath(filecount),"stream.mv2"),os.path.join(getItemTempPath(filecount),"stream.small.mv2"),scalefactor)
2682  os.remove(os.path.join(getItemTempPath(filecount),"stream.mv2"))
2683  os.rename(os.path.join(getItemTempPath(filecount),"stream.small.mv2"),os.path.join(getItemTempPath(filecount),"stream.mv2"))
2684  else:
2685  write( "Unpackaged total %.2f Mb. About %.0f Mb will be unused." % ((allfiles/mega),(mv2space-totalvideosize)/mega))
2686 
2687 
2689 
2690 def createDVDAuthorXML(screensize, numberofitems):
2691  """Creates the xml file for dvdauthor to use the MythBurn menus."""
2692 
2693  #Get the main menu node (we must only have 1)
2694  menunode=themeDOM.getElementsByTagName("menu")
2695  if menunode.length!=1:
2696  fatalError("Cannot find the menu element in the theme file")
2697  menunode=menunode[0]
2698 
2699  menuitems=menunode.getElementsByTagName("item")
2700  #Total number of video items on a single menu page (no less than 1!)
2701  itemsperpage = menuitems.length
2702  write( "Menu items per page %s" % itemsperpage)
2703  autoplaymenu = 2 + ((numberofitems + itemsperpage - 1)//itemsperpage)
2704 
2705  if wantChapterMenu:
2706  #Get the chapter menu node (we must only have 1)
2707  submenunode=themeDOM.getElementsByTagName("submenu")
2708  if submenunode.length!=1:
2709  fatalError("Cannot find the submenu element in the theme file")
2710 
2711  submenunode=submenunode[0]
2712 
2713  chapteritems=submenunode.getElementsByTagName("chapter")
2714  #Total number of video items on a single menu page (no less than 1!)
2715  chapters = chapteritems.length
2716  write( "Chapters per recording %s" % chapters)
2717 
2718  del chapteritems
2719  del submenunode
2720 
2721  #Page number counter
2722  page=1
2723 
2724  #Item counter to indicate current video item
2725  itemnum=1
2726 
2727  write( "Creating DVD XML file for dvd author")
2728 
2729  dvddom = xml.dom.minidom.parseString(
2730  '''<dvdauthor>
2731  <vmgm>
2732  <menus lang="en">
2733  <pgc entry="title">
2734  </pgc>
2735  </menus>
2736  </vmgm>
2737  </dvdauthor>''')
2738 
2739  dvdauthor_element=dvddom.documentElement
2740  menus_element = dvdauthor_element.childNodes[1].childNodes[1]
2741 
2742  dvdauthor_element.insertBefore( dvddom.createComment("""
2743  DVD Variables
2744  g0=not used
2745  g1=not used
2746  g2=title number selected on current menu page (see g4)
2747  g3=1 if intro movie has played
2748  g4=last menu page on display
2749  g5=next title to autoplay (0 or > # titles means no more autoplay)
2750  """), dvdauthor_element.firstChild )
2751  dvdauthor_element.insertBefore(dvddom.createComment("dvdauthor XML file created by MythBurn script"), dvdauthor_element.firstChild )
2752 
2753  menus_element.appendChild( dvddom.createComment("Title menu used to hold intro movie") )
2754 
2755  dvdauthor_element.setAttribute("dest",os.path.join(getTempPath(),"dvd"))
2756 
2757  video = dvddom.createElement("video")
2758  video.setAttribute("format",videomode)
2759 
2760  # set aspect ratio
2761  if mainmenuAspectRatio == "4:3":
2762  video.setAttribute("aspect", "4:3")
2763  else:
2764  video.setAttribute("aspect", "16:9")
2765  video.setAttribute("widescreen", "nopanscan")
2766 
2767  menus_element.appendChild(video)
2768 
2769  pgc=menus_element.childNodes[1]
2770 
2771  if wantIntro:
2772  #code to skip over intro if its already played
2773  pre = dvddom.createElement("pre")
2774  pgc.appendChild(pre)
2775  vmgm_pre_node=pre
2776  del pre
2777 
2778  node = themeDOM.getElementsByTagName("intro")[0]
2779  introFile = node.attributes["filename"].value
2780 
2781  #Pick the correct intro movie based on video format ntsc/pal
2782  vob = dvddom.createElement("vob")
2783  vob.setAttribute("file",os.path.join(getThemeFile(themeName, videomode + '_' + introFile)))
2784  pgc.appendChild(vob)
2785  del vob
2786 
2787  #We use g3 to indicate that the intro has been played at least once
2788  #default g2 to point to first recording
2789  post = dvddom.createElement("post")
2790  post .appendChild(dvddom.createTextNode("{g3=1;g2=1;jump menu 2;}"))
2791  pgc.appendChild(post)
2792  del post
2793  else:
2794  # If there's no intro, we need to jump to the next menu
2795  post = dvddom.createElement("post")
2796  post .appendChild(dvddom.createTextNode("{g3=1;g2=1;jump menu 2;}"))
2797  pgc.appendChild(post)
2798  del post
2799 
2800  while itemnum <= numberofitems:
2801  write( "Menu page %s" % page)
2802 
2803  #For each menu page we need to create a new PGC structure
2804  menupgc = dvddom.createElement("pgc")
2805  menus_element.appendChild(menupgc)
2806 
2807  menupgc.appendChild( dvddom.createComment("Menu Page %s" % page) )
2808 
2809  #Make sure the button last highlighted is selected
2810  #g4 holds the menu page last displayed
2811  pre = dvddom.createElement("pre")
2812  pre.appendChild(dvddom.createTextNode("{button=g2*1024;g4=%s;}" % page))
2813  menupgc.appendChild(pre)
2814 
2815  vob = dvddom.createElement("vob")
2816  vob.setAttribute("file",os.path.join(getTempPath(),"menu-%s.mpg" % page))
2817  menupgc.appendChild(vob)
2818 
2819  #Loop menu forever
2820  post = dvddom.createElement("post")
2821  post.appendChild(dvddom.createTextNode("jump cell 1;"))
2822  menupgc.appendChild(post)
2823 
2824  #Default settings for this page
2825 
2826  #Number of video items on this menu page
2827  itemsonthispage=0
2828 
2829  endbuttons = []
2830  #Loop through all the items on this menu page
2831  while itemnum <= numberofitems and itemsonthispage < itemsperpage:
2832  menuitem=menuitems[ itemsonthispage ]
2833 
2834  itemsonthispage+=1
2835 
2836  #Get the XML containing information about this item
2837  infoDOM = xml.dom.minidom.parse( os.path.join(getItemTempPath(itemnum),"info.xml") )
2838  #Error out if its the wrong XML
2839  if infoDOM.documentElement.tagName != "fileinfo":
2840  fatalError("The info.xml file (%s) doesn't look right" % os.path.join(getItemTempPath(itemnum),"info.xml"))
2841 
2842  #write( themedom.toprettyxml())
2843 
2844  #Add this recording to this page's menu...
2845  button=dvddom.createElement("button")
2846  button.setAttribute("name","%s" % itemnum)
2847  button.appendChild(dvddom.createTextNode("{g2=" + "%s" % itemsonthispage + "; g5=0; jump title %s;}" % itemnum))
2848  menupgc.appendChild(button)
2849  del button
2850 
2851  #Create a TITLESET for each item
2852  titleset = dvddom.createElement("titleset")
2853  dvdauthor_element.appendChild(titleset)
2854 
2855  #Comment XML file with title of video
2856  comment = getText(infoDOM.getElementsByTagName("title")[0]).replace('--', '-')
2857  titleset.appendChild( dvddom.createComment(comment))
2858 
2859  menus= dvddom.createElement("menus")
2860  titleset.appendChild(menus)
2861 
2862  video = dvddom.createElement("video")
2863  video.setAttribute("format",videomode)
2864 
2865  # set the right aspect ratio
2866  if chaptermenuAspectRatio == "4:3":
2867  video.setAttribute("aspect", "4:3")
2868  elif chaptermenuAspectRatio == "16:9":
2869  video.setAttribute("aspect", "16:9")
2870  video.setAttribute("widescreen", "nopanscan")
2871  else:
2872  # use same aspect ratio as the video
2873  if getAspectRatioOfVideo(itemnum) > aspectRatioThreshold:
2874  video.setAttribute("aspect", "16:9")
2875  video.setAttribute("widescreen", "nopanscan")
2876  else:
2877  video.setAttribute("aspect", "4:3")
2878 
2879  menus.appendChild(video)
2880 
2881  if wantChapterMenu:
2882  mymenupgc = dvddom.createElement("pgc")
2883  menus.appendChild(mymenupgc)
2884 
2885  pre = dvddom.createElement("pre")
2886  mymenupgc.appendChild(pre)
2887  if wantDetailsPage:
2888  pre.appendChild(dvddom.createTextNode("{button=s7 - 1 * 1024;}"))
2889  else:
2890  pre.appendChild(dvddom.createTextNode("{button=s7 * 1024;}"))
2891 
2892  vob = dvddom.createElement("vob")
2893  vob.setAttribute("file",os.path.join(getTempPath(),"chaptermenu-%s.mpg" % itemnum))
2894  mymenupgc.appendChild(vob)
2895 
2896  #Loop menu forever
2897  post = dvddom.createElement("post")
2898  post.appendChild(dvddom.createTextNode("jump cell 1;"))
2899  mymenupgc.appendChild(post)
2900 
2901  # the first chapter MUST be 00:00:00 if its not dvdauthor adds it which
2902  # throws of the chapter selection - so make sure we add it if needed so we
2903  # can compensate for it in the chapter selection menu
2904  firstChapter = 0
2905  thumblist = createVideoChapters(itemnum, chapters, getLengthOfVideo(itemnum), False)
2906  chapterlist = thumblist.split(",")
2907  if chapterlist[0] != '00:00:00':
2908  firstChapter = 1
2909  x = 1
2910  while x <= chapters:
2911  #Add this recording to this page's menu...
2912  button = dvddom.createElement("button")
2913  button.setAttribute("name","%s" % x)
2914  if wantDetailsPage:
2915  button.appendChild(dvddom.createTextNode("jump title %s chapter %s;" % (1, firstChapter + x + 1)))
2916  else:
2917  button.appendChild(dvddom.createTextNode("jump title %s chapter %s;" % (1, firstChapter + x)))
2918 
2919  mymenupgc.appendChild(button)
2920  del button
2921  x += 1
2922 
2923  #add the titlemenu button if required
2924  submenunode = themeDOM.getElementsByTagName("submenu")
2925  submenunode = submenunode[0]
2926  titlemenunodes = submenunode.getElementsByTagName("titlemenu")
2927  if titlemenunodes.length > 0:
2928  button = dvddom.createElement("button")
2929  button.setAttribute("name","titlemenu")
2930  button.appendChild(dvddom.createTextNode("{jump vmgm menu;}"))
2931  mymenupgc.appendChild(button)
2932  del button
2933 
2934  titles = dvddom.createElement("titles")
2935  titleset.appendChild(titles)
2936 
2937  # set the right aspect ratio
2938  title_video = dvddom.createElement("video")
2939  title_video.setAttribute("format",videomode)
2940 
2941  if getAspectRatioOfVideo(itemnum) > aspectRatioThreshold:
2942  title_video.setAttribute("aspect", "16:9")
2943  title_video.setAttribute("widescreen", "nopanscan")
2944  else:
2945  title_video.setAttribute("aspect", "4:3")
2946 
2947  titles.appendChild(title_video)
2948 
2949  #set right audio format
2950  if doesFileExist(os.path.join(getItemTempPath(itemnum), "stream0.mp2")):
2951  title_audio = dvddom.createElement("audio")
2952  title_audio.setAttribute("format", "mp2")
2953  else:
2954  title_audio = dvddom.createElement("audio")
2955  title_audio.setAttribute("format", "ac3")
2956 
2957  titles.appendChild(title_audio)
2958 
2959  pgc = dvddom.createElement("pgc")
2960  titles.appendChild(pgc)
2961 
2962  if wantDetailsPage:
2963  #add the detail page intro for this item
2964  vob = dvddom.createElement("vob")
2965  vob.setAttribute("file",os.path.join(getTempPath(),"details-%s.mpg" % itemnum))
2966  pgc.appendChild(vob)
2967 
2968  vob = dvddom.createElement("vob")
2969  if wantChapterMenu:
2970  thumblist = createVideoChapters(itemnum, chapters, getLengthOfVideo(itemnum), False)
2971  chapterlist = thumblist.split(",")
2972  if chapterlist[0] != '00:00:00':
2973  thumblist = '00:00:00,' + thumblist
2974  vob.setAttribute("chapters", thumblist)
2975  else:
2976  vob.setAttribute("chapters",
2978  chapterLength,
2979  getLengthOfVideo(itemnum)))
2980 
2981  vob.setAttribute("file",os.path.join(getItemTempPath(itemnum),"final.vob"))
2982  pgc.appendChild(vob)
2983 
2984  post = dvddom.createElement("post")
2985  post.appendChild(dvddom.createTextNode("if (g5 eq %s) call vmgm menu %s; call vmgm menu %s;" % (itemnum + 1, autoplaymenu, page + 1)))
2986  pgc.appendChild(post)
2987 
2988  #Quick variable tidy up (not really required under Python)
2989  del titleset
2990  del titles
2991  del menus
2992  del video
2993  del pgc
2994  del vob
2995  del post
2996 
2997  #Loop through all the nodes inside this menu item and pick previous / next buttons
2998  for node in menuitem.childNodes:
2999 
3000  if node.nodeName=="previous":
3001  if page>1:
3002  button=dvddom.createElement("button")
3003  button.setAttribute("name","previous")
3004  button.appendChild(dvddom.createTextNode("{g2=1;jump menu %s;}" % page ))
3005  endbuttons.append(button)
3006 
3007 
3008  elif node.nodeName=="next":
3009  if itemnum < numberofitems:
3010  button=dvddom.createElement("button")
3011  button.setAttribute("name","next")
3012  button.appendChild(dvddom.createTextNode("{g2=1;jump menu %s;}" % (page + 2)))
3013  endbuttons.append(button)
3014 
3015  elif node.nodeName=="playall":
3016  button=dvddom.createElement("button")
3017  button.setAttribute("name","playall")
3018  button.appendChild(dvddom.createTextNode("{g5=1; jump menu %s;}" % autoplaymenu))
3019  endbuttons.append(button)
3020 
3021  #On to the next item
3022  itemnum+=1
3023 
3024  #Move on to the next page
3025  page+=1
3026 
3027  for button in endbuttons:
3028  menupgc.appendChild(button)
3029  del button
3030 
3031  menupgc = dvddom.createElement("pgc")
3032  menus_element.appendChild(menupgc)
3033  menupgc.setAttribute("pause","inf")
3034  menupgc.appendChild( dvddom.createComment("Autoplay hack") )
3035 
3036  dvdcode = ""
3037  while (itemnum > 1):
3038  itemnum-=1
3039  dvdcode += "if (g5 eq %s) {g5 = %s; jump title %s;} " % (itemnum, itemnum + 1, itemnum)
3040  dvdcode += "g5 = 0; jump menu 1;"
3041 
3042  pre = dvddom.createElement("pre")
3043  pre.appendChild(dvddom.createTextNode(dvdcode))
3044  menupgc.appendChild(pre)
3045 
3046  if wantIntro:
3047  #Menu creation is finished so we know how many pages were created
3048  #add to to jump to the correct one automatically
3049  dvdcode="if (g3 eq 1) {"
3050  while (page>1):
3051  page-=1;
3052  dvdcode+="if (g4 eq %s) " % page
3053  dvdcode+="jump menu %s;" % (page + 1)
3054  if (page>1):
3055  dvdcode+=" else "
3056  dvdcode+="}"
3057  vmgm_pre_node.appendChild(dvddom.createTextNode(dvdcode))
3058 
3059  #write(dvddom.toprettyxml())
3060  #Save xml to file
3061  WriteXMLToFile (dvddom,os.path.join(getTempPath(),"dvdauthor.xml"))
3062 
3063  #Destroy the DOM and free memory
3064  dvddom.unlink()
3065 
3066 
3068 
3069 def createDVDAuthorXMLNoMainMenu(screensize, numberofitems):
3070  """Creates the xml file for dvdauthor to use the MythBurn menus."""
3071 
3072  # creates a simple DVD with only a chapter menus shown before each video
3073  # can contain an intro movie and each title can have a details page
3074  # displayed before each title
3075 
3076  write( "Creating DVD XML file for dvd author (No Main Menu)")
3077  #FIXME:
3078  assert False
3079 
3080 
3082 
3083 def createDVDAuthorXMLNoMenus(screensize, numberofitems):
3084  """Creates the xml file for dvdauthor containing no menus."""
3085 
3086  # creates a simple DVD with no menus that chains the videos one after the other
3087  # can contain an intro movie and each title can have a details page
3088  # displayed before each title
3089 
3090  write( "Creating DVD XML file for dvd author (No Menus)")
3091 
3092  dvddom = xml.dom.minidom.parseString(
3093  '''
3094  <dvdauthor>
3095  <vmgm>
3096  <menus lang="en">
3097  <pgc entry="title" pause="0">
3098  </pgc>
3099  </menus>
3100  </vmgm>
3101  </dvdauthor>''')
3102 
3103  dvdauthor_element = dvddom.documentElement
3104  menus = dvdauthor_element.childNodes[1].childNodes[1]
3105  menu_pgc = menus.childNodes[1]
3106 
3107  dvdauthor_element.insertBefore(dvddom.createComment("dvdauthor XML file created by MythBurn script"), dvdauthor_element.firstChild )
3108  dvdauthor_element.setAttribute("dest",os.path.join(getTempPath(),"dvd"))
3109 
3110  # create pgc for menu 1 holds the intro if required, blank mpg if not
3111  if wantIntro:
3112  video = dvddom.createElement("video")
3113  video.setAttribute("format", videomode)
3114 
3115  # set aspect ratio
3116  if mainmenuAspectRatio == "4:3":
3117  video.setAttribute("aspect", "4:3")
3118  else:
3119  video.setAttribute("aspect", "16:9")
3120  video.setAttribute("widescreen", "nopanscan")
3121  menus.appendChild(video)
3122 
3123  pre = dvddom.createElement("pre")
3124  pre.appendChild(dvddom.createTextNode("if (g2==1) jump menu 2;"))
3125  menu_pgc.appendChild(pre)
3126 
3127  node = themeDOM.getElementsByTagName("intro")[0]
3128  introFile = node.attributes["filename"].value
3129 
3130  vob = dvddom.createElement("vob")
3131  vob.setAttribute("file", getThemeFile(themeName, videomode + '_' + introFile))
3132  menu_pgc.appendChild(vob)
3133 
3134  post = dvddom.createElement("post")
3135  post.appendChild(dvddom.createTextNode("g2=1; jump menu 2;"))
3136  menu_pgc.appendChild(post)
3137  del menu_pgc
3138  del post
3139  del pre
3140  del vob
3141  else:
3142  pre = dvddom.createElement("pre")
3143  pre.appendChild(dvddom.createTextNode("g2=1;jump menu 2;"))
3144  menu_pgc.appendChild(pre)
3145 
3146  vob = dvddom.createElement("vob")
3147  vob.setAttribute("file", getThemeFile(themeName, videomode + '_' + "blank.mpg"))
3148  menu_pgc.appendChild(vob)
3149 
3150  del menu_pgc
3151  del pre
3152  del vob
3153 
3154  # create menu 2 - dummy menu that allows us to jump to each titleset in sequence
3155  menu_pgc = dvddom.createElement("pgc")
3156 
3157  preText = "if (g1==0) g1=1;"
3158  for i in range(numberofitems):
3159  preText += "if (g1==%d) jump titleset %d menu;" % (i + 1, i + 1)
3160 
3161  pre = dvddom.createElement("pre")
3162  pre.appendChild(dvddom.createTextNode(preText))
3163  menu_pgc.appendChild(pre)
3164 
3165  vob = dvddom.createElement("vob")
3166  vob.setAttribute("file", getThemeFile(themeName, videomode + '_' + "blank.mpg"))
3167  menu_pgc.appendChild(vob)
3168  menus.appendChild(menu_pgc)
3169 
3170  # for each title add a <titleset> section
3171  itemNum = 1
3172  while itemNum <= numberofitems:
3173  write( "Adding item %s" % itemNum)
3174 
3175  titleset = dvddom.createElement("titleset")
3176  dvdauthor_element.appendChild(titleset)
3177 
3178  # create menu
3179  menu = dvddom.createElement("menus")
3180  menupgc = dvddom.createElement("pgc")
3181  menu.appendChild(menupgc)
3182  titleset.appendChild(menu)
3183 
3184  if wantDetailsPage:
3185  #add the detail page intro for this item
3186  vob = dvddom.createElement("vob")
3187  vob.setAttribute("file", os.path.join(getTempPath(),"details-%s.mpg" % itemNum))
3188  menupgc.appendChild(vob)
3189 
3190  post = dvddom.createElement("post")
3191  post.appendChild(dvddom.createTextNode("jump title 1;"))
3192  menupgc.appendChild(post)
3193  del post
3194  else:
3195  #add dummy menu for this item
3196  pre = dvddom.createElement("pre")
3197  pre.appendChild(dvddom.createTextNode("jump title 1;"))
3198  menupgc.appendChild(pre)
3199  del pre
3200 
3201  vob = dvddom.createElement("vob")
3202  vob.setAttribute("file", getThemeFile(themeName, videomode + '_' + "blank.mpg"))
3203  menupgc.appendChild(vob)
3204 
3205  titles = dvddom.createElement("titles")
3206 
3207  # set the right aspect ratio
3208  title_video = dvddom.createElement("video")
3209  title_video.setAttribute("format", videomode)
3210 
3211  # use aspect ratio of video
3212  if getAspectRatioOfVideo(itemNum) > aspectRatioThreshold:
3213  title_video.setAttribute("aspect", "16:9")
3214  title_video.setAttribute("widescreen", "nopanscan")
3215  else:
3216  title_video.setAttribute("aspect", "4:3")
3217 
3218  titles.appendChild(title_video)
3219 
3220  pgc = dvddom.createElement("pgc")
3221 
3222  vob = dvddom.createElement("vob")
3223  vob.setAttribute("file", os.path.join(getItemTempPath(itemNum), "final.vob"))
3224  vob.setAttribute("chapters", createVideoChaptersFixedLength(itemNum,
3225  chapterLength,
3226  getLengthOfVideo(itemNum)))
3227  pgc.appendChild(vob)
3228 
3229  del vob
3230  del menupgc
3231 
3232  post = dvddom.createElement("post")
3233  if itemNum == numberofitems:
3234  post.appendChild(dvddom.createTextNode("exit;"))
3235  else:
3236  post.appendChild(dvddom.createTextNode("g1=%d;call vmgm menu 2;" % (itemNum + 1)))
3237 
3238  pgc.appendChild(post)
3239 
3240  titles.appendChild(pgc)
3241  titleset.appendChild(titles)
3242 
3243  del pgc
3244  del titles
3245  del title_video
3246  del post
3247  del titleset
3248 
3249  itemNum +=1
3250 
3251  #Save xml to file
3252  WriteXMLToFile (dvddom,os.path.join(getTempPath(),"dvdauthor.xml"))
3253 
3254  #Destroy the DOM and free memory
3255  dvddom.unlink()
3256 
3257 
3259 
3261  previewfolder = os.path.join(getItemTempPath(videoitem), "preview")
3262  if os.path.exists(previewfolder):
3263  deleteAllFilesInFolder(previewfolder)
3264  os.rmdir (previewfolder)
3265  os.makedirs(previewfolder)
3266  return previewfolder
3267 
3268 
3270 
3271 def generateVideoPreview(videoitem, itemonthispage, menuitem, starttime, menulength, previewfolder):
3272  """generate thumbnails for a preview in a menu"""
3273 
3274  positionx = 9999
3275  positiony = 9999
3276  width = 0
3277  height = 0
3278  maskpicture = None
3279 
3280  #run through the theme items and find any graphics that is using a movie identifier
3281  for node in menuitem.childNodes:
3282  if node.nodeName=="graphic":
3283  if node.attributes["filename"].value == "%movie":
3284  #This is a movie preview item so we need to generate the thumbnails
3285  inputfile = os.path.join(getItemTempPath(videoitem),"stream.mv2")
3286  outputfile = os.path.join(previewfolder, "preview-i%d-t%%1-f%%2.jpg" % itemonthispage)
3287  width = getScaledAttribute(node, "w")
3288  height = getScaledAttribute(node, "h")
3289  frames = int(secondsToFrames(menulength))
3290 
3291  command = "mytharchivehelper -q -q --createthumbnail --infile %s --thumblist '%s' --outfile %s --framecount %d" % (quoteCmdArg(inputfile), starttime, quoteCmdArg(outputfile), frames)
3292  result = runCommand(command)
3293  if (result != 0):
3294  write( "mytharchivehelper failed with code %d. Command = %s" % (result, command) )
3295 
3296  positionx = getScaledAttribute(node, "x")
3297  positiony = getScaledAttribute(node, "y")
3298 
3299  #see if this graphics item has a mask
3300  if node.hasAttribute("mask"):
3301  imagemaskfilename = getThemeFile(themeName, node.attributes["mask"].value)
3302  if node.attributes["mask"].value != "" and doesFileExist(imagemaskfilename):
3303  maskpicture = Image.open(imagemaskfilename,"r").resize((width, height))
3304  maskpicture = maskpicture.convert("RGBA")
3305 
3306  return (positionx, positiony, width, height, maskpicture)
3307 
3308 
3310 
3311 def drawThemeItem(page, itemsonthispage, itemnum, menuitem, bgimage, draw,
3312  bgimagemask, drawmask, highlightcolor, spumuxdom, spunode,
3313  numberofitems, chapternumber, chapterlist):
3314  """Draws text and graphics onto a dvd menu, called by
3315  createMenu and createChapterMenu"""
3316 
3317  #Get the XML containing information about this item
3318  infoDOM = xml.dom.minidom.parse(os.path.join(getItemTempPath(itemnum), "info.xml"))
3319 
3320  #Error out if its the wrong XML
3321  if infoDOM.documentElement.tagName != "fileinfo":
3322  fatalError("The info.xml file (%s) doesn't look right" %
3323  os.path.join(getItemTempPath(itemnum),"info.xml"))
3324 
3325  #boundarybox holds the max and min dimensions for this item
3326  #so we can auto build a menu highlight box
3327  boundarybox = 9999,9999,0,0
3328  wantHighlightBox = True
3329 
3330  #Loop through all the nodes inside this menu item
3331  for node in menuitem.childNodes:
3332 
3333  #Process each type of item to add it onto the background image
3334  if node.nodeName=="graphic":
3335  #Overlay graphic image onto background
3336 
3337  # draw background if required
3338  paintBackground(bgimage, node)
3339 
3340  # if this graphic item is a movie thumbnail then we dont process it here
3341  if node.attributes["filename"].value == "%movie":
3342  # this is a movie item but we must still update the boundary box
3343  boundarybox = checkBoundaryBox(boundarybox, node)
3344  else:
3345  imagefilename = expandItemText(infoDOM,
3346  node.attributes["filename"].value,
3347  itemnum, page, itemsonthispage,
3348  chapternumber, chapterlist)
3349 
3350  if doesFileExist(imagefilename) == False:
3351  if imagefilename == node.attributes["filename"].value:
3352  imagefilename = getThemeFile(themeName,
3353  node.attributes["filename"].value)
3354 
3355  # see if an image mask exists
3356  maskfilename = None
3357  if node.hasAttribute("mask") and node.attributes["mask"].value != "":
3358  maskfilename = getThemeFile(themeName, node.attributes["mask"].value)
3359 
3360  # if this is a thumb image and is a MythVideo coverart image then preserve
3361  # its aspect ratio unless overriden later by the theme
3362  if (node.attributes["filename"].value == "%thumbnail"
3363  and getText(infoDOM.getElementsByTagName("coverfile")[0]) !=""):
3364  stretch = False
3365  else:
3366  stretch = True
3367 
3368  if paintImage(imagefilename, maskfilename, node, bgimage, stretch):
3369  boundarybox = checkBoundaryBox(boundarybox, node)
3370  else:
3371  write("Image file does not exist '%s'" % imagefilename)
3372 
3373  elif node.nodeName == "text":
3374  # Apply some text to the background, including wordwrap if required.
3375 
3376  # draw background if required
3377  paintBackground(bgimage, node)
3378 
3379  text = expandItemText(infoDOM,node.attributes["value"].value,
3380  itemnum, page, itemsonthispage,
3381  chapternumber, chapterlist)
3382 
3383  if text>"":
3384  paintText(draw, bgimage, text, node)
3385 
3386  boundarybox = checkBoundaryBox(boundarybox, node)
3387  del text
3388 
3389  elif node.nodeName=="previous":
3390  if page>1:
3391  #Overlay previous graphic button onto background
3392 
3393  # draw background if required
3394  paintBackground(bgimage, node)
3395 
3396  paintButton(draw, bgimage, bgimagemask, node, infoDOM,
3397  itemnum, page, itemsonthispage, chapternumber,
3398  chapterlist)
3399 
3400  button = spumuxdom.createElement("button")
3401  button.setAttribute("name","previous")
3402  button.setAttribute("x0","%s" % getScaledAttribute(node, "x"))
3403  button.setAttribute("y0","%s" % getScaledAttribute(node, "y"))
3404  button.setAttribute("x1","%s" % (getScaledAttribute(node, "x") +
3405  getScaledAttribute(node, "w")))
3406  button.setAttribute("y1","%s" % (getScaledAttribute(node, "y") +
3407  getScaledAttribute(node, "h")))
3408  spunode.appendChild(button)
3409 
3410  write( "Added previous page button")
3411 
3412 
3413  elif node.nodeName == "next":
3414  if itemnum < numberofitems:
3415  #Overlay next graphic button onto background
3416 
3417  # draw background if required
3418  paintBackground(bgimage, node)
3419 
3420  paintButton(draw, bgimage, bgimagemask, node, infoDOM,
3421  itemnum, page, itemsonthispage, chapternumber,
3422  chapterlist)
3423 
3424  button = spumuxdom.createElement("button")
3425  button.setAttribute("name","next")
3426  button.setAttribute("x0","%s" % getScaledAttribute(node, "x"))
3427  button.setAttribute("y0","%s" % getScaledAttribute(node, "y"))
3428  button.setAttribute("x1","%s" % (getScaledAttribute(node, "x") +
3429  getScaledAttribute(node, "w")))
3430  button.setAttribute("y1","%s" % (getScaledAttribute(node, "y") +
3431  getScaledAttribute(node, "h")))
3432  spunode.appendChild(button)
3433 
3434  write("Added next page button")
3435 
3436  elif node.nodeName=="playall":
3437  #Overlay playall graphic button onto background
3438 
3439  # draw background if required
3440  paintBackground(bgimage, node)
3441 
3442  paintButton(draw, bgimage, bgimagemask, node, infoDOM, itemnum, page,
3443  itemsonthispage, chapternumber, chapterlist)
3444 
3445  button = spumuxdom.createElement("button")
3446  button.setAttribute("name","playall")
3447  button.setAttribute("x0","%s" % getScaledAttribute(node, "x"))
3448  button.setAttribute("y0","%s" % getScaledAttribute(node, "y"))
3449  button.setAttribute("x1","%s" % (getScaledAttribute(node, "x") +
3450  getScaledAttribute(node, "w")))
3451  button.setAttribute("y1","%s" % (getScaledAttribute(node, "y") +
3452  getScaledAttribute(node, "h")))
3453  spunode.appendChild(button)
3454 
3455  write("Added playall button")
3456 
3457  elif node.nodeName == "titlemenu":
3458  if itemnum < numberofitems:
3459  #Overlay next graphic button onto background
3460 
3461  # draw background if required
3462  paintBackground(bgimage, node)
3463 
3464  paintButton(draw, bgimage, bgimagemask, node, infoDOM,
3465  itemnum, page, itemsonthispage, chapternumber,
3466  chapterlist)
3467 
3468  button = spumuxdom.createElement("button")
3469  button.setAttribute("name","titlemenu")
3470  button.setAttribute("x0","%s" % getScaledAttribute(node, "x"))
3471  button.setAttribute("y0","%s" % getScaledAttribute(node, "y"))
3472  button.setAttribute("x1","%s" % (getScaledAttribute(node, "x") +
3473  getScaledAttribute(node, "w")))
3474  button.setAttribute("y1","%s" % (getScaledAttribute(node, "y") +
3475  getScaledAttribute(node, "h")))
3476  spunode.appendChild(button)
3477 
3478  write( "Added titlemenu button")
3479 
3480  elif node.nodeName=="button":
3481  #Overlay item graphic/text button onto background
3482 
3483  # draw background if required
3484  paintBackground(bgimage, node)
3485 
3486  wantHighlightBox = False
3487 
3488  paintButton(draw, bgimage, bgimagemask, node, infoDOM, itemnum, page,
3489  itemsonthispage, chapternumber, chapterlist)
3490 
3491  boundarybox = checkBoundaryBox(boundarybox, node)
3492 
3493 
3494  elif node.nodeName=="#text" or node.nodeName=="#comment":
3495  #Do nothing
3496  assert True
3497  else:
3498  write( "Dont know how to process %s" % node.nodeName)
3499 
3500  if drawmask is None:
3501  return
3502 
3503  #Draw the selection mask for this item
3504  if wantHighlightBox == True:
3505  # Make the boundary box bigger than the content to avoid over writing it
3506  boundarybox=boundarybox[0]-1,boundarybox[1]-1,boundarybox[2]+1,boundarybox[3]+1
3507  drawmask.rectangle(boundarybox,outline=highlightcolor)
3508 
3509  # Draw another line to make the box thicker - PIL does not support linewidth
3510  boundarybox=boundarybox[0]-1,boundarybox[1]-1,boundarybox[2]+1,boundarybox[3]+1
3511  drawmask.rectangle(boundarybox,outline=highlightcolor)
3512 
3513  node = spumuxdom.createElement("button")
3514  #Fiddle this for chapter marks....
3515  if chapternumber>0:
3516  node.setAttribute("name","%s" % chapternumber)
3517  else:
3518  node.setAttribute("name","%s" % itemnum)
3519  node.setAttribute("x0","%d" % int(boundarybox[0]))
3520  node.setAttribute("y0","%d" % int(boundarybox[1]))
3521  node.setAttribute("x1","%d" % int(boundarybox[2] + 1))
3522  node.setAttribute("y1","%d" % int(boundarybox[3] + 1))
3523  spunode.appendChild(node)
3524 
3525 
3527 
3528 def createMenu(screensize, screendpi, numberofitems):
3529  """Creates all the necessary menu images and files for the MythBurn menus."""
3530 
3531  #Get the main menu node (we must only have 1)
3532  menunode=themeDOM.getElementsByTagName("menu")
3533  if menunode.length!=1:
3534  fatalError("Cannot find menu element in theme file")
3535  menunode=menunode[0]
3536 
3537  menuitems=menunode.getElementsByTagName("item")
3538  #Total number of video items on a single menu page (no less than 1!)
3539  itemsperpage = menuitems.length
3540  write( "Menu items per page %s" % itemsperpage)
3541 
3542  #Get background image filename
3543  backgroundfilename = menunode.attributes["background"].value
3544  if backgroundfilename=="":
3545  fatalError("Background image is not set in theme file")
3546 
3547  backgroundfilename = getThemeFile(themeName,backgroundfilename)
3548  write( "Background image file is %s" % backgroundfilename)
3549  if not doesFileExist(backgroundfilename):
3550  fatalError("Background image not found (%s)" % backgroundfilename)
3551 
3552  #Get highlight color
3553  highlightcolor = "red"
3554  if menunode.hasAttribute("highlightcolor"):
3555  highlightcolor = menunode.attributes["highlightcolor"].value
3556 
3557  #Get menu music
3558  menumusic = "menumusic.ac3"
3559  if menunode.hasAttribute("music"):
3560  menumusic = menunode.attributes["music"].value
3561 
3562  #Get menu length
3563  menulength = 15
3564  if menunode.hasAttribute("length"):
3565  menulength = int(menunode.attributes["length"].value)
3566 
3567  write("Music is %s, length is %s seconds" % (menumusic, menulength))
3568 
3569  #Page number counter
3570  page=1
3571 
3572  #Item counter to indicate current video item
3573  itemnum=1
3574 
3575  write("Creating DVD menus")
3576 
3577  while itemnum <= numberofitems:
3578  write("Menu page %s" % page)
3579 
3580  #need to check if any of the videos are flaged as movies
3581  #and if so generate the required preview
3582 
3583  write("Creating Preview Video")
3584  previewitem = itemnum
3585  itemsonthispage = 0
3586  haspreview = False
3587 
3588  previewx = []
3589  previewy = []
3590  previeww = []
3591  previewh = []
3592  previewmask = []
3593 
3594  while previewitem <= numberofitems and itemsonthispage < itemsperpage:
3595  menuitem=menuitems[ itemsonthispage ]
3596  itemsonthispage+=1
3597 
3598  #make sure the preview folder is empty and present
3599  previewfolder = createEmptyPreviewFolder(previewitem)
3600 
3601  #and then generate the preview if required (px=9999 means not required)
3602  px, py, pw, ph, maskimage = generateVideoPreview(previewitem, itemsonthispage, menuitem, 0, menulength, previewfolder)
3603  previewx.append(px)
3604  previewy.append(py)
3605  previeww.append(pw)
3606  previewh.append(ph)
3607  previewmask.append(maskimage)
3608  if px != 9999:
3609  haspreview = True
3610 
3611  previewitem+=1
3612 
3613  #previews generated but need to save where we started from
3614  savedpreviewitem = itemnum
3615 
3616  #Number of video items on this menu page
3617  itemsonthispage=0
3618 
3619  #instead of loading the background image and drawing on it we now
3620  #make a transparent image and draw all items on it. This overlay
3621  #image is then added to the required background image when the
3622  #preview items are added (the reason for this is it will assist
3623  #if the background image is actually a video)
3624 
3625  overlayimage=Image.new("RGBA",screensize)
3626  draw=ImageDraw.Draw(overlayimage)
3627 
3628  #Create image to hold button masks (same size as background)
3629  bgimagemask=Image.new("RGBA",overlayimage.size)
3630  drawmask=ImageDraw.Draw(bgimagemask)
3631 
3632  spumuxdom = xml.dom.minidom.parseString('<subpictures><stream><spu force="yes" start="00:00:00.0" highlight="" select="" ></spu></stream></subpictures>')
3633  spunode = spumuxdom.documentElement.firstChild.firstChild
3634 
3635  #Loop through all the items on this menu page
3636  while itemnum <= numberofitems and itemsonthispage < itemsperpage:
3637  menuitem=menuitems[ itemsonthispage ]
3638 
3639  itemsonthispage+=1
3640 
3641  drawThemeItem(page, itemsonthispage,
3642  itemnum, menuitem, overlayimage,
3643  draw, bgimagemask, drawmask, highlightcolor,
3644  spumuxdom, spunode, numberofitems, 0,"")
3645 
3646  #On to the next item
3647  itemnum+=1
3648 
3649  #Paste the overlay image onto the background
3650  bgimage=Image.open(backgroundfilename,"r").resize(screensize)
3651  bgimage.paste(overlayimage, (0,0), overlayimage)
3652 
3653  #Save this menu image and its mask
3654  rgb_bgimage=bgimage.convert('RGB')
3655  rgb_bgimage.save(os.path.join(getTempPath(),"background-%s.jpg" % page),"JPEG", quality=99)
3656  del rgb_bgimage
3657  bgimagemask.save(os.path.join(getTempPath(),"backgroundmask-%s.png" % page),"PNG",quality=99,optimize=0,dpi=screendpi)
3658 
3659  #now that the base background has been made and all the previews generated
3660  #we need to add the previews to the background
3661  #Assumption: We assume that there is nothing in the location of where the items go
3662  #(ie, no text on the images)
3663 
3664  itemsonthispage = 0
3665 
3666  #numframes should be the number of preview images that have been created
3667  numframes=secondsToFrames(menulength)
3668 
3669  # only generate the preview video if required.
3670  if haspreview == True:
3671  write( "Generating the preview images" )
3672  framenum = 0
3673  while framenum < numframes:
3674  previewitem = savedpreviewitem
3675  itemsonthispage = 0
3676  while previewitem <= numberofitems and itemsonthispage < itemsperpage:
3677  itemsonthispage+=1
3678  if previewx[itemsonthispage-1] != 9999:
3679  previewpath = os.path.join(getItemTempPath(previewitem), "preview")
3680  previewfile = "preview-i%d-t1-f%d.jpg" % (itemsonthispage, framenum)
3681  imagefile = os.path.join(previewpath, previewfile)
3682 
3683  if doesFileExist(imagefile):
3684  picture = Image.open(imagefile, "r").resize((previeww[itemsonthispage-1], previewh[itemsonthispage-1]))
3685  picture = picture.convert("RGBA")
3686  imagemaskfile = os.path.join(previewpath, "mask-i%d.png" % itemsonthispage)
3687  if previewmask[itemsonthispage-1] is not None:
3688  bgimage.paste(picture, (previewx[itemsonthispage-1], previewy[itemsonthispage-1]), previewmask[itemsonthispage-1])
3689  else:
3690  bgimage.paste(picture, (previewx[itemsonthispage-1], previewy[itemsonthispage-1]))
3691  del picture
3692  previewitem+=1
3693  #bgimage.save(os.path.join(getTempPath(),"background-%s-f%06d.png" % (page, framenum)),"PNG",quality=100,optimize=0,dpi=screendpi)
3694  rgb_bgimage=bgimage.convert('RGB')
3695  rgb_bgimage.save(os.path.join(getTempPath(),"background-%s-f%06d.jpg" % (page, framenum)),"JPEG",quality=99)
3696  del rgb_bgimage
3697  framenum+=1
3698 
3699  spumuxdom.documentElement.firstChild.firstChild.setAttribute("select",os.path.join(getTempPath(),"backgroundmask-%s.png" % page))
3700  spumuxdom.documentElement.firstChild.firstChild.setAttribute("highlight",os.path.join(getTempPath(),"backgroundmask-%s.png" % page))
3701 
3702  #Release large amounts of memory ASAP !
3703  del draw
3704  del bgimage
3705  del drawmask
3706  del bgimagemask
3707  del overlayimage
3708  del previewx
3709  del previewy
3710  del previewmask
3711 
3712  WriteXMLToFile (spumuxdom,os.path.join(getTempPath(),"spumux-%s.xml" % page))
3713 
3714  if mainmenuAspectRatio == "4:3":
3715  aspect_ratio = 2
3716  else:
3717  aspect_ratio = 3
3718 
3719  write("Encoding Menu Page %s using aspect ratio '%s'" % (page, mainmenuAspectRatio))
3720  if haspreview == True:
3721  encodeMenu(os.path.join(getTempPath(),"background-%s-f%%06d.jpg" % page),
3722  os.path.join(getTempPath(),"temp.m2v"),
3723  getThemeFile(themeName,menumusic),
3724  menulength,
3725  os.path.join(getTempPath(),"temp.mpg"),
3726  os.path.join(getTempPath(),"spumux-%s.xml" % page),
3727  os.path.join(getTempPath(),"menu-%s.mpg" % page),
3728  aspect_ratio)
3729  else:
3730  encodeMenu(os.path.join(getTempPath(),"background-%s.jpg" % page),
3731  os.path.join(getTempPath(),"temp.m2v"),
3732  getThemeFile(themeName,menumusic),
3733  menulength,
3734  os.path.join(getTempPath(),"temp.mpg"),
3735  os.path.join(getTempPath(),"spumux-%s.xml" % page),
3736  os.path.join(getTempPath(),"menu-%s.mpg" % page),
3737  aspect_ratio)
3738 
3739  #Move on to the next page
3740  page+=1
3741 
3742 
3744 
3745 def createChapterMenu(screensize, screendpi, numberofitems):
3746  """Creates all the necessary menu images and files for the MythBurn menus."""
3747 
3748  #Get the main menu node (we must only have 1)
3749  menunode=themeDOM.getElementsByTagName("submenu")
3750  if menunode.length!=1:
3751  fatalError("Cannot find submenu element in theme file")
3752  menunode=menunode[0]
3753 
3754  menuitems=menunode.getElementsByTagName("chapter")
3755  #Total number of video items on a single menu page (no less than 1!)
3756  itemsperpage = menuitems.length
3757  write( "Chapter items per page %s " % itemsperpage)
3758 
3759  #Get background image filename
3760  backgroundfilename = menunode.attributes["background"].value
3761  if backgroundfilename=="":
3762  fatalError("Background image is not set in theme file")
3763  backgroundfilename = getThemeFile(themeName,backgroundfilename)
3764  write( "Background image file is %s" % backgroundfilename)
3765  if not doesFileExist(backgroundfilename):
3766  fatalError("Background image not found (%s)" % backgroundfilename)
3767 
3768  #Get highlight color
3769  highlightcolor = "red"
3770  if menunode.hasAttribute("highlightcolor"):
3771  highlightcolor = menunode.attributes["highlightcolor"].value
3772 
3773  #Get menu music
3774  menumusic = "menumusic.ac3"
3775  if menunode.hasAttribute("music"):
3776  menumusic = menunode.attributes["music"].value
3777 
3778  #Get menu length
3779  menulength = 15
3780  if menunode.hasAttribute("length"):
3781  menulength = int(menunode.attributes["length"].value)
3782 
3783  write("Music is %s, length is %s seconds" % (menumusic, menulength))
3784 
3785  #Page number counter
3786  page=1
3787 
3788  write( "Creating DVD sub-menus")
3789 
3790  while page <= numberofitems:
3791  write( "Sub-menu %s " % page)
3792 
3793  #instead of loading the background image and drawing on it we now
3794  #make a transparent image and draw all items on it. This overlay
3795  #image is then added to the required background image when the
3796  #preview items are added (the reason for this is it will assist
3797  #if the background image is actually a video)
3798 
3799  overlayimage=Image.new("RGBA",screensize, (0,0,0,0))
3800  draw=ImageDraw.Draw(overlayimage)
3801 
3802  #Create image to hold button masks (same size as background)
3803  bgimagemask=Image.new("RGBA",overlayimage.size, (0,0,0,0))
3804  drawmask=ImageDraw.Draw(bgimagemask)
3805 
3806  spumuxdom = xml.dom.minidom.parseString('<subpictures><stream><spu force="yes" start="00:00:00.0" highlight="" select="" ></spu></stream></subpictures>')
3807  spunode = spumuxdom.documentElement.firstChild.firstChild
3808 
3809  #Extract the thumbnails
3810  chapterlist=createVideoChapters(page,itemsperpage,getLengthOfVideo(page),True)
3811  chapterlist=chapterlist.split(",")
3812 
3813  #now need to preprocess the menu to see if any preview videos are required
3814  #This must be done on an individual basis since we do the resize as the
3815  #images are extracted.
3816 
3817  #first make sure the preview folder is empty and present
3818  previewfolder = createEmptyPreviewFolder(page)
3819 
3820  haspreview = False
3821 
3822  previewtime = 0
3823  previewchapter = 0
3824  previewx = []
3825  previewy = []
3826  previeww = []
3827  previewh = []
3828  previewmask = []
3829 
3830  while previewchapter < itemsperpage:
3831  menuitem=menuitems[ previewchapter ]
3832 
3833  previewtime = timeStringToSeconds(chapterlist[previewchapter])
3834 
3835  #generate the preview if required (px=9999 means not required)
3836  px, py, pw, ph, maskimage = generateVideoPreview(page, previewchapter, menuitem, previewtime, menulength, previewfolder)
3837  previewx.append(px)
3838  previewy.append(py)
3839  previeww.append(pw)
3840  previewh.append(ph)
3841  previewmask.append(maskimage)
3842 
3843  if px != 9999:
3844  haspreview = True
3845 
3846  previewchapter+=1
3847 
3848  #Loop through all the items on this menu page
3849  chapter=0
3850  while chapter < itemsperpage: # and itemsonthispage < itemsperpage:
3851  menuitem=menuitems[ chapter ]
3852  chapter+=1
3853 
3854  drawThemeItem(page, itemsperpage, page, menuitem,
3855  overlayimage, draw,
3856  bgimagemask, drawmask, highlightcolor,
3857  spumuxdom, spunode,
3858  999, chapter, chapterlist)
3859 
3860  #Save this menu image and its mask
3861  bgimage=Image.open(backgroundfilename,"r").resize(screensize)
3862  bgimage.paste(overlayimage, (0,0), overlayimage)
3863  rgb_bgimage=bgimage.convert('RGB')
3864  rgb_bgimage.save(os.path.join(getTempPath(),"chaptermenu-%s.jpg" % page),"JPEG", quality=99)
3865  del rgb_bgimage
3866 
3867  bgimagemask.save(os.path.join(getTempPath(),"chaptermenumask-%s.png" % page),"PNG",quality=90,optimize=0)
3868 
3869  if haspreview == True:
3870  numframes=secondsToFrames(menulength)
3871 
3872  #numframes should be the number of preview images that have been created
3873 
3874  write( "Generating the preview images" )
3875  framenum = 0
3876  while framenum < numframes:
3877  previewchapter = 0
3878  while previewchapter < itemsperpage:
3879  if previewx[previewchapter] != 9999:
3880  previewpath = os.path.join(getItemTempPath(page), "preview")
3881  previewfile = "preview-i%d-t1-f%d.jpg" % (previewchapter, framenum)
3882  imagefile = os.path.join(previewpath, previewfile)
3883 
3884  if doesFileExist(imagefile):
3885  picture = Image.open(imagefile, "r").resize((previeww[previewchapter], previewh[previewchapter]))
3886  picture = picture.convert("RGBA")
3887  imagemaskfile = os.path.join(previewpath, "mask-i%d.png" % previewchapter)
3888  if previewmask[previewchapter] is not None:
3889  bgimage.paste(picture, (previewx[previewchapter], previewy[previewchapter]), previewmask[previewchapter])
3890  else:
3891  bgimage.paste(picture, (previewx[previewchapter], previewy[previewchapter]))
3892  del picture
3893  previewchapter+=1
3894  rgb_bgimage=bgimage.convert('RGB')
3895  rgb_bgimage.save(os.path.join(getTempPath(),"chaptermenu-%s-f%06d.jpg" % (page, framenum)),"JPEG",quality=99)
3896  del rgb_bgimage
3897  framenum+=1
3898 
3899  spumuxdom.documentElement.firstChild.firstChild.setAttribute("select",os.path.join(getTempPath(),"chaptermenumask-%s.png" % page))
3900  spumuxdom.documentElement.firstChild.firstChild.setAttribute("highlight",os.path.join(getTempPath(),"chaptermenumask-%s.png" % page))
3901 
3902  #Release large amounts of memory ASAP !
3903  del draw
3904  del bgimage
3905  del drawmask
3906  del bgimagemask
3907  del overlayimage
3908  del previewx
3909  del previewy
3910  del previewmask
3911 
3912  #write( spumuxdom.toprettyxml())
3913  WriteXMLToFile (spumuxdom,os.path.join(getTempPath(),"chapterspumux-%s.xml" % page))
3914 
3915  if chaptermenuAspectRatio == "4:3":
3916  aspect_ratio = '2'
3917  elif chaptermenuAspectRatio == "16:9":
3918  aspect_ratio = '3'
3919  else:
3920  if getAspectRatioOfVideo(page) > aspectRatioThreshold:
3921  aspect_ratio = '3'
3922  else:
3923  aspect_ratio = '2'
3924 
3925  write("Encoding Chapter Menu Page %s using aspect ratio '%s'" % (page, chaptermenuAspectRatio))
3926 
3927  if haspreview == True:
3928  encodeMenu(os.path.join(getTempPath(),"chaptermenu-%s-f%%06d.jpg" % page),
3929  os.path.join(getTempPath(),"temp.m2v"),
3930  getThemeFile(themeName,menumusic),
3931  menulength,
3932  os.path.join(getTempPath(),"temp.mpg"),
3933  os.path.join(getTempPath(),"chapterspumux-%s.xml" % page),
3934  os.path.join(getTempPath(),"chaptermenu-%s.mpg" % page),
3935  aspect_ratio)
3936  else:
3937  encodeMenu(os.path.join(getTempPath(),"chaptermenu-%s.jpg" % page),
3938  os.path.join(getTempPath(),"temp.m2v"),
3939  getThemeFile(themeName,menumusic),
3940  menulength,
3941  os.path.join(getTempPath(),"temp.mpg"),
3942  os.path.join(getTempPath(),"chapterspumux-%s.xml" % page),
3943  os.path.join(getTempPath(),"chaptermenu-%s.mpg" % page),
3944  aspect_ratio)
3945 
3946  #Move on to the next page
3947  page+=1
3948 
3949 
3951 
3952 def createDetailsPage(screensize, screendpi, numberofitems):
3953  """Creates all the necessary images and files for the details page."""
3954 
3955  write( "Creating details pages")
3956 
3957  #Get the detailspage node (we must only have 1)
3958  detailnode=themeDOM.getElementsByTagName("detailspage")
3959  if detailnode.length!=1:
3960  fatalError("Cannot find detailspage element in theme file")
3961  detailnode=detailnode[0]
3962 
3963  #Get background image filename
3964  backgroundfilename = detailnode.attributes["background"].value
3965  if backgroundfilename=="":
3966  fatalError("Background image is not set in theme file")
3967  backgroundfilename = getThemeFile(themeName,backgroundfilename)
3968  write( "Background image file is %s" % backgroundfilename)
3969  if not doesFileExist(backgroundfilename):
3970  fatalError("Background image not found (%s)" % backgroundfilename)
3971 
3972  #Get menu music
3973  menumusic = "menumusic.ac3"
3974  if detailnode.hasAttribute("music"):
3975  menumusic = detailnode.attributes["music"].value
3976 
3977  #Get menu length
3978  menulength = 15
3979  if detailnode.hasAttribute("length"):
3980  menulength = int(detailnode.attributes["length"].value)
3981 
3982  write("Music is %s, length is %s seconds" % (menumusic, menulength))
3983 
3984  #Item counter to indicate current video item
3985  itemnum=1
3986 
3987  while itemnum <= numberofitems:
3988  write( "Creating details page for %s" % itemnum)
3989 
3990  #make sure the preview folder is empty and present
3991  previewfolder = createEmptyPreviewFolder(itemnum)
3992  haspreview = False
3993 
3994  #and then generate the preview if required (px=9999 means not required)
3995  previewx, previewy, previeww, previewh, previewmask = generateVideoPreview(itemnum, 1, detailnode, 0, menulength, previewfolder)
3996  if previewx != 9999:
3997  haspreview = True
3998 
3999  #instead of loading the background image and drawing on it we now
4000  #make a transparent image and draw all items on it. This overlay
4001  #image is then added to the required background image when the
4002  #preview items are added (the reason for this is it will assist
4003  #if the background image is actually a video)
4004 
4005  overlayimage=Image.new("RGBA",screensize, (0,0,0,0))
4006  draw=ImageDraw.Draw(overlayimage)
4007 
4008  spumuxdom = xml.dom.minidom.parseString('<subpictures><stream><spu force="yes" start="00:00:00.0" highlight="" select="" ></spu></stream></subpictures>')
4009  spunode = spumuxdom.documentElement.firstChild.firstChild
4010 
4011  drawThemeItem(0, 0, itemnum, detailnode, overlayimage, draw, None, None,
4012  "", spumuxdom, spunode, numberofitems, 0, "")
4013 
4014  #Save this details image
4015  bgimage=Image.open(backgroundfilename,"r").resize(screensize)
4016  bgimage.paste(overlayimage, (0,0), overlayimage)
4017  rgb_bgimage=bgimage.convert('RGB')
4018  rgb_bgimage.save(os.path.join(getTempPath(),"details-%s.jpg" % itemnum),"JPEG", quality=99)
4019  del rgb_bgimage
4020 
4021  if haspreview == True:
4022  numframes=secondsToFrames(menulength)
4023 
4024  #numframes should be the number of preview images that have been created
4025  write( "Generating the detail preview images" )
4026  framenum = 0
4027  while framenum < numframes:
4028  if previewx != 9999:
4029  previewpath = os.path.join(getItemTempPath(itemnum), "preview")
4030  previewfile = "preview-i%d-t1-f%d.jpg" % (1, framenum)
4031  imagefile = os.path.join(previewpath, previewfile)
4032 
4033  if doesFileExist(imagefile):
4034  picture = Image.open(imagefile, "r").resize((previeww, previewh))
4035  picture = picture.convert("RGBA")
4036  imagemaskfile = os.path.join(previewpath, "mask-i%d.png" % 1)
4037  if previewmask is not None:
4038  bgimage.paste(picture, (previewx, previewy), previewmask)
4039  else:
4040  bgimage.paste(picture, (previewx, previewy))
4041  del picture
4042  rgb_bgimage=bgimage.convert('RGB')
4043  rgb_bgimage.save(os.path.join(getTempPath(),"details-%s-f%06d.jpg" % (itemnum, framenum)),"JPEG",quality=99)
4044  del rgb_bgimage
4045  framenum+=1
4046 
4047 
4048  #Release large amounts of memory ASAP !
4049  del draw
4050  del bgimage
4051 
4052  # always use the same aspect ratio as the video
4053  aspect_ratio='2'
4054  if getAspectRatioOfVideo(itemnum) > aspectRatioThreshold:
4055  aspect_ratio='3'
4056 
4057  #write( spumuxdom.toprettyxml())
4058  WriteXMLToFile (spumuxdom,os.path.join(getTempPath(),"detailsspumux-%s.xml" % itemnum))
4059 
4060  write("Encoding Details Page %s" % itemnum)
4061  if haspreview == True:
4062  encodeMenu(os.path.join(getTempPath(),"details-%s-f%%06d.jpg" % itemnum),
4063  os.path.join(getTempPath(),"temp.m2v"),
4064  getThemeFile(themeName,menumusic),
4065  menulength,
4066  os.path.join(getTempPath(),"temp.mpg"),
4067  "",
4068  os.path.join(getTempPath(),"details-%s.mpg" % itemnum),
4069  aspect_ratio)
4070  else:
4071  encodeMenu(os.path.join(getTempPath(),"details-%s.jpg" % itemnum),
4072  os.path.join(getTempPath(),"temp.m2v"),
4073  getThemeFile(themeName,menumusic),
4074  menulength,
4075  os.path.join(getTempPath(),"temp.mpg"),
4076  "",
4077  os.path.join(getTempPath(),"details-%s.mpg" % itemnum),
4078  aspect_ratio)
4079 
4080  #On to the next item
4081  itemnum+=1
4082 
4083 
4085 
4086 def isMediaAVIFile(file):
4087  fh = open(file, 'rb')
4088  Magic = fh.read(4)
4089  fh.close()
4090  return Magic=="RIFF"
4091 
4092 
4094 
4095 def processAudio(folder):
4096  """encode audio to ac3 for better compression and compatability with NTSC players"""
4097 
4098  # process track 1
4099  if not encodetoac3 and doesFileExist(os.path.join(folder,'stream0.mp2')):
4100  #don't re-encode to ac3 if the user doesn't want it
4101  write( "Audio track 1 is in mp2 format - NOT re-encoding to ac3")
4102  elif doesFileExist(os.path.join(folder,'stream0.mp2'))==True:
4103  write( "Audio track 1 is in mp2 format - re-encoding to ac3")
4104  encodeAudio("ac3",os.path.join(folder,'stream0.mp2'), os.path.join(folder,'stream0.ac3'),True)
4105  elif doesFileExist(os.path.join(folder,'stream0.mpa'))==True:
4106  write( "Audio track 1 is in mpa format - re-encoding to ac3")
4107  encodeAudio("ac3",os.path.join(folder,'stream0.mpa'), os.path.join(folder,'stream0.ac3'),True)
4108  elif doesFileExist(os.path.join(folder,'stream0.ac3'))==True:
4109  write( "Audio is already in ac3 format")
4110  else:
4111  fatalError("Track 1 - Unknown audio format or de-multiplex failed!")
4112 
4113  # process track 2
4114  if not encodetoac3 and doesFileExist(os.path.join(folder,'stream1.mp2')):
4115  #don't re-encode to ac3 if the user doesn't want it
4116  write( "Audio track 2 is in mp2 format - NOT re-encoding to ac3")
4117  elif doesFileExist(os.path.join(folder,'stream1.mp2'))==True:
4118  write( "Audio track 2 is in mp2 format - re-encoding to ac3")
4119  encodeAudio("ac3",os.path.join(folder,'stream1.mp2'), os.path.join(folder,'stream1.ac3'),True)
4120  elif doesFileExist(os.path.join(folder,'stream1.mpa'))==True:
4121  write( "Audio track 2 is in mpa format - re-encoding to ac3")
4122  encodeAudio("ac3",os.path.join(folder,'stream1.mpa'), os.path.join(folder,'stream1.ac3'),True)
4123  elif doesFileExist(os.path.join(folder,'stream1.ac3'))==True:
4124  write( "Audio is already in ac3 format")
4125 
4126 
4128 
4129 # tuple index constants
4130 VIDEO_INDEX = 0
4131 VIDEO_CODEC = 1
4132 VIDEO_ID = 2
4133 
4134 AUDIO_INDEX = 0
4135 AUDIO_CODEC = 1
4136 AUDIO_ID = 2
4137 AUDIO_LANG = 3
4138 
4139 def selectStreams(folder):
4140  """Choose the streams we want from the source file"""
4141 
4142  video = (-1, 'N/A', -1) # index, codec, ID
4143  audio1 = (-1, 'N/A', -1, 'N/A') # index, codec, ID, lang
4144  audio2 = (-1, 'N/A', -1, 'N/A')
4145 
4146  #open the XML containing information about this file
4147  infoDOM = xml.dom.minidom.parse(os.path.join(folder, 'streaminfo.xml'))
4148  #error out if its the wrong XML
4149  if infoDOM.documentElement.tagName != "file":
4150  fatalError("This does not look like a stream info file (%s)" % os.path.join(folder, 'streaminfo.xml'))
4151 
4152 
4153  #get video ID, CODEC
4154  nodes = infoDOM.getElementsByTagName("video")
4155  if nodes.length == 0:
4156  write("Didn't find any video elements in stream info file.!!!")
4157  write("");
4158  sys.exit(1)
4159  if nodes.length > 1:
4160  write("Found more than one video element in stream info file.!!!")
4161  node = nodes[0]
4162  video = (int(node.attributes["ffmpegindex"].value), node.attributes["codec"].value, int(node.attributes["id"].value))
4163 
4164  #get audioID's - we choose the best 2 audio streams using this algorithm
4165  # 1. if there is one or more stream(s) using the 1st preferred language we use that
4166  # 2. if there is one or more stream(s) using the 2nd preferred language we use that
4167  # 3. if we still haven't found a stream we use the stream with the lowest PID
4168  # 4. we prefer ac3 over mp2
4169  # 5. if there are more than one stream with the chosen language we use the one with the lowest PID
4170 
4171  write("Preferred audio languages %s and %s" % (preferredlang1, preferredlang2))
4172 
4173  nodes = infoDOM.getElementsByTagName("audio")
4174 
4175  if nodes.length == 0:
4176  write("Didn't find any audio elements in stream info file.!!!")
4177  write("");
4178  sys.exit(1)
4179 
4180  found = False
4181  # first try to find a stream with ac3 and preferred language 1
4182  for node in nodes:
4183  index = int(node.attributes["ffmpegindex"].value)
4184  lang = node.attributes["language"].value
4185  format = node.attributes["codec"].value.upper()
4186  pid = int(node.attributes["id"].value)
4187  if lang == preferredlang1 and format == "AC3":
4188  if found:
4189  if pid < audio1[AUDIO_ID]:
4190  audio1 = (index, format, pid, lang)
4191  else:
4192  audio1 = (index, format, pid, lang)
4193  found = True
4194 
4195  # second try to find a stream with mp2 and preferred language 1
4196  if not found:
4197  for node in nodes:
4198  index = int(node.attributes["ffmpegindex"].value)
4199  lang = node.attributes["language"].value
4200  format = node.attributes["codec"].value.upper()
4201  pid = int(node.attributes["id"].value)
4202  if lang == preferredlang1 and format == "MP2":
4203  if found:
4204  if pid < audio1[AUDIO_ID]:
4205  audio1 = (index, format, pid, lang)
4206  else:
4207  audio1 = (index, format, pid, lang)
4208  found = True
4209 
4210  # finally use the stream with the lowest pid, prefer ac3 over mp2
4211  if not found:
4212  for node in nodes:
4213  index = int(node.attributes["ffmpegindex"].value)
4214  format = node.attributes["codec"].value.upper()
4215  pid = int(node.attributes["id"].value)
4216  if not found:
4217  audio1 = (index, format, pid, lang)
4218  found = True
4219  else:
4220  if format == "AC3" and audio1[AUDIO_CODEC] == "MP2":
4221  audio1 = (index, format, pid, lang)
4222  else:
4223  if pid < audio1[AUDIO_ID]:
4224  audio1 = (index, format, pid, lang)
4225 
4226  # do we need to find a second audio stream?
4227  if preferredlang1 != preferredlang2 and nodes.length > 1:
4228  found = False
4229  # first try to find a stream with ac3 and preferred language 2
4230  for node in nodes:
4231  index = int(node.attributes["ffmpegindex"].value)
4232  lang = node.attributes["language"].value
4233  format = node.attributes["codec"].value.upper()
4234  pid = int(node.attributes["id"].value)
4235  if lang == preferredlang2 and format == "AC3":
4236  if found:
4237  if pid < audio2[AUDIO_ID]:
4238  audio2 = (index, format, pid, lang)
4239  else:
4240  audio2 = (index, format, pid, lang)
4241  found = True
4242 
4243  # second try to find a stream with mp2 and preferred language 2
4244  if not found:
4245  for node in nodes:
4246  index = int(node.attributes["ffmpegindex"].value)
4247  lang = node.attributes["language"].value
4248  format = node.attributes["codec"].value.upper()
4249  pid = int(node.attributes["id"].value)
4250  if lang == preferredlang2 and format == "MP2":
4251  if found:
4252  if pid < audio2[AUDIO_ID]:
4253  audio2 = (index, format, pid, lang)
4254  else:
4255  audio2 = (index, format, pid, lang)
4256  found = True
4257 
4258  # finally use the stream with the lowest pid, prefer ac3 over mp2
4259  if not found:
4260  for node in nodes:
4261  index = int(node.attributes["ffmpegindex"].value)
4262  format = node.attributes["codec"].value.upper()
4263  pid = int(node.attributes["id"].value)
4264  if not found:
4265  # make sure we don't choose the same stream as audio1
4266  if pid != audio1[AUDIO_ID]:
4267  audio2 = (index, format, pid, lang)
4268  found = True
4269  else:
4270  if format == "AC3" and audio2[AUDIO_CODEC] == "MP2" and pid != audio1[AUDIO_ID]:
4271  audio2 = (index, format, pid, lang)
4272  else:
4273  if pid < audio2[AUDIO_ID] and pid != audio1[AUDIO_ID]:
4274  audio2 = (index, format, pid, lang)
4275 
4276  write("Video id: 0x%x, Audio1: [%d] 0x%x (%s, %s), Audio2: [%d] - 0x%x (%s, %s)" % \
4277  (video[VIDEO_ID], audio1[AUDIO_INDEX], audio1[AUDIO_ID], audio1[AUDIO_CODEC], audio1[AUDIO_LANG], \
4278  audio2[AUDIO_INDEX], audio2[AUDIO_ID], audio2[AUDIO_CODEC], audio2[AUDIO_LANG]))
4279 
4280  return (video, audio1, audio2)
4281 
4282 
4284 
4285 # tuple index constants
4286 SUBTITLE_INDEX = 0
4287 SUBTITLE_CODEC = 1
4288 SUBTITLE_ID = 2
4289 SUBTITLE_LANG = 3
4290 
4292  """Choose the subtitle stream we want from the source file"""
4293 
4294  subtitle = (-1, 'N/A', -1, 'N/A') # index, codec, ID, lang
4295 
4296  #open the XML containing information about this file
4297  infoDOM = xml.dom.minidom.parse(os.path.join(folder, 'streaminfo.xml'))
4298  #error out if its the wrong XML
4299  if infoDOM.documentElement.tagName != "file":
4300  fatalError("This does not look like a stream info file (%s)" % os.path.join(folder, 'streaminfo.xml'))
4301 
4302 
4303  #get subtitle nodes
4304  nodes = infoDOM.getElementsByTagName("subtitle")
4305  if nodes.length == 0:
4306  write("Didn't find any subtitle elements in stream info file.")
4307  return subtitle
4308 
4309  write("Preferred languages %s and %s" % (preferredlang1, preferredlang2))
4310 
4311  found = False
4312  # first try to find a stream with preferred language 1
4313  for node in nodes:
4314  index = int(node.attributes["ffmpegindex"].value)
4315  lang = node.attributes["language"].value
4316  format = node.attributes["codec"].value.upper()
4317  pid = int(node.attributes["id"].value)
4318  if not found and lang == preferredlang1 and format == "dvbsub":
4319  subtitle = (index, format, pid, lang)
4320  found = True
4321 
4322  # second try to find a stream with preferred language 2
4323  if not found:
4324  for node in nodes:
4325  index = int(node.attributes["ffmpegindex"].value)
4326  lang = node.attributes["language"].value
4327  format = node.attributes["codec"].value.upper()
4328  pid = int(node.attributes["id"].value)
4329  if not found and lang == preferredlang2 and format == "dvbsub":
4330  subtitle = (index, format, pid, lang)
4331  found = True
4332 
4333  # finally use the first subtitle stream
4334  if not found:
4335  for node in nodes:
4336  index = int(node.attributes["ffmpegindex"].value)
4337  format = node.attributes["codec"].value.upper()
4338  pid = int(node.attributes["id"].value)
4339  if not found:
4340  subtitle = (index, format, pid, lang)
4341  found = True
4342 
4343  write("Subtitle id: 0x%x" % (subtitle[SUBTITLE_ID]))
4344 
4345  return subtitle
4346 
4347 
4349 
4350 def selectAspectRatio(folder):
4351  """figure out what aspect ratio we want from the source file"""
4352 
4353  #this should be smarter and look though the file for any AR changes
4354  #at the moment it just uses the AR found at the start of the file
4355 
4356  #open the XML containing information about this file
4357  infoDOM = xml.dom.minidom.parse(os.path.join(folder, 'streaminfo.xml'))
4358  #error out if its the wrong XML
4359  if infoDOM.documentElement.tagName != "file":
4360  fatalError("This does not look like a stream info file (%s)" % os.path.join(folder, 'streaminfo.xml'))
4361 
4362 
4363  #get aspect ratio
4364  nodes = infoDOM.getElementsByTagName("video")
4365  if nodes.length == 0:
4366  write("Didn't find any video elements in stream info file.!!!")
4367  write("");
4368  sys.exit(1)
4369  if nodes.length > 1:
4370  write("Found more than one video element in stream info file.!!!")
4371  node = nodes[0]
4372  try:
4373  ar = float(node.attributes["aspectratio"].value)
4374  if ar > float(4.0/3.0 - 0.01) and ar < float(4.0/3.0 + 0.01):
4375  aspectratio = "4:3"
4376  write("Aspect ratio is 4:3")
4377  elif ar > float(16.0/9.0 - 0.01) and ar < float(16.0/9.0 + 0.01):
4378  aspectratio = "16:9"
4379  write("Aspect ratio is 16:9")
4380  else:
4381  write("Unknown aspect ratio %f - Using 16:9" % ar)
4382  aspectratio = "16:9"
4383  except:
4384  aspectratio = "16:9"
4385 
4386  return aspectratio
4387 
4388 
4390 
4391 def getVideoCodec(folder):
4392  """Get the video codec from the streaminfo.xml for the file"""
4393 
4394  #open the XML containing information about this file
4395  infoDOM = xml.dom.minidom.parse(os.path.join(folder, 'streaminfo.xml'))
4396  #error out if its the wrong XML
4397  if infoDOM.documentElement.tagName != "file":
4398  fatalError("This does not look like a stream info file (%s)" % os.path.join(folder, 'streaminfo.xml'))
4399 
4400  nodes = infoDOM.getElementsByTagName("video")
4401  if nodes.length == 0:
4402  write("Didn't find any video elements in stream info file!!!")
4403  write("");
4404  sys.exit(1)
4405  if nodes.length > 1:
4406  write("Found more than one video element in stream info file!!!")
4407  node = nodes[0]
4408  return node.attributes["codec"].value
4409 
4410 
4412 
4413 def getFileType(folder):
4414  """Get the overall file type from the streaminfo.xml for the file"""
4415 
4416  #open the XML containing information about this file
4417  infoDOM = xml.dom.minidom.parse(os.path.join(folder, 'streaminfo.xml'))
4418  #error out if its the wrong XML
4419  if infoDOM.documentElement.tagName != "file":
4420  fatalError("This does not look like a stream info file (%s)" % os.path.join(folder, 'streaminfo.xml'))
4421 
4422  nodes = infoDOM.getElementsByTagName("file")
4423  if nodes.length == 0:
4424  write("Didn't find any file elements in stream info file!!!")
4425  write("");
4426  sys.exit(1)
4427  if nodes.length > 1:
4428  write("Found more than one file element in stream info file!!!")
4429  node = nodes[0]
4430 
4431  return node.attributes["type"].value
4432 
4433 
4435 
4436 def getStreamList(folder):
4437 
4438  # choose which streams we need
4439  video, audio1, audio2 = selectStreams(folder)
4440 
4441  streamList = "0x%x" % video[VIDEO_ID]
4442 
4443  if audio1[AUDIO_ID] != -1:
4444  streamList += ",0x%x" % audio1[AUDIO_ID]
4445 
4446  if audio2[AUDIO_ID] != -1:
4447  streamList += ",0x%x" % audio2[AUDIO_ID]
4448 
4449  # add subtitle stream id if required
4450  if addSubtitles:
4451  subtitles = selectSubtitleStream(folder)
4452  if subtitles[SUBTITLE_ID] != -1:
4453  streamList += ",0x%x" % subtitles[SUBTITLE_ID]
4454 
4455  return streamList;
4456 
4457 
4458 
4460 
4461 def isFileOkayForDVD(file, folder):
4462  """return true if the file is dvd compliant"""
4463 
4464  if not getVideoCodec(folder).lower().startswith("mpeg2video"):
4465  return False
4466 
4467 
4468 # if (getAudioCodec(folder)).lower() != "ac3" and encodeToAC3:
4469 # return False
4470 
4471  videosize = getVideoSize(os.path.join(folder, "streaminfo.xml"))
4472 
4473  # has the user elected to re-encode the file
4474  if file.hasAttribute("encodingprofile"):
4475  if file.attributes["encodingprofile"].value != "NONE":
4476  write("File will be re-encoded using profile %s" % file.attributes["encodingprofile"].value)
4477  return False
4478 
4479  if not isResolutionOkayForDVD(videosize):
4480  # file does not have a dvd resolution
4481  if file.hasAttribute("encodingprofile"):
4482  if file.attributes["encodingprofile"].value == "NONE":
4483  write("WARNING: File does not have a DVD compliant resolution but "
4484  "you have selected not to re-encode the file")
4485  return True
4486  else:
4487  return False
4488 
4489  return True
4490 
4491 
4494 
4495 def processFile(file, folder, count):
4496  """Process a single video/recording file ready for burning."""
4497 
4498  if useprojectx:
4499  doProcessFileProjectX(file, folder, count)
4500  else:
4501  doProcessFile(file, folder, count)
4502 
4503 
4506 
4507 def doProcessFile(file, folder, count):
4508  """Process a single video/recording file ready for burning."""
4509 
4510  write( "*************************************************************")
4511  write( "Processing %s %d: '%s'" % (file.attributes["type"].value, count, file.attributes["filename"].value))
4512  write( "*************************************************************")
4513 
4514  #As part of this routine we need to pre-process the video this MAY mean:
4515  #1. removing commercials/cleaning up mpeg2 stream
4516  #2. encoding to mpeg2 (if its an avi for instance or isn't DVD compatible)
4517  #3. selecting audio track to use and encoding audio from mp2 into ac3
4518  #4. de-multiplexing into video and audio steams)
4519 
4520  mediafile=""
4521 
4522  if file.hasAttribute("localfilename"):
4523  mediafile=file.attributes["localfilename"].value
4524  elif file.attributes["type"].value=="recording":
4525  mediafile = file.attributes["filename"].value
4526  elif file.attributes["type"].value=="video":
4527  mediafile=os.path.join(videopath, file.attributes["filename"].value)
4528  elif file.attributes["type"].value=="file":
4529  mediafile=file.attributes["filename"].value
4530  else:
4531  fatalError("Unknown type of video file it must be 'recording', 'video' or 'file'.")
4532 
4533  #Get the XML containing information about this item
4534  infoDOM = xml.dom.minidom.parse( os.path.join(folder,"info.xml") )
4535  #Error out if its the wrong XML
4536  if infoDOM.documentElement.tagName != "fileinfo":
4537  fatalError("The info.xml file (%s) doesn't look right" % os.path.join(folder,"info.xml"))
4538 
4539  #If this is an mpeg2 myth recording and there is a cut list available and the user wants to use it
4540  #run mythtranscode to cut out commercials etc
4541  if file.attributes["type"].value == "recording":
4542  #can only use mythtranscode to cut commercials on mpeg2 files
4543  write("File type is '%s'" % getFileType(folder))
4544  write("Video codec is '%s'" % getVideoCodec(folder))
4545  if (getVideoCodec(folder)).lower().startswith("mpeg2video"):
4546  if file.attributes["usecutlist"].value == "1" and getText(infoDOM.getElementsByTagName("hascutlist")[0]) == "yes":
4547  # Run from local file?
4548  if file.hasAttribute("localfilename"):
4549  localfile = file.attributes["localfilename"].value
4550  else:
4551  localfile = ""
4552  write("File has a cut list - running mythtranscode to remove unwanted segments")
4553  chanid = getText(infoDOM.getElementsByTagName("chanid")[0])
4554  starttime = getText(infoDOM.getElementsByTagName("starttime")[0])
4555  if runMythtranscode(chanid, starttime, os.path.join(folder,'newfile.mpg'), True, localfile):
4556  mediafile = os.path.join(folder,'newfile.mpg')
4557  else:
4558  write("Failed to run mythtranscode to remove unwanted segments")
4559  else:
4560  #does the user always want to run recordings through mythtranscode?
4561  #may help to fix any errors in the file
4562  if (alwaysRunMythtranscode == True or
4563  (getFileType(folder) == "mpegts" and isFileOkayForDVD(file, folder))):
4564  # Run from local file?
4565  if file.hasAttribute("localfilename"):
4566  localfile = file.attributes["localfilename"].value
4567  else:
4568  localfile = ""
4569  write("Running mythtranscode --mpeg2 to fix any errors")
4570  chanid = getText(infoDOM.getElementsByTagName("chanid")[0])
4571  starttime = getText(infoDOM.getElementsByTagName("starttime")[0])
4572  if runMythtranscode(chanid, starttime, os.path.join(folder, 'newfile.mpg'), False, localfile):
4573  mediafile = os.path.join(folder, 'newfile.mpg')
4574  else:
4575  write("Failed to run mythtranscode to fix any errors")
4576  else:
4577  #does the user always want to run mpeg2 files through mythtranscode?
4578  #may help to fix any errors in the file
4579  write("File type is '%s'" % getFileType(folder))
4580  write("Video codec is '%s'" % getVideoCodec(folder))
4581 
4582  if (alwaysRunMythtranscode == True and
4583  getVideoCodec(folder).lower().startswith("mpeg2video") and
4584  isFileOkayForDVD(file, folder)):
4585  if file.hasAttribute("localfilename"):
4586  localfile = file.attributes["localfilename"].value
4587  else:
4588  localfile = file.attributes["filename"].value
4589  write("Running mythtranscode --mpeg2 to fix any errors")
4590  chanid = -1
4591  starttime = -1
4592  if runMythtranscode(chanid, starttime, os.path.join(folder, 'newfile.mpg'), False, localfile):
4593  mediafile = os.path.join(folder, 'newfile.mpg')
4594  else:
4595  write("Failed to run mythtranscode to fix any errors")
4596 
4597  #do we need to re-encode the file to make it DVD compliant?
4598  if not isFileOkayForDVD(file, folder):
4599  if getFileType(folder) == 'nuv':
4600  #file is a nuv file which mythffmpeg has problems reading so use mythtranscode to pass
4601  #the video and audio streams to mythffmpeg to do the reencode
4602 
4603  #we need to re-encode the file, make sure we get the right video/audio streams
4604  #would be good if we could also split the file at the same time
4605  getStreamInformation(mediafile, os.path.join(folder, "streaminfo.xml"), 0)
4606 
4607  #choose which streams we need
4608  video, audio1, audio2 = selectStreams(folder)
4609 
4610  #choose which aspect ratio we should use
4611  aspectratio = selectAspectRatio(folder)
4612 
4613  write("Re-encoding audio and video from nuv file")
4614 
4615  # what encoding profile should we use
4616  if file.hasAttribute("encodingprofile"):
4617  profile = file.attributes["encodingprofile"].value
4618  else:
4619  profile = defaultEncodingProfile
4620 
4621  if file.hasAttribute("localfilename"):
4622  mediafile = file.attributes["localfilename"].value
4623  chanid = -1
4624  starttime = -1
4625  usecutlist = -1
4626  elif file.attributes["type"].value == "recording":
4627  mediafile = -1
4628  chanid = getText(infoDOM.getElementsByTagName("chanid")[0])
4629  starttime = getText(infoDOM.getElementsByTagName("starttime")[0])
4630  usecutlist = (file.attributes["usecutlist"].value == "1" and
4631  getText(infoDOM.getElementsByTagName("hascutlist")[0]) == "yes")
4632  else:
4633  chanid = -1
4634  starttime = -1
4635  usecutlist = -1
4636 
4637  encodeNuvToMPEG2(chanid, starttime, mediafile, os.path.join(folder, "newfile2.mpg"), folder,
4638  profile, usecutlist)
4639  mediafile = os.path.join(folder, 'newfile2.mpg')
4640  else:
4641  #we need to re-encode the file, make sure we get the right video/audio streams
4642  #would be good if we could also split the file at the same time
4643  getStreamInformation(mediafile, os.path.join(folder, "streaminfo.xml"), 0)
4644 
4645  #choose which streams we need
4646  video, audio1, audio2 = selectStreams(folder)
4647 
4648  #choose which aspect ratio we should use
4649  aspectratio = selectAspectRatio(folder)
4650 
4651  write("Re-encoding audio and video")
4652 
4653  # Run from local file?
4654  if file.hasAttribute("localfilename"):
4655  mediafile = file.attributes["localfilename"].value
4656 
4657  # what encoding profile should we use
4658  if file.hasAttribute("encodingprofile"):
4659  profile = file.attributes["encodingprofile"].value
4660  else:
4661  profile = defaultEncodingProfile
4662 
4663  #do the re-encode
4664  encodeVideoToMPEG2(mediafile, os.path.join(folder, "newfile2.mpg"), video,
4665  audio1, audio2, aspectratio, profile)
4666  mediafile = os.path.join(folder, 'newfile2.mpg')
4667 
4668  #remove the old mediafile that was run through mythtranscode
4669  #if it exists
4670  if debug_keeptempfiles==False:
4671  if os.path.exists(os.path.join(folder, "newfile.mpg")):
4672  os.remove(os.path.join(folder,'newfile.mpg'))
4673 
4674  # the file is now DVD compliant split it into video and audio parts
4675 
4676  # find out what streams we have available now
4677  getStreamInformation(mediafile, os.path.join(folder, "streaminfo.xml"), 1)
4678 
4679  # choose which streams we need
4680  video, audio1, audio2 = selectStreams(folder)
4681 
4682  # now attempt to split the source file into video and audio parts
4683  write("Splitting MPEG stream into audio and video parts")
4684  deMultiplexMPEG2File(folder, mediafile, video, audio1, audio2)
4685 
4686  # remove intermediate files
4687  if debug_keeptempfiles==False:
4688  if os.path.exists(os.path.join(folder, "newfile.mpg")):
4689  os.remove(os.path.join(folder,'newfile.mpg'))
4690  if os.path.exists(os.path.join(folder, "newfile2.mpg")):
4691  os.remove(os.path.join(folder,'newfile2.mpg'))
4692 
4693  # we now have a video stream and one or more audio streams
4694  # check if we need to convert any of the audio streams to ac3
4695  processAudio(folder)
4696 
4697  # if we don't already have one find a title thumbnail image
4698  titleImage = os.path.join(folder, "title.jpg")
4699  if not os.path.exists(titleImage):
4700  # if the file is a recording try to use its preview image for the thumb
4701  if file.attributes["type"].value == "recording":
4702  previewImage = file.attributes["filename"].value + ".png"
4703  if usebookmark == True and os.path.exists(previewImage):
4704  copy(previewImage, titleImage)
4705  else:
4706  extractVideoFrame(os.path.join(folder, "stream.mv2"), titleImage, thumboffset)
4707  else:
4708  extractVideoFrame(os.path.join(folder, "stream.mv2"), titleImage, thumboffset)
4709 
4710  write( "*************************************************************")
4711  write( "Finished processing '%s'" % file.attributes["filename"].value)
4712  write( "*************************************************************")
4713 
4714 
4715 
4718 
4719 def doProcessFileProjectX(file, folder, count):
4720  """Process a single video/recording file ready for burning."""
4721 
4722  write( "*************************************************************")
4723  write( "Processing %s %d: '%s'" % (file.attributes["type"].value, count, file.attributes["filename"].value))
4724  write( "*************************************************************")
4725 
4726  #As part of this routine we need to pre-process the video this MAY mean:
4727  #1. encoding to mpeg2 (if its an avi for instance or isn't DVD compatible)
4728  #2. removing commercials/cleaning up mpeg2 stream
4729  #3. selecting audio track(s) to use and encoding audio from mp2 into ac3
4730  #4. de-multiplexing into video and audio steams
4731 
4732  mediafile=""
4733 
4734  if file.hasAttribute("localfilename"):
4735  mediafile=file.attributes["localfilename"].value
4736  elif file.attributes["type"].value=="recording":
4737  mediafile = file.attributes["filename"].value
4738  elif file.attributes["type"].value=="video":
4739  mediafile=os.path.join(videopath, file.attributes["filename"].value)
4740  elif file.attributes["type"].value=="file":
4741  mediafile=file.attributes["filename"].value
4742  else:
4743  fatalError("Unknown type of video file it must be 'recording', 'video' or 'file'.")
4744 
4745  #Get the XML containing information about this item
4746  infoDOM = xml.dom.minidom.parse( os.path.join(folder,"info.xml") )
4747  #Error out if its the wrong XML
4748  if infoDOM.documentElement.tagName != "fileinfo":
4749  fatalError("The info.xml file (%s) doesn't look right" % os.path.join(folder,"info.xml"))
4750 
4751  #do we need to re-encode the file to make it DVD compliant?
4752  if not isFileOkayForDVD(file, folder):
4753  if getFileType(folder) == 'nuv':
4754  #file is a nuv file which mythffmpeg has problems reading so use mythtranscode to pass
4755  #the video and audio streams to mythffmpeg to do the reencode
4756 
4757  #we need to re-encode the file, make sure we get the right video/audio streams
4758  #would be good if we could also split the file at the same time
4759  getStreamInformation(mediafile, os.path.join(folder, "streaminfo.xml"), 0)
4760 
4761  #choose which streams we need
4762  video, audio1, audio2 = selectStreams(folder)
4763 
4764  #choose which aspect ratio we should use
4765  aspectratio = selectAspectRatio(folder)
4766 
4767  write("Re-encoding audio and video from nuv file")
4768 
4769  # what encoding profile should we use
4770  if file.hasAttribute("encodingprofile"):
4771  profile = file.attributes["encodingprofile"].value
4772  else:
4773  profile = defaultEncodingProfile
4774 
4775  if file.hasAttribute("localfilename"):
4776  mediafile = file.attributes["localfilename"].value
4777  chanid = -1
4778  starttime = -1
4779  usecutlist = -1
4780  elif file.attributes["type"].value == "recording":
4781  mediafile = -1
4782  chanid = getText(infoDOM.getElementsByTagName("chanid")[0])
4783  starttime = getText(infoDOM.getElementsByTagName("starttime")[0])
4784  usecutlist = (file.attributes["usecutlist"].value == "1" and
4785  getText(infoDOM.getElementsByTagName("hascutlist")[0]) == "yes")
4786  else:
4787  chanid = -1
4788  starttime = -1
4789  usecutlist = -1
4790 
4791  encodeNuvToMPEG2(chanid, starttime, mediafile, os.path.join(folder, "newfile2.mpg"), folder,
4792  profile, usecutlist)
4793  mediafile = os.path.join(folder, 'newfile2.mpg')
4794  else:
4795  #we need to re-encode the file, make sure we get the right video/audio streams
4796  #would be good if we could also split the file at the same time
4797  getStreamInformation(mediafile, os.path.join(folder, "streaminfo.xml"), 0)
4798 
4799  #choose which streams we need
4800  video, audio1, audio2 = selectStreams(folder)
4801 
4802  #choose which aspect ratio we should use
4803  aspectratio = selectAspectRatio(folder)
4804 
4805  write("Re-encoding audio and video")
4806 
4807  # Run from local file?
4808  if file.hasAttribute("localfilename"):
4809  mediafile = file.attributes["localfilename"].value
4810 
4811  # what encoding profile should we use
4812  if file.hasAttribute("encodingprofile"):
4813  profile = file.attributes["encodingprofile"].value
4814  else:
4815  profile = defaultEncodingProfile
4816 
4817  #do the re-encode
4818  encodeVideoToMPEG2(mediafile, os.path.join(folder, "newfile2.mpg"), video,
4819  audio1, audio2, aspectratio, profile)
4820  mediafile = os.path.join(folder, 'newfile2.mpg')
4821 
4822  #remove an intermediate file
4823  if os.path.exists(os.path.join(folder, "newfile1.mpg")):
4824  os.remove(os.path.join(folder,'newfile1.mpg'))
4825 
4826  # the file is now DVD compliant now we need to remove commercials
4827  # and split it into video, audio, subtitle parts
4828 
4829  # find out what streams we have available now
4830  getStreamInformation(mediafile, os.path.join(folder, "streaminfo.xml"), 1)
4831 
4832  # choose which streams we need
4833  video, audio1, audio2 = selectStreams(folder)
4834 
4835  # now attempt to split the source file into video and audio parts
4836  # using projectX
4837 
4838  # If this is an mpeg2 myth recording and there is a cut list available and the
4839  # user wants to use it run projectx to cut out commercials etc
4840  if file.attributes["type"].value == "recording":
4841  if file.attributes["usecutlist"].value == "1" and getText(infoDOM.getElementsByTagName("hascutlist")[0]) == "yes":
4842  chanid = getText(infoDOM.getElementsByTagName("chanid")[0])
4843  starttime = getText(infoDOM.getElementsByTagName("starttime")[0])
4844  write("File has a cut list - running Project-X to remove unwanted segments")
4845  if not runProjectX(chanid, starttime, folder, True, mediafile):
4846  fatalError("Failed to run Project-X to remove unwanted segments and demux")
4847  else:
4848  # no cutlist so just demux this file
4849  chanid = getText(infoDOM.getElementsByTagName("chanid")[0])
4850  starttime = getText(infoDOM.getElementsByTagName("starttime")[0])
4851  write("Using Project-X to demux file")
4852  if not runProjectX(chanid, starttime, folder, False, mediafile):
4853  fatalError("Failed to run Project-X to demux file")
4854  else:
4855  # just demux this file
4856  chanid = -1
4857  starttime = -1
4858  write("Running Project-X to demux file")
4859  if not runProjectX(chanid, starttime, folder, False, mediafile):
4860  fatalError("Failed to run Project-X to demux file")
4861 
4862  # we now have a video stream and one or more audio streams
4863  # check if we need to convert any of the audio streams to ac3
4864  processAudio(folder)
4865 
4866  # if we don't already have one find a title thumbnail image
4867  titleImage = os.path.join(folder, "title.jpg")
4868  if not os.path.exists(titleImage):
4869  # if the file is a recording try to use its preview image for the thumb
4870  if file.attributes["type"].value == "recording":
4871  previewImage = file.attributes["filename"].value + ".png"
4872  if usebookmark == True and os.path.exists(previewImage):
4873  copy(previewImage, titleImage)
4874  else:
4875  extractVideoFrame(os.path.join(folder, "stream.mv2"), titleImage, thumboffset)
4876  else:
4877  extractVideoFrame(os.path.join(folder, "stream.mv2"), titleImage, thumboffset)
4878 
4879  write( "*************************************************************")
4880  write( "Finished processing file '%s'" % file.attributes["filename"].value)
4881  write( "*************************************************************")
4882 
4883 
4885 
4886 def copyRemote(files, tmpPath):
4887  '''go through the list of files looking for files on remote filesytems
4888  and copy them to a local file for quicker processing'''
4889  localTmpPath = os.path.join(tmpPath, "localcopy")
4890  for node in files:
4891  tmpfile = node.attributes["filename"].value
4892  filename = os.path.basename(tmpfile)
4893 
4894  res = runCommand("mytharchivehelper -q -q --isremote --infile " + quoteCmdArg(tmpfile))
4895  #If User wants to, copy remote files to a tmp dir
4896  if res == 2 and copyremoteFiles==True:
4897  # file is on a remote filesystem so copy it to a local file
4898  write("Copying file from " + tmpfile)
4899  write("to " + os.path.join(localTmpPath, filename))
4900 
4901  # Copy file
4902  if not doesFileExist(os.path.join(localTmpPath, filename)):
4903  copy(tmpfile, os.path.join(localTmpPath, filename))
4904 
4905  # update node
4906  node.setAttribute("localfilename", os.path.join(localTmpPath, filename))
4907  elif res == 3:
4908  # file is on a remote myth backend so copy it to a local file
4909  write("Copying file from " + tmpfile)
4910  localfile = os.path.join(localTmpPath, filename)
4911  write("to " + localfile)
4912 
4913  # Copy file
4914  if not doesFileExist(localfile):
4915  runCommand("mythutil --copyfile --infile " + quoteCmdArg(tmpfile) + " --outfile " + quoteCmdArg(localfile))
4916 
4917  # update node
4918  node.setAttribute("localfilename", localfile)
4919  return files
4920 
4921 
4923 
4924 def processJob(job):
4925  """Starts processing a MythBurn job, expects XML nodes to be passed as input."""
4926  global wantIntro, wantMainMenu, wantChapterMenu, wantDetailsPage
4927  global themeDOM, themeName, themeFonts
4928 
4929 
4930  media=job.getElementsByTagName("media")
4931 
4932  if media.length==1:
4933 
4934  themeName=job.attributes["theme"].value
4935 
4936  #Check theme exists
4937  if not validateTheme(themeName):
4938  fatalError("Failed to validate theme (%s)" % themeName)
4939  #Get the theme XML
4940  themeDOM = getThemeConfigurationXML(themeName)
4941 
4942  #Pre generate all the fonts we need
4943  loadFonts(themeDOM)
4944 
4945  #Update the global flags
4946  nodes=themeDOM.getElementsByTagName("intro")
4947  wantIntro = (nodes.length > 0)
4948 
4949  nodes=themeDOM.getElementsByTagName("menu")
4950  wantMainMenu = (nodes.length > 0)
4951 
4952  nodes=themeDOM.getElementsByTagName("submenu")
4953  wantChapterMenu = (nodes.length > 0)
4954 
4955  nodes=themeDOM.getElementsByTagName("detailspage")
4956  wantDetailsPage = (nodes.length > 0)
4957 
4958  write( "wantIntro: %d, wantMainMenu: %d, wantChapterMenu: %d, wantDetailsPage: %d" \
4959  % (wantIntro, wantMainMenu, wantChapterMenu, wantDetailsPage))
4960 
4961  if videomode=="ntsc":
4962  format=dvdNTSC
4963  dpi=dvdNTSCdpi
4964  elif videomode=="pal":
4965  format=dvdPAL
4966  dpi=dvdPALdpi
4967  else:
4968  fatalError("Unknown videomode is set (%s)" % videomode)
4969 
4970  write( "Final DVD Video format will be " + videomode)
4971 
4972 
4973  #Loop through all the files
4974  files=media[0].getElementsByTagName("file")
4975  filecount=0
4976  if files.length > 0:
4977  write( "There are %s file(s) to process" % files.length)
4978 
4979  if debug_secondrunthrough==False:
4980  #Delete all the temporary files that currently exist
4982  localCopyFolder=os.path.join(getTempPath(),"localcopy")
4983  os.makedirs(localCopyFolder)
4984  files=copyRemote(files,getTempPath())
4985 
4986  #First pass through the files to be recorded - sense check
4987  #we dont want to find half way through this long process that
4988  #a file does not exist, or is the wrong format!!
4989  for node in files:
4990  filecount+=1
4991 
4992  #Generate a temp folder name for this file
4993  folder=getItemTempPath(filecount)
4994 
4995  if debug_secondrunthrough==False:
4996  os.makedirs(folder)
4997  #Do the pre-process work
4998  preProcessFile(node,folder,filecount)
4999 
5000  if debug_secondrunthrough==False:
5001  #Loop through all the files again but this time do more serious work!
5002  filecount=0
5003  for node in files:
5004  filecount+=1
5005  folder=getItemTempPath(filecount)
5006 
5007  #Process this file
5008  processFile(node,folder,filecount)
5009 
5010  #We can only create the menus after the videos have been processed
5011  #and the commercials cut out so we get the correct run time length
5012  #for the chapter marks and thumbnails.
5013  #create the DVD menus...
5014  if wantMainMenu:
5015  createMenu(format, dpi, files.length)
5016 
5017  #Submenus are visible when you select the chapter menu while the recording is playing
5018  if wantChapterMenu:
5019  createChapterMenu(format, dpi, files.length)
5020 
5021  #Details Page are displayed just before playing each recording
5022  if wantDetailsPage:
5023  createDetailsPage(format, dpi, files.length)
5024 
5025  #DVD Author file
5026  if not wantMainMenu and not wantChapterMenu:
5027  createDVDAuthorXMLNoMenus(format, files.length)
5028  elif not wantMainMenu:
5029  createDVDAuthorXMLNoMainMenu(format, files.length)
5030  else:
5031  createDVDAuthorXML(format, files.length)
5032 
5033  #Check all the files will fit onto a recordable DVD
5034  if mediatype == DVD_DL:
5035  # dual layer
5036  performMPEG2Shrink(files, dvdrsize[1])
5037  else:
5038  #single layer
5039  performMPEG2Shrink(files, dvdrsize[0])
5040 
5041  filecount=0
5042  for node in files:
5043  filecount+=1
5044  folder=getItemTempPath(filecount)
5045  #Multiplex this file
5046  #(This also removes non-required audio feeds inside mpeg streams
5047  #(through re-multiplexing) we only take 1 video and 1 or 2 audio streams)
5048  pid=multiplexMPEGStream(os.path.join(folder,'stream.mv2'),
5049  os.path.join(folder,'stream0'),
5050  os.path.join(folder,'stream1'),
5051  os.path.join(folder,'final.vob'),
5052  calcSyncOffset(filecount))
5053 
5054  #Now all the files are completed and ready to be burnt
5055  runDVDAuthor()
5056 
5057  #Delete dvdauthor work files
5058  if debug_keeptempfiles==False:
5059  filecount=0
5060  for node in files:
5061  filecount+=1
5062  folder=getItemTempPath(filecount)
5063  if os.path.exists(os.path.join(folder, "stream.mv2")):
5064  os.remove(os.path.join(folder,'stream.mv2'))
5065  if os.path.exists(os.path.join(folder, "stream0.mp2")):
5066  os.remove(os.path.join(folder,'stream0.mp2'))
5067  if os.path.exists(os.path.join(folder, "stream1.mp2")):
5068  os.remove(os.path.join(folder,'stream1.mp2'))
5069  if os.path.exists(os.path.join(folder, "stream0.ac3")):
5070  os.remove(os.path.join(folder,'stream0.ac3'))
5071  if os.path.exists(os.path.join(folder, "stream1.ac3")):
5072  os.remove(os.path.join(folder,'stream1.ac3'))
5073 
5074  #Get DVD title from first processed file
5075  #Get the XML containing information about this item
5076  infoDOM = xml.dom.minidom.parse( os.path.join(getItemTempPath(1),"info.xml") )
5077  #Error out if its the wrong XML
5078  if infoDOM.documentElement.tagName != "fileinfo":
5079  fatalError("The info.xml file (%s) doesn't look right" % os.path.join(folder,"info.xml"))
5080  title = expandItemText(infoDOM,"%title",1,0,0,0,0)
5081  # replace all non-ascii-characters
5082  title = title.encode('ascii', 'replace').decode('ascii', 'replace')
5083  title = title.strip()
5084 
5085  # replace not-allowed characters
5086  index = 0
5087  title_new = ''
5088  while (index < len(title)) and (index < 32):
5089  if title[index].isalnum and title[index] != ' ':
5090  title_new += title[index]
5091  else:
5092  title_new += '_'
5093  index = index + 1
5094 
5095  title = title_new.upper()
5096 
5097  if len(title) < 1:
5098  title = 'UNNAMED'
5099 
5100  #Create the DVD ISO image
5101  if docreateiso == True or mediatype == FILE:
5102  CreateDVDISO(title)
5103 
5104  #Burn the DVD ISO image
5105  if doburn == True and mediatype != FILE:
5106  BurnDVDISO(title)
5107 
5108  #Move the created iso image to the given location
5109  if mediatype == FILE and savefilename != "":
5110  write("Moving ISO image to: %s" % savefilename)
5111  try:
5112  os.rename(os.path.join(getTempPath(), 'mythburn.iso'), savefilename)
5113  except:
5114  f1 = open(os.path.join(getTempPath(), 'mythburn.iso'), 'rb')
5115  f2 = open(savefilename, 'wb')
5116  data = f1.read(1024 * 1024)
5117  while data:
5118  f2.write(data)
5119  data = f1.read(1024 * 1024)
5120  f1.close()
5121  f2.close()
5122  os.unlink(os.path.join(getTempPath(), 'mythburn.iso'))
5123  else:
5124  write( "Nothing to do! (files)")
5125  else:
5126  write( "Nothing to do! (media)")
5127  return
5128 
5129 
5131 
5132 def usage():
5133  write("""
5134  -h/--help (Show this usage)
5135  -j/--jobfile file (use file as the job file)
5136  -l/--progresslog file (log file to output progress messages)
5137 
5138  """)
5139 
5140 
5142 
5143 def main():
5144  global sharepath, scriptpath, cpuCount, videopath, gallerypath, musicpath
5145  global videomode, temppath, logpath, dvddrivepath, dbVersion, preferredlang1
5146  global preferredlang2, useFIFO, encodetoac3, alwaysRunMythtranscode
5147  global copyremoteFiles, mainmenuAspectRatio, chaptermenuAspectRatio, dateformat
5148  global timeformat, clearArchiveTable, nicelevel, drivespeed, path_mplex
5149  global path_dvdauthor, path_mkisofs, path_growisofs, path_M2VRequantiser, addSubtitles
5150  global path_jpeg2yuv, path_spumux, path_mpeg2enc, path_projectx, useprojectx, progresslog
5151  global progressfile, jobfile
5152 
5153  write( "mythburn.py (%s) starting up..." % VERSION)
5154 
5155  #Ensure we are running at least python 2.3.5
5156  if not hasattr(sys, "hexversion") or sys.hexversion < 0x20305F0:
5157  sys.stderr.write("Sorry, your Python is too old. Please upgrade at least to 2.3.5\n")
5158  sys.exit(1)
5159 
5160  # figure out where this script is located
5161  scriptpath = os.path.dirname(sys.argv[0])
5162  scriptpath = os.path.abspath(scriptpath)
5163  write("script path:" + scriptpath)
5164 
5165  # figure out where the myth share directory is located
5166  sharepath = os.path.split(scriptpath)[0]
5167  sharepath = os.path.split(sharepath)[0]
5168  write("myth share path:" + sharepath)
5169 
5170  # process any command line options
5171  try:
5172  opts, args = getopt.getopt(sys.argv[1:], "j:hl:", ["jobfile=", "help", "progresslog="])
5173  except getopt.GetoptError:
5174  # print usage and exit
5175  usage()
5176  sys.exit(2)
5177 
5178  for o, a in opts:
5179  if o in ("-h", "--help"):
5180  usage()
5181  sys.exit()
5182  if o in ("-j", "--jobfile"):
5183  jobfile = str(a)
5184  write("passed job file: " + a)
5185  if o in ("-l", "--progresslog"):
5186  progresslog = str(a)
5187  write("passed progress log file: " + a)
5188 
5189  #if we have been given a progresslog filename to write to open it
5190  if progresslog != "":
5191  if os.path.exists(progresslog):
5192  os.remove(progresslog)
5193  progressfile = codecs.open(progresslog, 'w', 'utf-8')
5194  write( "mythburn.py (%s) starting up..." % VERSION)
5195 
5196  #Get mysql database parameters
5197  #getMysqlDBParameters()
5198 
5199  saveSetting("MythArchiveLastRunStart", time.strftime("%Y-%m-%d %H:%M:%S "))
5200  saveSetting("MythArchiveLastRunType", "DVD")
5201  saveSetting("MythArchiveLastRunStatus", "Running")
5202 
5203  cpuCount = getCPUCount()
5204 
5205  #if the script is run from the web interface the PATH environment variable does not include
5206  #many of the bin locations we need so just append a few likely locations where our required
5207  #executables may be
5208  if not os.environ['PATH'].endswith(':'):
5209  os.environ['PATH'] += ":"
5210  os.environ['PATH'] += "/bin:/sbin:/usr/local/bin:/usr/bin:/opt/bin:" + installPrefix +"/bin:"
5211 
5212  #Get defaults from MythTV database
5213  defaultsettings = getDefaultParametersFromMythTVDB()
5214  videopath = defaultsettings.get("VideoStartupDir", None)
5215  gallerypath = defaultsettings.get("GalleryDir", None)
5216  musicpath = defaultsettings.get("MusicLocation", None)
5217  videomode = defaultsettings["MythArchiveVideoFormat"].lower()
5218  temppath = os.path.join(defaultsettings["MythArchiveTempDir"], "work")
5219  logpath = os.path.join(defaultsettings["MythArchiveTempDir"], "logs")
5220  write("temppath: " + temppath)
5221  write("logpath: " + logpath)
5222  dvddrivepath = defaultsettings["MythArchiveDVDLocation"]
5223  dbVersion = defaultsettings["DBSchemaVer"]
5224  preferredlang1 = defaultsettings["ISO639Language0"]
5225  preferredlang2 = defaultsettings["ISO639Language1"]
5226  useFIFO = (defaultsettings["MythArchiveUseFIFO"] == '1')
5227  alwaysRunMythtranscode = (defaultsettings["MythArchiveAlwaysUseMythTranscode"] == '1')
5228  copyremoteFiles = (defaultsettings["MythArchiveCopyRemoteFiles"] == '1')
5229  mainmenuAspectRatio = defaultsettings["MythArchiveMainMenuAR"]
5230  chaptermenuAspectRatio = defaultsettings["MythArchiveChapterMenuAR"]
5231  dateformat = defaultsettings.get("MythArchiveDateFormat", "%a %d %b %Y")
5232  timeformat = defaultsettings.get("MythArchiveTimeFormat", "%I:%M %p")
5233  drivespeed = int(defaultsettings.get("MythArchiveDriveSpeed", "0"))
5234  if "MythArchiveClearArchiveTable" in defaultsettings:
5235  clearArchiveTable = (defaultsettings["MythArchiveClearArchiveTable"] == '1')
5236  nicelevel = defaultsettings.get("JobQueueCPU", "0")
5237 
5238  # external commands
5239  path_mplex = [defaultsettings["MythArchiveMplexCmd"], os.path.split(defaultsettings["MythArchiveMplexCmd"])[1]]
5240  path_dvdauthor = [defaultsettings["MythArchiveDvdauthorCmd"], os.path.split(defaultsettings["MythArchiveDvdauthorCmd"])[1]]
5241  path_mkisofs = [defaultsettings["MythArchiveMkisofsCmd"], os.path.split(defaultsettings["MythArchiveMkisofsCmd"])[1]]
5242  path_growisofs = [defaultsettings["MythArchiveGrowisofsCmd"], os.path.split(defaultsettings["MythArchiveGrowisofsCmd"])[1]]
5243  path_M2VRequantiser = [defaultsettings["MythArchiveM2VRequantiserCmd"], os.path.split(defaultsettings["MythArchiveM2VRequantiserCmd"])[1]]
5244  path_jpeg2yuv = [defaultsettings["MythArchiveJpeg2yuvCmd"], os.path.split(defaultsettings["MythArchiveJpeg2yuvCmd"])[1]]
5245  path_spumux = [defaultsettings["MythArchiveSpumuxCmd"], os.path.split(defaultsettings["MythArchiveSpumuxCmd"])[1]]
5246  path_mpeg2enc = [defaultsettings["MythArchiveMpeg2encCmd"], os.path.split(defaultsettings["MythArchiveMpeg2encCmd"])[1]]
5247 
5248  path_projectx = [defaultsettings["MythArchiveProjectXCmd"], os.path.split(defaultsettings["MythArchiveProjectXCmd"])[1]]
5249  useprojectx = (defaultsettings["MythArchiveUseProjectX"] == '1')
5250  addSubtitles = (defaultsettings["MythArchiveAddSubtitles"] == '1')
5251 
5252  # sanity check
5253  if path_projectx[0] == "":
5254  useprojectx = False
5255 
5256  if nicelevel == '1':
5257  nicelevel = 10
5258  elif nicelevel == '2':
5259  nicelevel = 0
5260  else:
5261  nicelevel = 17
5262 
5263  nicelevel = os.nice(nicelevel)
5264  write( "Setting process priority to %s" % nicelevel)
5265 
5266  try:
5267  import psutil
5268  except ImportError:
5269  write( "Cannot change ionice level")
5270  else:
5271  write( "Setting ionice level to idle")
5272  p = psutil.Process(os.getpid())
5273  p.ionice(psutil.IOPRIO_CLASS_IDLE)
5274 
5275  import errno
5276 
5277  try:
5278  # Attempt to create a lock file so any UI knows we are running.
5279  # Testing for and creation of the lock is one atomic operation.
5280  lckpath = os.path.join(logpath, "mythburn.lck")
5281  try:
5282  fd = os.open(lckpath, os.O_WRONLY | os.O_CREAT | os.O_EXCL)
5283  try:
5284  os.write(fd, b"%d\n" % os.getpid())
5285  os.close(fd)
5286  except:
5287  os.remove(lckpath)
5288  raise
5289  except OSError as e:
5290  if e.errno == errno.EEXIST:
5291  write("Lock file exists -- already running???")
5292  sys.exit(1)
5293  else:
5294  fatalError("cannot create lockfile: %s" % e)
5295  # if we get here, we own the lock
5296 
5297  try:
5298  #Load XML input file from disk
5299  jobDOM = xml.dom.minidom.parse(jobfile)
5300 
5301  #Error out if its the wrong XML
5302  if jobDOM.documentElement.tagName != "mythburn":
5303  fatalError("Job file doesn't look right!")
5304 
5305  #process each job
5306  jobcount=0
5307  jobs=jobDOM.getElementsByTagName("job")
5308  for job in jobs:
5309  jobcount+=1
5310  write( "Processing Mythburn job number %s." % jobcount)
5311 
5312  #get any options from the job file if present
5313  options = job.getElementsByTagName("options")
5314  if options.length > 0:
5315  getOptions(options)
5316 
5317  processJob(job)
5318 
5319  jobDOM.unlink()
5320 
5321  # clear the archiveitems table
5322  if clearArchiveTable == True:
5324 
5325  saveSetting("MythArchiveLastRunStatus", "Success")
5326  saveSetting("MythArchiveLastRunEnd", time.strftime("%Y-%m-%d %H:%M:%S "))
5327  write("Finished processing jobs!!!")
5328  finally:
5329  # remove our lock file
5330  os.remove(lckpath)
5331 
5332  # make sure the files we created are read/writable by all
5333  os.system("chmod -R a+rw-x+X %s" % defaultsettings["MythArchiveTempDir"])
5334  except SystemExit:
5335  write("Terminated")
5336  except:
5337  write('-'*60)
5338  traceback.print_exc(file=sys.stdout)
5339  if progresslog != "":
5340  traceback.print_exc(file=progressfile)
5341  write('-'*60)
5342  saveSetting("MythArchiveLastRunStatus", "Failed")
5343  saveSetting("MythArchiveLastRunEnd", time.strftime("%Y-%m-%d %H:%M:%S "))
5344 
5345 if __name__ == "__main__":
5346  main()
5347 
5348 os.putenv("LC_ALL", oldlocale)
mythburn.createEmptyPreviewFolder
def createEmptyPreviewFolder(videoitem)
Creates the directory to hold the preview images for an animated menu.
Definition: mythburn.py:3260
mythburn.simple_fix_rtl
def simple_fix_rtl(str)
Definition: mythburn.py:243
mythburn.getFileType
def getFileType(folder)
gets file container type from the stream info xml file
Definition: mythburn.py:4413
mythburn.FontDef
class to hold a font definition
Definition: mythburn.py:259
mythburn.paintText
def paintText(draw, image, text, node, color=None, x=None, y=None, width=None, height=None)
Paint some theme text on to an image.
Definition: mythburn.py:1167
mythburn.createDVDAuthorXMLNoMenus
def createDVDAuthorXMLNoMenus(screensize, numberofitems)
Creates the DVDAuthor xml file used to create an Autoplay DVD.
Definition: mythburn.py:3083
mythburn.getEncodingProfilePath
def getEncodingProfilePath()
Get the directory where all encoder profile files are located.
Definition: mythburn.py:385
mythburn.getAspectRatioOfVideo
def getAspectRatioOfVideo(index)
Gets the aspect ratio of a video file from its stream info file.
Definition: mythburn.py:699
getsize
static uint32_t getsize(int fd)
Definition: avi.cpp:64
mythburn.processJob
def processJob(job)
processes one job
Definition: mythburn.py:4924
mythburn.FontDef.fontFile
fontFile
Definition: mythburn.py:262
mythburn.quoteString
def quoteString(str)
Return the input string with single quotes escaped.
Definition: mythburn.py:347
mythburn.getStreamList
def getStreamList(folder)
get the list of required stream ids for a file
Definition: mythburn.py:4436
mythburn.checkBoundaryBox
def checkBoundaryBox(boundarybox, node)
Check if boundary box need adjusting.
Definition: mythburn.py:1325
mythburn.clearArchiveItems
def clearArchiveItems()
Remove all archive items from the archiveitems DB table.
Definition: mythburn.py:937
mythburn.isResolutionOkayForDVD
def isResolutionOkayForDVD(videoresolution)
Returns True if the given resolution is a DVD compliant one.
Definition: mythburn.py:458
mythburn.main
def main()
The main starting point for mythburn.py.
Definition: mythburn.py:5143
mythburn.getLengthOfVideo
def getLengthOfVideo(index)
Gets the duration of a video file from its stream info file.
Definition: mythburn.py:622
mythburn.getFileInformation
def getFileInformation(file, folder)
Creates an info xml file from details in the job file or from the DB.
Definition: mythburn.py:1395
mythburn.multiplexMPEGStream
def multiplexMPEGStream(video, audio1, audio2, destination, syncOffset)
Recombines a video and one or two audio streams back together adding in the NAV packets required to c...
Definition: mythburn.py:1620
mythburn.doProcessFile
def doProcessFile(file, folder, count)
process a single file ready for burning using mythtranscode/mythreplex to cut and demux
Definition: mythburn.py:4507
mythburn.doProcessFileProjectX
def doProcessFileProjectX(file, folder, count)
process a single file ready for burning using projectX to cut and demux
Definition: mythburn.py:4719
mythburn.validateTheme
def validateTheme(theme)
Returns True if the theme.xml file can be found for the given theme.
Definition: mythburn.py:449
mythburn.quoteCmdArg
def quoteCmdArg(arg)
Escape quotes in a command line argument.
Definition: mythburn.py:399
mythburn.processAudio
def processAudio(folder)
checks to see if an audio stream need to be converted to ac3
Definition: mythburn.py:4095
mythburn.deMultiplexMPEG2File
def deMultiplexMPEG2File(folder, mediafile, video, audio1, audio2)
Splits a file into the separate audio and video streams using mythreplex.
Definition: mythburn.py:2459
mythburn.generateProjectXCutlist
def generateProjectXCutlist(chanid, starttime, folder)
Create a projectX cut list for a recording.
Definition: mythburn.py:1805
mythburn.createChapterMenu
def createChapterMenu(screensize, screendpi, numberofitems)
creates a chapter menu for a file on a DVD
Definition: mythburn.py:3745
mythburn.write
def write(text, progress=True)
Definition: mythburn.py:307
mythburn.saveSetting
def saveSetting(name, data)
Save a setting to the settings table in the DB.
Definition: mythburn.py:930
mythburn.getItemTempPath
def getItemTempPath(itemnumber)
Creates a file path where the temp files for a video file can be created.
Definition: mythburn.py:443
mythburn.loadFonts
def loadFonts(themeDOM)
Load the font defintions from a DVD theme file.
Definition: mythburn.py:1349
mythburn.FontDef.drawText
def drawText(self, text, color=None)
Definition: mythburn.py:276
mythburn.getOptions
def getOptions(options)
Load the options from the options node passed in the job file.
Definition: mythburn.py:947
mythburn.calculateFileSizes
def calculateFileSizes(files)
Calculates the total size of all the video, audio and menu files.
Definition: mythburn.py:2539
mythburn.nonfatalError
def nonfatalError(msg)
Definition: mythburn.py:337
mythburn.getScaledAttribute
def getScaledAttribute(node, attribute)
Scale a theme position/size depending on the current video mode.
Definition: mythburn.py:1006
mythburn.selectSubtitleStream
def selectSubtitleStream(folder)
Definition: mythburn.py:4291
mythburn.paintBackground
def paintBackground(image, node)
Paints a background rectangle onto an image.
Definition: mythburn.py:1064
mythburn.copyRemote
def copyRemote(files, tmpPath)
copy files on remote filesystems to the local filesystem
Definition: mythburn.py:4886
mythburn.checkCancelFlag
def checkCancelFlag()
Check to see if the user has cancelled the DVD creation process.
Definition: mythburn.py:489
mythburn.checkSubtitles
def checkSubtitles(spumuxFile)
check the given spumux.xml file for consistancy
Definition: mythburn.py:1958
mythburn.getText
def getText(node)
Returns the text contents from a given XML element.
Definition: mythburn.py:407
mythburn.createVideoChaptersFixedLength
def createVideoChaptersFixedLength(itemnum, segment, lengthofvideo)
Creates some fixed length chapter marks.
Definition: mythburn.py:835
mythburn.createMenu
def createMenu(screensize, screendpi, numberofitems)
creates the main menu for a DVD
Definition: mythburn.py:3528
mythburn.getStreamInformation
def getStreamInformation(filename, xmlFilename, lenMethod)
Creates a stream xml file for a video file.
Definition: mythburn.py:1721
mythburn.deleteEverythingInFolder
def deleteEverythingInFolder(folder)
Romoves all the objects from a directory.
Definition: mythburn.py:476
mythburn.encodeAudio
def encodeAudio(format, sourcefile, destinationfile, deletesourceafterencode)
Re-encodes an audio stream to ac3.
Definition: mythburn.py:1596
mythburn.processFile
def processFile(file, folder, count)
process a single file ready for burning using either mythtranscode/mythreplex or ProjectX as the cutt...
Definition: mythburn.py:4495
mythburn.isMediaAVIFile
def isMediaAVIFile(file)
checks if a file is an avi file
Definition: mythburn.py:4086
MythFile::copy
MBASE_PUBLIC long long copy(QFile &dst, QFile &src, uint block_size=0)
Copies src file to dst file.
Definition: mythmiscutil.cpp:263
mythburn.createDetailsPage
def createDetailsPage(screensize, screendpi, numberofitems)
creates the details page for a file on a DVD
Definition: mythburn.py:3952
mythburn.extractVideoFrames
def extractVideoFrames(source, destination, thumbList)
Grabs a list of single frames from a file.
Definition: mythburn.py:2034
mythburn.FontDef.color
color
Definition: mythburn.py:264
mythburn.FontDef.shadowColor
shadowColor
Definition: mythburn.py:266
mythburn.FontDef.__init__
def __init__(self, name=None, fontFile=None, size=19, color="white", effect="normal", shadowColor="black", shadowSize=1)
Definition: mythburn.py:260
mythburn.FontDef.size
size
Definition: mythburn.py:263
mythburn.paintImage
def paintImage(filename, maskfilename, imageDom, destimage, stretch=True)
Paint an image on the background image.
Definition: mythburn.py:1249
mythburn.encodeMenu
def encodeMenu(background, tempvideo, music, musiclength, tempmovie, xmlfile, finaloutput, aspectratio)
Creates a short mpeg file from a jpeg image and an ac3 sound track.
Definition: mythburn.py:528
mythburn.encodeNuvToMPEG2
def encodeNuvToMPEG2(chanid, starttime, mediafile, destvideofile, folder, profile, usecutlist)
Re-encodes a nuv file to mpeg2 optionally removing commercials.
Definition: mythburn.py:2144
mythburn.CreateDVDISO
def CreateDVDISO(title)
Creates an ISO image from the contents of a directory.
Definition: mythburn.py:2280
mythburn.encodeVideoToMPEG2
def encodeVideoToMPEG2(source, destvideofile, video, audio1, audio2, aspectratio, profile)
Re-encodes a file to mpeg2.
Definition: mythburn.py:2048
mythburn.usage
def usage()
show usage
Definition: mythburn.py:5132
mythburn.getAudioParams
def getAudioParams(folder)
Gets the audio sample rate and number of channels of a video file from its stream info file.
Definition: mythburn.py:643
mythburn.selectAspectRatio
def selectAspectRatio(folder)
gets the video aspect ratio from the stream info xml file
Definition: mythburn.py:4350
mythburn.createDVDAuthorXMLNoMainMenu
def createDVDAuthorXMLNoMainMenu(screensize, numberofitems)
Creates the DVDAuthor xml file used to create a DVD with no main menu.
Definition: mythburn.py:3069
mythburn.FontDef.effect
effect
Definition: mythburn.py:265
mythburn.doesFileExist
def doesFileExist(file)
Returns true/false if a given file or path exists.
Definition: mythburn.py:392
mythburn.expandItemText
def expandItemText(infoDOM, text, itemnumber, pagenumber, keynumber, chapternumber, chapterlist)
Substitutes some text from a theme file with the required values.
Definition: mythburn.py:971
mythburn.long
long
Definition: mythburn.py:14
mythburn.extractVideoFrame
def extractVideoFrame(source, destination, seconds)
Grabs a sequence of consecutive frames from a file.
Definition: mythburn.py:2004
mythburn.runM2VRequantiser
def runM2VRequantiser(source, destination, factor)
Run M2VRequantiser.
Definition: mythburn.py:2514
mythburn.getCPUCount
def getCPUCount()
Try to work out how many cpus we have available.
Definition: mythburn.py:361
mythburn.selectStreams
def selectStreams(folder)
Definition: mythburn.py:4139
mythburn.createVideoChapters
def createVideoChapters(itemnum, numofchapters, lengthofvideo, getthumbnails)
Creates a set of chapter points evenly spread thoughout a file Optionally grabs the thumbnails from t...
Definition: mythburn.py:788
mythburn.getFormatedLengthOfVideo
def getFormatedLengthOfVideo(index)
Gets the length of a video file and returns it as a string.
Definition: mythburn.py:747
mythburn.getFontPathName
def getFontPathName(fontname)
Returns the path where we can find our fonts.
Definition: mythburn.py:437
mythburn.getVideoSize
def getVideoSize(xmlFilename)
Gets the video width and height from a file's stream xml file.
Definition: mythburn.py:1740
mythburn.FontDef.shadowSize
shadowSize
Definition: mythburn.py:267
mythburn.createDVDAuthorXML
def createDVDAuthorXML(screensize, numberofitems)
Creates the DVDAuthor xml file used to create a standard DVD with menus.
Definition: mythburn.py:2690
mythburn.getTempPath
def getTempPath()
Directory where all temporary files will be created.
Definition: mythburn.py:354
mythburn.ts2pts
def ts2pts(time)
convert time stamp to pts
Definition: mythburn.py:1947
mythburn.paintButton
def paintButton(draw, bgimage, bgimagemask, node, infoDOM, itemnum, page, itemsonthispage, chapternumber, chapterlist)
Paints a button onto an image.
Definition: mythburn.py:1084
mythburn.drawThemeItem
def drawThemeItem(page, itemsonthispage, itemnum, menuitem, bgimage, draw, bgimagemask, drawmask, highlightcolor, spumuxdom, spunode, numberofitems, chapternumber, chapterlist)
Draws text and graphics onto a dvd menu.
Definition: mythburn.py:3311
mythburn.runCommand
def runCommand(command)
Runs an external command checking to see if the user has cancelled the DVD creation process.
Definition: mythburn.py:502
mythburn.WriteXMLToFile
def WriteXMLToFile(myDOM, filename)
Write an xml file to disc.
Definition: mythburn.py:1540
mythburn.secondsToFrames
def secondsToFrames(seconds)
Convert a time in seconds to a frame number.
Definition: mythburn.py:515
mythburn.runMythtranscode
def runMythtranscode(chanid, starttime, destination, usecutlist, localfile)
Run a file though the lossless encoder optionally removing commercials.
Definition: mythburn.py:1764
mythburn.FontDef.name
name
Definition: mythburn.py:261
mythburn.BurnDVDISO
def BurnDVDISO(title)
Burns the contents of a directory to create a DVD.
Definition: mythburn.py:2300
mythburn.fatalError
def fatalError(msg)
Display an error message and exit.
Definition: mythburn.py:323
mythburn.getThemeFile
def getThemeFile(theme, file)
Try to find a theme file.
Definition: mythburn.py:417
mythburn.timeStringToSeconds
def timeStringToSeconds(formatedtime)
Convert a time string of format 00:00:00 to number of seconds.
Definition: mythburn.py:773
mythburn.getVideoParams
def getVideoParams(folder)
Gets the video resolution, frames per second and aspect ratio of a video file from its stream info fi...
Definition: mythburn.py:663
mythburn.total_mv2_brl
def total_mv2_brl(files, rate)
returns total size of bitrate-limited m2v files
Definition: mythburn.py:2585
mythburn.deleteAllFilesInFolder
def deleteAllFilesInFolder(folder)
Removes all the files from a directory.
Definition: mythburn.py:467
mythburn.isFileOkayForDVD
def isFileOkayForDVD(file, folder)
check if file is DVD compliant
Definition: mythburn.py:4461
mythburn.unicode
unicode
Definition: mythburn.py:8
mythburn.intelliDraw
def intelliDraw(drawer, text, font, containerWidth)
Splits some text into lines so it will fit into a given container.
Definition: mythburn.py:1017
mythburn.getThemeConfigurationXML
def getThemeConfigurationXML(theme)
Load the theme.xml file for a DVD theme.
Definition: mythburn.py:609
mythburn.findEncodingProfile
def findEncodingProfile(profile)
Return an xml node from a re-encoding profile xml file for a given profile name.
Definition: mythburn.py:564
mythburn.calcSyncOffset
def calcSyncOffset(index)
Calculates the sync offset between the video and first audio stream.
Definition: mythburn.py:719
mythburn.getVideoCodec
def getVideoCodec(folder)
gets video stream codec from the stream info xml file
Definition: mythburn.py:4391
mythburn.frameToTime
def frameToTime(frame, fps)
Convert a frame number to a time string.
Definition: mythburn.py:760
mythburn.runProjectX
def runProjectX(chanid, starttime, folder, usecutlist, file)
Use Project-X to cut commercials and/or demux an mpeg2 file.
Definition: mythburn.py:1840
mythburn.CDROM
Definition: mythburn.py:117
mythburn.generateVideoPreview
def generateVideoPreview(videoitem, itemonthispage, menuitem, starttime, menulength, previewfolder)
Generates the thumbnail images used to create animated menus.
Definition: mythburn.py:3271
mythburn.performMPEG2Shrink
def performMPEG2Shrink(files, dvdrsize)
Uses requantiser if available to shrink the video streams so they will fit on a DVD.
Definition: mythburn.py:2606
mythburn.getDefaultParametersFromMythTVDB
def getDefaultParametersFromMythTVDB()
Reads a load of settings from DB.
Definition: mythburn.py:870
mythburn.preProcessFile
def preProcessFile(file, folder, count)
Pre-process a single video/recording file.
Definition: mythburn.py:1558
mythburn.runDVDAuthor
def runDVDAuthor()
Runs DVDAuthor to create a DVD file structure.
Definition: mythburn.py:2269
find
static pid_list_t::iterator find(const PIDInfoMap &map, pid_list_t &list, pid_list_t::iterator begin, pid_list_t::iterator end, bool find_open)
Definition: dvbstreamhandler.cpp:363
mythburn.FontDef.font
font
Definition: mythburn.py:268
mythburn.fix_rtl
def fix_rtl
Definition: mythburn.py:251
mythburn.FontDef.getFont
def getFont(self)
Definition: mythburn.py:270