#!/usr/bin/python # Copyright 2003 Tom Rothamel <tom-potw@rothamel.us> # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation files # (the "Software"), to deal in the Software without restriction, # including without limitation the rights to use, copy, modify, merge, # publish, distribute, sublicense, and/or sell copies of the Software, # and to permit persons to whom the Software is furnished to do so, # subject to the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. import Image import ImageDraw import ImageFont import dvdlib import optparse import os import os.path import signal import sys def system(cmd): """ Runs a command, aborting execution if the command fails. @param cmd: The command line, a string. """ rv = os.system(cmd) if rv: print "Aborting, subcommand returned", rv sys.exit(1) def group_into_menus(): """ Takes the _DVDItems in dvdlib._allitems, and groups them into menus. @return: A list of lists of _DVDItems, with each list corresponding to a menu. """ menu = [] menus = [] for i in dvdlib._allitems: if i.menu_delimiter(): menus.append(menu) menu = [] else: menu.append(i) if len(menu): menus.append(menu) return menus class MenuDraw(object): """ This class is responsible for making menus. An instance of this object is passed into the L{draw} method of every element of dvdlib, which then calls methods on the objects reachable from here to draw the menus, and add information about the buttons. After the menus have been drawn, the generate function is used to generate the menu movie. @ivar background: The background image. @ivar normal: The menu image, in normal state. @ivar higlighted: The menu image, in hilighted state. @ivar selected: The menu image, in selected state. @ivar bd, nd, hd, sd: Drawing contexts for background, normal, higlighted, and selected images. @ivar crx: The x location the cursor will return to. @ivar x: The current x location. @ivar y: The current y location. @ivar background_audio: The audio file that backs the menu. @ivar frames: The number of frames of menu to write. @ivar button: Serial numbers used to allocate new buttons. @ivar buttons: A tuple (x0, y0, x1, y1, label), representing the location of each labelled button. """ def __init__(self): self.background = Image.new("RGB", (704, 480)) self.normal = Image.new("RGB", (704, 480)) self.highlighted = Image.new("RGB", (704, 480)) self.selected = Image.new("RGB", (704, 480)) self.bd = ImageDraw.Draw(self.background) self.nd = ImageDraw.Draw(self.normal) self.hd = ImageDraw.Draw(self.highlighted) self.sd = ImageDraw.Draw(self.selected) self.crx = 0 self.x = 0 self.y = 0 self.background_audio = "background.mp2" self.frames = 1 # 30 * 4 self.button = 1 self.buttons = [] def generate(self, prefix): """ This creates the menu mpeg. @param prefix: The created menu will be named prefix + '.mpg'. Other files created will also begin with prefix. """ # Save the various images. self.background.save(prefix + "_background.png") self.normal.quantize(4).save(prefix + "_normal.png") self.highlighted.quantize(4).save(prefix + "_highlighted.png") self.selected.quantize(4).save(prefix + "_selected.png") # Make a movie file. dvdlib._image_to_mpeg(prefix + "_background.png", self.frames, self.background_audio, prefix + "_nosub") # Write out the subtitle file. f = file("%s.xml" % prefix, "w") print >>f, "<subpictures>" print >>f, "<stream>" print >>f, '<spu start="00:00:00.00" image="%s_normal.png" ' \ 'highlight="%s_highlighted.png" select="%s_selected.png" ' \ 'force="yes" transparent="000000">' \ % ( prefix, prefix, prefix ) for x0, y0, x1, y1, label in self.buttons: print >>f, '<button x0="%d" y0="%d" x1="%d" y1="%d" label="%d" />' \ % (x0, y0, x1, y1, label) print >>f, "</spu>" print >>f, "</stream>" print >>f, "</subpictures>" f.close() # Multiplex in the subtitles. system("spumux -P %s.xml < %s_nosub.mpg > %s.mpg" % (prefix, prefix, prefix)) def process_menus(emit): """ Emit the dvdauthor xml for the menus. @param emit: The file to emit the xml to. """ emit('<vmgm>') emit('<menus>') menus = group_into_menus() for index, m in enumerate(menus): # We count from 1 in the DVD-universe. index += 1 # Chain actions in the menu. act = None for i in m: act = i.set_action(act) # Ask things in the menu to draw themselves. md = MenuDraw() for i in m: i.draw(md) # Generate the mpg. md.generate("menu%d" % index) # Write out the entry for this menu. emit('<pgc>') emit('<vob file="menu%d.mpg" />' % index) for i in m: i.button_xml(emit) emit('</pgc>') emit('</menus>') emit('</vmgm>') def process_titles(emit): """ This emits the dvdauthor xml for all the titles. @param emit: The file the xml will be written to. """ # Emit the xml for all the titles. for i in dvdlib._allitems: i.title_xml(emit) def main(): # Parse the options. op = optparse.OptionParser() op.add_option("--fast", action="store_true", dest="fast", default=False, help="Do not process the titles, only the menus.") op.add_option("--noencode", action="store_false", dest="encode", default=True, help="Do not recompress mpeg files for menus.") global options options, args = op.parse_args() dvdlib._options = options if len(args) < 1: op.error("Not enough arguments.") if os.path.exists("dvd") and not options.fast: op.error("The dvd/ directory exists and --fast not given. Remove it and try again.") menufile = file(args[0], "r") # Place all of the stuff we want to export to the menu code into # a context dict. context = {} for k in dir(dvdlib): if 'a' < k[0] < 'z': context[k] = dvdlib.__dict__[k] # Run the menu. exec menufile in context menufile.close() # Fix the gorram python SIGPIPE handling. signal.signal(signal.SIGPIPE, signal.SIG_DFL) # Write dvd.xml. dvdxml = file("dvd.xml", "w") def emit(text, *args): print >>dvdxml, text % args # Write out the dvdauthor xml file. emit('<dvdauthor dest="dvd/" jumppad="on">') process_menus(emit) if not options.fast: process_titles(emit) emit('</dvdauthor>') dvdxml.close() # Call dvdauthor to create the dvd image. system("dvdauthor -x dvd.xml") system("cp '%s' dvd/" % args[0]) if __name__ == "__main__": main()