예제 #1
0
 def __init__(self, custom_options=[]):
     """Initialize Video with a string, list, or dictionary of options."""
     # TODO: Possibly eliminate code repetition w/ Disc & Menu by adding
     # a base class and inheriting
     self.options = OptionDict(self.optiondefs)
     self.options.override(custom_options)
     self.parent = None
     self.children = []
예제 #2
0
 def __init__(self, custom_options=[]):
     """Initialize Video with a string, list, or dictionary of options."""
     # TODO: Possibly eliminate code repetition w/ Disc & Menu by adding
     # a base class and inheriting
     self.options = OptionDict(self.optiondefs)
     self.options.override(custom_options)
     self.parent = None
     self.children = []
예제 #3
0
 def __init__(self, custom_options=[]):
     """Initialize Disc with a string or list of options."""
     self.options = OptionDict(self.optiondefs)
     self.options.override(custom_options)
     self.parent = None
     self.children = []
예제 #4
0
class Disc:
    """A video disc containing video titles and optional menus."""
    # List of valid options with documentation
    optiondefs = [
        Option('out', 'NAME', None,
            """Output prefix or disc name."""),
        Option('format', 'vcd|svcd|dvd', 'dvd',
            """Create a disc of the specified format."""),
        Option('tvsys', 'pal|ntsc', 'ntsc',
            """Make the disc for the specified TV system."""),
        Option('topmenu', 'MENUNAME', None,
            """Use MENUNAME for the top-level menu on the disc.""")
    ]

    def __init__(self, custom_options=[]):
        """Initialize Disc with a string or list of options."""
        self.options = OptionDict(self.optiondefs)
        self.options.override(custom_options)
        self.parent = None
        self.children = []

    def generate(self):
        """Write dvdauthor or vcdimager XML for the element, to
        the file specified by the disc's 'out' option."""
        if self.options['format'] == 'dvd':
            xml = self.dvd_disc_xml()
        elif self.options['format'] in ['vcd', 'svcd']:
            xml = self.vcd_disc_xml()
        outfile = open(self.options['out'], 'w')
        outfile.write(xml)


    # ===========================================================
    # Disc XML generators
    
    def vcd_disc_xml(self):
        xml = """<?xml version="1.0"?>
        <!DOCTYPE videocd PUBLIC "-//GNU/DTD VideoCD//EN"
          "http://www.gnu.org/software/vcdimager/videocd.dtd">
        <videocd xmlns="http://www.gnu.org/software/vcdimager/1.0/"
        """
        # format == vcd|svcd, version=1.0 (svcd) | 2.0 (vcd)
        format = self.options['format']
        if format == 'vcd':
            version = "2.0"
        else:
            version = "1.0"
        xml += 'class="%s" version="%s">\n' % (format, version)
    
        if format == 'svcd':
            xml += '<option name="update scan offsets" value="true" />'
    
        xml += """<info>
          <album-id>VIDEO_DISC</album-id>
          <volume-count>1</volume-count>
          <volume-number>1</volume-number>
          <restriction>0</restriction>
        </info>
        <pvd>
          <volume-id>VIDEO_DISC</volume-id>
          <system-id>CD-RTOS CD-BRIDGE</system-id>
        </pvd>
        """
        # TODO:
        # segment-items
        # sequence-items
        # pbc + selections
        xml += '</videocd>'
    
    
    def dvd_disc_xml(self):
        """Return a string containing dvdauthor XML for this disc."""
        xml = '<dvdauthor dest="%s">\n' % self.name.replace(' ', '_')
        xml += '<vmgm>\n'
        # If there's a topmenu, write vmgm-level XML for it
        if len(self.children) == 1:
            topmenu = self.children[0]
            xml += '  <menus>\n'
            xml += '    <video />\n'
            xml += '    <pgc entry="title">\n'
            xml += '      <vob file="%s" />\n' % topmenu['out']
            # TODO: Add buttons for each submenu
            # <button>jump titleset N menu;</button>
            num = 1
            for submenus in topmenu.children:
                xml += '      <button>jump titleset %d menu;</button>\n' % num
                num += 1
            xml += '    </pgc>\n'
    
        xml += '</vmgm>\n'
        # TODO: add titlesets for each submenu
        for menu in topmenu.children:
            xml += '<titleset>\n'
            xml += self.dvd_menu_xml(menu)
            for video in menu.children:
                xml += self.dvd_video_xml(video)
            xml += '</titleset>\n'
            
        xml += '</dvdauthor>\n'
        return xml
    
    
    # ===========================================================
    # Menu XML generators
    
    def dvd_menu_xml(self, menu):
        """Return a string containing dvdauthor XML for the given Menu."""
        xml = '<menus>\n'
        xml += '  <video />\n'
        xml += '  <pgc entry="root">\n'
        xml += '  <vob file="%s" />\n' % menu['out']
        # For each child ('titles' target), add a button
        num = 1
        for target in menu.children:
            xml += '    <button>jump title %d;</button>\n' % num
            num += 1
        xml += '    <button>jump vmgm menu;</button>\n'
        xml += '  </pgc>\n'
        xml += '</menus>\n'
        return xml
    
    
    # ===========================================================
    # Video XML generators
    
    def dvd_video_xml(self, video):
        """Return a string containing dvdauthor XML for the given Video."""
    
        chap_str = '0'
        for chap in video['chapters']:
            chap_str += ',' + chap
    
        xml = '  <pgc>\n'
        xml += '    <vob file="%s" chapters="%s" />\n' % \
                (video['out'], chap_str)
        xml += '    <post>call menu;</post>\n'
        xml += '  </pgc>\n'
        return xml
예제 #5
0
 def __init__(self, custom_options=[]):
     """Initialize Menu with a string or list of options."""
     self.options = OptionDict(self.optiondefs)
     self.options.override(custom_options)
     self.parent = None
     self.children = []
예제 #6
0
class Menu:
    """A menu for navigating the titles on a video disc.

    Menus are generated based on a collection of user-settable options. These
    define the target format, and titles to be listed on the menu, among other
    things such as font, color, and background.
    
    The primary output from a Menu is an .mpg video file suitable for use as
    a video disc navigational menu.
    """
    # Dictionary of valid options with documentation
    optiondefs = [
        Option('out', 'NAME', None, """Output prefix or menu name."""),
        Option(
            'titles', '"TITLE" [, "TITLE"]', [],
            """Comma-separated list of quoted titles; these are the titles that
            will be displayed (and linked) from the menu."""),
        Option('format', 'vcd|svcd|dvd', 'dvd',
               """Generate a menu compliant with the specified disc format"""),
        Option('tvsys', 'pal|ntsc', 'ntsc',
               """Make the menu for the specified TV system"""),
        Option('background', 'IMAGE', None,
               """Use IMAGE (in most any graphic format) as a background."""),
        Option('audio', 'AUDIOFILE', None,
               """Use AUDIOFILE for background music while the menu plays."""),
        Option('font', 'FONTNAME', 'Helvetica',
               """Use FONTNAME for the menu text."""),
        Option('fontsize', 'NUM', '24', """Use a font size of NUM pixels."""),
        Option('align', 'west|north|east|south|center', 'northwest'),
        Option(
            'textcolor', 'COLOR', 'white',
            """Color of menu text. COLOR may be a hexadecimal triplet (#RRGGBB or
            #RGB), or a color name from 'convert -list color."""),
        Option('highlightcolor', 'COLOR', 'red',
               """Color of menu highlights."""),
        Option('selectcolor', 'COLOR', 'green',
               """Color of menu selections."""),

        # Thumbnail menus and effects
        Option(
            'thumbnails', 'FILE [, FILE ...]', [],
            """Create thumbnails of the provided list of video files, which
            should correspond to the given -titles list."""),
        Option(
            'choices', '[list|thumbnails]', 'list',
            """Display links as a list of titles, or as a grid of labeled
                thumbnail videos."""),
        Option('border', 'NUM', '0',
               """Add a border of NUM pixels around thumbnails."""),
        Option('effects', 'shadow|round|glass [, ...]', [],
               """Add the listed effects to the thumbnails.""")
    ]

    def __init__(self, custom_options=[]):
        """Initialize Menu with a string or list of options."""
        self.options = OptionDict(self.optiondefs)
        self.options.override(custom_options)
        self.parent = None
        self.children = []

    def preproc(self):
        if self.options['format'] == 'dvd':
            width = 720
            samprate = 48000
            if self.options['tvsys'] == 'ntsc':
                height = 480
            else:
                height = 576
        else:
            width = 352
            samprate = 44100
            if self.options['tvsys'] == 'ntsc':
                height = 240
            else:
                height = 288

        # Make sure number of thumbs and titles match
        if self.options['thumbnails']:
            numthumbs = len(self.options['thumbnails'])
            numtitles = len(self.options['titles'])
            if numthumbs > numtitles:
                log.error('More thumbnails than titles!')
            elif numthumbs < numtitles:
                log.error('More titles than thumbnails!')

        self.options['samprate'] = samprate
        # TODO: Proper safe area. Hardcoded to 90% for now.
        self.options['expand'] = (width, height)
        self.options['scale'] = (int(width * 0.9), int(height * 0.9))

    def generate(self):
        """Generate the element, and return the filename of the
        resulting menu."""
        self.preproc()
        # TODO: Raise exceptions
        # Generate a menu of the appropriate format
        if self.options['thumbnails']:
            log.info('Generating a menu with thumbnail videos...')
            thumbmenu.generate(self.options)
        else:
            log.info('Generating a DVD menu with text titles...')
            textmenu.generate(self.options)
예제 #7
0
class Disc:
    """A video disc containing video titles and optional menus."""
    # List of valid options with documentation
    optiondefs = [
        Option('out', 'NAME', None, """Output prefix or disc name."""),
        Option('format', 'vcd|svcd|dvd', 'dvd',
               """Create a disc of the specified format."""),
        Option('tvsys', 'pal|ntsc', 'ntsc',
               """Make the disc for the specified TV system."""),
        Option('topmenu', 'MENUNAME', None,
               """Use MENUNAME for the top-level menu on the disc.""")
    ]

    def __init__(self, custom_options=[]):
        """Initialize Disc with a string or list of options."""
        self.options = OptionDict(self.optiondefs)
        self.options.override(custom_options)
        self.parent = None
        self.children = []

    def generate(self):
        """Write dvdauthor or vcdimager XML for the element, to
        the file specified by the disc's 'out' option."""
        if self.options['format'] == 'dvd':
            xml = self.dvd_disc_xml()
        elif self.options['format'] in ['vcd', 'svcd']:
            xml = self.vcd_disc_xml()
        outfile = open(self.options['out'], 'w')
        outfile.write(xml)

    # ===========================================================
    # Disc XML generators

    def vcd_disc_xml(self):
        xml = """<?xml version="1.0"?>
        <!DOCTYPE videocd PUBLIC "-//GNU/DTD VideoCD//EN"
          "http://www.gnu.org/software/vcdimager/videocd.dtd">
        <videocd xmlns="http://www.gnu.org/software/vcdimager/1.0/"
        """
        # format == vcd|svcd, version=1.0 (svcd) | 2.0 (vcd)
        format = self.options['format']
        if format == 'vcd':
            version = "2.0"
        else:
            version = "1.0"
        xml += 'class="%s" version="%s">\n' % (format, version)

        if format == 'svcd':
            xml += '<option name="update scan offsets" value="true" />'

        xml += """<info>
          <album-id>VIDEO_DISC</album-id>
          <volume-count>1</volume-count>
          <volume-number>1</volume-number>
          <restriction>0</restriction>
        </info>
        <pvd>
          <volume-id>VIDEO_DISC</volume-id>
          <system-id>CD-RTOS CD-BRIDGE</system-id>
        </pvd>
        """
        # TODO:
        # segment-items
        # sequence-items
        # pbc + selections
        xml += '</videocd>'

    def dvd_disc_xml(self):
        """Return a string containing dvdauthor XML for this disc."""
        xml = '<dvdauthor dest="%s">\n' % self.name.replace(' ', '_')
        xml += '<vmgm>\n'
        # If there's a topmenu, write vmgm-level XML for it
        if len(self.children) == 1:
            topmenu = self.children[0]
            xml += '  <menus>\n'
            xml += '    <video />\n'
            xml += '    <pgc entry="title">\n'
            xml += '      <vob file="%s" />\n' % topmenu['out']
            # TODO: Add buttons for each submenu
            # <button>jump titleset N menu;</button>
            num = 1
            for submenus in topmenu.children:
                xml += '      <button>jump titleset %d menu;</button>\n' % num
                num += 1
            xml += '    </pgc>\n'

        xml += '</vmgm>\n'
        # TODO: add titlesets for each submenu
        for menu in topmenu.children:
            xml += '<titleset>\n'
            xml += self.dvd_menu_xml(menu)
            for video in menu.children:
                xml += self.dvd_video_xml(video)
            xml += '</titleset>\n'

        xml += '</dvdauthor>\n'
        return xml

    # ===========================================================
    # Menu XML generators

    def dvd_menu_xml(self, menu):
        """Return a string containing dvdauthor XML for the given Menu."""
        xml = '<menus>\n'
        xml += '  <video />\n'
        xml += '  <pgc entry="root">\n'
        xml += '  <vob file="%s" />\n' % menu['out']
        # For each child ('titles' target), add a button
        num = 1
        for target in menu.children:
            xml += '    <button>jump title %d;</button>\n' % num
            num += 1
        xml += '    <button>jump vmgm menu;</button>\n'
        xml += '  </pgc>\n'
        xml += '</menus>\n'
        return xml

    # ===========================================================
    # Video XML generators

    def dvd_video_xml(self, video):
        """Return a string containing dvdauthor XML for the given Video."""

        chap_str = '0'
        for chap in video['chapters']:
            chap_str += ',' + chap

        xml = '  <pgc>\n'
        xml += '    <vob file="%s" chapters="%s" />\n' % \
                (video['out'], chap_str)
        xml += '    <post>call menu;</post>\n'
        xml += '  </pgc>\n'
        return xml
예제 #8
0
class Menu:
    """A menu for navigating the titles on a video disc.

    Menus are generated based on a collection of user-settable options. These
    define the target format, and titles to be listed on the menu, among other
    things such as font, color, and background.
    
    The primary output from a Menu is an .mpg video file suitable for use as
    a video disc navigational menu.
    """
    # Dictionary of valid options with documentation
    optiondefs = [
        Option('out', 'NAME', None,
            """Output prefix or menu name."""),

        Option('titles', '"TITLE" [, "TITLE"]', [],
            """Comma-separated list of quoted titles; these are the titles that
            will be displayed (and linked) from the menu."""),

        Option('format', 'vcd|svcd|dvd', 'dvd',
            """Generate a menu compliant with the specified disc format"""),
        Option('tvsys', 'pal|ntsc', 'ntsc',
            """Make the menu for the specified TV system"""),
        Option('background', 'IMAGE', None,
            """Use IMAGE (in most any graphic format) as a background."""),
        Option('audio', 'AUDIOFILE', None,
            """Use AUDIOFILE for background music while the menu plays."""),
        Option('font', 'FONTNAME', 'Helvetica',
            """Use FONTNAME for the menu text."""),
        Option('fontsize', 'NUM', '24',
            """Use a font size of NUM pixels."""),
        Option('align', 'west|north|east|south|center', 'northwest'),
        Option('textcolor', 'COLOR', 'white',
            """Color of menu text. COLOR may be a hexadecimal triplet (#RRGGBB or
            #RGB), or a color name from 'convert -list color."""),
        Option('highlightcolor', 'COLOR', 'red',
            """Color of menu highlights."""),
        Option('selectcolor', 'COLOR', 'green',
            """Color of menu selections."""),

        # Thumbnail menus and effects
        Option('thumbnails', 'FILE [, FILE ...]', [],
            """Create thumbnails of the provided list of video files, which
            should correspond to the given -titles list."""),
        Option('choices', '[list|thumbnails]', 'list',
                """Display links as a list of titles, or as a grid of labeled
                thumbnail videos."""),
        Option('border', 'NUM', '0',
                """Add a border of NUM pixels around thumbnails."""),
        Option('effects', 'shadow|round|glass [, ...]', [],
                """Add the listed effects to the thumbnails.""")
    ]

    def __init__(self, custom_options=[]):
        """Initialize Menu with a string or list of options."""
        self.options = OptionDict(self.optiondefs)
        self.options.override(custom_options)
        self.parent = None
        self.children = []

    def preproc(self):
        if self.options['format'] == 'dvd':
            width = 720
            samprate = 48000
            if self.options['tvsys'] == 'ntsc':
                height = 480
            else:
                height = 576
        else:
            width = 352
            samprate = 44100
            if self.options['tvsys'] == 'ntsc':
                height = 240
            else:
                height = 288

        # Make sure number of thumbs and titles match
        if self.options['thumbnails']:
            numthumbs = len(self.options['thumbnails'])
            numtitles = len(self.options['titles'])
            if numthumbs > numtitles:
                log.error('More thumbnails than titles!')
            elif numthumbs < numtitles:
                log.error('More titles than thumbnails!')

        self.options['samprate'] = samprate
        # TODO: Proper safe area. Hardcoded to 90% for now.
        self.options['expand'] = (width, height)
        self.options['scale'] = (int(width * 0.9), int(height * 0.9))

    def generate(self):
        """Generate the element, and return the filename of the
        resulting menu."""
        self.preproc()
        # TODO: Raise exceptions
        # Generate a menu of the appropriate format
        if self.options['thumbnails']:
            log.info('Generating a menu with thumbnail videos...')
            thumbmenu.generate(self.options)
        else:
            log.info('Generating a DVD menu with text titles...')
            textmenu.generate(self.options)
예제 #9
0
class Video:
    """A video title for (optional) inclusion on a video disc.

    Encapsulates all user-configurable options, including the input file,
    target format and TV system, and any other argument that may be passed to
    the 'pytovid' command-line script.
    """
    # Dictionary of valid options with documentation
    optiondefs = [
        Option('in', 'FILENAME', None, """Input video file, in any format."""),
        Option('out', 'NAME', None, """Output prefix or name."""),

        # New options to (eventually) replace -vcd, -pal etc.
        Option('format', 'vcd|svcd|dvd|half-dvd|dvd-vcd', 'dvd',
               """Make video compliant with the specified format"""),
        Option('tvsys', 'ntsc|pal', 'ntsc',
               """Make the video compliant with the specified TV system"""),
        Option(
            'filters', 'denoise|contrast|deblock[, ...]', [],
            """Apply the given filters to the video before or during
            encoding."""),
        Option('vcd', '', False, alias=('format', 'vcd')),
        Option('svcd', '', False, alias=('format', 'svcd')),
        Option('dvd', '', False, alias=('format', 'dvd')),
        Option('half-dvd', '', False, alias=('format', 'half-dvd')),
        Option('dvd-vcd', '', False, alias=('format', 'dvd-vcd')),
        Option('ntsc', '', False, alias=('tvsys', 'ntsc')),
        Option('ntscfilm', '', False),
        Option('pal', '', False, alias=('tvsys', 'pal')),
        Option(
            'aspect', 'WIDTH:HEIGHT', "4:3",
            """Force the input video to be the given aspect ratio, where WIDTH
            and HEIGHT are integers."""),
        Option(
            'quality', '[1-10]', 8,
            """Desired output quality, on a scale of 1 to 10, with 10 giving
            the best quality at the expense of a larger output file. Output
            size can vary by approximately a factor of 4 (that is, -quality 1
            output can be 25% the size of -quality 10 output). Your results may
            vary."""),
        Option(
            'vbitrate', '[0-9800]', None,
            """Maximum bitrate to use for video (in kbits/sec). Must be within
            allowable limits for the given format. Overrides default values.
            Ignored for VCD."""),
        Option(
            'abitrate', '[0-1536]', None,
            """Encode audio at NUM kilobits per second.  Reasonable values
            include 128, 224, and 384. The default is 224 kbits/sec, good
            enough for most encodings. The value must be within the allowable
            range for the chosen disc format; Ignored for VCD, which must be
            224."""),
        Option(
            'safe', '[0-100]%', "100%",
            """Fit the video within a safe area defined by PERCENT. For
            example, '-safe 90%' will scale the video to 90% of the
            width/height of the output resolution, and pad the edges with a
            black border. Use this if some of the picture is cut off when
            played on your TV."""),
        Option(
            'interlaced', '', False,
            """Do interlaced encoding of the input video. Use this option if
            your video is interlaced, and you want to preserve as much picture
            quality as possible. Ignored for VCD."""),
        Option(
            'deinterlace', '', False,
            """Use this option if your source video is interlaced. You can
            usually tell if you can see a bunch of horizontal lines when you
            pause the video during playback. If you have recorded a video from
            TV or your VCR, it may be interlaced. Use this option to convert to
            progressive (non-interlaced) video. This option is DEPRECATED, and
            will probably be ditched in favor of interlaced encoding, which is
            better in almost every way."""),
        Option(
            'subtitles', 'FILE', None,
            """Get subtitles from FILE and encode them into the video.
            WARNING: This hard-codes the subtitles into the video, and you
            cannot turn them off while viewing the video. By default, no
            subtitles are loaded. If your video is already compliant with the
            chosen output format, it will be re-encoded to include the
            subtitles."""),
        Option(
            'normalize', '', False,
            """Normalize the volume of the audio. Useful if the audio is too
            quiet or too loud, or you want to make volume consistent for a
            bunch of videos."""),
        Option(
            'method', 'mpeg2enc|mencoder|ffmpeg', 'mencoder',
            """Encode using the given tool. The mpeg2enc method uses mplayer to
            rip the audio and video, and mpeg2enc to encode the video. The
            mencoder and ffmpeg methods do all encoding with the respective
            tool.""")
    ]

    def __init__(self, custom_options=[]):
        """Initialize Video with a string, list, or dictionary of options."""
        # TODO: Possibly eliminate code repetition w/ Disc & Menu by adding
        # a base class and inheriting
        self.options = OptionDict(self.optiondefs)
        self.options.override(custom_options)
        self.parent = None
        self.children = []

    def preproc(self):
        """Do preprocessing common to all backends."""
        self.infile = MultimediaFile(self.options['in'])

        width, height = get_resolution(self.options['format'],
                                       self.options['tvsys'])
        # Convert aspect (ratio) to a floating-point value
        src_aspect = ratio_to_float(self.options['aspect'])
        # Use anamorphic widescreen for any video 16:9 or wider
        # (Only DVD supports this)
        if src_aspect >= 1.7 and self.options['format'] == 'dvd':
            target_aspect = 16.0 / 9.0
            widescreen = True
        else:
            target_aspect = 4.0 / 3.0
            widescreen = False
        # If aspect matches target, no letterboxing is necessary
        # (Match within a tolerance of 0.05)
        if abs(src_aspect - target_aspect) < 0.05:
            scale = (width, height)
            expand = False
        # If aspect is wider than target, letterbox vertically
        elif src_aspect > target_aspect:
            scale = (width, height * dvd_aspect / src_aspect)
            expand = (width, height)
        # Otherwise (rare), letterbox horizontally
        else:
            scale = (width * src_aspect / dvd_aspect, height)
            expand = (width, height)
        # If infile is already the correct size, don't scale
        if self.infile.video:
            in_res = (self.infile.video.spec['width'],
                      self.infile.video.spec['height'])
            if in_res == scale:
                scale = False
                log.debug('Infile resolution matches target resolution.')
                log.debug('No scaling will be done.')
        # TODO: Calculate safe area
        # Other commonly-used values
        if 'dvd' in self.options['format']:
            samprate = 48000
        else:
            samprate = 44100
        if self.options['tvsys'] == 'pal':
            fps = 25.00
        elif self.options['tvsys'] == 'ntsc':
            fps = 29.97

        # Set audio/video bitrates based on target format, quality, or
        # user-defined values (if given)
        vbitrate = self.options['vbitrate']
        abitrate = self.options['abitrate']
        # Audio and video bitrates
        if self.options['format'] == 'vcd':
            abitrate = 224
            vbitrate = 1152
        else:
            # If user didn't override, use reasonable defaults
            if not vbitrate:
                # TODO: Adjust bitrate based on -quality
                if self.options['format'] in ['svcd', 'dvd-vcd']:
                    vbitrate = 2600
                else:
                    vbitrate = 7000
            if not abitrate:
                abitrate = 224

        # Add .mpg to outfile if not already present
        if not self.options['out'].endswith('.mpg'):
            self.options['out'] += '.mpg'

        # Set options for use by the encoder backends
        self.options['abitrate'] = abitrate
        self.options['vbitrate'] = vbitrate
        self.options['scale'] = scale
        self.options['expand'] = expand
        self.options['samprate'] = samprate
        self.options['fps'] = fps
        self.options['widescreen'] = widescreen

    def generate(self):
        """Generate a video element by encoding an input file to a target
        standard."""
        self.preproc()
        method = self.options['method']
        if method == 'mpeg2enc':
            log.info("generate(): Encoding with mpeg2enc")
            mpeg2enc.encode(self.infile, self.options)
        elif method == 'mencoder':
            log.info("generate(): Encoding with mencoder")
            mencoder.encode(self.infile, self.options)
        elif method == 'ffmpeg':
            log.info("generate(): Encoding with ffmpeg")
            ffmpeg.encode(self.infile, self.options)
        else:
            log.info("The '%s' encoder is not yet supported." % method)
            log.info("Perhaps you'd like to write a backend for it? :-)")
            sys.exit()
예제 #10
0
class Video:
    """A video title for (optional) inclusion on a video disc.

    Encapsulates all user-configurable options, including the input file,
    target format and TV system, and any other argument that may be passed to
    the 'pytovid' command-line script.
    """
    # Dictionary of valid options with documentation
    optiondefs = [
        Option('in', 'FILENAME', None,
            """Input video file, in any format."""),
        Option('out', 'NAME', None,
            """Output prefix or name."""),

        # New options to (eventually) replace -vcd, -pal etc.
        Option('format', 'vcd|svcd|dvd|half-dvd|dvd-vcd', 'dvd',
            """Make video compliant with the specified format"""),
        Option('tvsys', 'ntsc|pal', 'ntsc',
            """Make the video compliant with the specified TV system"""),
        Option('filters', 'denoise|contrast|deblock[, ...]', [],
            """Apply the given filters to the video before or during
            encoding."""),
            
        Option('vcd', '', False, alias=('format', 'vcd')),
        Option('svcd', '', False, alias=('format', 'svcd')),
        Option('dvd', '', False, alias=('format', 'dvd')),
        Option('half-dvd', '', False, alias=('format', 'half-dvd')),
        Option('dvd-vcd', '', False, alias=('format', 'dvd-vcd')),
        Option('ntsc', '', False, alias=('tvsys', 'ntsc')),
        Option('ntscfilm', '', False),
        Option('pal', '', False, alias=('tvsys', 'pal')),

        Option('aspect', 'WIDTH:HEIGHT', "4:3",
            """Force the input video to be the given aspect ratio, where WIDTH
            and HEIGHT are integers."""),
        Option('quality', '[1-10]', 8,
            """Desired output quality, on a scale of 1 to 10, with 10 giving
            the best quality at the expense of a larger output file. Output
            size can vary by approximately a factor of 4 (that is, -quality 1
            output can be 25% the size of -quality 10 output). Your results may
            vary."""),
        Option('vbitrate', '[0-9800]', None,
            """Maximum bitrate to use for video (in kbits/sec). Must be within
            allowable limits for the given format. Overrides default values.
            Ignored for VCD."""),
        Option('abitrate', '[0-1536]', None,
            """Encode audio at NUM kilobits per second.  Reasonable values
            include 128, 224, and 384. The default is 224 kbits/sec, good
            enough for most encodings. The value must be within the allowable
            range for the chosen disc format; Ignored for VCD, which must be
            224."""),
        Option('safe', '[0-100]%', "100%",
            """Fit the video within a safe area defined by PERCENT. For
            example, '-safe 90%' will scale the video to 90% of the
            width/height of the output resolution, and pad the edges with a
            black border. Use this if some of the picture is cut off when
            played on your TV."""),
        Option('interlaced', '', False,
            """Do interlaced encoding of the input video. Use this option if
            your video is interlaced, and you want to preserve as much picture
            quality as possible. Ignored for VCD."""),
        Option('deinterlace', '', False,
            """Use this option if your source video is interlaced. You can
            usually tell if you can see a bunch of horizontal lines when you
            pause the video during playback. If you have recorded a video from
            TV or your VCR, it may be interlaced. Use this option to convert to
            progressive (non-interlaced) video. This option is DEPRECATED, and
            will probably be ditched in favor of interlaced encoding, which is
            better in almost every way."""),
        Option('subtitles', 'FILE', None,
            """Get subtitles from FILE and encode them into the video.
            WARNING: This hard-codes the subtitles into the video, and you
            cannot turn them off while viewing the video. By default, no
            subtitles are loaded. If your video is already compliant with the
            chosen output format, it will be re-encoded to include the
            subtitles."""),
        Option('normalize', '', False,
            """Normalize the volume of the audio. Useful if the audio is too
            quiet or too loud, or you want to make volume consistent for a
            bunch of videos."""),
        Option('method', 'mpeg2enc|mencoder|ffmpeg', 'mencoder',
            """Encode using the given tool. The mpeg2enc method uses mplayer to
            rip the audio and video, and mpeg2enc to encode the video. The
            mencoder and ffmpeg methods do all encoding with the respective
            tool.""")
    ]

    def __init__(self, custom_options=[]):
        """Initialize Video with a string, list, or dictionary of options."""
        # TODO: Possibly eliminate code repetition w/ Disc & Menu by adding
        # a base class and inheriting
        self.options = OptionDict(self.optiondefs)
        self.options.override(custom_options)
        self.parent = None
        self.children = []


    def preproc(self):
        """Do preprocessing common to all backends."""
        self.infile = MultimediaFile(self.options['in'])
        
        width, height = get_resolution(self.options['format'], self.options['tvsys'])
        # Convert aspect (ratio) to a floating-point value
        src_aspect = ratio_to_float(self.options['aspect'])
        # Use anamorphic widescreen for any video 16:9 or wider
        # (Only DVD supports this)
        if src_aspect >= 1.7 and self.options['format'] == 'dvd':
            target_aspect = 16.0/9.0
            widescreen = True
        else:
            target_aspect = 4.0/3.0
            widescreen = False
        # If aspect matches target, no letterboxing is necessary
        # (Match within a tolerance of 0.05)
        if abs(src_aspect - target_aspect) < 0.05:
            scale = (width, height)
            expand = False
        # If aspect is wider than target, letterbox vertically
        elif src_aspect > target_aspect:
            scale = (width, height * dvd_aspect / src_aspect)
            expand = (width, height)
        # Otherwise (rare), letterbox horizontally
        else:
            scale = (width * src_aspect / dvd_aspect, height)
            expand = (width, height)
        # If infile is already the correct size, don't scale
        if self.infile.video:
            in_res = (self.infile.video.spec['width'],
                      self.infile.video.spec['height'])
            if in_res == scale:
                scale = False
                log.debug('Infile resolution matches target resolution.')
                log.debug('No scaling will be done.')
        # TODO: Calculate safe area
        # Other commonly-used values
        if 'dvd' in self.options['format']:
            samprate = 48000
        else:
            samprate = 44100
        if self.options['tvsys'] == 'pal':
            fps = 25.00
        elif self.options['tvsys'] == 'ntsc':
            fps = 29.97
        
        # Set audio/video bitrates based on target format, quality, or
        # user-defined values (if given)
        vbitrate = self.options['vbitrate']
        abitrate = self.options['abitrate']
        # Audio and video bitrates
        if self.options['format'] == 'vcd':
            abitrate = 224
            vbitrate = 1152
        else:
            # If user didn't override, use reasonable defaults
            if not vbitrate:
                # TODO: Adjust bitrate based on -quality
                if self.options['format'] in ['svcd', 'dvd-vcd']:
                    vbitrate = 2600
                else:
                    vbitrate = 7000
            if not abitrate:
                abitrate = 224

        # Add .mpg to outfile if not already present
        if not self.options['out'].endswith('.mpg'):
            self.options['out'] += '.mpg'

        # Set options for use by the encoder backends
        self.options['abitrate'] = abitrate
        self.options['vbitrate'] = vbitrate
        self.options['scale'] = scale
        self.options['expand'] = expand
        self.options['samprate'] = samprate
        self.options['fps'] = fps
        self.options['widescreen'] = widescreen

    def generate(self):
        """Generate a video element by encoding an input file to a target
        standard."""
        self.preproc()
        method = self.options['method']
        if method == 'mpeg2enc':
            log.info("generate(): Encoding with mpeg2enc")
            mpeg2enc.encode(self.infile, self.options)
        elif method == 'mencoder':
            log.info("generate(): Encoding with mencoder")
            mencoder.encode(self.infile, self.options)
        elif method == 'ffmpeg':
            log.info("generate(): Encoding with ffmpeg")
            ffmpeg.encode(self.infile, self.options)
        else:
            log.info("The '%s' encoder is not yet supported." % method)
            log.info("Perhaps you'd like to write a backend for it? :-)")
            sys.exit()