Commits

boralyl committed 9c9ad7b

Initial import

Comments (0)

Files changed (9)

+syntax: glob
+*.kate-swp
+*.pyc
+*.swp
+#!/usr/bin/python
+from mkiso.mkiso import main
+
+
+main()

mkiso/__init__.py

Empty file added.
+#!/usr/bin/python
+"""
+Utility script to automate the process of converting multiple video files
+in different formats into a DVD ISO with chapters specified by the -c or --chapters
+switch (defaults to 10 minutes).
+
+TODO: What other command line args are useful?  Maybe a switch to remove
+      converted mpegs
+"""
+import argparse
+import os
+import re
+import shutil
+import subprocess
+
+from utils import get_chapters, parse_args, run_cmd
+from videotypes import AviFile, InvalidVideoFormatError, Mp4File, MkvFile
+
+
+VALID_EXTENSIONS = ('avi', 'mkv', 'mpg', 'mp4')
+
+
+class ISOCreator(object):
+    """
+    Class for converting videos into DVD ISOs
+    """
+
+    def __init__(self, video_files, output_file, verbose, chapters):
+        self.video_files = video_files
+        self.output_file = output_file
+        self.converted_videos = []
+        self.verbose = verbose
+        self.chapters = chapters
+        os.putenv("VIDEO_FORMAT", "NTSC")
+
+    def convert_video(self, video):
+        """
+        Converts videos into DVD MPEG2 formats
+        """
+        if self.verbose:
+            print "Converting %s to MPEG2..." % (video, )
+        ext = video.split('.')[-1]
+        mpg_file = ".".join(video.split('.')[:-1]) + ".mpg"
+        if ext not in VALID_EXTENSIONS:
+            raise InvalidVideoFormatError(video)
+        elif ext == 'avi':
+            avi = AviFile(video)
+            success = avi.convert()
+            if success:
+                self.converted_videos.append(mpg_file)
+        elif ext == 'mkv':
+            mkv = MkvFile(video)
+            success = mkv.convert()
+            if success:
+                self.converted_videos.append(mpg_file)
+        elif ext == 'mpg':
+            self.converted_videos.append(video)
+        elif ext == 'mp4':
+            mp4 = Mp4File(video)
+            success = mp4.convert()
+            if success:
+                self.converted_videos.append(mpg_file)
+
+    def dvdauthor(self):
+        """
+        Uses dvdauthor to create titles and chapters
+        """
+        if self.verbose:
+            print "Running DVDAuthor to create titles and chapters..."
+        xml = "<dvdauthor><vmgm /><titleset><titles><pgc>"
+        for vid in self.converted_videos:
+            xml += "<vob file=\"%s\" chapters=\"%s\" />" % (vid,
+                get_chapters(self.chapters))
+        xml += "</pgc></titles></titleset></dvdauthor>"
+        fp = open("/tmp/dvd.xml", "w")
+        fp.write(xml)
+        fp.close()
+        os.mkdir("/tmp/DVD")
+        cmd = "dvdauthor -o /tmp/DVD -x /tmp/dvd.xml"
+        run_cmd(cmd)
+        cmd2 = "dvdauthor -T -o /tmp/DVD"
+        run_cmd(cmd2)
+
+    def mkiso(self):
+        """
+        Creates an ISO
+        """
+        if self.verbose:
+            print "Creating the ISO..."
+        cmd = "mkisofs -dvd-video -o %s /tmp/DVD" % (self.output_file, )
+        run_cmd(cmd)
+
+    def run(self):
+        """
+        Runs the main loop
+        """
+        for video in self.video_files:
+            video = os.path.abspath(video)
+            self.convert_video(video)
+        self.dvdauthor()
+        self.mkiso()
+        self.cleanup()
+
+    def cleanup(self):
+        """
+        Removed mpg files and any other non-necessary files created
+        """
+        if self.verbose:
+            print "Cleaning up files..."
+        shutil.rmtree("/tmp/DVD")
+        os.remove("/tmp/dvd.xml")
+
+
+def main():
+    args = parse_args()
+    creator = ISOCreator(args.video_files, args.output, args.verbose, args.chapters)
+    creator.run()
+
+
+if __name__ == "__main__":
+    main()

mkiso/progressbar.py

+# -*- coding: utf-8 -*-
+# Copyright: 2009 Nadia Alramli
+# License: BSD
+# http://nadiana.com/animated-terminal-progress-bar-in-python
+"""Draws an animated terminal progress bar
+Usage:
+    p = ProgressBar("blue")
+    p.render(percentage, message)
+"""
+
+import terminal
+import sys
+
+class ProgressBar(object):
+    """Terminal progress bar class"""
+    TEMPLATE = (
+     '%(percent)-2s%% %(color)s%(progress)s%(normal)s%(empty)s %(message)s\n'
+    )
+    PADDING = 7
+
+    def __init__(self, color=None, width=None, block='█', empty=' '):
+        """
+        color -- color name (BLUE GREEN CYAN RED MAGENTA YELLOW WHITE BLACK)
+        width -- bar width (optinal)
+        block -- progress display character (default '█')
+        empty -- bar display character (default ' ')
+        """
+        if color:
+            self.color = getattr(terminal, color.upper())
+        else:
+            self.color = ''
+        if width and width < terminal.COLUMNS - self.PADDING:
+            self.width = width
+        else:
+            # Adjust to the width of the terminal
+            self.width = terminal.COLUMNS - self.PADDING
+        self.block = block
+        self.empty = empty
+        self.progress = None
+        self.lines = 0
+
+    def render(self, percent, message = ''):
+        """Print the progress bar
+        percent -- the progress percentage %
+        message -- message string (optional)
+        """
+        inline_msg_len = 0
+        if message:
+            # The length of the first line in the message
+            inline_msg_len = len(message.splitlines()[0])
+        if inline_msg_len + self.width + self.PADDING > terminal.COLUMNS:
+            # The message is too long to fit in one line.
+            # Adjust the bar width to fit.
+            bar_width = terminal.COLUMNS - inline_msg_len -self.PADDING
+        else:
+            bar_width = self.width
+
+        # Check if render is called for the first time
+        if self.progress != None:
+            self.clear()
+        self.progress = (bar_width * percent) / 100
+        data = self.TEMPLATE % {
+            'percent': percent,
+            'color': self.color,
+            'progress': self.block * self.progress,
+            'normal': terminal.NORMAL,
+            'empty': self.empty * (bar_width - self.progress),
+            'message': message
+        }
+        sys.stdout.write(data)
+        sys.stdout.flush()
+        # The number of lines printed
+        self.lines = len(data.splitlines())
+
+    def clear(self):
+        """Clear all printed lines"""
+        sys.stdout.write(
+            self.lines * (terminal.UP + terminal.BOL + terminal.CLEAR_EOL)
+        )

mkiso/terminal.py

+# Copyright: 2009 Nadia Alramli
+# License: BSD
+
+"""Terminal controller module
+Example of usage:
+    print BG_BLUE + 'Text on blue background' + NORMAL
+    print BLUE + UNDERLINE + 'Blue underlined text' + NORMAL
+    print BLUE + BG_YELLOW + BOLD + 'text' + NORMAL
+"""
+
+import sys
+
+# The current module
+MODULE = sys.modules[__name__]
+
+COLORS = "BLUE GREEN CYAN RED MAGENTA YELLOW WHITE BLACK".split()
+# List of terminal controls, you can add more to the list.
+CONTROLS = {
+    'BOL':'cr', 'UP':'cuu1', 'DOWN':'cud1', 'LEFT':'cub1', 'RIGHT':'cuf1',
+    'CLEAR_SCREEN':'clear', 'CLEAR_EOL':'el', 'CLEAR_BOL':'el1',
+    'CLEAR_EOS':'ed', 'BOLD':'bold', 'BLINK':'blink', 'DIM':'dim',
+    'REVERSE':'rev', 'UNDERLINE':'smul', 'NORMAL':'sgr0',
+    'HIDE_CURSOR':'cinvis', 'SHOW_CURSOR':'cnorm'
+}
+
+# List of numeric capabilities
+VALUES = {
+    'COLUMNS':'cols', # Width of the terminal (None for unknown)
+    'LINES':'lines',  # Height of the terminal (None for unknown)
+    'MAX_COLORS': 'colors',
+}
+
+def default():
+    """Set the default attribute values"""
+    for color in COLORS:
+        setattr(MODULE, color, '')
+        setattr(MODULE, 'BG_%s' % color, '')
+    for control in CONTROLS:
+        setattr(MODULE, control, '')
+    for value in VALUES:
+        setattr(MODULE, value, None)
+
+def setup():
+    """Set the terminal control strings"""
+    # Initializing the terminal
+    curses.setupterm()
+    # Get the color escape sequence template or '' if not supported
+    # setab and setaf are for ANSI escape sequences
+    bgColorSeq = curses.tigetstr('setab') or curses.tigetstr('setb') or ''
+    fgColorSeq = curses.tigetstr('setaf') or curses.tigetstr('setf') or ''
+
+    for color in COLORS:
+        # Get the color index from curses
+        colorIndex = getattr(curses, 'COLOR_%s' % color)
+        # Set the color escape sequence after filling the template with index
+        setattr(MODULE, color, curses.tparm(fgColorSeq, colorIndex))
+        # Set background escape sequence
+        setattr(
+            MODULE, 'BG_%s' % color, curses.tparm(bgColorSeq, colorIndex)
+        )
+    for control in CONTROLS:
+        # Set the control escape sequence
+        setattr(MODULE, control, curses.tigetstr(CONTROLS[control]) or '')
+    for value in VALUES:
+        # Set terminal related values
+        setattr(MODULE, value, curses.tigetnum(VALUES[value]))
+
+def render(text):
+    """Helper function to apply controls easily
+    Example:
+    apply("%(GREEN)s%(BOLD)stext%(NORMAL)s") -> a bold green text
+    """
+    return text % MODULE.__dict__
+
+try:
+    import curses
+    setup()
+except Exception, e:
+    # There is a failure; set all attributes to default
+    print 'Warning: %s' % e
+    default()
+import argparse
+import subprocess
+
+
+def parse_args():
+    parser = argparse.ArgumentParser(description="Converts list of videos to"
+        " DVD compaitble iso")
+    parser.add_argument("-v", "--verbose", action="store_true", dest="verbose",
+        default=False, help="Enables verbose mode.")
+    parser.add_argument("-c", "--chapters", dest="chapters", type=int,
+        default=10, help="Specify the time in minutes between chapters.")
+    parser.add_argument("-o", required=True, dest="output",
+        help="Output file.")
+    parser.add_argument("video_files", nargs="+",
+        help="Video files to convert.")
+    return parser.parse_args()
+
+
+def get_chapters(x):
+    """
+    Returns a comma seperated list of chapters
+    for every x minutes in 2hr 30mins
+    example:
+    >>> get_chapters(30)
+    >>> 0:00:00,0:30:00,1:00:00,1:30:00,2:00:00,2:30:00
+    """
+    max_time = 150
+    num_chapters = (max_time / x) + 1
+    chapters = []
+    for i in xrange(0, num_chapters):
+        m = i * x
+        h = m / 60
+        if h > 0:
+            m = m - (h * 60)
+        chapters.append("%s:%s:00" % (h, str(m).zfill(2)))
+    return ",".join(chapters)
+
+
+def run_cmd(cmd, show_output=False):
+    """
+    Runs an arbitrary command, and handles any errors that occur
+    TODO: Make this more robust.
+    """
+    cmd = cmd.split(' ')
+    proc = subprocess.Popen(cmd, stdout=subprocess.PIPE,
+        stderr=subprocess.STDOUT)
+    output, error = proc.communicate()
+    if show_output:
+        return output
+    if error:
+        print error
+        return False
+    else:
+        return True

mkiso/videotypes.py

+#!/usr/bin/python
+"""
+This file contains classes of possible video types and each class is
+responsible for defining how it converts it's filetype to a MPEG2 compaitable
+format.
+"""
+import re
+
+from utils import run_cmd
+
+
+class InvalidVideoFormatError(Exception):
+
+    def __str__(self):
+        return "%s is not a valid video format." % (self.args[0], )
+
+
+class VideoFile(object):
+
+    def __init__(self, filename):
+        self.filename = filename
+        self.mpg_file = ".".join(filename.split('.')[:-1]) + ".mpg"
+
+    def get_convert_cmd(self):
+        pass
+
+    def convert(self):
+        return run_cmd(self.get_convert_cmd())
+
+
+class MkvFile(VideoFile):
+
+    STREAM_RE = re.compile(
+        "Stream\s\#(?P<stream>0\.[01]{1})(\(eng\))?\:\s(?P<type>[a-zA-Z]+)\:")
+
+    def get_convert_cmd(self):
+        vstream, astream = self.get_streams()
+        return "ffmpeg -threads 2 -v 1 -i %s -r ntsc -target dvd -b 4771k -s 720x480 -acodec copy -copyts -aspect 16:9 %s -map %s -map %s" % (
+            self.filename, self.mpg_file, vstream, astream)
+
+    def get_streams(self):
+        cmd = "ffmpeg -i %s" % (self.filename, )
+        info = run_cmd(cmd, show_output=True)
+        matches = self.STREAM_RE.findall(info)
+        if matches[0][-1] == 'Audio':
+            audio = matches[0][0]
+            video = matches[1][0]
+        else:
+            audio = matches[1][0]
+            video = matches[0][0]
+        return (video, audio)
+
+
+class AviFile(VideoFile):
+
+    def get_convert_cmd(self):
+        return "ffmpeg -i %s -target ntsc-dvd -aspect 16:9 -sameq %s" % (
+            self.filename, self.mpg_file)
+
+
+class Mp4File(VideoFile):
+
+    def get_convert_cmd(self):
+        return "ffmpeg -i %s -target ntsc-dvd -aspect 16:9 -s 720x480 %s" % (
+            self.filename, self.mpg_file)
+
+from setuptools import setup
+
+
+setup(
+    name='Make ISO',
+    version='0.1.0',
+    url='https://bitbucket.org/boralyl/htps-scripts',
+    description='Converts various video formats to MPEG2 format and generates ISO',
+    author='Aaron',
+    classifiers=[
+        'Environment :: Console',
+        'License :: OSI Approved :: GNU General Public License (GPL)',
+        'Operating System :: POSIX',
+        'Programming Language :: Python',
+    ],
+    packages=['mkiso'],
+    entry_points={
+        'console_scripts': [
+            'makeiso = mkiso.mkiso:main',
+        ],
+    }
+)