00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
00036
00037
00038
00039
00040
00041
00042
00043
00044
00045
00046
00047 VERSION="0.1.20120304-1"
00048
00049
00050
00051
00052 debug_keeptempfiles = False
00053
00054
00055
00056
00057
00058
00059
00060 debug_secondrunthrough = False
00061
00062
00063 defaultEncodingProfile = "SP"
00064
00065
00066 useSyncOffset = True
00067
00068
00069
00070 addCutlistChapters = False
00071
00072
00073 encodetoac3 = True
00074
00075
00076
00077
00078 import os
00079 import sys
00080 import string
00081 import getopt
00082 import traceback
00083 import signal
00084 import xml.dom.minidom
00085 import Image
00086 import ImageDraw
00087 import ImageFont
00088 import ImageColor
00089 import unicodedata
00090 import time
00091 import datetime
00092 import tempfile
00093 from fcntl import ioctl
00094 import CDROM
00095 from shutil import copy
00096
00097 import MythTV
00098 from MythTV.altdict import OrdDict
00099
00100
00101 DVD_SL = 0
00102 DVD_DL = 1
00103 DVD_RW = 2
00104 FILE = 3
00105
00106 dvdPAL=(720,576)
00107 dvdNTSC=(720,480)
00108 dvdPALdpi=(75,80)
00109 dvdNTSCdpi=(81,72)
00110
00111 dvdPALHalfD1="352x576"
00112 dvdNTSCHalfD1="352x480"
00113 dvdPALD1="%sx%s" % (dvdPAL[0],dvdPAL[1])
00114 dvdNTSCD1="%sx%s" % (dvdNTSC[0],dvdNTSC[1])
00115
00116
00117 dvdrsize=(4482,8106)
00118
00119 frameratePAL=25
00120 framerateNTSC=29.97
00121
00122
00123 aspectRatioThreshold = 1.4
00124
00125
00126 temppath=""
00127 logpath=""
00128 scriptpath=""
00129 sharepath=""
00130 videopath=""
00131 defaultsettings=""
00132 videomode=""
00133 gallerypath=""
00134 musicpath=""
00135 dateformat=""
00136 timeformat=""
00137 dbVersion=""
00138 preferredlang1=""
00139 preferredlang2=""
00140 useFIFO = True
00141 alwaysRunMythtranscode = False
00142 copyremoteFiles = False
00143 thumboffset = 10
00144 usebookmark = True
00145 clearArchiveTable = True
00146 nicelevel = 17;
00147 drivespeed = 0;
00148
00149
00150 mainmenuAspectRatio = "16:9"
00151
00152
00153
00154 chaptermenuAspectRatio = "Video"
00155
00156
00157 chapterLength = 5 * 60;
00158
00159
00160 jobfile="mydata.xml"
00161
00162
00163 progresslog = ""
00164 progressfile = open("/dev/null", 'w')
00165
00166
00167 dvddrivepath = "/dev/dvd"
00168
00169
00170 docreateiso = False
00171 doburn = True
00172 erasedvdrw = False
00173 mediatype = DVD_SL
00174 savefilename = ''
00175
00176 installPrefix = ""
00177
00178
00179 jobDOM = None
00180
00181
00182 themeDOM = None
00183 themeName = ''
00184
00185
00186 themeFonts = {}
00187
00188
00189 cpuCount = 1
00190
00191 DB = MythTV.MythDB()
00192 MVID = MythTV.MythVideo(db=DB)
00193
00194 configHostname = DB.gethostname()
00195
00196
00197
00198
00199
00200 def simple_fix_rtl(str):
00201 return str
00202
00203
00204 try:
00205 import pyfribidi
00206 except ImportError:
00207 sys.stdout.write("Using simple_fix_rtl\n")
00208 fix_rtl = simple_fix_rtl
00209 else:
00210 sys.stdout.write("Using pyfribidi.log2vis\n")
00211 fix_rtl = pyfribidi.log2vis
00212
00213
00214
00215
00216 class FontDef(object):
00217 def __init__(self, name=None, fontFile=None, size=19, color="white", effect="normal", shadowColor="black", shadowSize=1):
00218 self.name = name
00219 self.fontFile = fontFile
00220 self.size = size
00221 self.color = color
00222 self.effect = effect
00223 self.shadowColor = shadowColor
00224 self.shadowSize = shadowSize
00225 self.font = None
00226
00227 def getFont(self):
00228 if self.font == None:
00229 self.font = ImageFont.truetype(self.fontFile, int(self.size))
00230
00231 return self.font
00232
00233 def drawText(self, text, color=None):
00234 if self.font == None:
00235 self.font = ImageFont.truetype(self.fontFile, int(self.size))
00236
00237 if color == None:
00238 color = self.color
00239
00240 textwidth, textheight = self.font.getsize(text)
00241
00242 image = Image.new("RGBA", (textwidth + (self.shadowSize * 2), textheight), (0,0,0,0))
00243 draw = ImageDraw.ImageDraw(image)
00244
00245 if self.effect == "shadow":
00246 draw.text((self.shadowSize,self.shadowSize), text, font=self.font, fill=self.shadowColor)
00247 draw.text((0,0), text, font=self.font, fill=color)
00248 elif self.effect == "outline":
00249 for x in range(0, self.shadowSize * 2 + 1):
00250 for y in range(0, self.shadowSize * 2 + 1):
00251 draw.text((x, y), text, font=self.font, fill=self.shadowColor)
00252
00253 draw.text((self.shadowSize,self.shadowSize), text, font=self.font, fill=color)
00254 else:
00255 draw.text((0,0), text, font=self.font, fill=color)
00256
00257 bbox = image.getbbox()
00258 image = image.crop(bbox)
00259 return image
00260
00261
00262
00263
00264 def write(text, progress=True):
00265 """Simple place to channel all text output through"""
00266
00267 text = text.encode("utf-8", "replace")
00268 sys.stdout.write(text + "\n")
00269 sys.stdout.flush()
00270
00271 if progress == True and progresslog != "":
00272 progressfile.write(time.strftime("%Y-%m-%d %H:%M:%S ") + text + "\n")
00273 progressfile.flush()
00274
00275
00276
00277
00278 def fatalError(msg):
00279 """Display an error message and exit app"""
00280 write("*"*60)
00281 write("ERROR: " + msg)
00282 write("See mythburn.log for more information.")
00283 write("*"*60)
00284 write("")
00285 saveSetting("MythArchiveLastRunResult", "Failed: " + quoteString(msg));
00286 saveSetting("MythArchiveLastRunEnd", time.strftime("%Y-%m-%d %H:%M:%S "))
00287 sys.exit(0)
00288
00289
00290
00291
00292 def nonfatalError(msg):
00293 """Display a warning message"""
00294 write("*"*60)
00295 write("WARNING: " + msg)
00296 write("*"*60)
00297 write("")
00298
00299
00300
00301
00302 def quoteString(str):
00303 """Return the input string with single quotes escaped."""
00304 return str.replace("'", "'\"'\"'")
00305
00306
00307
00308
00309 def getTempPath():
00310 """This is the folder where all temporary files will be created."""
00311 return temppath
00312
00313
00314
00315
00316 def getCPUCount():
00317 """return the number of CPUs"""
00318 cpustat = open("/proc/cpuinfo")
00319 cpudata = cpustat.readlines()
00320 cpustat.close()
00321
00322 cpucount = 0
00323 for line in cpudata:
00324 tokens = line.split()
00325 if len(tokens) > 0:
00326 if tokens[0] == "processor":
00327 cpucount += 1
00328
00329 if cpucount == 0:
00330 cpucount = 1
00331
00332 write("Found %d CPUs" % cpucount)
00333
00334 return cpucount
00335
00336
00337
00338
00339 def getEncodingProfilePath():
00340 """This is the folder where all encoder profile files are located."""
00341 return os.path.join(sharepath, "mytharchive", "encoder_profiles")
00342
00343
00344
00345
00346 def doesFileExist(file):
00347 """Returns true/false if a given file or path exists."""
00348 return os.path.exists( file )
00349
00350
00351
00352
00353 def quoteCmdArg(arg):
00354 arg = arg.replace('"', '\\"')
00355 arg = arg.replace('`', '\\`')
00356 return '"%s"' % arg
00357
00358
00359
00360
00361 def getText(node):
00362 """Returns the text contents from a given XML element."""
00363 if node.childNodes.length>0:
00364 return node.childNodes[0].data
00365 else:
00366 return ""
00367
00368
00369
00370
00371 def getThemeFile(theme,file):
00372 """Find a theme file - first look in the specified theme directory then look in the
00373 shared music and image directories"""
00374 if os.path.exists(os.path.join(sharepath, "mytharchive", "themes", theme, file)):
00375 return os.path.join(sharepath, "mytharchive", "themes", theme, file)
00376
00377 if os.path.exists(os.path.join(sharepath, "mytharchive", "images", file)):
00378 return os.path.join(sharepath, "mytharchive", "images", file)
00379
00380 if os.path.exists(os.path.join(sharepath, "mytharchive", "intro", file)):
00381 return os.path.join(sharepath, "mytharchive", "intro", file)
00382
00383 if os.path.exists(os.path.join(sharepath, "mytharchive", "music", file)):
00384 return os.path.join(sharepath, "mytharchive", "music", file)
00385
00386 fatalError("Cannot find theme file '%s' in theme '%s'" % (file, theme))
00387
00388
00389
00390
00391 def getFontPathName(fontname):
00392 return os.path.join(sharepath, "fonts", fontname)
00393
00394
00395
00396
00397 def getItemTempPath(itemnumber):
00398 return os.path.join(getTempPath(),"%s" % itemnumber)
00399
00400
00401
00402
00403 def validateTheme(theme):
00404
00405 file = getThemeFile(theme,"theme.xml")
00406 write("Looking for: " + file)
00407 return doesFileExist( getThemeFile(theme,"theme.xml") )
00408
00409
00410
00411
00412 def isResolutionOkayForDVD(videoresolution):
00413 if videomode=="ntsc":
00414 return videoresolution==(720,480) or videoresolution==(704,480) or videoresolution==(352,480) or videoresolution==(352,240)
00415 else:
00416 return videoresolution==(720,576) or videoresolution==(704,576) or videoresolution==(352,576) or videoresolution==(352,288)
00417
00418
00419
00420
00421 def deleteAllFilesInFolder(folder):
00422 """Does what it says on the tin!."""
00423 for root, dirs, deletefiles in os.walk(folder, topdown=False):
00424 for name in deletefiles:
00425 os.remove(os.path.join(root, name))
00426
00427
00428
00429
00430 def deleteEverythingInFolder(folder):
00431 for root, dirs, files in os.walk(folder, topdown=False):
00432 for name in files:
00433 os.remove(os.path.join(root, name))
00434 for name in dirs:
00435 if os.path.islink(os.path.join(root, name)):
00436 os.remove(os.path.join(root, name))
00437 else:
00438 os.rmdir(os.path.join(root, name))
00439
00440
00441
00442
00443 def checkCancelFlag():
00444 """Checks to see if the user has cancelled this run"""
00445 if os.path.exists(os.path.join(logpath, "mythburncancel.lck")):
00446 os.remove(os.path.join(logpath, "mythburncancel.lck"))
00447 write('*'*60)
00448 write("Job has been cancelled at users request")
00449 write('*'*60)
00450 sys.exit(1)
00451
00452
00453
00454
00455
00456 def runCommand(command):
00457 checkCancelFlag()
00458
00459
00460 try:
00461 oldlocale = os.environ["LC_ALL"]
00462 except:
00463 oldlocale = ""
00464 os.putenv("LC_ALL", "en_US.UTF-8")
00465 result = os.system(command.encode('utf-8'))
00466 os.putenv("LC_ALL", oldlocale)
00467
00468 if os.WIFEXITED(result):
00469 result = os.WEXITSTATUS(result)
00470 checkCancelFlag()
00471 return result
00472
00473
00474
00475
00476 def secondsToFrames(seconds):
00477 """Convert a time in seconds to a frame position"""
00478 if videomode=="pal":
00479 framespersecond=frameratePAL
00480 else:
00481 framespersecond=framerateNTSC
00482
00483 frames=int(seconds * framespersecond)
00484 return frames
00485
00486
00487
00488
00489 def encodeMenu(background, tempvideo, music, musiclength, tempmovie, xmlfile, finaloutput, aspectratio):
00490 if videomode=="pal":
00491 framespersecond=frameratePAL
00492 else:
00493 framespersecond=framerateNTSC
00494
00495 totalframes=int(musiclength * framespersecond)
00496
00497 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" \
00498 % (totalframes, framespersecond, quoteCmdArg(background), quoteCmdArg(path_mpeg2enc[0]), aspectratio, quoteCmdArg(tempvideo))
00499 result = runCommand(command)
00500 if result<>0:
00501 fatalError("Failed while running jpeg2yuv - %s" % command)
00502
00503 command = quoteCmdArg(path_mplex[0]) + " -f 8 -v 0 -o %s %s %s" % (quoteCmdArg(tempmovie), quoteCmdArg(tempvideo), quoteCmdArg(music))
00504 result = runCommand(command)
00505 if result<>0:
00506 fatalError("Failed while running mplex - %s" % command)
00507
00508 if xmlfile != "":
00509 command = quoteCmdArg(path_spumux[0]) + " -m dvd -s 0 %s < %s > %s" % (quoteCmdArg(xmlfile), quoteCmdArg(tempmovie), quoteCmdArg(finaloutput))
00510 result = runCommand(command)
00511 if result<>0:
00512 fatalError("Failed while running spumux - %s" % command)
00513 else:
00514 os.rename(tempmovie, finaloutput)
00515
00516 if os.path.exists(tempvideo):
00517 os.remove(tempvideo)
00518 if os.path.exists(tempmovie):
00519 os.remove(tempmovie)
00520
00521
00522
00523
00524
00525 def findEncodingProfile(profile):
00526 """Returns the XML node for the given encoding profile"""
00527
00528
00529
00530
00531 if videomode == "ntsc":
00532 filename = os.path.expanduser("~/.mythtv/MythArchive/ffmpeg_dvd_ntsc.xml")
00533 else:
00534 filename = os.path.expanduser("~/.mythtv/MythArchive/ffmpeg_dvd_pal.xml")
00535
00536 if not os.path.exists(filename):
00537
00538 if videomode == "ntsc":
00539 filename = getEncodingProfilePath() + "/ffmpeg_dvd_ntsc.xml"
00540 else:
00541 filename = getEncodingProfilePath() + "/ffmpeg_dvd_pal.xml"
00542
00543 write("Using encoder profiles from %s" % filename)
00544
00545 DOM = xml.dom.minidom.parse(filename)
00546
00547
00548 if DOM.documentElement.tagName != "encoderprofiles":
00549 fatalError("Profile xml file doesn't look right (%s)" % filename)
00550
00551 profiles = DOM.getElementsByTagName("profile")
00552 for node in profiles:
00553 if getText(node.getElementsByTagName("name")[0]) == profile:
00554 write("Encoding profile (%s) found" % profile)
00555 return node
00556
00557 fatalError("Encoding profile (%s) not found" % profile)
00558 return None
00559
00560
00561
00562
00563 def getThemeConfigurationXML(theme):
00564 """Loads the XML file from disk for a specific theme"""
00565
00566
00567 themeDOM = xml.dom.minidom.parse( getThemeFile(theme,"theme.xml") )
00568
00569 if themeDOM.documentElement.tagName != "mythburntheme":
00570 fatalError("Theme xml file doesn't look right (%s)" % theme)
00571 return themeDOM
00572
00573
00574
00575
00576 def getLengthOfVideo(index):
00577 """Returns the length of a video file (in seconds)"""
00578
00579
00580 infoDOM = xml.dom.minidom.parse(os.path.join(getItemTempPath(index), 'streaminfo.xml'))
00581
00582
00583 if infoDOM.documentElement.tagName != "file":
00584 fatalError("Stream info file doesn't look right (%s)" % os.path.join(getItemTempPath(index), 'streaminfo.xml'))
00585 file = infoDOM.getElementsByTagName("file")[0]
00586 if file.attributes["cutduration"].value != 'N/A':
00587 duration = int(file.attributes["cutduration"].value)
00588 else:
00589 duration = 0;
00590
00591 return duration
00592
00593
00594
00595
00596
00597 def getAudioParams(folder):
00598 """Returns the audio bitrate and no of channels for a file from its streaminfo.xml"""
00599
00600
00601 infoDOM = xml.dom.minidom.parse(os.path.join(folder, 'streaminfo.xml'))
00602
00603
00604 if infoDOM.documentElement.tagName != "file":
00605 fatalError("Stream info file doesn't look right (%s)" % os.path.join(folder, 'streaminfo.xml'))
00606 audio = infoDOM.getElementsByTagName("file")[0].getElementsByTagName("streams")[0].getElementsByTagName("audio")[0]
00607
00608 samplerate = audio.attributes["samplerate"].value
00609 channels = audio.attributes["channels"].value
00610
00611 return (samplerate, channels)
00612
00613
00614
00615
00616
00617 def getVideoParams(folder):
00618 """Returns the video resolution, fps and aspect ratio for the video file from the streamindo.xml file"""
00619
00620
00621 infoDOM = xml.dom.minidom.parse(os.path.join(folder, 'streaminfo.xml'))
00622
00623
00624 if infoDOM.documentElement.tagName != "file":
00625 fatalError("Stream info file doesn't look right (%s)" % os.path.join(getItemTempPath(index), 'streaminfo.xml'))
00626 video = infoDOM.getElementsByTagName("file")[0].getElementsByTagName("streams")[0].getElementsByTagName("video")[0]
00627
00628 if video.attributes["aspectratio"].value != 'N/A':
00629 aspect_ratio = video.attributes["aspectratio"].value
00630 else:
00631 aspect_ratio = "1.77778"
00632
00633 videores = video.attributes["width"].value + 'x' + video.attributes["height"].value
00634 fps = video.attributes["fps"].value
00635
00636
00637 if videomode=="pal":
00638 fr=frameratePAL
00639 else:
00640 fr=framerateNTSC
00641
00642 if float(fr) != float(fps):
00643 write("WARNING: frames rates do not match")
00644 write("The frame rate for %s should be %s but the stream info file "
00645 "report a fps of %s" % (videomode, fr, fps))
00646 fps = fr
00647
00648 return (videores, fps, aspect_ratio)
00649
00650
00651
00652
00653 def getAspectRatioOfVideo(index):
00654 """Returns the aspect ratio of the video file (1.333, 1.778, etc)"""
00655
00656
00657 infoDOM = xml.dom.minidom.parse(os.path.join(getItemTempPath(index), 'streaminfo.xml'))
00658
00659
00660 if infoDOM.documentElement.tagName != "file":
00661 fatalError("Stream info file doesn't look right (%s)" % os.path.join(getItemTempPath(index), 'streaminfo.xml'))
00662 video = infoDOM.getElementsByTagName("file")[0].getElementsByTagName("streams")[0].getElementsByTagName("video")[0]
00663 if video.attributes["aspectratio"].value != 'N/A':
00664 aspect_ratio = float(video.attributes["aspectratio"].value)
00665 else:
00666 aspect_ratio = 1.77778;
00667 write("aspect ratio is: %s" % aspect_ratio)
00668 return aspect_ratio
00669
00670
00671
00672
00673 def calcSyncOffset(index):
00674 """Returns the sync offset between the video and first audio stream"""
00675
00676
00677
00678 infoDOM = xml.dom.minidom.parse(os.path.join(getItemTempPath(index), 'streaminfo.xml'))
00679
00680
00681 if infoDOM.documentElement.tagName != "file":
00682 fatalError("Stream info file doesn't look right (%s)" % os.path.join(getItemTempPath(index), 'streaminfo_orig.xml'))
00683
00684 video = infoDOM.getElementsByTagName("file")[0].getElementsByTagName("streams")[0].getElementsByTagName("video")[0]
00685 video_start = float(video.attributes["start_time"].value)
00686
00687 audio = infoDOM.getElementsByTagName("file")[0].getElementsByTagName("streams")[0].getElementsByTagName("audio")[0]
00688 audio_start = float(audio.attributes["start_time"].value)
00689
00690
00691
00692
00693 sync_offset = int((video_start - audio_start) * 1000)
00694
00695
00696 return sync_offset
00697
00698
00699
00700
00701 def getFormatedLengthOfVideo(index):
00702 duration = getLengthOfVideo(index)
00703
00704 minutes = int(duration / 60)
00705 seconds = duration % 60
00706 hours = int(minutes / 60)
00707 minutes %= 60
00708
00709 return '%02d:%02d:%02d' % (hours, minutes, seconds)
00710
00711
00712
00713
00714 def frameToTime(frame, fps):
00715 sec = int(frame / fps)
00716 frame = frame - int(sec * fps)
00717 mins = sec / 60
00718 sec %= 60
00719 hour = mins / 60
00720 mins %= 60
00721
00722 return '%02d:%02d:%02d' % (hour, mins, sec)
00723
00724
00725
00726
00727
00728 def createVideoChapters(itemnum, numofchapters, lengthofvideo, getthumbnails):
00729 """Returns numofchapters chapter marks even spaced through a certain time period"""
00730
00731
00732 infoDOM = xml.dom.minidom.parse(os.path.join(getItemTempPath(itemnum),"info.xml"))
00733 thumblistNode = infoDOM.getElementsByTagName("thumblist")
00734 if thumblistNode.length > 0:
00735 thumblist = getText(thumblistNode[0])
00736 write("Using user defined thumb images - %s" % thumblist)
00737 return thumblist
00738
00739
00740 segment=int(lengthofvideo / numofchapters)
00741
00742 write( "Video length is %s seconds. Each chapter will be %s seconds" % (lengthofvideo,segment))
00743
00744 chapters=[]
00745
00746 thumbList=[]
00747 starttime=0
00748 count=1
00749 while count<=numofchapters:
00750 chapters.append(time.strftime("%H:%M:%S",time.gmtime(starttime)))
00751
00752 if starttime==0:
00753 if thumboffset < segment:
00754 thumbList.append(str(thumboffset))
00755 else:
00756 thumbList.append(str(starttime))
00757 else:
00758 thumbList.append(str(starttime))
00759
00760 starttime+=segment
00761 count+=1
00762
00763 chapters = ','.join(chapters)
00764 thumbList = ','.join(thumbList)
00765
00766 if getthumbnails==True:
00767 extractVideoFrames( os.path.join(getItemTempPath(itemnum),"stream.mv2"),
00768 os.path.join(getItemTempPath(itemnum),"chapter-%1.jpg"), thumbList)
00769
00770 return chapters
00771
00772
00773
00774
00775 def createVideoChaptersFixedLength(itemnum, segment, lengthofvideo):
00776 """Returns chapter marks at cut list ends,
00777 or evenly spaced chapters 'segment' seconds through the file"""
00778
00779
00780 if addCutlistChapters == True:
00781
00782
00783
00784 infoDOM = xml.dom.minidom.parse(os.path.join(getItemTempPath(itemnum),"info.xml"))
00785 chapterlistNode = infoDOM.getElementsByTagName("chapterlist")
00786 if chapterlistNode.length > 0:
00787 chapterlist = getText(chapterlistNode[0])
00788 write("Using commercial end marks - %s" % chapterlist)
00789 return chapterlist
00790
00791 if lengthofvideo < segment:
00792 return "00:00:00"
00793
00794 numofchapters = lengthofvideo / segment + 1;
00795 chapters = "00:00:00"
00796 starttime = 0
00797 count = 2
00798 while count <= numofchapters:
00799 starttime += segment
00800 chapters += "," + time.strftime("%H:%M:%S", time.gmtime(starttime))
00801 count += 1
00802
00803 write("Fixed length chapters: %s" % chapters)
00804
00805 return chapters
00806
00807
00808
00809
00810 def getDefaultParametersFromMythTVDB():
00811 """Reads settings from MythTV database"""
00812
00813 write( "Obtaining MythTV settings from MySQL database for hostname " + configHostname)
00814
00815
00816 sqlstatement="""SELECT value, data FROM settings WHERE value IN(
00817 'DBSchemaVer',
00818 'ISO639Language0',
00819 'ISO639Language1')
00820 OR (hostname=%s AND value IN(
00821 'VideoStartupDir',
00822 'GalleryDir',
00823 'MusicLocation',
00824 'MythArchiveVideoFormat',
00825 'MythArchiveTempDir',
00826 'MythArchiveMplexCmd',
00827 'MythArchiveDvdauthorCmd',
00828 'MythArchiveMkisofsCmd',
00829 'MythArchiveM2VRequantiserCmd',
00830 'MythArchiveMpg123Cmd',
00831 'MythArchiveProjectXCmd',
00832 'MythArchiveDVDLocation',
00833 'MythArchiveGrowisofsCmd',
00834 'MythArchiveJpeg2yuvCmd',
00835 'MythArchiveSpumuxCmd',
00836 'MythArchiveMpeg2encCmd',
00837 'MythArchiveCopyRemoteFiles',
00838 'MythArchiveAlwaysUseMythTranscode',
00839 'MythArchiveUseProjectX',
00840 'MythArchiveAddSubtitles',
00841 'MythArchiveUseFIFO',
00842 'MythArchiveMainMenuAR',
00843 'MythArchiveChapterMenuAR',
00844 'MythArchiveDateFormat',
00845 'MythArchiveTimeFormat',
00846 'MythArchiveClearArchiveTable',
00847 'MythArchiveDriveSpeed',
00848 'JobQueueCPU'
00849 )) ORDER BY value"""
00850
00851
00852 cursor = DB.cursor()
00853
00854 cursor.execute(sqlstatement, configHostname)
00855
00856 result = cursor.fetchall()
00857
00858 cfg = {}
00859 for i in range(len(result)):
00860 cfg[result[i][0]] = result[i][1]
00861
00862
00863 if not "MythArchiveTempDir" in cfg:
00864 fatalError("Can't find the setting for the temp directory. \nHave you run setup in the frontend?")
00865 return cfg
00866
00867
00868
00869
00870 def saveSetting(name, data):
00871 host = DB.gethostname()
00872 DB.settings[host][name] = data
00873
00874
00875
00876
00877 def clearArchiveItems():
00878 ''' Remove all archive items from the archiveitems DB table'''
00879
00880 write("Removing all archive items from the archiveitems DB table")
00881 with DB as cursor:
00882 cursor.execute("DELETE FROM archiveitems")
00883
00884
00885
00886
00887 def getOptions(options):
00888 global doburn
00889 global docreateiso
00890 global erasedvdrw
00891 global mediatype
00892 global savefilename
00893
00894 if options.length == 0:
00895 fatalError("Trying to read the options from the job file but none found?")
00896 options = options[0]
00897
00898 doburn = options.attributes["doburn"].value != '0'
00899 docreateiso = options.attributes["createiso"].value != '0'
00900 erasedvdrw = options.attributes["erasedvdrw"].value != '0'
00901 mediatype = int(options.attributes["mediatype"].value)
00902 savefilename = options.attributes["savefilename"].value
00903
00904 write("Options - mediatype = %d, doburn = %d, createiso = %d, erasedvdrw = %d" \
00905 % (mediatype, doburn, docreateiso, erasedvdrw))
00906 write(" savefilename = '%s'" % savefilename)
00907
00908
00909
00910
00911 def expandItemText(infoDOM, text, itemnumber, pagenumber, keynumber,chapternumber, chapterlist ):
00912 """Replaces keywords in a string with variables from the XML and filesystem"""
00913 text=string.replace(text,"%page","%s" % pagenumber)
00914
00915
00916 if getText( infoDOM.getElementsByTagName("coverfile")[0]) =="":
00917 text=string.replace(text,"%thumbnail", os.path.join( getItemTempPath(itemnumber), "title.jpg"))
00918 else:
00919 text=string.replace(text,"%thumbnail", getText( infoDOM.getElementsByTagName("coverfile")[0]) )
00920
00921 text=string.replace(text,"%itemnumber","%s" % itemnumber )
00922 text=string.replace(text,"%keynumber","%s" % keynumber )
00923
00924 text=string.replace(text,"%title",getText( infoDOM.getElementsByTagName("title")[0]) )
00925 text=string.replace(text,"%subtitle",getText( infoDOM.getElementsByTagName("subtitle")[0]) )
00926 text=string.replace(text,"%description",getText( infoDOM.getElementsByTagName("description")[0]) )
00927 text=string.replace(text,"%type",getText( infoDOM.getElementsByTagName("type")[0]) )
00928
00929 text=string.replace(text,"%recordingdate",getText( infoDOM.getElementsByTagName("recordingdate")[0]) )
00930 text=string.replace(text,"%recordingtime",getText( infoDOM.getElementsByTagName("recordingtime")[0]) )
00931
00932 text=string.replace(text,"%duration", getFormatedLengthOfVideo(itemnumber))
00933
00934 text=string.replace(text,"%myfolder",getThemeFile(themeName,""))
00935
00936 if chapternumber>0:
00937 text=string.replace(text,"%chapternumber","%s" % chapternumber )
00938 text=string.replace(text,"%chaptertime","%s" % chapterlist[chapternumber - 1] )
00939 text=string.replace(text,"%chapterthumbnail", os.path.join( getItemTempPath(itemnumber), "chapter-%s.jpg" % chapternumber))
00940
00941 return text
00942
00943
00944
00945
00946 def getScaledAttribute(node, attribute):
00947 """ Returns a value taken from attribute in node scaled for the current video mode"""
00948
00949 if videomode == "pal" or attribute == "x" or attribute == "w":
00950 return int(node.attributes[attribute].value)
00951 else:
00952 return int(float(node.attributes[attribute].value) / 1.2)
00953
00954
00955
00956
00957 def intelliDraw(drawer, text, font, containerWidth):
00958 """Based on http://mail.python.org/pipermail/image-sig/2004-December/003064.html"""
00959
00960
00961
00962
00963
00964
00965
00966 words = text.split()
00967 lines = []
00968 lines.append(words)
00969 finished = False
00970 line = 0
00971 while not finished:
00972 thistext = lines[line]
00973 newline = []
00974 innerFinished = False
00975 while not innerFinished:
00976
00977
00978
00979 if drawer.textsize(' '.join(thistext),font.getFont())[0] > containerWidth:
00980
00981
00982
00983 if str(thistext).find(' ') != -1:
00984 newline.insert(0,thistext.pop(-1))
00985 else:
00986
00987 innerFinished = True
00988 else:
00989 innerFinished = True
00990 if len(newline) > 0:
00991 lines.append(newline)
00992 line = line + 1
00993 else:
00994 finished = True
00995 tmp = []
00996 for i in lines:
00997 tmp.append( fix_rtl( ' '.join(i) ) )
00998 lines = tmp
00999 return lines
01000
01001
01002
01003
01004 def paintBackground(image, node):
01005 if node.hasAttribute("bgcolor"):
01006 bgcolor = node.attributes["bgcolor"].value
01007 x = getScaledAttribute(node, "x")
01008 y = getScaledAttribute(node, "y")
01009 w = getScaledAttribute(node, "w")
01010 h = getScaledAttribute(node, "h")
01011 r,g,b = ImageColor.getrgb(bgcolor)
01012
01013 if node.hasAttribute("bgalpha"):
01014 a = int(node.attributes["bgalpha"].value)
01015 else:
01016 a = 255
01017
01018 image.paste((r, g, b, a), (x, y, x + w, y + h))
01019
01020
01021
01022
01023
01024 def paintButton(draw, bgimage, bgimagemask, node, infoDOM, itemnum, page,
01025 itemsonthispage, chapternumber, chapterlist):
01026
01027 imagefilename = getThemeFile(themeName, node.attributes["filename"].value)
01028 if not doesFileExist(imagefilename):
01029 fatalError("Cannot find image for menu button (%s)." % imagefilename)
01030 maskimagefilename = getThemeFile(themeName, node.attributes["mask"].value)
01031 if not doesFileExist(maskimagefilename):
01032 fatalError("Cannot find mask image for menu button (%s)." % maskimagefilename)
01033
01034 picture = Image.open(imagefilename,"r").resize(
01035 (getScaledAttribute(node, "w"), getScaledAttribute(node, "h")))
01036 picture = picture.convert("RGBA")
01037 bgimage.paste(picture, (getScaledAttribute(node, "x"),
01038 getScaledAttribute(node, "y")), picture)
01039 del picture
01040
01041
01042 textnode = node.getElementsByTagName("textnormal")
01043 if textnode.length > 0:
01044 textnode = textnode[0]
01045 text = expandItemText(infoDOM,textnode.attributes["value"].value,
01046 itemnum, page, itemsonthispage,
01047 chapternumber,chapterlist)
01048
01049 if text > "":
01050 paintText(draw, bgimage, text, textnode)
01051
01052 del text
01053
01054 write( "Added button image %s" % imagefilename)
01055
01056 picture = Image.open(maskimagefilename,"r").resize(
01057 (getScaledAttribute(node, "w"), getScaledAttribute(node, "h")))
01058 picture = picture.convert("RGBA")
01059 bgimagemask.paste(picture, (getScaledAttribute(node, "x"),
01060 getScaledAttribute(node, "y")),picture)
01061
01062
01063
01064 textnode = node.getElementsByTagName("textselected")
01065 if textnode.length > 0:
01066 textnode = textnode[0]
01067 text = expandItemText(infoDOM, textnode.attributes["value"].value,
01068 itemnum, page, itemsonthispage,
01069 chapternumber, chapterlist)
01070 textImage = Image.new("RGBA",picture.size)
01071 textDraw = ImageDraw.Draw(textImage)
01072
01073 if text > "":
01074 paintText(textDraw, textImage, text, textnode, "white",
01075 getScaledAttribute(node, "x") - getScaledAttribute(textnode, "x"),
01076 getScaledAttribute(node, "y") - getScaledAttribute(textnode, "y"),
01077 getScaledAttribute(textnode, "w"),
01078 getScaledAttribute(textnode, "h"))
01079
01080
01081 (width, height) = textImage.size
01082 for y in range(height):
01083 for x in range(width):
01084 if textImage.getpixel((x,y)) < (100, 100, 100, 255):
01085 textImage.putpixel((x,y), (0, 0, 0, 0))
01086 else:
01087 textImage.putpixel((x,y), (255, 255, 255, 255))
01088
01089 if textnode.hasAttribute("colour"):
01090 color = textnode.attributes["colour"].value
01091 elif textnode.hasAttribute("color"):
01092 color = textnode.attributes["color"].value
01093 else:
01094 color = "white"
01095
01096 bgimagemask.paste(color,
01097 (getScaledAttribute(textnode, "x"),
01098 getScaledAttribute(textnode, "y")),
01099 textImage)
01100
01101 del text, textImage, textDraw
01102 del picture
01103
01104
01105
01106
01107 def paintText(draw, image, text, node, color = None,
01108 x = None, y = None, width = None, height = None):
01109 """Takes a piece of text and draws it onto an image inside a bounding box."""
01110
01111
01112 if x == None:
01113 x = getScaledAttribute(node, "x")
01114 y = getScaledAttribute(node, "y")
01115 width = getScaledAttribute(node, "w")
01116 height = getScaledAttribute(node, "h")
01117
01118 font = themeFonts[node.attributes["font"].value]
01119
01120 if color == None:
01121 if node.hasAttribute("colour"):
01122 color = node.attributes["colour"].value
01123 elif node.hasAttribute("color"):
01124 color = node.attributes["color"].value
01125 else:
01126 color = None
01127
01128 if node.hasAttribute("halign"):
01129 halign = node.attributes["halign"].value
01130 elif node.hasAttribute("align"):
01131 halign = node.attributes["align"].value
01132 else:
01133 halign = "left"
01134
01135 if node.hasAttribute("valign"):
01136 valign = node.attributes["valign"].value
01137 else:
01138 valign = "top"
01139
01140 if node.hasAttribute("vindent"):
01141 vindent = int(node.attributes["vindent"].value)
01142 else:
01143 vindent = 0
01144
01145 if node.hasAttribute("hindent"):
01146 hindent = int(node.attributes["hindent"].value)
01147 else:
01148 hindent = 0
01149
01150 lines = intelliDraw(draw, text, font, width - (hindent * 2))
01151 j = 0
01152
01153
01154 textImage = font.drawText(lines[0])
01155 h = int(textImage.size[1] * 1.1)
01156
01157 for i in lines:
01158 if (j * h) < (height - (vindent * 2) - h):
01159 textImage = font.drawText(i, color)
01160 write( "Wrapped text = " + i.encode("ascii", "replace"), False)
01161
01162 if halign == "left":
01163 xoffset = hindent
01164 elif halign == "center" or halign == "centre":
01165 xoffset = (width / 2) - (textImage.size[0] / 2)
01166 elif halign == "right":
01167 xoffset = width - textImage.size[0] - hindent
01168 else:
01169 xoffset = hindent
01170
01171 if valign == "top":
01172 yoffset = vindent
01173 elif valign == "center" or halign == "centre":
01174 yoffset = (height / 2) - (textImage.size[1] / 2)
01175 elif valign == "bottom":
01176 yoffset = height - textImage.size[1] - vindent
01177 else:
01178 yoffset = vindent
01179
01180 image.paste(textImage, (x + xoffset,y + yoffset + j * h), textImage)
01181 else:
01182 write( "Truncated text = " + i.encode("ascii", "replace"), False)
01183
01184 j = j + 1
01185
01186
01187
01188
01189 def paintImage(filename, maskfilename, imageDom, destimage, stretch=True):
01190 """Paste the image specified in the filename into the specified image"""
01191
01192 if not doesFileExist(filename):
01193 write("Image file (%s) does not exist" % filename)
01194 return False
01195
01196 picture = Image.open(filename, "r")
01197 xpos = getScaledAttribute(imageDom, "x")
01198 ypos = getScaledAttribute(imageDom, "y")
01199 w = getScaledAttribute(imageDom, "w")
01200 h = getScaledAttribute(imageDom, "h")
01201 (imgw, imgh) = picture.size
01202 write("Image (%s, %s) into space of (%s, %s) at (%s, %s)" % (imgw, imgh, w, h, xpos, ypos), False)
01203
01204
01205 if imageDom.hasAttribute("stretch"):
01206 if imageDom.attributes["stretch"].value == "True":
01207 stretch = True
01208 else:
01209 stretch = False
01210
01211 if stretch == True:
01212 imgw = w;
01213 imgh = h;
01214 else:
01215 if float(w)/imgw < float(h)/imgh:
01216
01217 imgh = imgh*w/imgw
01218 imgw = w
01219 if imageDom.hasAttribute("valign"):
01220 valign = imageDom.attributes["valign"].value
01221 else:
01222 valign = "center"
01223
01224 if valign == "bottom":
01225 ypos += h - imgh
01226 if valign == "center":
01227 ypos += (h - imgh)/2
01228 else:
01229
01230 imgw = imgw*h/imgh
01231 imgh = h
01232 if imageDom.hasAttribute("halign"):
01233 halign = imageDom.attributes["halign"].value
01234 else:
01235 halign = "center"
01236
01237 if halign == "right":
01238 xpos += w - imgw
01239 if halign == "center":
01240 xpos += (w - imgw)/2
01241
01242 write("Image resized to (%s, %s) at (%s, %s)" % (imgw, imgh, xpos, ypos), False)
01243 picture = picture.resize((imgw, imgh))
01244 picture = picture.convert("RGBA")
01245
01246 if maskfilename <> None and doesFileExist(maskfilename):
01247 maskpicture = Image.open(maskfilename, "r").resize((imgw, imgh))
01248 maskpicture = maskpicture.convert("RGBA")
01249 else:
01250 maskpicture = picture
01251
01252 destimage.paste(picture, (xpos, ypos), maskpicture)
01253 del picture
01254 if maskfilename <> None and doesFileExist(maskfilename):
01255 del maskpicture
01256
01257 write ("Added image %s" % filename)
01258
01259 return True
01260
01261
01262
01263
01264
01265 def checkBoundaryBox(boundarybox, node):
01266
01267
01268
01269 if getText(node.attributes["static"]) == "False":
01270 if getScaledAttribute(node, "x") < boundarybox[0]:
01271 boundarybox = getScaledAttribute(node, "x"), boundarybox[1], boundarybox[2], boundarybox[3]
01272
01273 if getScaledAttribute(node, "y") < boundarybox[1]:
01274 boundarybox = boundarybox[0], getScaledAttribute(node, "y"), boundarybox[2], boundarybox[3]
01275
01276 if (getScaledAttribute(node, "x") + getScaledAttribute(node, "w")) > boundarybox[2]:
01277 boundarybox = boundarybox[0], boundarybox[1], getScaledAttribute(node, "x") + \
01278 getScaledAttribute(node, "w"), boundarybox[3]
01279
01280 if (getScaledAttribute(node, "y") + getScaledAttribute(node, "h")) > boundarybox[3]:
01281 boundarybox = boundarybox[0], boundarybox[1], boundarybox[2], \
01282 getScaledAttribute(node, "y") + getScaledAttribute(node, "h")
01283
01284 return boundarybox
01285
01286
01287
01288
01289 def loadFonts(themeDOM):
01290 global themeFonts
01291
01292
01293 nodelistfonts = themeDOM.getElementsByTagName("font")
01294
01295 fontnumber = 0
01296 for node in nodelistfonts:
01297 filename = getText(node)
01298
01299 if node.hasAttribute("name"):
01300 name = node.attributes["name"].value
01301 else:
01302 name = str(fontnumber)
01303
01304 fontsize = getScaledAttribute(node, "size")
01305
01306 if node.hasAttribute("color"):
01307 color = node.attributes["color"].value
01308 else:
01309 color = "white"
01310
01311 if node.hasAttribute("effect"):
01312 effect = node.attributes["effect"].value
01313 else:
01314 effect = "normal"
01315
01316 if node.hasAttribute("shadowsize"):
01317 shadowsize = int(node.attributes["shadowsize"].value)
01318 else:
01319 shadowsize = 0
01320
01321 if node.hasAttribute("shadowcolor"):
01322 shadowcolor = node.attributes["shadowcolor"].value
01323 else:
01324 shadowcolor = "black"
01325
01326 themeFonts[name] = FontDef(name, getFontPathName(filename),
01327 fontsize, color, effect, shadowcolor, shadowsize)
01328
01329 write( "Loading font %s, %s size %s" % (fontnumber,getFontPathName(filename),fontsize) )
01330 fontnumber+=1
01331
01332
01333
01334
01335 def getFileInformation(file, folder):
01336 outputfile = os.path.join(folder, "info.xml")
01337 impl = xml.dom.minidom.getDOMImplementation()
01338 infoDOM = impl.createDocument(None, "fileinfo", None)
01339 top_element = infoDOM.documentElement
01340
01341 data = OrdDict((('chanid',''),
01342 ('type',''), ('filename',''),
01343 ('title',''), ('recordingdate',''),
01344 ('recordingtime',''), ('subtitle',''),
01345 ('description',''), ('rating',''),
01346 ('coverfile',''), ('cutlist','')))
01347
01348
01349 details = file.getElementsByTagName("details")
01350 if details.length > 0:
01351 data.type = file.attributes["type"].value
01352 data.filename = file.attributes["filename"].value
01353 data.title = details[0].attributes["title"].value
01354 data.recordingdate = details[0].attributes["startdate"].value
01355 data.recordingtime = details[0].attributes["starttime"].value
01356 data.subtitle = details[0].attributes["subtitle"].value
01357 data.description = getText(details[0])
01358
01359
01360 if file.attributes["type"].value=="recording":
01361 filename = file.attributes["filename"].value
01362 try:
01363 rec = DB.searchRecorded(basename=os.path.basename(filename)).next()
01364 except StopIteration:
01365 fatalError("Failed to get recording details from the DB for %s" % filename)
01366
01367 data.chanid = rec.chanid
01368 data.recordingtime = rec.starttime.isoformat()
01369 data.recordingdate = rec.starttime.isoformat()
01370
01371 cutlist = rec.markup.getcutlist()
01372 if len(cutlist):
01373 data.hascutlist = 'yes'
01374 if file.attributes["usecutlist"].value == "0" and addCutlistChapters == True:
01375 chapterlist = ['00:00:00']
01376 res, fps, ar = getVideoParams(folder)
01377 for s,e in cutlist:
01378 chapterlist.append(frameToTime(s, float(fps)))
01379 data.chapterlist = ','.join(chapterlist)
01380 else:
01381 data.hascutlist = 'no'
01382
01383 elif file.attributes["type"].value=="recording":
01384 filename = file.attributes["filename"].value
01385 try:
01386 rec = DB.searchRecorded(basename=os.path.basename(filename)).next()
01387 except StopIteration:
01388 fatalError("Failed to get recording details from the DB for %s" % filename)
01389
01390 write(" " + rec.title)
01391 data.type = file.attributes["type"].value
01392 data.filename = filename
01393 data.title = rec.title
01394 data.recordingdate = rec.progstart.strftime(dateformat)
01395 data.recordingtime = rec.progstart.strftime(timeformat)
01396 data.subtitle = rec.subtitle
01397 data.description = rec.description
01398 data.rating = str(rec.stars)
01399 data.chanid = rec.chanid
01400 data.starttime = rec.starttime.isoformat()
01401
01402 cutlist = rec.markup.getcutlist()
01403 if len(cutlist):
01404 data.hascutlist = 'yes'
01405 if file.attributes["usecutlist"].value == "0" and addCutlistChapters == True:
01406 chapterlist = ['00:00:00']
01407 res, fps, ar = getVideoParams(folder)
01408 for s,e in cutlist:
01409 chapterlist.append(frameToTime(s, float(fps)))
01410 data.chapterlist = ','.join(chapterlist)
01411 else:
01412 data.hascutlist = 'no'
01413
01414 elif file.attributes["type"].value=="video":
01415 filename = file.attributes["filename"].value
01416 try:
01417 vid = MVID.searchVideos(file=filename).next()
01418 except StopIteration:
01419 vid = Video.fromFilename(filename)
01420
01421 data.type = file.attributes["type"].value
01422 data.filename = filename
01423 data.title = vid.title
01424
01425 if vid.year != 1895:
01426 data.recordingdate = str(vid.year)
01427
01428 data.subtitle = vid.subtitle
01429
01430 if (vid.plot is not None) and (vid.plot != 'None'):
01431 data.description = vid.plot
01432
01433 data.rating = str(vid.userrating)
01434
01435 if doesFileExist(vid.coverfile):
01436 data.coverfile = vid.coverfile
01437
01438 elif file.attributes["type"].value=="file":
01439 data.type = file.attributes["type"].value
01440 data.filename = file.attributes["filename"].value
01441 data.title = file.attributes["filename"].value
01442
01443
01444 thumbs = file.getElementsByTagName("thumbimages")
01445 if thumbs.length > 0:
01446 thumbs = thumbs[0]
01447 thumbs = file.getElementsByTagName("thumb")
01448 thumblist = []
01449 res, fps, ar = getVideoParams(folder)
01450
01451 for thumb in thumbs:
01452 caption = thumb.attributes["caption"].value
01453 frame = thumb.attributes["frame"].value
01454 filename = thumb.attributes["filename"].value
01455 if caption != "Title":
01456 thumblist.append(frameToTime(int(frame), float(fps)))
01457
01458
01459 copy(filename, folder)
01460
01461 data.thumblist = ','.join(thumblist)
01462
01463 for k,v in data.items():
01464 write( "Node = %s, Data = %s" % (k, v))
01465 node = infoDOM.createElement(k)
01466
01467
01468
01469
01470
01471
01472 node.appendChild(infoDOM.createTextNode(unicode(v)))
01473 top_element.appendChild(node)
01474
01475 WriteXMLToFile (infoDOM, outputfile)
01476
01477
01478
01479
01480 def WriteXMLToFile(myDOM, filename):
01481
01482 f=open(filename, 'w')
01483 f.write(myDOM.toxml("UTF-8"))
01484 f.close()
01485
01486
01487
01488
01489
01490 def preProcessFile(file, folder, count):
01491 """Pre-process a single video/recording file."""
01492
01493 write( "Pre-processing %s %d: '%s'" % (file.attributes["type"].value, count, file.attributes["filename"].value))
01494
01495
01496
01497
01498
01499 mediafile=""
01500
01501 if file.attributes["type"].value == "recording":
01502 mediafile = file.attributes["filename"].value
01503 elif file.attributes["type"].value == "video":
01504 mediafile = os.path.join(videopath, file.attributes["filename"].value)
01505 elif file.attributes["type"].value == "file":
01506 mediafile = file.attributes["filename"].value
01507 else:
01508 fatalError("Unknown type of video file it must be 'recording', 'video' or 'file'.")
01509
01510 if doesFileExist(mediafile) == False:
01511 fatalError("Source file does not exist: " + mediafile)
01512
01513 if file.hasAttribute("localfilename"):
01514 mediafile = file.attributes["localfilename"].value
01515
01516 getStreamInformation(mediafile, os.path.join(folder, "streaminfo.xml"), 0)
01517 copy(os.path.join(folder, "streaminfo.xml"), os.path.join(folder, "streaminfo_orig.xml"))
01518
01519 getFileInformation(file, folder)
01520
01521 videosize = getVideoSize(os.path.join(folder, "streaminfo.xml"))
01522
01523 write( "Video resolution is %s by %s" % (videosize[0], videosize[1]))
01524
01525
01526
01527
01528 def encodeAudio(format, sourcefile, destinationfile, deletesourceafterencode):
01529 write( "Encoding audio to "+format)
01530 if format == "ac3":
01531 cmd = "mythffmpeg -v 0 -y "
01532
01533 if cpuCount > 1:
01534 cmd += "-threads %d " % cpuCount
01535
01536 cmd += "-i %s -f ac3 -ab 192k -ar 48000 %s" % (quoteCmdArg(sourcefile), quoteCmdArg(destinationfile))
01537 result = runCommand(cmd)
01538
01539 if result != 0:
01540 fatalError("Failed while running mythffmpeg to re-encode the audio to ac3\n"
01541 "Command was %s" % cmd)
01542 else:
01543 fatalError("Unknown encodeAudio format " + format)
01544
01545 if deletesourceafterencode==True:
01546 os.remove(sourcefile)
01547
01548
01549
01550
01551
01552 def multiplexMPEGStream(video, audio1, audio2, destination, syncOffset):
01553 """multiplex one video and one or two audio streams together"""
01554
01555 write("Multiplexing MPEG stream to %s" % destination)
01556
01557
01558 if useprojectx:
01559 syncOffset = 0
01560 else:
01561 if useSyncOffset == True:
01562 write("Adding sync offset of %dms" % syncOffset)
01563 else:
01564 write("Using sync offset is disabled - it would be %dms" % syncOffset)
01565 syncOffset = 0
01566
01567 if doesFileExist(destination)==True:
01568 os.remove(destination)
01569
01570
01571 if doesFileExist(audio1 + ".ac3"):
01572 audio1 = audio1 + ".ac3"
01573 elif doesFileExist(audio1 + ".mp2"):
01574 audio1 = audio1 + ".mp2"
01575 else:
01576 fatalError("No audio stream available!")
01577
01578 if doesFileExist(audio2 + ".ac3"):
01579 audio2 = audio2 + ".ac3"
01580 elif doesFileExist(audio2 + ".mp2"):
01581 audio2 = audio2 + ".mp2"
01582
01583
01584
01585 if os.path.exists(os.path.dirname(destination) + "/stream.d/spumux.xml"):
01586 localUseFIFO=False
01587 else:
01588 localUseFIFO=useFIFO
01589
01590 if localUseFIFO==True:
01591 os.mkfifo(destination)
01592 mode=os.P_NOWAIT
01593 else:
01594 mode=os.P_WAIT
01595
01596 checkCancelFlag()
01597
01598 if not doesFileExist(audio2):
01599 write("Available streams - video and one audio stream")
01600 result=os.spawnlp(mode, path_mplex[0], path_mplex[1],
01601 '-M',
01602 '-f', '8',
01603 '-v', '0',
01604 '--sync-offset', '%sms' % syncOffset,
01605 '-o', destination,
01606 video,
01607 audio1)
01608 else:
01609 write("Available streams - video and two audio streams")
01610 result=os.spawnlp(mode, path_mplex[0], path_mplex[1],
01611 '-M',
01612 '-f', '8',
01613 '-v', '0',
01614 '--sync-offset', '%sms' % syncOffset,
01615 '-o', destination,
01616 video,
01617 audio1,
01618 audio2)
01619
01620 if localUseFIFO == True:
01621 write( "Multiplex started PID=%s" % result)
01622 return result
01623 else:
01624 if result != 0:
01625 fatalError("mplex failed with result %d" % result)
01626
01627
01628 if os.path.exists(os.path.dirname(destination) + "/stream.d/spumux.xml"):
01629 write("Checking integrity of subtitle pngs")
01630 command = quoteCmdArg(os.path.join(scriptpath, "testsubtitlepngs.sh")) + " " + quoteCmdArg(os.path.dirname(destination) + "/stream.d/spumux.xml")
01631 result = runCommand(command)
01632 if result<>0:
01633 fatalError("Failed while running testsubtitlepngs.sh - %s" % command)
01634
01635 write("Running spumux to add subtitles")
01636 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"))
01637 result = runCommand(command)
01638 if result<>0:
01639 nonfatalError("Failed while running spumux.\n"
01640 "Command was - %s.\n"
01641 "Look in the full log to see why it failed" % command)
01642 os.remove(os.path.splitext(destination)[0] + "-sub.mpg")
01643 else:
01644 os.rename(os.path.splitext(destination)[0] + "-sub.mpg", destination)
01645
01646 return True
01647
01648
01649
01650
01651
01652 def getStreamInformation(filename, xmlFilename, lenMethod):
01653 """create a stream.xml file for filename"""
01654
01655 command = "mytharchivehelper -q -q --getfileinfo --infile %s --outfile %s --method %d" % (quoteCmdArg(filename), quoteCmdArg(xmlFilename), lenMethod)
01656
01657
01658 result = runCommand(command)
01659
01660 if result <> 0:
01661 fatalError("Failed while running mytharchivehelper to get stream information.\n"
01662 "Result: %d, Command was %s" % (result, command))
01663
01664
01665 infoDOM = xml.dom.minidom.parse(xmlFilename)
01666 write(xmlFilename + ":-\n" + infoDOM.toprettyxml(" ", ""), False)
01667
01668
01669
01670
01671 def getVideoSize(xmlFilename):
01672 """Get video width and height from stream.xml file"""
01673
01674
01675 infoDOM = xml.dom.minidom.parse(xmlFilename)
01676
01677
01678 if infoDOM.documentElement.tagName != "file":
01679 fatalError("This info file doesn't look right (%s)." % xmlFilename)
01680 nodes = infoDOM.getElementsByTagName("video")
01681 if nodes.length == 0:
01682 fatalError("Didn't find any video elements in stream info file. (%s)" % xmlFilename)
01683
01684 if nodes.length > 1:
01685 write("Found more than one video element in stream info file.!!!")
01686 node = nodes[0]
01687 width = int(node.attributes["width"].value)
01688 height = int(node.attributes["height"].value)
01689
01690 return (width, height)
01691
01692
01693
01694
01695 def runMythtranscode(chanid, starttime, destination, usecutlist, localfile):
01696 """Use mythtranscode to cut commercials and/or clean up an mpeg2 file"""
01697
01698 rec = DB.searchRecorded(chanid=chanid, starttime=starttime).next()
01699 cutlist = rec.markup.getcutlist()
01700
01701 cutlist_s = ""
01702 if usecutlist and len(cutlist):
01703 cutlist_s = "'"
01704 for cut in cutlist:
01705 cutlist_s += ' %d-%d ' % cut
01706 cutlist_s += "'"
01707 write("Using cutlist: %s" % cutlist_s)
01708
01709 if (localfile != ""):
01710 localfile = quoteFilename(localfile)
01711 if usecutlist == True:
01712 command = "mythtranscode --mpeg2 --honorcutlist %s --infile %s --outfile %s" % (cutlist_s, quoteCmdArg(localfile), quoteCmdArg(destination))
01713 else:
01714 command = "mythtranscode --mpeg2 --infile %s --outfile %s" % (quoteCmdArg(localfile), quoteCmdArg(destination))
01715 else:
01716 if usecutlist == True:
01717 command = "mythtranscode --mpeg2 --honorcutlist --chanid %s --starttime %s --outfile %s" % (chanid, starttime, quoteCmdArg(destination))
01718 else:
01719 command = "mythtranscode --mpeg2 --chanid %s --starttime %s --outfile %s" % (chanid, starttime, quoteCmdArg(destination))
01720
01721 result = runCommand(command)
01722
01723 if (result != 0):
01724 write("Failed while running mythtranscode to cut commercials and/or clean up an mpeg2 file.\n"
01725 "Result: %d, Command was %s" % (result, command))
01726 return False;
01727
01728 return True
01729
01730
01731
01732
01733
01734 def generateProjectXCutlist(chanid, starttime, folder):
01735 """generate cutlist_x.txt for ProjectX"""
01736
01737 rec = DB.searchRecorded(chanid=chanid, starttime=starttime).next()
01738 cutlist = rec.markup.getcutlist()
01739
01740 if len(cutlist):
01741 with open(os.path.join(folder, "cutlist_x.txt"), 'w') as cutlist_f:
01742 cutlist_f.write("CollectionPanel.CutMode=2\n")
01743 i = 0
01744 for cut in cutlist:
01745
01746
01747
01748 if i == 0:
01749 if cut[0] != 0:
01750 cutlist_f.write('0\n%d\n' % cut[0])
01751 cutlist_f.write('%d\n' % cut[1])
01752 elif i == len(cutlist) - 1:
01753 cutlist_f.write('%d\n' % cut[0])
01754 if cut[1] != 9999999:
01755 cutlist_f.write('%d\n9999999\n' % cut[1])
01756 else:
01757 cutlist_f.write('%d\n%d\n' % cut)
01758
01759 i+=1
01760 return True
01761 else:
01762 write("No cutlist in the DB for chanid %s, starttime %s" % chanid, starttime)
01763 return False
01764
01765
01766
01767
01768 def runProjectX(chanid, starttime, folder, usecutlist, file):
01769 """Use Project-X to cut commercials and demux an mpeg2 file"""
01770
01771 if usecutlist:
01772 if generateProjectXCutlist(chanid, starttime, folder) == False:
01773 write("Failed to generate Project-X cutlist.")
01774 return False
01775
01776 if os.path.exists(file) != True:
01777 write("Error: input file doesn't exist on local filesystem")
01778 return False
01779
01780 command = quoteCmdArg(path_projectx[0]) + " %s -id '%s' -set ExternPanel.appendPidToFileName=1 -out %s -name stream" % (quoteCmdArg(file), getStreamList(folder), quoteCmdArg(folder))
01781 if usecutlist == True:
01782 command += " -cut %s" % quoteCmdArg(os.path.join(folder, "cutlist_x.txt"))
01783 write(command)
01784 result = runCommand(command)
01785
01786 if (result != 0):
01787 write("Failed while running Project-X to cut commercials and/or demux an mpeg2 file.\n"
01788 "Result: %d, Command was %s" % (result, command))
01789 return False;
01790
01791
01792
01793 video, audio1, audio2 = selectStreams(folder)
01794 if addSubtitles:
01795 subtitles = selectSubtitleStream(folder)
01796
01797 videoID_hex = "0x%x" % video[VIDEO_ID]
01798 if audio1[AUDIO_ID] != -1:
01799 audio1ID_hex = "0x%x" % audio1[AUDIO_ID]
01800 else:
01801 audio1ID_hex = ""
01802 if audio2[AUDIO_ID] != -1:
01803 audio2ID_hex = "0x%x" % audio2[AUDIO_ID]
01804 else:
01805 audio2ID_hex = ""
01806 if addSubtitles and subtitles[SUBTITLE_ID] != -1:
01807 subtitlesID_hex = "0x%x" % subtitles[SUBTITLE_ID]
01808 else:
01809 subtitlesID_hex = ""
01810
01811
01812 files = os.listdir(folder)
01813 for file in files:
01814 if file[0:9] == "stream{0x":
01815 PID = file[7:13]
01816 SubID = file[19:23]
01817 if PID == videoID_hex or SubID == videoID_hex:
01818 os.rename(os.path.join(folder, file), os.path.join(folder, "stream.mv2"))
01819 elif PID == audio1ID_hex or SubID == audio1ID_hex:
01820 os.rename(os.path.join(folder, file), os.path.join(folder, "stream0." + file[-3:]))
01821 elif PID == audio2ID_hex or SubID == audio2ID_hex:
01822 os.rename(os.path.join(folder, file), os.path.join(folder, "stream1." + file[-3:]))
01823 elif PID == subtitlesID_hex or SubID == subtitlesID_hex:
01824 if file[-3:] == "sup":
01825 os.rename(os.path.join(folder, file), os.path.join(folder, "stream.sup"))
01826 else:
01827 os.rename(os.path.join(folder, file), os.path.join(folder, "stream.sup.IFO"))
01828
01829
01830
01831
01832 files = os.listdir(folder)
01833 for file in files:
01834 if file[0:9] == "stream{0x":
01835 if not os.path.exists(os.path.join(folder, "stream.mv2")) and file[-3:] == "m2v":
01836 os.rename(os.path.join(folder, file), os.path.join(folder, "stream.mv2"))
01837 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":
01838 os.rename(os.path.join(folder, file), os.path.join(folder, "stream0.ac3"))
01839 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":
01840 os.rename(os.path.join(folder, file), os.path.join(folder, "stream0.mp2"))
01841 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":
01842 os.rename(os.path.join(folder, file), os.path.join(folder, "stream1.ac3"))
01843 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":
01844 os.rename(os.path.join(folder, file), os.path.join(folder, "stream1.mp2"))
01845 elif not os.path.exists(os.path.join(folder, "stream.sup")) and file[-3:] == "sup":
01846 os.rename(os.path.join(folder, file), os.path.join(folder, "stream.sup"))
01847 elif not os.path.exists(os.path.join(folder, "stream.sup.IFO")) and file[-3:] == "IFO":
01848 os.rename(os.path.join(folder, file), os.path.join(folder, "stream.sup.IFO"))
01849
01850
01851
01852
01853 if addSubtitles:
01854 if (os.path.exists(os.path.join(folder, "stream.sup")) and
01855 os.path.exists(os.path.join(folder, "stream.sup.IFO"))):
01856 write("Found DVB subtitles converting to DVD subtitles")
01857 command = "mytharchivehelper -q -q --sup2dast "
01858 command += " --infile %s --ifofile %s --delay 0" % (quoteCmdArg(os.path.join(folder, "stream.sup")), quoteCmdArg(os.path.join(folder, "stream.sup.IFO")))
01859
01860 result = runCommand(command)
01861
01862 if result != 0:
01863 write("Failed while running mytharchivehelper to convert DVB subtitles to DVD subtitles.\n"
01864 "Result: %d, Command was %s" % (result, command))
01865 return False
01866
01867
01868 checkSubtitles(os.path.join(folder, "stream.d", "spumux.xml"))
01869
01870 return True
01871
01872
01873
01874
01875 def ts2pts(time):
01876 h = int(time[0:2]) * 3600 * 90000
01877 m = int(time[3:5]) * 60 * 90000
01878 s = int(time[6:8]) * 90000
01879 ms = int(time[9:11]) * 90
01880
01881 return h + m + s + ms
01882
01883
01884
01885
01886 def checkSubtitles(spumuxFile):
01887
01888
01889 subDOM = xml.dom.minidom.parse(spumuxFile)
01890
01891
01892 if subDOM.documentElement.tagName != "subpictures":
01893 fatalError("This does not look like a spumux.xml file (%s)" % spumuxFile)
01894
01895 streamNodes = subDOM.getElementsByTagName("stream")
01896 if streamNodes.length == 0:
01897 write("Didn't find any stream elements in file.!!!")
01898 return
01899 streamNode = streamNodes[0]
01900
01901 nodes = subDOM.getElementsByTagName("spu")
01902 if nodes.length == 0:
01903 write("Didn't find any spu elements in file.!!!")
01904 return
01905
01906 lastStart = -1
01907 lastEnd = -1
01908 for node in nodes:
01909 errored = False
01910 start = ts2pts(node.attributes["start"].value)
01911 end = ts2pts(node.attributes["end"].value)
01912 image = node.attributes["image"].value
01913
01914 if end <= start:
01915 errored = True
01916 if start <= lastEnd:
01917 errored = True
01918
01919 if errored:
01920 write("removing subtitle: %s to %s - (%d - %d (%d))" % (node.attributes["start"].value, node.attributes["end"].value, start, end, lastEnd), False)
01921 streamNode.removeChild(node)
01922 node.unlink()
01923
01924 lastStart = start
01925 lastEnd = end
01926
01927 WriteXMLToFile(subDOM, spumuxFile)
01928
01929
01930
01931
01932 def extractVideoFrame(source, destination, seconds):
01933 write("Extracting thumbnail image from %s at position %s" % (source, seconds))
01934 write("Destination file %s" % destination)
01935
01936 if doesFileExist(destination) == False:
01937
01938 if videomode=="pal":
01939 fr=frameratePAL
01940 else:
01941 fr=framerateNTSC
01942
01943 command = "mytharchivehelper -q -q --createthumbnail --infile %s --thumblist '%s' --outfile %s" % (quoteCmdArg(source), seconds, quoteCmdArg(destination))
01944 result = runCommand(command)
01945 if result <> 0:
01946 fatalError("Failed while running mytharchivehelper to get thumbnails.\n"
01947 "Result: %d, Command was %s" % (result, command))
01948 try:
01949 myimage=Image.open(destination,"r")
01950
01951 if myimage.format <> "JPEG":
01952 write( "Something went wrong with thumbnail capture - " + myimage.format)
01953 return (0L,0L)
01954 else:
01955 return myimage.size
01956 except IOError:
01957 return (0L, 0L)
01958
01959
01960
01961
01962 def extractVideoFrames(source, destination, thumbList):
01963 write("Extracting thumbnail images from: %s - at %s" % (source, thumbList))
01964 write("Destination file %s" % destination)
01965
01966 command = "mytharchivehelper -q -q --createthumbnail --infile %s --thumblist '%s' --outfile %s" % (quoteCmdArg(source), thumbList, quoteCmdArg(destination))
01967 write(command)
01968 result = runCommand(command)
01969 if result <> 0:
01970 fatalError("Failed while running mytharchivehelper to get thumbnails.\n"
01971 "Result: %d, Command was %s" % (result, command))
01972
01973
01974
01975
01976 def encodeVideoToMPEG2(source, destvideofile, video, audio1, audio2, aspectratio, profile):
01977 """Encodes an unknown video source file eg. AVI to MPEG2 video and AC3 audio, use mythffmpeg"""
01978
01979 profileNode = findEncodingProfile(profile)
01980
01981 passes = int(getText(profileNode.getElementsByTagName("passes")[0]))
01982
01983 command = "mythffmpeg"
01984
01985 if cpuCount > 1:
01986 command += " -threads %d" % cpuCount
01987
01988 parameters = profileNode.getElementsByTagName("parameter")
01989
01990 for param in parameters:
01991 name = param.attributes["name"].value
01992 value = param.attributes["value"].value
01993
01994
01995 if value == "%inputfile":
01996 value = quoteCmdArg(source)
01997 if value == "%outputfile":
01998 value = quoteCmdArg(destvideofile)
01999 if value == "%aspect":
02000 value = aspectratio
02001
02002
02003 if audio1[AUDIO_CODEC] == "AC3":
02004 if name == "-acodec":
02005 value = "copy"
02006 if name == "-ar" or name == "-ab" or name == "-ac":
02007 name = ""
02008 value = ""
02009
02010 if name != "":
02011 command += " " + name
02012
02013 if value != "":
02014 command += " " + value
02015
02016
02017
02018 if audio2[AUDIO_ID] != -1:
02019 for param in parameters:
02020 name = param.attributes["name"].value
02021 value = param.attributes["value"].value
02022
02023
02024 if audio1[AUDIO_CODEC] == "AC3":
02025 if name == "-acodec":
02026 value = "copy"
02027 if name == "-ar" or name == "-ab" or name == "-ac":
02028 name = ""
02029 value = ""
02030
02031 if name == "-acodec" or name == "-ar" or name == "-ab" or name == "-ac":
02032 command += " " + name + " " + value
02033
02034 command += " -newaudio"
02035
02036
02037 command += " -map 0:%d -map 0:%d " % (video[VIDEO_INDEX], audio1[AUDIO_INDEX])
02038 if audio2[AUDIO_ID] != -1:
02039 command += "-map 0:%d" % (audio2[AUDIO_INDEX])
02040
02041 if passes == 1:
02042 write(command)
02043 result = runCommand(command)
02044 if result!=0:
02045 fatalError("Failed while running mythffmpeg to re-encode video.\n"
02046 "Command was %s" % command)
02047
02048 else:
02049 passLog = os.path.join(getTempPath(), 'pass')
02050
02051 pass1 = string.replace(command, "%passno","1")
02052 pass1 = string.replace(pass1, "%passlogfile", quoteCmdArg(passLog))
02053 write("Pass 1 - " + pass1)
02054 result = runCommand(pass1)
02055
02056 if result!=0:
02057 fatalError("Failed while running mythffmpeg (Pass 1) to re-encode video.\n"
02058 "Command was %s" % command)
02059
02060 if os.path.exists(destvideofile):
02061 os.remove(destvideofile)
02062
02063 pass2 = string.replace(command, "%passno","2")
02064 pass2 = string.replace(pass2, "%passlogfile", passLog)
02065 write("Pass 2 - " + pass2)
02066 result = runCommand(pass2)
02067
02068 if result!=0:
02069 fatalError("Failed while running mythffmpeg (Pass 2) to re-encode video.\n"
02070 "Command was %s" % command)
02071
02072
02073
02074 def encodeNuvToMPEG2(chanid, starttime, mediafile, destvideofile, folder, profile, usecutlist):
02075 """Encodes a nuv video source file to MPEG2 video and AC3 audio, using mythtranscode & mythffmpeg"""
02076
02077
02078 if ((doesFileExist(os.path.join(folder, "audout")) or doesFileExist(os.path.join(folder, "vidout")))):
02079 fatalError("Something is wrong! Found one or more stale fifo's from mythtranscode\n"
02080 "Delete the fifos in '%s' and start again" % folder)
02081
02082 profileNode = findEncodingProfile(profile)
02083 parameters = profileNode.getElementsByTagName("parameter")
02084
02085
02086 outvideobitrate = "5000k"
02087 if videomode == "ntsc":
02088 outvideores = "720x480"
02089 else:
02090 outvideores = "720x576"
02091
02092 outaudiochannels = 2
02093 outaudiobitrate = 384
02094 outaudiosamplerate = 48000
02095 outaudiocodec = "ac3"
02096 deinterlace = 0
02097 croptop = 0
02098 cropright = 0
02099 cropbottom = 0
02100 cropleft = 0
02101 qmin = 5
02102 qmax = 31
02103 qdiff = 31
02104
02105 for param in parameters:
02106 name = param.attributes["name"].value
02107 value = param.attributes["value"].value
02108
02109
02110 if name == "-acodec":
02111 outaudiocodec = value
02112 if name == "-ac":
02113 outaudiochannels = value
02114 if name == "-ab":
02115 outaudiobitrate = value
02116 if name == "-ar":
02117 outaudiosamplerate = value
02118 if name == "-b":
02119 outvideobitrate = value
02120 if name == "-s":
02121 outvideores = value
02122 if name == "-deinterlace":
02123 deinterlace = 1
02124 if name == "-croptop":
02125 croptop = value
02126 if name == "-cropright":
02127 cropright = value
02128 if name == "-cropbottom":
02129 cropbottom = value
02130 if name == "-cropleft":
02131 cropleft = value
02132 if name == "-qmin":
02133 qmin = value
02134 if name == "-qmax":
02135 qmax = value
02136 if name == "-qdiff":
02137 qdiff = value
02138
02139 if chanid != -1:
02140 if (usecutlist == True):
02141 PID=os.spawnlp(os.P_NOWAIT, "mythtranscode", "mythtranscode",
02142 '--profile', '27',
02143 '--chanid', chanid,
02144 '--starttime', starttime,
02145 '--honorcutlist',
02146 '--fifodir', folder)
02147 write("mythtranscode started (using cut list) PID = %s" % PID)
02148 else:
02149 PID=os.spawnlp(os.P_NOWAIT, "mythtranscode", "mythtranscode",
02150 '--profile', '27',
02151 '--chanid', chanid,
02152 '--starttime', starttime,
02153 '--fifodir', folder)
02154
02155 write("mythtranscode started PID = %s" % PID)
02156 elif mediafile != -1:
02157 PID=os.spawnlp(os.P_NOWAIT, "mythtranscode", "mythtranscode",
02158 '--profile', '27',
02159 '--infile', mediafile,
02160 '--fifodir', folder)
02161 write("mythtranscode started (using file) PID = %s" % PID)
02162 else:
02163 fatalError("no video source passed to encodeNuvToMPEG2.\n")
02164
02165
02166 samplerate, channels = getAudioParams(folder)
02167 videores, fps, aspectratio = getVideoParams(folder)
02168
02169 command = "mythffmpeg -y "
02170
02171 if cpuCount > 1:
02172 command += "-threads %d " % cpuCount
02173
02174 command += "-f s16le -ar %s -ac %s -i %s " % (samplerate, channels, quoteCmdArg(os.path.join(folder, "audout")))
02175 command += "-f rawvideo -pix_fmt yuv420p -s %s -aspect %s -r %s " % (videores, aspectratio, fps)
02176 command += "-i %s " % quoteCmdArg(os.path.join(folder, "vidout"))
02177 command += "-aspect %s -r %s " % (aspectratio, fps)
02178 if (deinterlace == 1):
02179 command += "-deinterlace "
02180 command += "-croptop %s -cropright %s -cropbottom %s -cropleft %s " % (croptop, cropright, cropbottom, cropleft)
02181 command += "-s %s -b %s -vcodec mpeg2video " % (outvideores, outvideobitrate)
02182 command += "-qmin %s -qmax %s -qdiff %s " % (qmin, qmax, qdiff)
02183 command += "-ab %s -ar %s -acodec %s " % (outaudiobitrate, outaudiosamplerate, outaudiocodec)
02184 command += "-f dvd %s" % quoteCmdArg(destvideofile)
02185
02186
02187 tries = 30
02188 while (tries and not(doesFileExist(os.path.join(folder, "audout")) and
02189 doesFileExist(os.path.join(folder, "vidout")))):
02190 tries -= 1
02191 write("Waiting for mythtranscode to create the fifos")
02192 time.sleep(1)
02193
02194 if (not(doesFileExist(os.path.join(folder, "audout")) and doesFileExist(os.path.join(folder, "vidout")))):
02195 fatalError("Waited too long for mythtranscode to create the fifos - giving up!!")
02196
02197 write("Running mythffmpeg")
02198 result = runCommand(command)
02199 if result != 0:
02200 os.kill(PID, signal.SIGKILL)
02201 fatalError("Failed while running mythffmpeg to re-encode video.\n"
02202 "Command was %s" % command)
02203
02204
02205
02206
02207 def runDVDAuthor():
02208 write( "Starting dvdauthor")
02209 checkCancelFlag()
02210 result=os.spawnlp(os.P_WAIT, path_dvdauthor[0],path_dvdauthor[1],'-x',os.path.join(getTempPath(),'dvdauthor.xml'))
02211 if result<>0:
02212 fatalError("Failed while running dvdauthor. Result: %d" % result)
02213 write( "Finished dvdauthor")
02214
02215
02216
02217
02218 def CreateDVDISO(title):
02219 write("Creating ISO image")
02220 checkCancelFlag()
02221 command = quoteCmdArg(path_mkisofs[0]) + ' -dvd-video '
02222 command += ' -V ' + quoteCmdArg(title)
02223 command += ' -o ' + quoteCmdArg(os.path.join(getTempPath(), 'mythburn.iso'))
02224 command += " " + quoteCmdArg(os.path.join(getTempPath(),'dvd'))
02225
02226 result = runCommand(command)
02227
02228 if result<>0:
02229 fatalError("Failed while running mkisofs.\n"
02230 "Command was %s" % command)
02231
02232 write("Finished creating ISO image")
02233
02234
02235
02236
02237 def BurnDVDISO(title):
02238 write( "Burning ISO image to %s" % dvddrivepath)
02239
02240
02241
02242 def drivestatus():
02243 f = os.open(dvddrivepath, os.O_RDONLY | os.O_NONBLOCK)
02244 status = ioctl(f,CDROM.CDROM_DRIVE_STATUS, 0)
02245 os.close(f)
02246 return status
02247 def displayneededdisktype():
02248 if mediatype == DVD_SL:
02249 write("Please insert an empty single-layer disc (DVD+R or DVD-R).")
02250 if mediatype == DVD_DL:
02251 write("Please insert an empty double-layer disc (DVD+R DL or DVD-R DL).")
02252 if mediatype == DVD_RW:
02253 write("Please insert a rewritable disc (DVD+RW or DVD-RW).")
02254 def tray(action):
02255 runCommand("pumount " + quoteCmdArg(dvddrivepath));
02256 waitForDrive()
02257 try:
02258 f = os.open(drivepath, os.O_RDONLY | os.O_NONBLOCK)
02259 r = ioctl(f,action, 0)
02260 os.close(f)
02261 return True
02262 except:
02263 write("Failed to eject the disc!")
02264 return False
02265 finally:
02266 os.close(f)
02267 def waitForDrive():
02268 tries = 0
02269 while drivestatus() == CDROM.CDS_DRIVE_NOT_READY:
02270 checkCancelFlag()
02271 write("Waiting for drive")
02272 time.sleep(5)
02273 tries += 1
02274 if tries > 10:
02275
02276 write("Try a hard-reset of the device")
02277 tray(CDROM.CDROMRESET)
02278 tries = 0
02279
02280
02281
02282
02283
02284
02285 finished = False
02286 while not finished:
02287
02288 checkCancelFlag()
02289
02290
02291 waitForDrive()
02292
02293 if drivestatus() == CDROM.CDS_DISC_OK or drivestatus() == CDROM.CDS_NO_INFO:
02294
02295
02296
02297 runCommand("pumount " + quoteCmdArg(dvddrivepath));
02298
02299
02300 command = quoteCmdArg(path_growisofs[0]) + " -dvd-compat"
02301 if drivespeed != 0:
02302 command += " -speed=%d" % drivespeed
02303 if mediatype == DVD_RW and erasedvdrw == True:
02304 command += " -use-the-force-luke"
02305 command += " -Z " + quoteCmdArg(dvddrivepath) + " -dvd-video -V " + quoteCmdArg(title) + " " + quoteCmdArg(os.path.join(getTempPath(),'dvd'))
02306 write(command)
02307 write("Running growisofs to burn DVD")
02308
02309 result = runCommand(command)
02310 if result == 0:
02311 finished = True
02312 else:
02313 if result == 252:
02314 write("-"*60)
02315 write("You probably inserted a medium of wrong type.")
02316 elif (result == 156):
02317 write("-"*60)
02318 write("You probably inserted a non-empty, corrupt or too small medium.")
02319 elif (result == 144):
02320 write("-"*60)
02321 write("You inserted a non-empty medium.")
02322 else:
02323 write("-"*60)
02324 write("ERROR: Failed while running growisofs.")
02325 write("Result %d, Command was: %s" % (result, command))
02326 write("Please check mythburn.log for further information")
02327 write("-"*60)
02328 write("")
02329 write("Going to try it again until canceled by user:")
02330 write("-"*60)
02331 write("")
02332 displayneededdisktype()
02333
02334
02335 tray(CDROM.CDROMEJECT)
02336
02337
02338 elif drivestatus() == CDROM.CDS_TRAY_OPEN:
02339 displayneededdisktype()
02340 write("Waiting for tray to close.")
02341
02342 while drivestatus() == CDROM.CDS_TRAY_OPEN:
02343 checkCancelFlag()
02344 time.sleep(5)
02345 elif drivestatus() == CDROM.CDS_NO_DISC:
02346 tray(CDROM.CDROMEJECT)
02347 displayneededdisktype()
02348
02349 write("Finished burning ISO image")
02350
02351
02352
02353
02354
02355 def deMultiplexMPEG2File(folder, mediafile, video, audio1, audio2):
02356
02357 if getFileType(folder) == "mpegts":
02358 command = "mythreplex --demux --fix_sync -t TS -o %s " % quoteCmdArg(folder + "/stream")
02359 command += "-v %d " % (video[VIDEO_ID])
02360
02361 if audio1[AUDIO_ID] != -1:
02362 if audio1[AUDIO_CODEC] == 'MP2':
02363 command += "-a %d " % (audio1[AUDIO_ID])
02364 elif audio1[AUDIO_CODEC] == 'AC3':
02365 command += "-c %d " % (audio1[AUDIO_ID])
02366 elif audio1[AUDIO_CODEC] == 'EAC3':
02367 command += "-c %d " % (audio1[AUDIO_ID])
02368
02369 if audio2[AUDIO_ID] != -1:
02370 if audio2[AUDIO_CODEC] == 'MP2':
02371 command += "-a %d " % (audio2[AUDIO_ID])
02372 elif audio2[AUDIO_CODEC] == 'AC3':
02373 command += "-c %d " % (audio2[AUDIO_ID])
02374 elif audio2[AUDIO_CODEC] == 'EAC3':
02375 command += "-c %d " % (audio2[AUDIO_ID])
02376
02377 else:
02378 command = "mythreplex --demux --fix_sync -o %s " % quoteCmdArg(folder + "/stream")
02379 command += "-v %d " % (video[VIDEO_ID] & 255)
02380
02381 if audio1[AUDIO_ID] != -1:
02382 if audio1[AUDIO_CODEC] == 'MP2':
02383 command += "-a %d " % (audio1[AUDIO_ID] & 255)
02384 elif audio1[AUDIO_CODEC] == 'AC3':
02385 command += "-c %d " % (audio1[AUDIO_ID] & 255)
02386 elif audio1[AUDIO_CODEC] == 'EAC3':
02387 command += "-c %d " % (audio1[AUDIO_ID] & 255)
02388
02389
02390 if audio2[AUDIO_ID] != -1:
02391 if audio2[AUDIO_CODEC] == 'MP2':
02392 command += "-a %d " % (audio2[AUDIO_ID] & 255)
02393 elif audio2[AUDIO_CODEC] == 'AC3':
02394 command += "-c %d " % (audio2[AUDIO_ID] & 255)
02395 elif audio2[AUDIO_CODEC] == 'EAC3':
02396 command += "-c %d " % (audio2[AUDIO_ID] & 255)
02397
02398 mediafile = quoteCmdArg(mediafile)
02399 command += mediafile
02400 write("Running: " + command)
02401
02402 result = runCommand(command)
02403
02404 if result<>0:
02405 fatalError("Failed while running mythreplex. Command was %s" % command)
02406
02407
02408
02409
02410 def runM2VRequantiser(source,destination,factor):
02411 mega=1024.0*1024.0
02412 M2Vsize0 = os.path.getsize(source)
02413 write("Initial M2Vsize is %.2f Mb , target is %.2f Mb" % ( (float(M2Vsize0)/mega), (float(M2Vsize0)/(factor*mega)) ))
02414
02415 command = quoteCmdArg(path_M2VRequantiser[0])
02416 command += " %.5f " % factor
02417 command += " %s " % M2Vsize0
02418 command += " < %s " % quoteCmdArg(source)
02419 command += " > %s " % quoteCmdArg(destination)
02420
02421 write("Running: " + command)
02422 result = runCommand(command)
02423 if result<>0:
02424 fatalError("Failed while running M2VRequantiser. Command was %s" % command)
02425
02426 M2Vsize1 = os.path.getsize(destination)
02427
02428 write("M2Vsize after requant is %.2f Mb " % (float(M2Vsize1)/mega))
02429 fac1=float(M2Vsize0) / float(M2Vsize1)
02430 write("Factor demanded %.5f, achieved %.5f, ratio %.5f " % ( factor, fac1, fac1/factor))
02431
02432
02433
02434
02435 def calculateFileSizes(files):
02436 """ Returns the sizes of all video, audio and menu files"""
02437 filecount=0
02438 totalvideosize=0
02439 totalaudiosize=0
02440 totalmenusize=0
02441
02442 for node in files:
02443 filecount+=1
02444
02445 folder=getItemTempPath(filecount)
02446
02447 file=os.path.join(folder,"stream.mv2")
02448
02449 totalvideosize+=os.path.getsize(file)
02450
02451
02452 if doesFileExist(os.path.join(folder,"stream0.ac3")):
02453 totalaudiosize+=os.path.getsize(os.path.join(folder,"stream0.ac3"))
02454 if doesFileExist(os.path.join(folder,"stream0.mp2")):
02455 totalaudiosize+=os.path.getsize(os.path.join(folder,"stream0.mp2"))
02456
02457
02458 if doesFileExist(os.path.join(folder,"stream1.ac3")):
02459 totalaudiosize+=os.path.getsize(os.path.join(folder,"stream1.ac3"))
02460 if doesFileExist(os.path.join(folder,"stream1.mp2")):
02461 totalaudiosize+=os.path.getsize(os.path.join(folder,"stream1.mp2"))
02462
02463
02464 if doesFileExist(os.path.join(getTempPath(),"chaptermenu-%s.mpg" % filecount)):
02465 totalmenusize+=os.path.getsize(os.path.join(getTempPath(),"chaptermenu-%s.mpg" % filecount))
02466
02467
02468 if doesFileExist(os.path.join(getTempPath(),"details-%s.mpg" % filecount)):
02469 totalmenusize+=os.path.getsize(os.path.join(getTempPath(),"details-%s.mpg" % filecount))
02470
02471 filecount=1
02472 while doesFileExist(os.path.join(getTempPath(),"menu-%s.mpg" % filecount)):
02473 totalmenusize+=os.path.getsize(os.path.join(getTempPath(),"menu-%s.mpg" % filecount))
02474 filecount+=1
02475
02476 return totalvideosize,totalaudiosize,totalmenusize
02477
02478
02479
02480
02481 def total_mv2_brl(files,rate):
02482 tvsize=0
02483 filecount=0
02484 for node in files:
02485 filecount+=1
02486 folder=getItemTempPath(filecount)
02487 progduration=getLengthOfVideo(filecount)
02488 file=os.path.join(folder,"stream.mv2")
02489 progvsize=os.path.getsize(file)
02490 progvbitrate=progvsize/progduration
02491 if progvbitrate>rate :
02492 tvsize+=progduration*rate
02493 else:
02494 tvsize+=progvsize
02495
02496 return tvsize
02497
02498
02499
02500
02501
02502 def performMPEG2Shrink(files,dvdrsize):
02503 checkCancelFlag()
02504 mega=1024.0*1024.0
02505 fudge_pack=1.04
02506 fudge_requant=1.05
02507
02508 totalvideosize,totalaudiosize,totalmenusize=calculateFileSizes(files)
02509 allfiles=totalvideosize+totalaudiosize+totalmenusize
02510
02511
02512 write( "Total video %.2f Mb, audio %.2f Mb, menus %.2f Mb." % (totalvideosize/mega,totalaudiosize/mega,totalmenusize/mega))
02513
02514
02515 mv2space=((dvdrsize*mega-totalmenusize)/fudge_pack)-totalaudiosize
02516
02517 if mv2space<0:
02518 fatalError("Audio and menu files are too big. No room for video. Giving up!")
02519
02520 if totalvideosize>mv2space:
02521 write( "Video files are %.1f Mb too big. Need to shrink." % ((totalvideosize - mv2space)/mega) )
02522
02523 if path_M2VRequantiser[0] == "":
02524 fatalError("M2VRequantiser is not available to resize the files. Giving up!")
02525
02526 vsize=0
02527 duration=0
02528 filecount=0
02529 for node in files:
02530 filecount+=1
02531 folder=getItemTempPath(filecount)
02532 file=os.path.join(folder,"stream.mv2")
02533 vsize+=os.path.getsize(file)
02534 duration+=getLengthOfVideo(filecount)
02535
02536
02537
02538
02539
02540
02541
02542
02543
02544
02545
02546
02547
02548 vrLo=0.0
02549 vrHi=3.0*float(vsize)/duration
02550
02551 vrate=vrLo
02552 vrinc=vrHi-vrLo
02553 count=0
02554
02555 while count<30 :
02556 count+=1
02557 vrinc=vrinc*0.5
02558 vrtest=vrate+vrinc
02559 testsize=total_mv2_brl(files,vrtest)
02560 if (testsize<mv2space):
02561 vrate=vrtest
02562
02563 write("vrate %.3f kb/s, testsize %.4f , mv2space %.4f Mb " % ((vrate)/1000.0, (testsize)/mega, (mv2space)/mega) )
02564 filecount=0
02565 for node in files:
02566 filecount+=1
02567 folder=getItemTempPath(filecount)
02568 file=os.path.join(folder,"stream.mv2")
02569 progvsize=os.path.getsize(file)
02570 progduration=getLengthOfVideo(filecount)
02571 progvbitrate=progvsize/progduration
02572 write( "File %s, size %.2f Mb, rate %.2f, limit %.2f kb/s " %( filecount, float(progvsize)/mega, progvbitrate/1000.0, vrate/1000.0 ))
02573 if progvbitrate>vrate :
02574 scalefactor=1.0+(fudge_requant*float(progvbitrate-vrate)/float(vrate))
02575 if scalefactor>3.0 :
02576 write( "Large shrink factor. You may not like the result! ")
02577 runM2VRequantiser(os.path.join(getItemTempPath(filecount),"stream.mv2"),os.path.join(getItemTempPath(filecount),"stream.small.mv2"),scalefactor)
02578 os.remove(os.path.join(getItemTempPath(filecount),"stream.mv2"))
02579 os.rename(os.path.join(getItemTempPath(filecount),"stream.small.mv2"),os.path.join(getItemTempPath(filecount),"stream.mv2"))
02580 else:
02581 write( "Unpackaged total %.2f Mb. About %.0f Mb will be unused." % ((allfiles/mega),(mv2space-totalvideosize)/mega))
02582
02583
02584
02585
02586 def createDVDAuthorXML(screensize, numberofitems):
02587 """Creates the xml file for dvdauthor to use the MythBurn menus."""
02588
02589
02590 menunode=themeDOM.getElementsByTagName("menu")
02591 if menunode.length!=1:
02592 fatalError("Cannot find the menu element in the theme file")
02593 menunode=menunode[0]
02594
02595 menuitems=menunode.getElementsByTagName("item")
02596
02597 itemsperpage = menuitems.length
02598 write( "Menu items per page %s" % itemsperpage)
02599 autoplaymenu = 2 + ((numberofitems + itemsperpage - 1)/itemsperpage)
02600
02601 if wantChapterMenu:
02602
02603 submenunode=themeDOM.getElementsByTagName("submenu")
02604 if submenunode.length!=1:
02605 fatalError("Cannot find the submenu element in the theme file")
02606
02607 submenunode=submenunode[0]
02608
02609 chapteritems=submenunode.getElementsByTagName("chapter")
02610
02611 chapters = chapteritems.length
02612 write( "Chapters per recording %s" % chapters)
02613
02614 del chapteritems
02615 del submenunode
02616
02617
02618 page=1
02619
02620
02621 itemnum=1
02622
02623 write( "Creating DVD XML file for dvd author")
02624
02625 dvddom = xml.dom.minidom.parseString(
02626 '''<dvdauthor>
02627 <vmgm>
02628 <menus lang="en">
02629 <pgc entry="title">
02630 </pgc>
02631 </menus>
02632 </vmgm>
02633 </dvdauthor>''')
02634
02635 dvdauthor_element=dvddom.documentElement
02636 menus_element = dvdauthor_element.childNodes[1].childNodes[1]
02637
02638 dvdauthor_element.insertBefore( dvddom.createComment("""
02639 DVD Variables
02640 g0=not used
02641 g1=not used
02642 g2=title number selected on current menu page (see g4)
02643 g3=1 if intro movie has played
02644 g4=last menu page on display
02645 g5=next title to autoplay (0 or > # titles means no more autoplay)
02646 """), dvdauthor_element.firstChild )
02647 dvdauthor_element.insertBefore(dvddom.createComment("dvdauthor XML file created by MythBurn script"), dvdauthor_element.firstChild )
02648
02649 menus_element.appendChild( dvddom.createComment("Title menu used to hold intro movie") )
02650
02651 dvdauthor_element.setAttribute("dest",os.path.join(getTempPath(),"dvd"))
02652
02653 video = dvddom.createElement("video")
02654 video.setAttribute("format",videomode)
02655
02656
02657 if mainmenuAspectRatio == "4:3":
02658 video.setAttribute("aspect", "4:3")
02659 else:
02660 video.setAttribute("aspect", "16:9")
02661 video.setAttribute("widescreen", "nopanscan")
02662
02663 menus_element.appendChild(video)
02664
02665 pgc=menus_element.childNodes[1]
02666
02667 if wantIntro:
02668
02669 pre = dvddom.createElement("pre")
02670 pgc.appendChild(pre)
02671 vmgm_pre_node=pre
02672 del pre
02673
02674 node = themeDOM.getElementsByTagName("intro")[0]
02675 introFile = node.attributes["filename"].value
02676
02677
02678 vob = dvddom.createElement("vob")
02679 vob.setAttribute("pause","")
02680 vob.setAttribute("file",os.path.join(getThemeFile(themeName, videomode + '_' + introFile)))
02681 pgc.appendChild(vob)
02682 del vob
02683
02684
02685
02686 post = dvddom.createElement("post")
02687 post .appendChild(dvddom.createTextNode("{g3=1;g2=1;jump menu 2;}"))
02688 pgc.appendChild(post)
02689 del post
02690
02691 while itemnum <= numberofitems:
02692 write( "Menu page %s" % page)
02693
02694
02695 menupgc = dvddom.createElement("pgc")
02696 menus_element.appendChild(menupgc)
02697 menupgc.setAttribute("pause","inf")
02698
02699 menupgc.appendChild( dvddom.createComment("Menu Page %s" % page) )
02700
02701
02702
02703 pre = dvddom.createElement("pre")
02704 pre.appendChild(dvddom.createTextNode("{button=g2*1024;g4=%s;}" % page))
02705 menupgc.appendChild(pre)
02706
02707 vob = dvddom.createElement("vob")
02708 vob.setAttribute("file",os.path.join(getTempPath(),"menu-%s.mpg" % page))
02709 menupgc.appendChild(vob)
02710
02711
02712 post = dvddom.createElement("post")
02713 post.appendChild(dvddom.createTextNode("jump cell 1;"))
02714 menupgc.appendChild(post)
02715
02716
02717
02718
02719 itemsonthispage=0
02720
02721 endbuttons = []
02722
02723 while itemnum <= numberofitems and itemsonthispage < itemsperpage:
02724 menuitem=menuitems[ itemsonthispage ]
02725
02726 itemsonthispage+=1
02727
02728
02729 infoDOM = xml.dom.minidom.parse( os.path.join(getItemTempPath(itemnum),"info.xml") )
02730
02731 if infoDOM.documentElement.tagName != "fileinfo":
02732 fatalError("The info.xml file (%s) doesn't look right" % os.path.join(getItemTempPath(itemnum),"info.xml"))
02733
02734
02735
02736
02737 button=dvddom.createElement("button")
02738 button.setAttribute("name","%s" % itemnum)
02739 button.appendChild(dvddom.createTextNode("{g2=" + "%s" % itemsonthispage + "; g5=0; jump title %s;}" % itemnum))
02740 menupgc.appendChild(button)
02741 del button
02742
02743
02744 titleset = dvddom.createElement("titleset")
02745 dvdauthor_element.appendChild(titleset)
02746
02747
02748 comment = getText(infoDOM.getElementsByTagName("title")[0]).replace('--', '-')
02749 titleset.appendChild( dvddom.createComment(comment))
02750
02751 menus= dvddom.createElement("menus")
02752 titleset.appendChild(menus)
02753
02754 video = dvddom.createElement("video")
02755 video.setAttribute("format",videomode)
02756
02757
02758 if chaptermenuAspectRatio == "4:3":
02759 video.setAttribute("aspect", "4:3")
02760 elif chaptermenuAspectRatio == "16:9":
02761 video.setAttribute("aspect", "16:9")
02762 video.setAttribute("widescreen", "nopanscan")
02763 else:
02764
02765 if getAspectRatioOfVideo(itemnum) > aspectRatioThreshold:
02766 video.setAttribute("aspect", "16:9")
02767 video.setAttribute("widescreen", "nopanscan")
02768 else:
02769 video.setAttribute("aspect", "4:3")
02770
02771 menus.appendChild(video)
02772
02773 if wantChapterMenu:
02774 mymenupgc = dvddom.createElement("pgc")
02775 menus.appendChild(mymenupgc)
02776 mymenupgc.setAttribute("pause","inf")
02777
02778 pre = dvddom.createElement("pre")
02779 mymenupgc.appendChild(pre)
02780 if wantDetailsPage:
02781 pre.appendChild(dvddom.createTextNode("{button=s7 - 1 * 1024;}"))
02782 else:
02783 pre.appendChild(dvddom.createTextNode("{button=s7 * 1024;}"))
02784
02785 vob = dvddom.createElement("vob")
02786 vob.setAttribute("file",os.path.join(getTempPath(),"chaptermenu-%s.mpg" % itemnum))
02787 mymenupgc.appendChild(vob)
02788
02789
02790 post = dvddom.createElement("post")
02791 post.appendChild(dvddom.createTextNode("jump cell 1;"))
02792 mymenupgc.appendChild(post)
02793
02794
02795
02796
02797 firstChapter = 0
02798 thumbNode = infoDOM.getElementsByTagName("thumblist")
02799 if thumbNode.length > 0:
02800 thumblist = getText(thumbNode[0])
02801 chapterlist = string.split(thumblist, ",")
02802 if chapterlist[0] != '00:00:00':
02803 firstChapter = 1
02804 x=1
02805 while x<=chapters:
02806
02807 button=dvddom.createElement("button")
02808 button.setAttribute("name","%s" % x)
02809 if wantDetailsPage:
02810 button.appendChild(dvddom.createTextNode("jump title %s chapter %s;" % (1, firstChapter + x + 1)))
02811 else:
02812 button.appendChild(dvddom.createTextNode("jump title %s chapter %s;" % (1, firstChapter + x)))
02813
02814 mymenupgc.appendChild(button)
02815 del button
02816 x+=1
02817
02818
02819 submenunode = themeDOM.getElementsByTagName("submenu")
02820 submenunode = submenunode[0]
02821 titlemenunodes = submenunode.getElementsByTagName("titlemenu")
02822 if titlemenunodes.length > 0:
02823 button = dvddom.createElement("button")
02824 button.setAttribute("name","titlemenu")
02825 button.appendChild(dvddom.createTextNode("{jump vmgm menu;}"))
02826 mymenupgc.appendChild(button)
02827 del button
02828
02829 titles = dvddom.createElement("titles")
02830 titleset.appendChild(titles)
02831
02832
02833 title_video = dvddom.createElement("video")
02834 title_video.setAttribute("format",videomode)
02835
02836 if getAspectRatioOfVideo(itemnum) > aspectRatioThreshold:
02837 title_video.setAttribute("aspect", "16:9")
02838 title_video.setAttribute("widescreen", "nopanscan")
02839 else:
02840 title_video.setAttribute("aspect", "4:3")
02841
02842 titles.appendChild(title_video)
02843
02844
02845 if doesFileExist(os.path.join(getItemTempPath(itemnum), "stream0.mp2")):
02846 title_audio = dvddom.createElement("audio")
02847 title_audio.setAttribute("format", "mp2")
02848 else:
02849 title_audio = dvddom.createElement("audio")
02850 title_audio.setAttribute("format", "ac3")
02851
02852 titles.appendChild(title_audio)
02853
02854 pgc = dvddom.createElement("pgc")
02855 titles.appendChild(pgc)
02856
02857
02858 if wantDetailsPage:
02859
02860 vob = dvddom.createElement("vob")
02861 vob.setAttribute("file",os.path.join(getTempPath(),"details-%s.mpg" % itemnum))
02862 pgc.appendChild(vob)
02863
02864 vob = dvddom.createElement("vob")
02865 if wantChapterMenu:
02866 vob.setAttribute("chapters",
02867 createVideoChapters(itemnum,
02868 chapters,
02869 getLengthOfVideo(itemnum),
02870 False))
02871 else:
02872 vob.setAttribute("chapters",
02873 createVideoChaptersFixedLength(itemnum,
02874 chapterLength,
02875 getLengthOfVideo(itemnum)))
02876
02877 vob.setAttribute("file",os.path.join(getItemTempPath(itemnum),"final.vob"))
02878 pgc.appendChild(vob)
02879
02880 post = dvddom.createElement("post")
02881 post.appendChild(dvddom.createTextNode("if (g5 eq %s) call vmgm menu %s; call vmgm menu %s;" % (itemnum + 1, autoplaymenu, page + 1)))
02882 pgc.appendChild(post)
02883
02884
02885 del titleset
02886 del titles
02887 del menus
02888 del video
02889 del pgc
02890 del vob
02891 del post
02892
02893
02894 for node in menuitem.childNodes:
02895
02896 if node.nodeName=="previous":
02897 if page>1:
02898 button=dvddom.createElement("button")
02899 button.setAttribute("name","previous")
02900 button.appendChild(dvddom.createTextNode("{g2=1;jump menu %s;}" % page ))
02901 endbuttons.append(button)
02902
02903
02904 elif node.nodeName=="next":
02905 if itemnum < numberofitems:
02906 button=dvddom.createElement("button")
02907 button.setAttribute("name","next")
02908 button.appendChild(dvddom.createTextNode("{g2=1;jump menu %s;}" % (page + 2)))
02909 endbuttons.append(button)
02910
02911 elif node.nodeName=="playall":
02912 button=dvddom.createElement("button")
02913 button.setAttribute("name","playall")
02914 button.appendChild(dvddom.createTextNode("{g5=1; jump menu %s;}" % autoplaymenu))
02915 endbuttons.append(button)
02916
02917
02918 itemnum+=1
02919
02920
02921 page+=1
02922
02923 for button in endbuttons:
02924 menupgc.appendChild(button)
02925 del button
02926
02927 menupgc = dvddom.createElement("pgc")
02928 menus_element.appendChild(menupgc)
02929 menupgc.setAttribute("pause","inf")
02930 menupgc.appendChild( dvddom.createComment("Autoplay hack") )
02931
02932 dvdcode = ""
02933 while (itemnum > 1):
02934 itemnum-=1
02935 dvdcode += "if (g5 eq %s) {g5 = %s; jump title %s;} " % (itemnum, itemnum + 1, itemnum)
02936 dvdcode += "g5 = 0; jump menu 1;"
02937
02938 pre = dvddom.createElement("pre")
02939 pre.appendChild(dvddom.createTextNode(dvdcode))
02940 menupgc.appendChild(pre)
02941
02942 if wantIntro:
02943
02944
02945 dvdcode="if (g3 eq 1) {"
02946 while (page>1):
02947 page-=1;
02948 dvdcode+="if (g4 eq %s) " % page
02949 dvdcode+="jump menu %s;" % (page + 1)
02950 if (page>1):
02951 dvdcode+=" else "
02952 dvdcode+="}"
02953 vmgm_pre_node.appendChild(dvddom.createTextNode(dvdcode))
02954
02955
02956
02957 WriteXMLToFile (dvddom,os.path.join(getTempPath(),"dvdauthor.xml"))
02958
02959
02960 dvddom.unlink()
02961
02962
02963
02964
02965 def createDVDAuthorXMLNoMainMenu(screensize, numberofitems):
02966 """Creates the xml file for dvdauthor to use the MythBurn menus."""
02967
02968
02969
02970
02971
02972 write( "Creating DVD XML file for dvd author (No Main Menu)")
02973
02974 assert False
02975
02976
02977
02978
02979 def createDVDAuthorXMLNoMenus(screensize, numberofitems):
02980 """Creates the xml file for dvdauthor containing no menus."""
02981
02982
02983
02984
02985
02986 write( "Creating DVD XML file for dvd author (No Menus)")
02987
02988 dvddom = xml.dom.minidom.parseString(
02989 '''
02990 <dvdauthor>
02991 <vmgm>
02992 <menus lang="en">
02993 <pgc entry="title" pause="0">
02994 </pgc>
02995 </menus>
02996 </vmgm>
02997 </dvdauthor>''')
02998
02999 dvdauthor_element = dvddom.documentElement
03000 menus = dvdauthor_element.childNodes[1].childNodes[1]
03001 menu_pgc = menus.childNodes[1]
03002
03003 dvdauthor_element.insertBefore(dvddom.createComment("dvdauthor XML file created by MythBurn script"), dvdauthor_element.firstChild )
03004 dvdauthor_element.setAttribute("dest",os.path.join(getTempPath(),"dvd"))
03005
03006
03007 if wantIntro:
03008 video = dvddom.createElement("video")
03009 video.setAttribute("format", videomode)
03010
03011
03012 if mainmenuAspectRatio == "4:3":
03013 video.setAttribute("aspect", "4:3")
03014 else:
03015 video.setAttribute("aspect", "16:9")
03016 video.setAttribute("widescreen", "nopanscan")
03017 menus.appendChild(video)
03018
03019 pre = dvddom.createElement("pre")
03020 pre.appendChild(dvddom.createTextNode("if (g2==1) jump menu 2;"))
03021 menu_pgc.appendChild(pre)
03022
03023 node = themeDOM.getElementsByTagName("intro")[0]
03024 introFile = node.attributes["filename"].value
03025
03026 vob = dvddom.createElement("vob")
03027 vob.setAttribute("file", getThemeFile(themeName, videomode + '_' + introFile))
03028 menu_pgc.appendChild(vob)
03029
03030 post = dvddom.createElement("post")
03031 post.appendChild(dvddom.createTextNode("g2=1; jump menu 2;"))
03032 menu_pgc.appendChild(post)
03033 del menu_pgc
03034 del post
03035 del pre
03036 del vob
03037 else:
03038 pre = dvddom.createElement("pre")
03039 pre.appendChild(dvddom.createTextNode("g2=1;jump menu 2;"))
03040 menu_pgc.appendChild(pre)
03041
03042 vob = dvddom.createElement("vob")
03043 vob.setAttribute("file", getThemeFile(themeName, videomode + '_' + "blank.mpg"))
03044 menu_pgc.appendChild(vob)
03045
03046 del menu_pgc
03047 del pre
03048 del vob
03049
03050
03051 menu_pgc = dvddom.createElement("pgc")
03052 menu_pgc.setAttribute("pause", "0")
03053
03054 preText = "if (g1==0) g1=1;"
03055 for i in range(numberofitems):
03056 preText += "if (g1==%d) jump titleset %d menu;" % (i + 1, i + 1)
03057
03058 pre = dvddom.createElement("pre")
03059 pre.appendChild(dvddom.createTextNode(preText))
03060 menu_pgc.appendChild(pre)
03061
03062 vob = dvddom.createElement("vob")
03063 vob.setAttribute("file", getThemeFile(themeName, videomode + '_' + "blank.mpg"))
03064 menu_pgc.appendChild(vob)
03065 menus.appendChild(menu_pgc)
03066
03067
03068 itemNum = 1
03069 while itemNum <= numberofitems:
03070 write( "Adding item %s" % itemNum)
03071
03072 titleset = dvddom.createElement("titleset")
03073 dvdauthor_element.appendChild(titleset)
03074
03075
03076 menu = dvddom.createElement("menus")
03077 menupgc = dvddom.createElement("pgc")
03078 menu.appendChild(menupgc)
03079 menupgc.setAttribute("pause","0")
03080 titleset.appendChild(menu)
03081
03082 if wantDetailsPage:
03083
03084 vob = dvddom.createElement("vob")
03085 vob.setAttribute("file", os.path.join(getTempPath(),"details-%s.mpg" % itemNum))
03086 menupgc.appendChild(vob)
03087
03088 post = dvddom.createElement("post")
03089 post.appendChild(dvddom.createTextNode("jump title 1;"))
03090 menupgc.appendChild(post)
03091 del post
03092 else:
03093
03094 pre = dvddom.createElement("pre")
03095 pre.appendChild(dvddom.createTextNode("jump title 1;"))
03096 menupgc.appendChild(pre)
03097 del pre
03098
03099 vob = dvddom.createElement("vob")
03100 vob.setAttribute("file", getThemeFile(themeName, videomode + '_' + "blank.mpg"))
03101 menupgc.appendChild(vob)
03102
03103 titles = dvddom.createElement("titles")
03104
03105
03106 title_video = dvddom.createElement("video")
03107 title_video.setAttribute("format", videomode)
03108
03109
03110 if getAspectRatioOfVideo(itemNum) > aspectRatioThreshold:
03111 title_video.setAttribute("aspect", "16:9")
03112 title_video.setAttribute("widescreen", "nopanscan")
03113 else:
03114 title_video.setAttribute("aspect", "4:3")
03115
03116 titles.appendChild(title_video)
03117
03118 pgc = dvddom.createElement("pgc")
03119
03120 vob = dvddom.createElement("vob")
03121 vob.setAttribute("file", os.path.join(getItemTempPath(itemNum), "final.vob"))
03122 vob.setAttribute("chapters", createVideoChaptersFixedLength(itemNum,
03123 chapterLength,
03124 getLengthOfVideo(itemNum)))
03125 pgc.appendChild(vob)
03126
03127 del vob
03128 del menupgc
03129
03130 post = dvddom.createElement("post")
03131 if itemNum == numberofitems:
03132 post.appendChild(dvddom.createTextNode("exit;"))
03133 else:
03134 post.appendChild(dvddom.createTextNode("g1=%d;call vmgm menu 2;" % (itemNum + 1)))
03135
03136 pgc.appendChild(post)
03137
03138 titles.appendChild(pgc)
03139 titleset.appendChild(titles)
03140
03141 del pgc
03142 del titles
03143 del title_video
03144 del post
03145 del titleset
03146
03147 itemNum +=1
03148
03149
03150 WriteXMLToFile (dvddom,os.path.join(getTempPath(),"dvdauthor.xml"))
03151
03152
03153 dvddom.unlink()
03154
03155
03156
03157
03158 def createEmptyPreviewFolder(videoitem):
03159 previewfolder = os.path.join(getItemTempPath(videoitem), "preview")
03160 if os.path.exists(previewfolder):
03161 deleteAllFilesInFolder(previewfolder)
03162 os.rmdir (previewfolder)
03163 os.makedirs(previewfolder)
03164 return previewfolder
03165
03166
03167
03168
03169 def generateVideoPreview(videoitem, itemonthispage, menuitem, starttime, menulength, previewfolder):
03170 """generate thumbnails for a preview in a menu"""
03171
03172 positionx = 9999
03173 positiony = 9999
03174 width = 0
03175 height = 0
03176 maskpicture = None
03177
03178
03179 for node in menuitem.childNodes:
03180 if node.nodeName=="graphic":
03181 if node.attributes["filename"].value == "%movie":
03182
03183 inputfile = os.path.join(getItemTempPath(videoitem),"stream.mv2")
03184 outputfile = os.path.join(previewfolder, "preview-i%d-t%%1-f%%2.jpg" % itemonthispage)
03185 width = getScaledAttribute(node, "w")
03186 height = getScaledAttribute(node, "h")
03187 frames = int(secondsToFrames(menulength))
03188
03189 command = "mytharchivehelper -q -q --createthumbnail --infile %s --thumblist '%s' --outfile %s --framecount %d" % (quoteCmdArg(inputfile), starttime, quoteCmdArg(outputfile), frames)
03190 result = runCommand(command)
03191 if (result != 0):
03192 write( "mytharchivehelper failed with code %d. Command = %s" % (result, command) )
03193
03194 positionx = getScaledAttribute(node, "x")
03195 positiony = getScaledAttribute(node, "y")
03196
03197
03198 if node.hasAttribute("mask"):
03199 imagemaskfilename = getThemeFile(themeName, node.attributes["mask"].value)
03200 if node.attributes["mask"].value <> "" and doesFileExist(imagemaskfilename):
03201 maskpicture = Image.open(imagemaskfilename,"r").resize((width, height))
03202 maskpicture = maskpicture.convert("RGBA")
03203
03204 return (positionx, positiony, width, height, maskpicture)
03205
03206
03207
03208
03209 def drawThemeItem(page, itemsonthispage, itemnum, menuitem, bgimage, draw,
03210 bgimagemask, drawmask, highlightcolor, spumuxdom, spunode,
03211 numberofitems, chapternumber, chapterlist):
03212 """Draws text and graphics onto a dvd menu, called by
03213 createMenu and createChapterMenu"""
03214
03215
03216 infoDOM = xml.dom.minidom.parse(os.path.join(getItemTempPath(itemnum), "info.xml"))
03217
03218
03219 if infoDOM.documentElement.tagName != "fileinfo":
03220 fatalError("The info.xml file (%s) doesn't look right" %
03221 os.path.join(getItemTempPath(itemnum),"info.xml"))
03222
03223
03224
03225 boundarybox = 9999,9999,0,0
03226 wantHighlightBox = True
03227
03228
03229 for node in menuitem.childNodes:
03230
03231
03232 if node.nodeName=="graphic":
03233
03234
03235
03236 paintBackground(bgimage, node)
03237
03238
03239 if node.attributes["filename"].value == "%movie":
03240
03241 boundarybox = checkBoundaryBox(boundarybox, node)
03242 else:
03243 imagefilename = expandItemText(infoDOM,
03244 node.attributes["filename"].value,
03245 itemnum, page, itemsonthispage,
03246 chapternumber, chapterlist)
03247
03248 if doesFileExist(imagefilename) == False:
03249 if imagefilename == node.attributes["filename"].value:
03250 imagefilename = getThemeFile(themeName,
03251 node.attributes["filename"].value)
03252
03253
03254 maskfilename = None
03255 if node.hasAttribute("mask") and node.attributes["mask"].value <> "":
03256 maskfilename = getThemeFile(themeName, node.attributes["mask"].value)
03257
03258
03259
03260 if (node.attributes["filename"].value == "%thumbnail"
03261 and getText(infoDOM.getElementsByTagName("coverfile")[0]) !=""):
03262 stretch = False
03263 else:
03264 stretch = True
03265
03266 if paintImage(imagefilename, maskfilename, node, bgimage, stretch):
03267 boundarybox = checkBoundaryBox(boundarybox, node)
03268 else:
03269 write("Image file does not exist '%s'" % imagefilename)
03270
03271 elif node.nodeName == "text":
03272
03273
03274
03275 paintBackground(bgimage, node)
03276
03277 text = expandItemText(infoDOM,node.attributes["value"].value,
03278 itemnum, page, itemsonthispage,
03279 chapternumber, chapterlist)
03280
03281 if text>"":
03282 paintText(draw, bgimage, text, node)
03283
03284 boundarybox = checkBoundaryBox(boundarybox, node)
03285 del text
03286
03287 elif node.nodeName=="previous":
03288 if page>1:
03289
03290
03291
03292 paintBackground(bgimage, node)
03293
03294 paintButton(draw, bgimage, bgimagemask, node, infoDOM,
03295 itemnum, page, itemsonthispage, chapternumber,
03296 chapterlist)
03297
03298 button = spumuxdom.createElement("button")
03299 button.setAttribute("name","previous")
03300 button.setAttribute("x0","%s" % getScaledAttribute(node, "x"))
03301 button.setAttribute("y0","%s" % getScaledAttribute(node, "y"))
03302 button.setAttribute("x1","%s" % (getScaledAttribute(node, "x") +
03303 getScaledAttribute(node, "w")))
03304 button.setAttribute("y1","%s" % (getScaledAttribute(node, "y") +
03305 getScaledAttribute(node, "h")))
03306 spunode.appendChild(button)
03307
03308 write( "Added previous page button")
03309
03310
03311 elif node.nodeName == "next":
03312 if itemnum < numberofitems:
03313
03314
03315
03316 paintBackground(bgimage, node)
03317
03318 paintButton(draw, bgimage, bgimagemask, node, infoDOM,
03319 itemnum, page, itemsonthispage, chapternumber,
03320 chapterlist)
03321
03322 button = spumuxdom.createElement("button")
03323 button.setAttribute("name","next")
03324 button.setAttribute("x0","%s" % getScaledAttribute(node, "x"))
03325 button.setAttribute("y0","%s" % getScaledAttribute(node, "y"))
03326 button.setAttribute("x1","%s" % (getScaledAttribute(node, "x") +
03327 getScaledAttribute(node, "w")))
03328 button.setAttribute("y1","%s" % (getScaledAttribute(node, "y") +
03329 getScaledAttribute(node, "h")))
03330 spunode.appendChild(button)
03331
03332 write("Added next page button")
03333
03334 elif node.nodeName=="playall":
03335
03336
03337
03338 paintBackground(bgimage, node)
03339
03340 paintButton(draw, bgimage, bgimagemask, node, infoDOM, itemnum, page,
03341 itemsonthispage, chapternumber, chapterlist)
03342
03343 button = spumuxdom.createElement("button")
03344 button.setAttribute("name","playall")
03345 button.setAttribute("x0","%s" % getScaledAttribute(node, "x"))
03346 button.setAttribute("y0","%s" % getScaledAttribute(node, "y"))
03347 button.setAttribute("x1","%s" % (getScaledAttribute(node, "x") +
03348 getScaledAttribute(node, "w")))
03349 button.setAttribute("y1","%s" % (getScaledAttribute(node, "y") +
03350 getScaledAttribute(node, "h")))
03351 spunode.appendChild(button)
03352
03353 write("Added playall button")
03354
03355 elif node.nodeName == "titlemenu":
03356 if itemnum < numberofitems:
03357
03358
03359
03360 paintBackground(bgimage, node)
03361
03362 paintButton(draw, bgimage, bgimagemask, node, infoDOM,
03363 itemnum, page, itemsonthispage, chapternumber,
03364 chapterlist)
03365
03366 button = spumuxdom.createElement("button")
03367 button.setAttribute("name","titlemenu")
03368 button.setAttribute("x0","%s" % getScaledAttribute(node, "x"))
03369 button.setAttribute("y0","%s" % getScaledAttribute(node, "y"))
03370 button.setAttribute("x1","%s" % (getScaledAttribute(node, "x") +
03371 getScaledAttribute(node, "w")))
03372 button.setAttribute("y1","%s" % (getScaledAttribute(node, "y") +
03373 getScaledAttribute(node, "h")))
03374 spunode.appendChild(button)
03375
03376 write( "Added titlemenu button")
03377
03378 elif node.nodeName=="button":
03379
03380
03381
03382 paintBackground(bgimage, node)
03383
03384 wantHighlightBox = False
03385
03386 paintButton(draw, bgimage, bgimagemask, node, infoDOM, itemnum, page,
03387 itemsonthispage, chapternumber, chapterlist)
03388
03389 boundarybox = checkBoundaryBox(boundarybox, node)
03390
03391
03392 elif node.nodeName=="#text" or node.nodeName=="#comment":
03393
03394 assert True
03395 else:
03396 write( "Dont know how to process %s" % node.nodeName)
03397
03398 if drawmask == None:
03399 return
03400
03401
03402 if wantHighlightBox == True:
03403
03404 boundarybox=boundarybox[0]-1,boundarybox[1]-1,boundarybox[2]+1,boundarybox[3]+1
03405 drawmask.rectangle(boundarybox,outline=highlightcolor)
03406
03407
03408 boundarybox=boundarybox[0]-1,boundarybox[1]-1,boundarybox[2]+1,boundarybox[3]+1
03409 drawmask.rectangle(boundarybox,outline=highlightcolor)
03410
03411 node = spumuxdom.createElement("button")
03412
03413 if chapternumber>0:
03414 node.setAttribute("name","%s" % chapternumber)
03415 else:
03416 node.setAttribute("name","%s" % itemnum)
03417 node.setAttribute("x0","%d" % int(boundarybox[0]))
03418 node.setAttribute("y0","%d" % int(boundarybox[1]))
03419 node.setAttribute("x1","%d" % int(boundarybox[2] + 1))
03420 node.setAttribute("y1","%d" % int(boundarybox[3] + 1))
03421 spunode.appendChild(node)
03422
03423
03424
03425
03426 def createMenu(screensize, screendpi, numberofitems):
03427 """Creates all the necessary menu images and files for the MythBurn menus."""
03428
03429
03430 menunode=themeDOM.getElementsByTagName("menu")
03431 if menunode.length!=1:
03432 fatalError("Cannot find menu element in theme file")
03433 menunode=menunode[0]
03434
03435 menuitems=menunode.getElementsByTagName("item")
03436
03437 itemsperpage = menuitems.length
03438 write( "Menu items per page %s" % itemsperpage)
03439
03440
03441 backgroundfilename = menunode.attributes["background"].value
03442 if backgroundfilename=="":
03443 fatalError("Background image is not set in theme file")
03444
03445 backgroundfilename = getThemeFile(themeName,backgroundfilename)
03446 write( "Background image file is %s" % backgroundfilename)
03447 if not doesFileExist(backgroundfilename):
03448 fatalError("Background image not found (%s)" % backgroundfilename)
03449
03450
03451 highlightcolor = "red"
03452 if menunode.hasAttribute("highlightcolor"):
03453 highlightcolor = menunode.attributes["highlightcolor"].value
03454
03455
03456 menumusic = "menumusic.ac3"
03457 if menunode.hasAttribute("music"):
03458 menumusic = menunode.attributes["music"].value
03459
03460
03461 menulength = 15
03462 if menunode.hasAttribute("length"):
03463 menulength = int(menunode.attributes["length"].value)
03464
03465 write("Music is %s, length is %s seconds" % (menumusic, menulength))
03466
03467
03468 page=1
03469
03470
03471 itemnum=1
03472
03473 write("Creating DVD menus")
03474
03475 while itemnum <= numberofitems:
03476 write("Menu page %s" % page)
03477
03478
03479
03480
03481 write("Creating Preview Video")
03482 previewitem = itemnum
03483 itemsonthispage = 0
03484 haspreview = False
03485
03486 previewx = []
03487 previewy = []
03488 previeww = []
03489 previewh = []
03490 previewmask = []
03491
03492 while previewitem <= numberofitems and itemsonthispage < itemsperpage:
03493 menuitem=menuitems[ itemsonthispage ]
03494 itemsonthispage+=1
03495
03496
03497 previewfolder = createEmptyPreviewFolder(previewitem)
03498
03499
03500 px, py, pw, ph, maskimage = generateVideoPreview(previewitem, itemsonthispage, menuitem, 0, menulength, previewfolder)
03501 previewx.append(px)
03502 previewy.append(py)
03503 previeww.append(pw)
03504 previewh.append(ph)
03505 previewmask.append(maskimage)
03506 if px != 9999:
03507 haspreview = True
03508
03509 previewitem+=1
03510
03511
03512 savedpreviewitem = itemnum
03513
03514
03515 itemsonthispage=0
03516
03517
03518
03519
03520
03521
03522
03523 overlayimage=Image.new("RGBA",screensize)
03524 draw=ImageDraw.Draw(overlayimage)
03525
03526
03527 bgimagemask=Image.new("RGBA",overlayimage.size)
03528 drawmask=ImageDraw.Draw(bgimagemask)
03529
03530 spumuxdom = xml.dom.minidom.parseString('<subpictures><stream><spu force="yes" start="00:00:00.0" highlight="" select="" ></spu></stream></subpictures>')
03531 spunode = spumuxdom.documentElement.firstChild.firstChild
03532
03533
03534 while itemnum <= numberofitems and itemsonthispage < itemsperpage:
03535 menuitem=menuitems[ itemsonthispage ]
03536
03537 itemsonthispage+=1
03538
03539 drawThemeItem(page, itemsonthispage,
03540 itemnum, menuitem, overlayimage,
03541 draw, bgimagemask, drawmask, highlightcolor,
03542 spumuxdom, spunode, numberofitems, 0,"")
03543
03544
03545 itemnum+=1
03546
03547
03548 bgimage=Image.open(backgroundfilename,"r").resize(screensize)
03549 bgimage.paste(overlayimage, (0,0), overlayimage)
03550
03551
03552 bgimage.save(os.path.join(getTempPath(),"background-%s.jpg" % page),"JPEG", quality=99)
03553 bgimagemask.save(os.path.join(getTempPath(),"backgroundmask-%s.png" % page),"PNG",quality=99,optimize=0,dpi=screendpi)
03554
03555
03556
03557
03558
03559
03560 itemsonthispage = 0
03561
03562
03563 numframes=secondsToFrames(menulength)
03564
03565
03566 if haspreview == True:
03567 write( "Generating the preview images" )
03568 framenum = 0
03569 while framenum < numframes:
03570 previewitem = savedpreviewitem
03571 itemsonthispage = 0
03572 while previewitem <= numberofitems and itemsonthispage < itemsperpage:
03573 itemsonthispage+=1
03574 if previewx[itemsonthispage-1] != 9999:
03575 previewpath = os.path.join(getItemTempPath(previewitem), "preview")
03576 previewfile = "preview-i%d-t1-f%d.jpg" % (itemsonthispage, framenum)
03577 imagefile = os.path.join(previewpath, previewfile)
03578
03579 if doesFileExist(imagefile):
03580 picture = Image.open(imagefile, "r").resize((previeww[itemsonthispage-1], previewh[itemsonthispage-1]))
03581 picture = picture.convert("RGBA")
03582 imagemaskfile = os.path.join(previewpath, "mask-i%d.png" % itemsonthispage)
03583 if previewmask[itemsonthispage-1] != None:
03584 bgimage.paste(picture, (previewx[itemsonthispage-1], previewy[itemsonthispage-1]), previewmask[itemsonthispage-1])
03585 else:
03586 bgimage.paste(picture, (previewx[itemsonthispage-1], previewy[itemsonthispage-1]))
03587 del picture
03588 previewitem+=1
03589
03590 bgimage.save(os.path.join(getTempPath(),"background-%s-f%06d.jpg" % (page, framenum)),"JPEG",quality=99)
03591 framenum+=1
03592
03593 spumuxdom.documentElement.firstChild.firstChild.setAttribute("select",os.path.join(getTempPath(),"backgroundmask-%s.png" % page))
03594 spumuxdom.documentElement.firstChild.firstChild.setAttribute("highlight",os.path.join(getTempPath(),"backgroundmask-%s.png" % page))
03595
03596
03597 del draw
03598 del bgimage
03599 del drawmask
03600 del bgimagemask
03601 del overlayimage
03602 del previewx
03603 del previewy
03604 del previewmask
03605
03606 WriteXMLToFile (spumuxdom,os.path.join(getTempPath(),"spumux-%s.xml" % page))
03607
03608 if mainmenuAspectRatio == "4:3":
03609 aspect_ratio = 2
03610 else:
03611 aspect_ratio = 3
03612
03613 write("Encoding Menu Page %s using aspect ratio '%s'" % (page, mainmenuAspectRatio))
03614 if haspreview == True:
03615 encodeMenu(os.path.join(getTempPath(),"background-%s-f%%06d.jpg" % page),
03616 os.path.join(getTempPath(),"temp.m2v"),
03617 getThemeFile(themeName,menumusic),
03618 menulength,
03619 os.path.join(getTempPath(),"temp.mpg"),
03620 os.path.join(getTempPath(),"spumux-%s.xml" % page),
03621 os.path.join(getTempPath(),"menu-%s.mpg" % page),
03622 aspect_ratio)
03623 else:
03624 encodeMenu(os.path.join(getTempPath(),"background-%s.jpg" % page),
03625 os.path.join(getTempPath(),"temp.m2v"),
03626 getThemeFile(themeName,menumusic),
03627 menulength,
03628 os.path.join(getTempPath(),"temp.mpg"),
03629 os.path.join(getTempPath(),"spumux-%s.xml" % page),
03630 os.path.join(getTempPath(),"menu-%s.mpg" % page),
03631 aspect_ratio)
03632
03633
03634 page+=1
03635
03636
03637
03638
03639 def createChapterMenu(screensize, screendpi, numberofitems):
03640 """Creates all the necessary menu images and files for the MythBurn menus."""
03641
03642
03643 menunode=themeDOM.getElementsByTagName("submenu")
03644 if menunode.length!=1:
03645 fatalError("Cannot find submenu element in theme file")
03646 menunode=menunode[0]
03647
03648 menuitems=menunode.getElementsByTagName("chapter")
03649
03650 itemsperpage = menuitems.length
03651 write( "Chapter items per page %s " % itemsperpage)
03652
03653
03654 backgroundfilename = menunode.attributes["background"].value
03655 if backgroundfilename=="":
03656 fatalError("Background image is not set in theme file")
03657 backgroundfilename = getThemeFile(themeName,backgroundfilename)
03658 write( "Background image file is %s" % backgroundfilename)
03659 if not doesFileExist(backgroundfilename):
03660 fatalError("Background image not found (%s)" % backgroundfilename)
03661
03662
03663 highlightcolor = "red"
03664 if menunode.hasAttribute("highlightcolor"):
03665 highlightcolor = menunode.attributes["highlightcolor"].value
03666
03667
03668 menumusic = "menumusic.ac3"
03669 if menunode.hasAttribute("music"):
03670 menumusic = menunode.attributes["music"].value
03671
03672
03673 menulength = 15
03674 if menunode.hasAttribute("length"):
03675 menulength = int(menunode.attributes["length"].value)
03676
03677 write("Music is %s, length is %s seconds" % (menumusic, menulength))
03678
03679
03680 page=1
03681
03682 write( "Creating DVD sub-menus")
03683
03684 while page <= numberofitems:
03685 write( "Sub-menu %s " % page)
03686
03687
03688
03689
03690
03691
03692
03693 overlayimage=Image.new("RGBA",screensize, (0,0,0,0))
03694 draw=ImageDraw.Draw(overlayimage)
03695
03696
03697 bgimagemask=Image.new("RGBA",overlayimage.size, (0,0,0,0))
03698 drawmask=ImageDraw.Draw(bgimagemask)
03699
03700 spumuxdom = xml.dom.minidom.parseString('<subpictures><stream><spu force="yes" start="00:00:00.0" highlight="" select="" ></spu></stream></subpictures>')
03701 spunode = spumuxdom.documentElement.firstChild.firstChild
03702
03703
03704 chapterlist=createVideoChapters(page,itemsperpage,getLengthOfVideo(page),True)
03705 chapterlist=string.split(chapterlist,",")
03706
03707
03708
03709
03710
03711
03712 previewfolder = createEmptyPreviewFolder(page)
03713
03714 haspreview = False
03715
03716 previewsegment=int(getLengthOfVideo(page) / itemsperpage)
03717 previewtime = 0
03718 previewchapter = 0
03719 previewx = []
03720 previewy = []
03721 previeww = []
03722 previewh = []
03723 previewmask = []
03724
03725 while previewchapter < itemsperpage:
03726 menuitem=menuitems[ previewchapter ]
03727
03728
03729 px, py, pw, ph, maskimage = generateVideoPreview(page, previewchapter, menuitem, previewtime, menulength, previewfolder)
03730 previewx.append(px)
03731 previewy.append(py)
03732 previeww.append(pw)
03733 previewh.append(ph)
03734 previewmask.append(maskimage)
03735
03736 if px != 9999:
03737 haspreview = True
03738
03739 previewchapter+=1
03740 previewtime+=previewsegment
03741
03742
03743 chapter=0
03744 while chapter < itemsperpage:
03745 menuitem=menuitems[ chapter ]
03746 chapter+=1
03747
03748 drawThemeItem(page, itemsperpage, page, menuitem,
03749 overlayimage, draw,
03750 bgimagemask, drawmask, highlightcolor,
03751 spumuxdom, spunode,
03752 999, chapter, chapterlist)
03753
03754
03755 bgimage=Image.open(backgroundfilename,"r").resize(screensize)
03756 bgimage.paste(overlayimage, (0,0), overlayimage)
03757 bgimage.save(os.path.join(getTempPath(),"chaptermenu-%s.jpg" % page),"JPEG", quality=99)
03758
03759 bgimagemask.save(os.path.join(getTempPath(),"chaptermenumask-%s.png" % page),"PNG",quality=90,optimize=0)
03760
03761 if haspreview == True:
03762 numframes=secondsToFrames(menulength)
03763
03764
03765
03766 write( "Generating the preview images" )
03767 framenum = 0
03768 while framenum < numframes:
03769 previewchapter = 0
03770 while previewchapter < itemsperpage:
03771 if previewx[previewchapter] != 9999:
03772 previewpath = os.path.join(getItemTempPath(page), "preview")
03773 previewfile = "preview-i%d-t1-f%d.jpg" % (previewchapter, framenum)
03774 imagefile = os.path.join(previewpath, previewfile)
03775
03776 if doesFileExist(imagefile):
03777 picture = Image.open(imagefile, "r").resize((previeww[previewchapter], previewh[previewchapter]))
03778 picture = picture.convert("RGBA")
03779 imagemaskfile = os.path.join(previewpath, "mask-i%d.png" % previewchapter)
03780 if previewmask[previewchapter] != None:
03781 bgimage.paste(picture, (previewx[previewchapter], previewy[previewchapter]), previewmask[previewchapter])
03782 else:
03783 bgimage.paste(picture, (previewx[previewchapter], previewy[previewchapter]))
03784 del picture
03785 previewchapter+=1
03786 bgimage.save(os.path.join(getTempPath(),"chaptermenu-%s-f%06d.jpg" % (page, framenum)),"JPEG",quality=99)
03787 framenum+=1
03788
03789 spumuxdom.documentElement.firstChild.firstChild.setAttribute("select",os.path.join(getTempPath(),"chaptermenumask-%s.png" % page))
03790 spumuxdom.documentElement.firstChild.firstChild.setAttribute("highlight",os.path.join(getTempPath(),"chaptermenumask-%s.png" % page))
03791
03792
03793 del draw
03794 del bgimage
03795 del drawmask
03796 del bgimagemask
03797 del overlayimage
03798 del previewx
03799 del previewy
03800 del previewmask
03801
03802
03803 WriteXMLToFile (spumuxdom,os.path.join(getTempPath(),"chapterspumux-%s.xml" % page))
03804
03805 if chaptermenuAspectRatio == "4:3":
03806 aspect_ratio = '2'
03807 elif chaptermenuAspectRatio == "16:9":
03808 aspect_ratio = '3'
03809 else:
03810 if getAspectRatioOfVideo(page) > aspectRatioThreshold:
03811 aspect_ratio = '3'
03812 else:
03813 aspect_ratio = '2'
03814
03815 write("Encoding Chapter Menu Page %s using aspect ratio '%s'" % (page, chaptermenuAspectRatio))
03816
03817 if haspreview == True:
03818 encodeMenu(os.path.join(getTempPath(),"chaptermenu-%s-f%%06d.jpg" % page),
03819 os.path.join(getTempPath(),"temp.m2v"),
03820 getThemeFile(themeName,menumusic),
03821 menulength,
03822 os.path.join(getTempPath(),"temp.mpg"),
03823 os.path.join(getTempPath(),"chapterspumux-%s.xml" % page),
03824 os.path.join(getTempPath(),"chaptermenu-%s.mpg" % page),
03825 aspect_ratio)
03826 else:
03827 encodeMenu(os.path.join(getTempPath(),"chaptermenu-%s.jpg" % page),
03828 os.path.join(getTempPath(),"temp.m2v"),
03829 getThemeFile(themeName,menumusic),
03830 menulength,
03831 os.path.join(getTempPath(),"temp.mpg"),
03832 os.path.join(getTempPath(),"chapterspumux-%s.xml" % page),
03833 os.path.join(getTempPath(),"chaptermenu-%s.mpg" % page),
03834 aspect_ratio)
03835
03836
03837 page+=1
03838
03839
03840
03841
03842 def createDetailsPage(screensize, screendpi, numberofitems):
03843 """Creates all the necessary images and files for the details page."""
03844
03845 write( "Creating details pages")
03846
03847
03848 detailnode=themeDOM.getElementsByTagName("detailspage")
03849 if detailnode.length!=1:
03850 fatalError("Cannot find detailspage element in theme file")
03851 detailnode=detailnode[0]
03852
03853
03854 backgroundfilename = detailnode.attributes["background"].value
03855 if backgroundfilename=="":
03856 fatalError("Background image is not set in theme file")
03857 backgroundfilename = getThemeFile(themeName,backgroundfilename)
03858 write( "Background image file is %s" % backgroundfilename)
03859 if not doesFileExist(backgroundfilename):
03860 fatalError("Background image not found (%s)" % backgroundfilename)
03861
03862
03863 menumusic = "menumusic.ac3"
03864 if detailnode.hasAttribute("music"):
03865 menumusic = detailnode.attributes["music"].value
03866
03867
03868 menulength = 15
03869 if detailnode.hasAttribute("length"):
03870 menulength = int(detailnode.attributes["length"].value)
03871
03872 write("Music is %s, length is %s seconds" % (menumusic, menulength))
03873
03874
03875 itemnum=1
03876
03877 while itemnum <= numberofitems:
03878 write( "Creating details page for %s" % itemnum)
03879
03880
03881 previewfolder = createEmptyPreviewFolder(itemnum)
03882 haspreview = False
03883
03884
03885 previewx, previewy, previeww, previewh, previewmask = generateVideoPreview(itemnum, 1, detailnode, 0, menulength, previewfolder)
03886 if previewx != 9999:
03887 haspreview = True
03888
03889
03890
03891
03892
03893
03894
03895 overlayimage=Image.new("RGBA",screensize, (0,0,0,0))
03896 draw=ImageDraw.Draw(overlayimage)
03897
03898 spumuxdom = xml.dom.minidom.parseString('<subpictures><stream><spu force="yes" start="00:00:00.0" highlight="" select="" ></spu></stream></subpictures>')
03899 spunode = spumuxdom.documentElement.firstChild.firstChild
03900
03901 drawThemeItem(0, 0, itemnum, detailnode, overlayimage, draw, None, None,
03902 "", spumuxdom, spunode, numberofitems, 0, "")
03903
03904
03905 bgimage=Image.open(backgroundfilename,"r").resize(screensize)
03906 bgimage.paste(overlayimage, (0,0), overlayimage)
03907 bgimage.save(os.path.join(getTempPath(),"details-%s.jpg" % itemnum),"JPEG", quality=99)
03908
03909 if haspreview == True:
03910 numframes=secondsToFrames(menulength)
03911
03912
03913 write( "Generating the detail preview images" )
03914 framenum = 0
03915 while framenum < numframes:
03916 if previewx != 9999:
03917 previewpath = os.path.join(getItemTempPath(itemnum), "preview")
03918 previewfile = "preview-i%d-t1-f%d.jpg" % (1, framenum)
03919 imagefile = os.path.join(previewpath, previewfile)
03920
03921 if doesFileExist(imagefile):
03922 picture = Image.open(imagefile, "r").resize((previeww, previewh))
03923 picture = picture.convert("RGBA")
03924 imagemaskfile = os.path.join(previewpath, "mask-i%d.png" % 1)
03925 if previewmask != None:
03926 bgimage.paste(picture, (previewx, previewy), previewmask)
03927 else:
03928 bgimage.paste(picture, (previewx, previewy))
03929 del picture
03930 bgimage.save(os.path.join(getTempPath(),"details-%s-f%06d.jpg" % (itemnum, framenum)),"JPEG",quality=99)
03931 framenum+=1
03932
03933
03934
03935 del draw
03936 del bgimage
03937
03938
03939 aspect_ratio='2'
03940 if getAspectRatioOfVideo(itemnum) > aspectRatioThreshold:
03941 aspect_ratio='3'
03942
03943
03944 WriteXMLToFile (spumuxdom,os.path.join(getTempPath(),"detailsspumux-%s.xml" % itemnum))
03945
03946 write("Encoding Details Page %s" % itemnum)
03947 if haspreview == True:
03948 encodeMenu(os.path.join(getTempPath(),"details-%s-f%%06d.jpg" % itemnum),
03949 os.path.join(getTempPath(),"temp.m2v"),
03950 getThemeFile(themeName,menumusic),
03951 menulength,
03952 os.path.join(getTempPath(),"temp.mpg"),
03953 "",
03954 os.path.join(getTempPath(),"details-%s.mpg" % itemnum),
03955 aspect_ratio)
03956 else:
03957 encodeMenu(os.path.join(getTempPath(),"details-%s.jpg" % itemnum),
03958 os.path.join(getTempPath(),"temp.m2v"),
03959 getThemeFile(themeName,menumusic),
03960 menulength,
03961 os.path.join(getTempPath(),"temp.mpg"),
03962 "",
03963 os.path.join(getTempPath(),"details-%s.mpg" % itemnum),
03964 aspect_ratio)
03965
03966
03967 itemnum+=1
03968
03969
03970
03971
03972 def isMediaAVIFile(file):
03973 fh = open(file, 'rb')
03974 Magic = fh.read(4)
03975 fh.close()
03976 return Magic=="RIFF"
03977
03978
03979
03980
03981 def processAudio(folder):
03982 """encode audio to ac3 for better compression and compatability with NTSC players"""
03983
03984
03985 if not encodetoac3 and doesFileExist(os.path.join(folder,'stream0.mp2')):
03986
03987 write( "Audio track 1 is in mp2 format - NOT re-encoding to ac3")
03988 elif doesFileExist(os.path.join(folder,'stream0.mp2'))==True:
03989 write( "Audio track 1 is in mp2 format - re-encoding to ac3")
03990 encodeAudio("ac3",os.path.join(folder,'stream0.mp2'), os.path.join(folder,'stream0.ac3'),True)
03991 elif doesFileExist(os.path.join(folder,'stream0.mpa'))==True:
03992 write( "Audio track 1 is in mpa format - re-encoding to ac3")
03993 encodeAudio("ac3",os.path.join(folder,'stream0.mpa'), os.path.join(folder,'stream0.ac3'),True)
03994 elif doesFileExist(os.path.join(folder,'stream0.ac3'))==True:
03995 write( "Audio is already in ac3 format")
03996 else:
03997 fatalError("Track 1 - Unknown audio format or de-multiplex failed!")
03998
03999
04000 if not encodetoac3 and doesFileExist(os.path.join(folder,'stream1.mp2')):
04001
04002 write( "Audio track 2 is in mp2 format - NOT re-encoding to ac3")
04003 elif doesFileExist(os.path.join(folder,'stream1.mp2'))==True:
04004 write( "Audio track 2 is in mp2 format - re-encoding to ac3")
04005 encodeAudio("ac3",os.path.join(folder,'stream1.mp2'), os.path.join(folder,'stream1.ac3'),True)
04006 elif doesFileExist(os.path.join(folder,'stream1.mpa'))==True:
04007 write( "Audio track 2 is in mpa format - re-encoding to ac3")
04008 encodeAudio("ac3",os.path.join(folder,'stream1.mpa'), os.path.join(folder,'stream1.ac3'),True)
04009 elif doesFileExist(os.path.join(folder,'stream1.ac3'))==True:
04010 write( "Audio is already in ac3 format")
04011
04012
04013
04014
04015
04016 VIDEO_INDEX = 0
04017 VIDEO_CODEC = 1
04018 VIDEO_ID = 2
04019
04020 AUDIO_INDEX = 0
04021 AUDIO_CODEC = 1
04022 AUDIO_ID = 2
04023 AUDIO_LANG = 3
04024
04025 def selectStreams(folder):
04026 """Choose the streams we want from the source file"""
04027
04028 video = (-1, 'N/A', -1)
04029 audio1 = (-1, 'N/A', -1, 'N/A')
04030 audio2 = (-1, 'N/A', -1, 'N/A')
04031
04032
04033 infoDOM = xml.dom.minidom.parse(os.path.join(folder, 'streaminfo.xml'))
04034
04035 if infoDOM.documentElement.tagName != "file":
04036 fatalError("This does not look like a stream info file (%s)" % os.path.join(folder, 'streaminfo.xml'))
04037
04038
04039
04040 nodes = infoDOM.getElementsByTagName("video")
04041 if nodes.length == 0:
04042 write("Didn't find any video elements in stream info file.!!!")
04043 write("");
04044 sys.exit(1)
04045 if nodes.length > 1:
04046 write("Found more than one video element in stream info file.!!!")
04047 node = nodes[0]
04048 video = (int(node.attributes["ffmpegindex"].value), node.attributes["codec"].value, int(node.attributes["id"].value))
04049
04050
04051
04052
04053
04054
04055
04056
04057 write("Preferred audio languages %s and %s" % (preferredlang1, preferredlang2))
04058
04059 nodes = infoDOM.getElementsByTagName("audio")
04060
04061 if nodes.length == 0:
04062 write("Didn't find any audio elements in stream info file.!!!")
04063 write("");
04064 sys.exit(1)
04065
04066 found = False
04067
04068 for node in nodes:
04069 index = int(node.attributes["ffmpegindex"].value)
04070 lang = node.attributes["language"].value
04071 format = string.upper(node.attributes["codec"].value)
04072 pid = int(node.attributes["id"].value)
04073 if lang == preferredlang1 and format == "AC3":
04074 if found:
04075 if pid < audio1[AUDIO_ID]:
04076 audio1 = (index, format, pid, lang)
04077 else:
04078 audio1 = (index, format, pid, lang)
04079 found = True
04080
04081
04082 if not found:
04083 for node in nodes:
04084 index = int(node.attributes["ffmpegindex"].value)
04085 lang = node.attributes["language"].value
04086 format = string.upper(node.attributes["codec"].value)
04087 pid = int(node.attributes["id"].value)
04088 if lang == preferredlang1 and format == "MP2":
04089 if found:
04090 if pid < audio1[AUDIO_ID]:
04091 audio1 = (index, format, pid, lang)
04092 else:
04093 audio1 = (index, format, pid, lang)
04094 found = True
04095
04096
04097 if not found:
04098 for node in nodes:
04099 index = int(node.attributes["ffmpegindex"].value)
04100 format = string.upper(node.attributes["codec"].value)
04101 pid = int(node.attributes["id"].value)
04102 if not found:
04103 audio1 = (index, format, pid, lang)
04104 found = True
04105 else:
04106 if format == "AC3" and audio1[AUDIO_CODEC] == "MP2":
04107 audio1 = (index, format, pid, lang)
04108 else:
04109 if pid < audio1[AUDIO_ID]:
04110 audio1 = (index, format, pid, lang)
04111
04112
04113 if preferredlang1 != preferredlang2 and nodes.length > 1:
04114 found = False
04115
04116 for node in nodes:
04117 index = int(node.attributes["ffmpegindex"].value)
04118 lang = node.attributes["language"].value
04119 format = string.upper(node.attributes["codec"].value)
04120 pid = int(node.attributes["id"].value)
04121 if lang == preferredlang2 and format == "AC3":
04122 if found:
04123 if pid < audio2[AUDIO_ID]:
04124 audio2 = (index, format, pid, lang)
04125 else:
04126 audio2 = (index, format, pid, lang)
04127 found = True
04128
04129
04130 if not found:
04131 for node in nodes:
04132 index = int(node.attributes["ffmpegindex"].value)
04133 lang = node.attributes["language"].value
04134 format = string.upper(node.attributes["codec"].value)
04135 pid = int(node.attributes["id"].value)
04136 if lang == preferredlang2 and format == "MP2":
04137 if found:
04138 if pid < audio2[AUDIO_ID]:
04139 audio2 = (index, format, pid, lang)
04140 else:
04141 audio2 = (index, format, pid, lang)
04142 found = True
04143
04144
04145 if not found:
04146 for node in nodes:
04147 index = int(node.attributes["ffmpegindex"].value)
04148 format = string.upper(node.attributes["codec"].value)
04149 pid = int(node.attributes["id"].value)
04150 if not found:
04151
04152 if pid != audio1[AUDIO_ID]:
04153 audio2 = (index, format, pid, lang)
04154 found = True
04155 else:
04156 if format == "AC3" and audio2[AUDIO_CODEC] == "MP2" and pid != audio1[AUDIO_ID]:
04157 audio2 = (index, format, pid, lang)
04158 else:
04159 if pid < audio2[AUDIO_ID] and pid != audio1[AUDIO_ID]:
04160 audio2 = (index, format, pid, lang)
04161
04162 write("Video id: 0x%x, Audio1: [%d] 0x%x (%s, %s), Audio2: [%d] - 0x%x (%s, %s)" % \
04163 (video[VIDEO_ID], audio1[AUDIO_INDEX], audio1[AUDIO_ID], audio1[AUDIO_CODEC], audio1[AUDIO_LANG], \
04164 audio2[AUDIO_INDEX], audio2[AUDIO_ID], audio2[AUDIO_CODEC], audio2[AUDIO_LANG]))
04165
04166 return (video, audio1, audio2)
04167
04168
04169
04170
04171
04172 SUBTITLE_INDEX = 0
04173 SUBTITLE_CODEC = 1
04174 SUBTITLE_ID = 2
04175 SUBTITLE_LANG = 3
04176
04177 def selectSubtitleStream(folder):
04178 """Choose the subtitle stream we want from the source file"""
04179
04180 subtitle = (-1, 'N/A', -1, 'N/A')
04181
04182
04183 infoDOM = xml.dom.minidom.parse(os.path.join(folder, 'streaminfo.xml'))
04184
04185 if infoDOM.documentElement.tagName != "file":
04186 fatalError("This does not look like a stream info file (%s)" % os.path.join(folder, 'streaminfo.xml'))
04187
04188
04189
04190 nodes = infoDOM.getElementsByTagName("subtitle")
04191 if nodes.length == 0:
04192 write("Didn't find any subtitle elements in stream info file.")
04193 return subtitle
04194
04195 write("Preferred languages %s and %s" % (preferredlang1, preferredlang2))
04196
04197 found = False
04198
04199 for node in nodes:
04200 index = int(node.attributes["ffmpegindex"].value)
04201 lang = node.attributes["language"].value
04202 format = string.upper(node.attributes["codec"].value)
04203 pid = int(node.attributes["id"].value)
04204 if not found and lang == preferredlang1 and format == "dvbsub":
04205 subtitle = (index, format, pid, lang)
04206 found = True
04207
04208
04209 if not found:
04210 for node in nodes:
04211 index = int(node.attributes["ffmpegindex"].value)
04212 lang = node.attributes["language"].value
04213 format = string.upper(node.attributes["codec"].value)
04214 pid = int(node.attributes["id"].value)
04215 if not found and lang == preferredlang2 and format == "dvbsub":
04216 subtitle = (index, format, pid, lang)
04217 found = True
04218
04219
04220 if not found:
04221 for node in nodes:
04222 index = int(node.attributes["ffmpegindex"].value)
04223 format = string.upper(node.attributes["codec"].value)
04224 pid = int(node.attributes["id"].value)
04225 if not found:
04226 subtitle = (index, format, pid, lang)
04227 found = True
04228
04229 write("Subtitle id: 0x%x" % (subtitle[SUBTITLE_ID]))
04230
04231 return subtitle
04232
04233
04234
04235
04236 def selectAspectRatio(folder):
04237 """figure out what aspect ratio we want from the source file"""
04238
04239
04240
04241
04242
04243 infoDOM = xml.dom.minidom.parse(os.path.join(folder, 'streaminfo.xml'))
04244
04245 if infoDOM.documentElement.tagName != "file":
04246 fatalError("This does not look like a stream info file (%s)" % os.path.join(folder, 'streaminfo.xml'))
04247
04248
04249
04250 nodes = infoDOM.getElementsByTagName("video")
04251 if nodes.length == 0:
04252 write("Didn't find any video elements in stream info file.!!!")
04253 write("");
04254 sys.exit(1)
04255 if nodes.length > 1:
04256 write("Found more than one video element in stream info file.!!!")
04257 node = nodes[0]
04258 try:
04259 ar = float(node.attributes["aspectratio"].value)
04260 if ar > float(4.0/3.0 - 0.01) and ar < float(4.0/3.0 + 0.01):
04261 aspectratio = "4:3"
04262 write("Aspect ratio is 4:3")
04263 elif ar > float(16.0/9.0 - 0.01) and ar < float(16.0/9.0 + 0.01):
04264 aspectratio = "16:9"
04265 write("Aspect ratio is 16:9")
04266 else:
04267 write("Unknown aspect ratio %f - Using 16:9" % ar)
04268 aspectratio = "16:9"
04269 except:
04270 aspectratio = "16:9"
04271
04272 return aspectratio
04273
04274
04275
04276
04277 def getVideoCodec(folder):
04278 """Get the video codec from the streaminfo.xml for the file"""
04279
04280
04281 infoDOM = xml.dom.minidom.parse(os.path.join(folder, 'streaminfo.xml'))
04282
04283 if infoDOM.documentElement.tagName != "file":
04284 fatalError("This does not look like a stream info file (%s)" % os.path.join(folder, 'streaminfo.xml'))
04285
04286 nodes = infoDOM.getElementsByTagName("video")
04287 if nodes.length == 0:
04288 write("Didn't find any video elements in stream info file!!!")
04289 write("");
04290 sys.exit(1)
04291 if nodes.length > 1:
04292 write("Found more than one video element in stream info file!!!")
04293 node = nodes[0]
04294 return node.attributes["codec"].value
04295
04296
04297
04298
04299 def getFileType(folder):
04300 """Get the overall file type from the streaminfo.xml for the file"""
04301
04302
04303 infoDOM = xml.dom.minidom.parse(os.path.join(folder, 'streaminfo.xml'))
04304
04305 if infoDOM.documentElement.tagName != "file":
04306 fatalError("This does not look like a stream info file (%s)" % os.path.join(folder, 'streaminfo.xml'))
04307
04308 nodes = infoDOM.getElementsByTagName("file")
04309 if nodes.length == 0:
04310 write("Didn't find any file elements in stream info file!!!")
04311 write("");
04312 sys.exit(1)
04313 if nodes.length > 1:
04314 write("Found more than one file element in stream info file!!!")
04315 node = nodes[0]
04316
04317 return node.attributes["type"].value
04318
04319
04320
04321
04322 def getStreamList(folder):
04323
04324
04325 video, audio1, audio2 = selectStreams(folder)
04326
04327 streamList = "0x%x" % video[VIDEO_ID]
04328
04329 if audio1[AUDIO_ID] != -1:
04330 streamList += ",0x%x" % audio1[AUDIO_ID]
04331
04332 if audio2[AUDIO_ID] != -1:
04333 streamList += ",0x%x" % audio2[AUDIO_ID]
04334
04335
04336 if addSubtitles:
04337 subtitles = selectSubtitleStream(folder)
04338 if subtitles[SUBTITLE_ID] != -1:
04339 streamList += ",0x%x" % subtitles[SUBTITLE_ID]
04340
04341 return streamList;
04342
04343
04344
04345
04346
04347 def isFileOkayForDVD(file, folder):
04348 """return true if the file is dvd compliant"""
04349
04350 if string.lower(getVideoCodec(folder)) != "mpeg2video":
04351 return False
04352
04353
04354
04355
04356 videosize = getVideoSize(os.path.join(folder, "streaminfo.xml"))
04357
04358
04359 if file.hasAttribute("encodingprofile"):
04360 if file.attributes["encodingprofile"].value != "NONE":
04361 write("File will be re-encoded using profile %s" % file.attributes["encodingprofile"].value)
04362 return False
04363
04364 if not isResolutionOkayForDVD(videosize):
04365
04366 if file.hasAttribute("encodingprofile"):
04367 if file.attributes["encodingprofile"].value == "NONE":
04368 write("WARNING: File does not have a DVD compliant resolution but "
04369 "you have selected not to re-encode the file")
04370 return True
04371 else:
04372 return False
04373
04374 return True
04375
04376
04377
04378
04379
04380 def processFile(file, folder, count):
04381 """Process a single video/recording file ready for burning."""
04382
04383 if useprojectx:
04384 doProcessFileProjectX(file, folder, count)
04385 else:
04386 doProcessFile(file, folder, count)
04387
04388
04389
04390
04391
04392 def doProcessFile(file, folder, count):
04393 """Process a single video/recording file ready for burning."""
04394
04395 write( "*************************************************************")
04396 write( "Processing %s %d: '%s'" % (file.attributes["type"].value, count, file.attributes["filename"].value))
04397 write( "*************************************************************")
04398
04399
04400
04401
04402
04403
04404
04405 mediafile=""
04406
04407 if file.hasAttribute("localfilename"):
04408 mediafile=file.attributes["localfilename"].value
04409 elif file.attributes["type"].value=="recording":
04410 mediafile = file.attributes["filename"].value
04411 elif file.attributes["type"].value=="video":
04412 mediafile=os.path.join(videopath, file.attributes["filename"].value)
04413 elif file.attributes["type"].value=="file":
04414 mediafile=file.attributes["filename"].value
04415 else:
04416 fatalError("Unknown type of video file it must be 'recording', 'video' or 'file'.")
04417
04418
04419 infoDOM = xml.dom.minidom.parse( os.path.join(folder,"info.xml") )
04420
04421 if infoDOM.documentElement.tagName != "fileinfo":
04422 fatalError("The info.xml file (%s) doesn't look right" % os.path.join(folder,"info.xml"))
04423
04424
04425
04426 if file.attributes["type"].value == "recording":
04427
04428 write("File type is '%s'" % getFileType(folder))
04429 write("Video codec is '%s'" % getVideoCodec(folder))
04430 if string.lower(getVideoCodec(folder)) == "mpeg2video":
04431 if file.attributes["usecutlist"].value == "1" and getText(infoDOM.getElementsByTagName("hascutlist")[0]) == "yes":
04432
04433 if file.hasAttribute("localfilename"):
04434 localfile = file.attributes["localfilename"].value
04435 else:
04436 localfile = ""
04437 write("File has a cut list - running mythtranscode to remove unwanted segments")
04438 chanid = getText(infoDOM.getElementsByTagName("chanid")[0])
04439 starttime = getText(infoDOM.getElementsByTagName("starttime")[0])
04440 if runMythtranscode(chanid, starttime, os.path.join(folder,'newfile.mpg'), True, localfile):
04441 mediafile = os.path.join(folder,'newfile.mpg')
04442 else:
04443 write("Failed to run mythtranscode to remove unwanted segments")
04444 else:
04445
04446
04447 if (alwaysRunMythtranscode == True or
04448 (getFileType(folder) == "mpegts" and isFileOkayForDVD(file, folder))):
04449
04450 if file.hasAttribute("localfilename"):
04451 localfile = file.attributes["localfilename"].value
04452 else:
04453 localfile = ""
04454 write("Running mythtranscode --mpeg2 to fix any errors")
04455 chanid = getText(infoDOM.getElementsByTagName("chanid")[0])
04456 starttime = getText(infoDOM.getElementsByTagName("starttime")[0])
04457 if runMythtranscode(chanid, starttime, os.path.join(folder, 'newfile.mpg'), False, localfile):
04458 mediafile = os.path.join(folder, 'newfile.mpg')
04459 else:
04460 write("Failed to run mythtranscode to fix any errors")
04461 else:
04462
04463
04464 write("File type is '%s'" % getFileType(folder))
04465 write("Video codec is '%s'" % getVideoCodec(folder))
04466
04467 if (alwaysRunMythtranscode == True and
04468 string.lower(getVideoCodec(folder)) == "mpeg2video" and
04469 isFileOkayForDVD(file, folder)):
04470 if file.hasAttribute("localfilename"):
04471 localfile = file.attributes["localfilename"].value
04472 else:
04473 localfile = file.attributes["filename"].value
04474 write("Running mythtranscode --mpeg2 to fix any errors")
04475 chanid = -1
04476 starttime = -1
04477 if runMythtranscode(chanid, starttime, os.path.join(folder, 'newfile.mpg'), False, localfile):
04478 mediafile = os.path.join(folder, 'newfile.mpg')
04479 else:
04480 write("Failed to run mythtranscode to fix any errors")
04481
04482
04483 if not isFileOkayForDVD(file, folder):
04484 if getFileType(folder) == 'nuv':
04485
04486
04487
04488
04489
04490 getStreamInformation(mediafile, os.path.join(folder, "streaminfo.xml"), 0)
04491
04492
04493 video, audio1, audio2 = selectStreams(folder)
04494
04495
04496 aspectratio = selectAspectRatio(folder)
04497
04498 write("Re-encoding audio and video from nuv file")
04499
04500
04501 if file.hasAttribute("encodingprofile"):
04502 profile = file.attributes["encodingprofile"].value
04503 else:
04504 profile = defaultEncodingProfile
04505
04506 if file.hasAttribute("localfilename"):
04507 mediafile = file.attributes["localfilename"].value
04508 chanid = -1
04509 starttime = -1
04510 usecutlist = -1
04511 elif file.attributes["type"].value == "recording":
04512 mediafile = -1
04513 chanid = getText(infoDOM.getElementsByTagName("chanid")[0])
04514 starttime = getText(infoDOM.getElementsByTagName("starttime")[0])
04515 usecutlist = (file.attributes["usecutlist"].value == "1" and
04516 getText(infoDOM.getElementsByTagName("hascutlist")[0]) == "yes")
04517 else:
04518 chanid = -1
04519 starttime = -1
04520 usecutlist = -1
04521
04522 encodeNuvToMPEG2(chanid, starttime, mediafile, os.path.join(folder, "newfile2.mpg"), folder,
04523 profile, usecutlist)
04524 mediafile = os.path.join(folder, 'newfile2.mpg')
04525 else:
04526
04527
04528 getStreamInformation(mediafile, os.path.join(folder, "streaminfo.xml"), 0)
04529
04530
04531 video, audio1, audio2 = selectStreams(folder)
04532
04533
04534 aspectratio = selectAspectRatio(folder)
04535
04536 write("Re-encoding audio and video")
04537
04538
04539 if file.hasAttribute("localfilename"):
04540 mediafile = file.attributes["localfilename"].value
04541
04542
04543 if file.hasAttribute("encodingprofile"):
04544 profile = file.attributes["encodingprofile"].value
04545 else:
04546 profile = defaultEncodingProfile
04547
04548
04549 encodeVideoToMPEG2(mediafile, os.path.join(folder, "newfile2.mpg"), video,
04550 audio1, audio2, aspectratio, profile)
04551 mediafile = os.path.join(folder, 'newfile2.mpg')
04552
04553
04554
04555 if debug_keeptempfiles==False:
04556 if os.path.exists(os.path.join(folder, "newfile.mpg")):
04557 os.remove(os.path.join(folder,'newfile.mpg'))
04558
04559
04560
04561
04562 getStreamInformation(mediafile, os.path.join(folder, "streaminfo.xml"), 1)
04563
04564
04565 video, audio1, audio2 = selectStreams(folder)
04566
04567
04568 write("Splitting MPEG stream into audio and video parts")
04569 deMultiplexMPEG2File(folder, mediafile, video, audio1, audio2)
04570
04571
04572 if debug_keeptempfiles==False:
04573 if os.path.exists(os.path.join(folder, "newfile.mpg")):
04574 os.remove(os.path.join(folder,'newfile.mpg'))
04575 if os.path.exists(os.path.join(folder, "newfile2.mpg")):
04576 os.remove(os.path.join(folder,'newfile2.mpg'))
04577
04578
04579
04580 processAudio(folder)
04581
04582
04583 titleImage = os.path.join(folder, "title.jpg")
04584 if not os.path.exists(titleImage):
04585
04586 if file.attributes["type"].value == "recording":
04587 previewImage = file.attributes["filename"].value + ".png"
04588 if usebookmark == True and os.path.exists(previewImage):
04589 copy(previewImage, titleImage)
04590 else:
04591 extractVideoFrame(os.path.join(folder, "stream.mv2"), titleImage, thumboffset)
04592 else:
04593 extractVideoFrame(os.path.join(folder, "stream.mv2"), titleImage, thumboffset)
04594
04595 write( "*************************************************************")
04596 write( "Finished processing '%s'" % file.attributes["filename"].value)
04597 write( "*************************************************************")
04598
04599
04600
04601
04602
04603
04604 def doProcessFileProjectX(file, folder, count):
04605 """Process a single video/recording file ready for burning."""
04606
04607 write( "*************************************************************")
04608 write( "Processing %s %d: '%s'" % (file.attributes["type"].value, count, file.attributes["filename"].value))
04609 write( "*************************************************************")
04610
04611
04612
04613
04614
04615
04616
04617 mediafile=""
04618
04619 if file.hasAttribute("localfilename"):
04620 mediafile=file.attributes["localfilename"].value
04621 elif file.attributes["type"].value=="recording":
04622 mediafile = file.attributes["filename"].value
04623 elif file.attributes["type"].value=="video":
04624 mediafile=os.path.join(videopath, file.attributes["filename"].value)
04625 elif file.attributes["type"].value=="file":
04626 mediafile=file.attributes["filename"].value
04627 else:
04628 fatalError("Unknown type of video file it must be 'recording', 'video' or 'file'.")
04629
04630
04631 infoDOM = xml.dom.minidom.parse( os.path.join(folder,"info.xml") )
04632
04633 if infoDOM.documentElement.tagName != "fileinfo":
04634 fatalError("The info.xml file (%s) doesn't look right" % os.path.join(folder,"info.xml"))
04635
04636
04637 if not isFileOkayForDVD(file, folder):
04638 if getFileType(folder) == 'nuv':
04639
04640
04641
04642
04643
04644 getStreamInformation(mediafile, os.path.join(folder, "streaminfo.xml"), 0)
04645
04646
04647 video, audio1, audio2 = selectStreams(folder)
04648
04649
04650 aspectratio = selectAspectRatio(folder)
04651
04652 write("Re-encoding audio and video from nuv file")
04653
04654
04655 if file.hasAttribute("encodingprofile"):
04656 profile = file.attributes["encodingprofile"].value
04657 else:
04658 profile = defaultEncodingProfile
04659
04660 if file.hasAttribute("localfilename"):
04661 mediafile = file.attributes["localfilename"].value
04662 chanid = -1
04663 starttime = -1
04664 usecutlist = -1
04665 elif file.attributes["type"].value == "recording":
04666 mediafile = -1
04667 chanid = getText(infoDOM.getElementsByTagName("chanid")[0])
04668 starttime = getText(infoDOM.getElementsByTagName("starttime")[0])
04669 usecutlist = (file.attributes["usecutlist"].value == "1" and
04670 getText(infoDOM.getElementsByTagName("hascutlist")[0]) == "yes")
04671 else:
04672 chanid = -1
04673 starttime = -1
04674 usecutlist = -1
04675
04676 encodeNuvToMPEG2(chanid, starttime, mediafile, os.path.join(folder, "newfile2.mpg"), folder,
04677 profile, usecutlist)
04678 mediafile = os.path.join(folder, 'newfile2.mpg')
04679 else:
04680
04681
04682 getStreamInformation(mediafile, os.path.join(folder, "streaminfo.xml"), 0)
04683
04684
04685 video, audio1, audio2 = selectStreams(folder)
04686
04687
04688 aspectratio = selectAspectRatio(folder)
04689
04690 write("Re-encoding audio and video")
04691
04692
04693 if file.hasAttribute("localfilename"):
04694 mediafile = file.attributes["localfilename"].value
04695
04696
04697 if file.hasAttribute("encodingprofile"):
04698 profile = file.attributes["encodingprofile"].value
04699 else:
04700 profile = defaultEncodingProfile
04701
04702
04703 encodeVideoToMPEG2(mediafile, os.path.join(folder, "newfile2.mpg"), video,
04704 audio1, audio2, aspectratio, profile)
04705 mediafile = os.path.join(folder, 'newfile2.mpg')
04706
04707
04708 if os.path.exists(os.path.join(folder, "newfile1.mpg")):
04709 os.remove(os.path.join(folder,'newfile1.mpg'))
04710
04711
04712
04713
04714
04715 getStreamInformation(mediafile, os.path.join(folder, "streaminfo.xml"), 1)
04716
04717
04718 video, audio1, audio2 = selectStreams(folder)
04719
04720
04721
04722
04723
04724
04725 if file.attributes["type"].value == "recording":
04726 if file.attributes["usecutlist"].value == "1" and getText(infoDOM.getElementsByTagName("hascutlist")[0]) == "yes":
04727 chanid = getText(infoDOM.getElementsByTagName("chanid")[0])
04728 starttime = getText(infoDOM.getElementsByTagName("starttime")[0])
04729 write("File has a cut list - running Project-X to remove unwanted segments")
04730 if not runProjectX(chanid, starttime, folder, True, mediafile):
04731 fatalError("Failed to run Project-X to remove unwanted segments and demux")
04732 else:
04733
04734 chanid = getText(infoDOM.getElementsByTagName("chanid")[0])
04735 starttime = getText(infoDOM.getElementsByTagName("starttime")[0])
04736 write("Using Project-X to demux file")
04737 if not runProjectX(chanid, starttime, folder, False, mediafile):
04738 fatalError("Failed to run Project-X to demux file")
04739 else:
04740
04741 chanid = -1
04742 starttime = -1
04743 write("Running Project-X to demux file")
04744 if not runProjectX(chanid, starttime, folder, False, mediafile):
04745 fatalError("Failed to run Project-X to demux file")
04746
04747
04748
04749 processAudio(folder)
04750
04751
04752 titleImage = os.path.join(folder, "title.jpg")
04753 if not os.path.exists(titleImage):
04754
04755 if file.attributes["type"].value == "recording":
04756 previewImage = file.attributes["filename"].value + ".png"
04757 if usebookmark == True and os.path.exists(previewImage):
04758 copy(previewImage, titleImage)
04759 else:
04760 extractVideoFrame(os.path.join(folder, "stream.mv2"), titleImage, thumboffset)
04761 else:
04762 extractVideoFrame(os.path.join(folder, "stream.mv2"), titleImage, thumboffset)
04763
04764 write( "*************************************************************")
04765 write( "Finished processing file '%s'" % file.attributes["filename"].value)
04766 write( "*************************************************************")
04767
04768
04769
04770
04771 def copyRemote(files, tmpPath):
04772 '''go through the list of files looking for files on remote filesytems
04773 and copy them to a local file for quicker processing'''
04774 localTmpPath = os.path.join(tmpPath, "localcopy")
04775 for node in files:
04776 tmpfile = node.attributes["filename"].value
04777 filename = os.path.basename(tmpfile)
04778
04779 res = runCommand("mytharchivehelper -q -q --isremote --infile " + quoteCmdArg(tmpfile))
04780 if res == 2:
04781
04782 write("Copying file from " + tmpfile)
04783 write("to " + os.path.join(localTmpPath, filename))
04784
04785
04786 if not doesFileExist(os.path.join(localTmpPath, filename)):
04787 copy(tmpfile, os.path.join(localTmpPath, filename))
04788
04789
04790 node.setAttribute("localfilename", os.path.join(localTmpPath, filename))
04791 return files
04792
04793
04794
04795
04796 def processJob(job):
04797 """Starts processing a MythBurn job, expects XML nodes to be passed as input."""
04798 global wantIntro, wantMainMenu, wantChapterMenu, wantDetailsPage
04799 global themeDOM, themeName, themeFonts
04800
04801
04802 media=job.getElementsByTagName("media")
04803
04804 if media.length==1:
04805
04806 themeName=job.attributes["theme"].value
04807
04808
04809 if not validateTheme(themeName):
04810 fatalError("Failed to validate theme (%s)" % themeName)
04811
04812 themeDOM = getThemeConfigurationXML(themeName)
04813
04814
04815 loadFonts(themeDOM)
04816
04817
04818 nodes=themeDOM.getElementsByTagName("intro")
04819 wantIntro = (nodes.length > 0)
04820
04821 nodes=themeDOM.getElementsByTagName("menu")
04822 wantMainMenu = (nodes.length > 0)
04823
04824 nodes=themeDOM.getElementsByTagName("submenu")
04825 wantChapterMenu = (nodes.length > 0)
04826
04827 nodes=themeDOM.getElementsByTagName("detailspage")
04828 wantDetailsPage = (nodes.length > 0)
04829
04830 write( "wantIntro: %d, wantMainMenu: %d, wantChapterMenu: %d, wantDetailsPage: %d" \
04831 % (wantIntro, wantMainMenu, wantChapterMenu, wantDetailsPage))
04832
04833 if videomode=="ntsc":
04834 format=dvdNTSC
04835 dpi=dvdNTSCdpi
04836 elif videomode=="pal":
04837 format=dvdPAL
04838 dpi=dvdPALdpi
04839 else:
04840 fatalError("Unknown videomode is set (%s)" % videomode)
04841
04842 write( "Final DVD Video format will be " + videomode)
04843
04844
04845
04846 files=media[0].getElementsByTagName("file")
04847 filecount=0
04848 if files.length > 0:
04849 write( "There are %s file(s) to process" % files.length)
04850
04851 if debug_secondrunthrough==False:
04852
04853 deleteEverythingInFolder(getTempPath())
04854
04855
04856 if copyremoteFiles==True:
04857 if debug_secondrunthrough==False:
04858 localCopyFolder=os.path.join(getTempPath(),"localcopy")
04859 os.makedirs(localCopyFolder)
04860 files=copyRemote(files,getTempPath())
04861
04862
04863
04864
04865 for node in files:
04866 filecount+=1
04867
04868
04869 folder=getItemTempPath(filecount)
04870
04871 if debug_secondrunthrough==False:
04872 os.makedirs(folder)
04873
04874 preProcessFile(node,folder,filecount)
04875
04876 if debug_secondrunthrough==False:
04877
04878 filecount=0
04879 for node in files:
04880 filecount+=1
04881 folder=getItemTempPath(filecount)
04882
04883
04884 processFile(node,folder,filecount)
04885
04886
04887
04888
04889
04890 if wantMainMenu:
04891 createMenu(format, dpi, files.length)
04892
04893
04894 if wantChapterMenu:
04895 createChapterMenu(format, dpi, files.length)
04896
04897
04898 if wantDetailsPage:
04899 createDetailsPage(format, dpi, files.length)
04900
04901
04902 if not wantMainMenu and not wantChapterMenu:
04903 createDVDAuthorXMLNoMenus(format, files.length)
04904 elif not wantMainMenu:
04905 createDVDAuthorXMLNoMainMenu(format, files.length)
04906 else:
04907 createDVDAuthorXML(format, files.length)
04908
04909
04910 if mediatype == DVD_DL:
04911
04912 performMPEG2Shrink(files, dvdrsize[1])
04913 else:
04914
04915 performMPEG2Shrink(files, dvdrsize[0])
04916
04917 filecount=0
04918 for node in files:
04919 filecount+=1
04920 folder=getItemTempPath(filecount)
04921
04922
04923
04924 pid=multiplexMPEGStream(os.path.join(folder,'stream.mv2'),
04925 os.path.join(folder,'stream0'),
04926 os.path.join(folder,'stream1'),
04927 os.path.join(folder,'final.vob'),
04928 calcSyncOffset(filecount))
04929
04930
04931 runDVDAuthor()
04932
04933
04934 if debug_keeptempfiles==False:
04935 filecount=0
04936 for node in files:
04937 filecount+=1
04938 folder=getItemTempPath(filecount)
04939 if os.path.exists(os.path.join(folder, "stream.mv2")):
04940 os.remove(os.path.join(folder,'stream.mv2'))
04941 if os.path.exists(os.path.join(folder, "stream0.mp2")):
04942 os.remove(os.path.join(folder,'stream0.mp2'))
04943 if os.path.exists(os.path.join(folder, "stream1.mp2")):
04944 os.remove(os.path.join(folder,'stream1.mp2'))
04945 if os.path.exists(os.path.join(folder, "stream0.ac3")):
04946 os.remove(os.path.join(folder,'stream0.ac3'))
04947 if os.path.exists(os.path.join(folder, "stream1.ac3")):
04948 os.remove(os.path.join(folder,'stream1.ac3'))
04949
04950
04951
04952 infoDOM = xml.dom.minidom.parse( os.path.join(getItemTempPath(1),"info.xml") )
04953
04954 if infoDOM.documentElement.tagName != "fileinfo":
04955 fatalError("The info.xml file (%s) doesn't look right" % os.path.join(folder,"info.xml"))
04956 title = expandItemText(infoDOM,"%title",1,0,0,0,0)
04957
04958
04959 title.encode('ascii', 'replace').decode('ascii', 'replace')
04960 title.strip()
04961
04962 index = 0
04963 title_new = ''
04964 while (index < len(title)) and (index<=7):
04965 if title[index].isalnum and title[index] != ' ':
04966 title_new += title[index]
04967 else:
04968 title_new += '_'
04969 index = index + 1
04970 title = title_new.upper()
04971 if len(title) < 1:
04972 title = 'UNNAMED'
04973
04974
04975 if docreateiso == True or mediatype == FILE:
04976 CreateDVDISO(title)
04977
04978
04979 if doburn == True and mediatype != FILE:
04980 BurnDVDISO(title)
04981
04982
04983 if mediatype == FILE and savefilename != "":
04984 write("Moving ISO image to: %s" % savefilename)
04985 try:
04986 os.rename(os.path.join(getTempPath(), 'mythburn.iso'), savefilename)
04987 except:
04988 f1 = open(os.path.join(getTempPath(), 'mythburn.iso'), 'rb')
04989 f2 = open(savefilename, 'wb')
04990 data = f1.read(1024 * 1024)
04991 while data:
04992 f2.write(data)
04993 data = f1.read(1024 * 1024)
04994 f1.close()
04995 f2.close()
04996 os.unlink(os.path.join(getTempPath(), 'mythburn.iso'))
04997 else:
04998 write( "Nothing to do! (files)")
04999 else:
05000 write( "Nothing to do! (media)")
05001 return
05002
05003
05004
05005
05006 def usage():
05007 write("""
05008 -h/--help (Show this usage)
05009 -j/--jobfile file (use file as the job file)
05010 -l/--progresslog file (log file to output progress messages)
05011
05012 """)
05013
05014
05015
05016
05017 def main():
05018 global sharepath, scriptpath, cpuCount, videopath, gallerypath, musicpath
05019 global videomode, temppath, logpath, dvddrivepath, dbVersion, preferredlang1
05020 global preferredlang2, useFIFO, encodetoac3, alwaysRunMythtranscode
05021 global copyremoteFiles, mainmenuAspectRatio, chaptermenuAspectRatio, dateformat
05022 global timeformat, clearArchiveTable, nicelevel, drivespeed, path_mplex
05023 global path_dvdauthor, path_mkisofs, path_growisofs, path_M2VRequantiser, addSubtitles
05024 global path_jpeg2yuv, path_spumux, path_mpeg2enc, path_projectx, useprojectx, progresslog
05025 global progressfile, jobfile
05026
05027 write( "mythburn.py (%s) starting up..." % VERSION)
05028
05029
05030 if not hasattr(sys, "hexversion") or sys.hexversion < 0x20305F0:
05031 sys.stderr.write("Sorry, your Python is too old. Please upgrade at least to 2.3.5\n")
05032 sys.exit(1)
05033
05034
05035 scriptpath = os.path.dirname(sys.argv[0])
05036 scriptpath = os.path.abspath(scriptpath)
05037 write("script path:" + scriptpath)
05038
05039
05040 sharepath = os.path.split(scriptpath)[0]
05041 sharepath = os.path.split(sharepath)[0]
05042 write("myth share path:" + sharepath)
05043
05044
05045 try:
05046 opts, args = getopt.getopt(sys.argv[1:], "j:hl:", ["jobfile=", "help", "progresslog="])
05047 except getopt.GetoptError:
05048
05049 usage()
05050 sys.exit(2)
05051
05052 for o, a in opts:
05053 if o in ("-h", "--help"):
05054 usage()
05055 sys.exit()
05056 if o in ("-j", "--jobfile"):
05057 jobfile = str(a)
05058 write("passed job file: " + a)
05059 if o in ("-l", "--progresslog"):
05060 progresslog = str(a)
05061 write("passed progress log file: " + a)
05062
05063
05064 if progresslog != "":
05065 if os.path.exists(progresslog):
05066 os.remove(progresslog)
05067 progressfile = open(progresslog, 'w')
05068 write( "mythburn.py (%s) starting up..." % VERSION)
05069
05070
05071
05072
05073 saveSetting("MythArchiveLastRunStart", time.strftime("%Y-%m-%d %H:%M:%S "))
05074 saveSetting("MythArchiveLastRunType", "DVD")
05075 saveSetting("MythArchiveLastRunStatus", "Running")
05076
05077 cpuCount = getCPUCount()
05078
05079
05080
05081
05082 if not os.environ['PATH'].endswith(':'):
05083 os.environ['PATH'] += ":"
05084 os.environ['PATH'] += "/bin:/sbin:/usr/local/bin:/usr/bin:/opt/bin:" + installPrefix +"/bin:"
05085
05086
05087 defaultsettings = getDefaultParametersFromMythTVDB()
05088 videopath = defaultsettings.get("VideoStartupDir", None)
05089 gallerypath = defaultsettings.get("GalleryDir", None)
05090 musicpath = defaultsettings.get("MusicLocation", None)
05091 videomode = string.lower(defaultsettings["MythArchiveVideoFormat"])
05092 temppath = os.path.join(defaultsettings["MythArchiveTempDir"], "work")
05093 logpath = os.path.join(defaultsettings["MythArchiveTempDir"], "logs")
05094 write("temppath: " + temppath)
05095 write("logpath: " + logpath)
05096 dvddrivepath = defaultsettings["MythArchiveDVDLocation"]
05097 dbVersion = defaultsettings["DBSchemaVer"]
05098 preferredlang1 = defaultsettings["ISO639Language0"]
05099 preferredlang2 = defaultsettings["ISO639Language1"]
05100 useFIFO = (defaultsettings["MythArchiveUseFIFO"] == '1')
05101 alwaysRunMythtranscode = (defaultsettings["MythArchiveAlwaysUseMythTranscode"] == '1')
05102 copyremoteFiles = (defaultsettings["MythArchiveCopyRemoteFiles"] == '1')
05103 mainmenuAspectRatio = defaultsettings["MythArchiveMainMenuAR"]
05104 chaptermenuAspectRatio = defaultsettings["MythArchiveChapterMenuAR"]
05105 dateformat = defaultsettings.get("MythArchiveDateFormat", "%a %d %b %Y")
05106 timeformat = defaultsettings.get("MythArchiveTimeFormat", "%I:%M %p")
05107 drivespeed = int(defaultsettings.get("MythArchiveDriveSpeed", "0"))
05108 if "MythArchiveClearArchiveTable" in defaultsettings:
05109 clearArchiveTable = (defaultsettings["MythArchiveClearArchiveTable"] == '1')
05110 nicelevel = defaultsettings.get("JobQueueCPU", "0")
05111
05112
05113 path_mplex = [defaultsettings["MythArchiveMplexCmd"], os.path.split(defaultsettings["MythArchiveMplexCmd"])[1]]
05114 path_dvdauthor = [defaultsettings["MythArchiveDvdauthorCmd"], os.path.split(defaultsettings["MythArchiveDvdauthorCmd"])[1]]
05115 path_mkisofs = [defaultsettings["MythArchiveMkisofsCmd"], os.path.split(defaultsettings["MythArchiveMkisofsCmd"])[1]]
05116 path_growisofs = [defaultsettings["MythArchiveGrowisofsCmd"], os.path.split(defaultsettings["MythArchiveGrowisofsCmd"])[1]]
05117 path_M2VRequantiser = [defaultsettings["MythArchiveM2VRequantiserCmd"], os.path.split(defaultsettings["MythArchiveM2VRequantiserCmd"])[1]]
05118 path_jpeg2yuv = [defaultsettings["MythArchiveJpeg2yuvCmd"], os.path.split(defaultsettings["MythArchiveJpeg2yuvCmd"])[1]]
05119 path_spumux = [defaultsettings["MythArchiveSpumuxCmd"], os.path.split(defaultsettings["MythArchiveSpumuxCmd"])[1]]
05120 path_mpeg2enc = [defaultsettings["MythArchiveMpeg2encCmd"], os.path.split(defaultsettings["MythArchiveMpeg2encCmd"])[1]]
05121
05122 path_projectx = [defaultsettings["MythArchiveProjectXCmd"], os.path.split(defaultsettings["MythArchiveProjectXCmd"])[1]]
05123 useprojectx = (defaultsettings["MythArchiveUseProjectX"] == '1')
05124 addSubtitles = (defaultsettings["MythArchiveAddSubtitles"] == '1')
05125
05126
05127 if path_projectx[0] == "":
05128 useprojectx = False
05129
05130 if nicelevel == '1':
05131 nicelevel = 10
05132 elif nicelevel == '2':
05133 nicelevel = 0
05134 else:
05135 nicelevel = 17
05136
05137 nicelevel = os.nice(nicelevel)
05138 write( "Setting process priority to %s" % nicelevel)
05139
05140 import errno
05141
05142 try:
05143
05144
05145 lckpath = os.path.join(logpath, "mythburn.lck")
05146 try:
05147 fd = os.open(lckpath, os.O_WRONLY | os.O_CREAT | os.O_EXCL)
05148 try:
05149 os.write(fd, "%d\n" % os.getpid())
05150 os.close(fd)
05151 except:
05152 os.remove(lckpath)
05153 raise
05154 except OSError, e:
05155 if e.errno == errno.EEXIST:
05156 write("Lock file exists -- already running???")
05157 sys.exit(1)
05158 else:
05159 fatalError("cannot create lockfile: %s" % e)
05160
05161
05162 try:
05163
05164 jobDOM = xml.dom.minidom.parse(jobfile)
05165
05166
05167 if jobDOM.documentElement.tagName != "mythburn":
05168 fatalError("Job file doesn't look right!")
05169
05170
05171 jobcount=0
05172 jobs=jobDOM.getElementsByTagName("job")
05173 for job in jobs:
05174 jobcount+=1
05175 write( "Processing Mythburn job number %s." % jobcount)
05176
05177
05178 options = job.getElementsByTagName("options")
05179 if options.length > 0:
05180 getOptions(options)
05181
05182 processJob(job)
05183
05184 jobDOM.unlink()
05185
05186
05187 if clearArchiveTable == True:
05188 clearArchiveItems()
05189
05190 saveSetting("MythArchiveLastRunStatus", "Success")
05191 saveSetting("MythArchiveLastRunEnd", time.strftime("%Y-%m-%d %H:%M:%S "))
05192 write("Finished processing jobs!!!")
05193 finally:
05194
05195 os.remove(lckpath)
05196
05197
05198 os.system("chmod -R a+rw-x+X %s" % defaultsettings["MythArchiveTempDir"])
05199 except SystemExit:
05200 write("Terminated")
05201 except:
05202 write('-'*60)
05203 traceback.print_exc(file=sys.stdout)
05204 if progresslog != "":
05205 traceback.print_exc(file=progressfile)
05206 write('-'*60)
05207 saveSetting("MythArchiveLastRunStatus", "Failed")
05208 saveSetting("MythArchiveLastRunEnd", time.strftime("%Y-%m-%d %H:%M:%S "))
05209
05210 if __name__ == "__main__":
05211 main()