#!/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()