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