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 __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 = []
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
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 = []
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)
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)
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()
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()