Move some hacks into the git repo.
authorCarl Hetherington <cth@carlh.net>
Tue, 7 Feb 2012 12:12:01 +0000 (12:12 +0000)
committerCarl Hetherington <cth@carlh.net>
Tue, 7 Feb 2012 12:12:01 +0000 (12:12 +0000)
hacks/python-playback/config.py [new file with mode: 0644]
hacks/python-playback/dvdomatic [new file with mode: 0755]
hacks/python-playback/film.py [new file with mode: 0644]
hacks/python-playback/film_view.py [new file with mode: 0644]
hacks/python-playback/player.py [new file with mode: 0644]
hacks/python-playback/ratio.py [new file with mode: 0644]
hacks/python-playback/screens [new file with mode: 0644]
hacks/python-playback/screens.py [new file with mode: 0644]
hacks/python-playback/thumbs.py [new file with mode: 0644]
hacks/python-playback/util.py [new file with mode: 0644]
hacks/python-playback/xrandr-notes [new file with mode: 0644]

diff --git a/hacks/python-playback/config.py b/hacks/python-playback/config.py
new file mode 100644 (file)
index 0000000..fecf261
--- /dev/null
@@ -0,0 +1,2 @@
+
+LEFT_SCREEN_WIDTH = 1366
diff --git a/hacks/python-playback/dvdomatic b/hacks/python-playback/dvdomatic
new file mode 100755 (executable)
index 0000000..ce405f3
--- /dev/null
@@ -0,0 +1,209 @@
+#!/usr/bin/python
+
+import os
+import operator
+import traceback
+import pygtk
+pygtk.require('2.0')
+import gtk
+import glib
+import gobject
+import film
+import film_view
+import player
+import screens
+import thumbs
+import ratio
+import util
+
+FILM_DIRECTORY = '/home/carl/DVD'
+
+current_player = None
+films = []
+inhibit_selection_update = False
+
+def find_films():
+    global films
+    films = []
+    for root, dirs, files in os.walk(FILM_DIRECTORY):
+        for name in files:
+            if os.path.basename(name) == 'info':
+                films.append(film.Film(os.path.join(root, os.path.dirname(name))))
+
+    films.sort(key = operator.attrgetter('name'))
+
+def update_film_store():
+    global film_store
+    global films
+    global inhibit_selection_update
+    inhibit_selection_update = True
+    film_store.clear()
+    for f in films:
+        film_store.append([f.name])
+    inhibit_selection_update = False
+
+def update_screen_store(screen_store, screens):
+    screen_store.clear()
+    for s in screens.screens:
+        screen_store.append([s.name])
+
+def create_film_tree_view(film_store):
+    view = gtk.TreeView(film_store)
+    column = gtk.TreeViewColumn()
+    view.append_column(column)
+    cell = gtk.CellRendererText()
+    column.pack_start(cell)
+    column.add_attribute(cell, 'text', 0)
+    view.get_selection().set_mode(gtk.SELECTION_SINGLE)
+    return view
+
+def create_screen_view(screen_store):
+    view = gtk.TreeView(screen_store)
+    column = gtk.TreeViewColumn()
+    view.append_column(column)
+    cell = gtk.CellRendererText()
+    column.pack_start(cell)
+    column.add_attribute(cell, 'text', 0)
+    view.get_selection().set_mode(gtk.SELECTION_SINGLE)
+    return view
+
+def get_selected_film():
+    (model, iter) = film_tree_view.get_selection().get_selected()
+
+    for f in films:
+        if f.name == model.get(iter, 0)[0]:
+            return f
+
+    return None
+
+# @return Selected screen name
+def get_selected_screen():
+    (model, iter) = screen_view.get_selection().get_selected()
+    return model.get(iter, 0)[0]
+
+def film_selected(selection):
+    if inhibit_selection_update:
+        return
+
+    film_view.set(get_selected_film())
+    check_for_playability()
+
+def screen_selected(selection):
+    check_for_playability()
+
+def check_for_playability():
+    f = get_selected_film()
+    if screens.get_format(get_selected_screen(), f.ratio) is not None:
+        play_button.set_label("Play")
+        play_button.set_sensitive(True)
+    else:
+        play_button.set_label("Cannot play: no setting for %s on screen %s" % (ratio.find(f.ratio).name(), get_selected_screen()))
+        play_button.set_sensitive(False)
+
+def update_status(s):
+    global current_player
+    if current_player is None:
+        s.set_text("Not playing")
+        return True
+
+    position = current_player.time_pos
+    if position is None:
+        return True
+    position_hms = util.s_to_hms(position)
+
+    length = current_player.length
+    if length is None:
+        return True
+
+    remaining = length - position
+    remaining_hms = util.s_to_hms(remaining)
+    s.set_text("Playing: %d:%02d:%02d, %d:%02d:%02d remaining" % (position_hms[0], position_hms[1], position_hms[2], remaining_hms[0], remaining_hms[1], remaining_hms[2]))
+    return True
+
+def play_clicked(b):
+    global current_player
+    f = get_selected_film()
+    current_player = player.get_player(f, screens.get_format(get_selected_screen(), f.ratio))
+    print current_player.args
+
+def stop_clicked(b):
+    global current_player
+    if current_player is not None:
+        current_player.stop()
+        current_player = None
+
+def add_film_clicked(b):
+    global films
+    c = gtk.FileChooserDialog("New Film", main_window, gtk.FILE_CHOOSER_ACTION_CREATE_FOLDER, (("Add", gtk.RESPONSE_OK)))
+    c.set_current_folder(FILM_DIRECTORY)
+    if c.run() == gtk.RESPONSE_OK:
+        f = film.Film()
+        f.data = c.get_filename()
+        f.name = os.path.basename(c.get_filename())
+        f.write()
+        find_films()
+        update_film_store()
+        c.hide()
+
+        for i in range(0, len(films)):
+            if films[i].name == f.name:
+                film_tree_view.get_selection().select_path((i, ))
+
+main_window = gtk.Window(gtk.WINDOW_TOPLEVEL)
+main_window.set_title("DVD-o-matic")
+main_window.maximize()
+
+main_hbox = gtk.HBox()
+main_hbox.set_spacing(12)
+main_hbox.set_border_width(12)
+main_window.add(main_hbox)
+
+find_films()
+film_view = film_view.FilmView(main_window)
+screens = screens.Screens("screens")
+
+left_vbox = gtk.VBox()
+left_vbox.set_spacing(12)
+main_hbox.pack_start(left_vbox, False, False)
+right_vbox = gtk.VBox()
+right_vbox.set_spacing(12)
+main_hbox.pack_start(right_vbox)
+
+film_store = gtk.ListStore(gobject.TYPE_STRING)
+update_film_store()
+
+film_tree_view = create_film_tree_view(film_store)
+left_vbox.pack_start(film_tree_view, True, True)
+film_tree_view.get_selection().select_path((0, ))
+film_tree_view.get_selection().connect("changed", film_selected)
+
+add_film_button = gtk.Button(stock = gtk.STOCK_ADD)
+left_vbox.pack_start(add_film_button, False, False)
+add_film_button.connect("clicked", add_film_clicked)
+
+screen_store = gtk.ListStore(gobject.TYPE_STRING)
+update_screen_store(screen_store, screens)
+
+screen_view = create_screen_view(screen_store)
+left_vbox.pack_start(screen_view, False, False)
+screen_view.get_selection().select_path((0, ))
+screen_view.get_selection().connect("changed", screen_selected)
+
+right_vbox.pack_start(film_view, False, False)
+film_view.set(films[0])
+
+play_button = gtk.Button("Play")
+right_vbox.pack_start(play_button)
+play_button.connect("clicked", play_clicked)
+
+stop_button = gtk.Button("Stop")
+right_vbox.pack_start(stop_button)
+stop_button.connect("clicked", stop_clicked)
+
+status = gtk.Label()
+right_vbox.pack_start(status, False, False)
+glib.timeout_add_seconds(1, update_status, status)
+
+check_for_playability()
+main_window.show_all()
+gtk.main()
diff --git a/hacks/python-playback/film.py b/hacks/python-playback/film.py
new file mode 100644 (file)
index 0000000..3ad1280
--- /dev/null
@@ -0,0 +1,188 @@
+import os
+import subprocess
+import shlex
+import shutil
+import player
+
+class Film:
+    def __init__(self, data = None):
+        # File or directory containing content
+        self.content = None
+        # True if content is in DVD format
+        self.dvd = False
+        # DVD title number
+        self.dvd_title = 1
+        # Directory containing metadata
+        self.data = None
+        # Film name
+        self.name = None
+        # Number of pixels by which to crop the content from each edge
+        self.left_crop = 0
+        self.top_crop = 0
+        self.right_crop = 0
+        self.bottom_crop = 0
+        # Use deinterlacing filter
+        self.deinterlace = False
+        # Target ratio
+        self.ratio = 1.85
+        # Audio stream ID to play
+        self.aid = None
+
+        self.width = None
+        self.height = None
+        self.fps = None
+        self.length = None
+
+        if data is not None:
+            self.data = data
+            f = open(os.path.join(self.data, 'info'), 'r')
+            while 1:
+                l = f.readline()
+                if l == '':
+                    break
+
+                d = l.strip()
+            
+                s = d.find(' ')
+                if s != -1:
+                    key = d[:s]
+                    value = d[s+1:]
+                
+                    if key == 'name':
+                        self.name = value
+                    elif key == 'content':
+                        self.content = value
+                    elif key == 'dvd':
+                        self.dvd = int(value) == 1
+                    elif key == 'dvd_title':
+                        self.dvd_title = int(value)
+                    elif key == 'left_crop':
+                        self.left_crop = int(value)
+                    elif key == 'top_crop':
+                        self.top_crop = int(value)
+                    elif key == 'right_crop':
+                        self.right_crop = int(value)
+                    elif key == 'bottom_crop':
+                        self.bottom_crop = int(value)
+                    elif key == 'deinterlace':
+                        self.deinterlace = int(value) == 1
+                    elif key == 'ratio':
+                        self.ratio = float(value)
+                    elif key == 'aid':
+                        self.aid = int(value)
+                    elif key == 'width':
+                        self.width = int(value)
+                    elif key == 'height':
+                        self.height = int(value)
+                    elif key == 'fps':
+                        self.fps = float(value)
+                    elif key == 'length':
+                        self.length = float(value)
+
+        if self.width is None or self.height is None or self.fps is None or self.length is None:
+            self.update_content_metadata()
+
+    def write(self):
+        try:
+            os.mkdir(self.data)
+        except OSError:
+            pass
+
+        f = open(os.path.join(self.data, 'info'), 'w')
+        self.write_datum(f, 'name', self.name)
+        self.write_datum(f, 'content', self.content)
+        self.write_datum(f, 'dvd', int(self.dvd))
+        self.write_datum(f, 'dvd_title', self.dvd_title)
+        self.write_datum(f, 'left_crop', self.left_crop)
+        self.write_datum(f, 'top_crop', self.top_crop)
+        self.write_datum(f, 'right_crop', self.right_crop)
+        self.write_datum(f, 'bottom_crop', self.bottom_crop)
+        self.write_datum(f, 'deinterlace', int(self.deinterlace))
+        self.write_datum(f, 'ratio', self.ratio)
+        self.write_datum(f, 'aid', self.aid)
+        self.write_datum(f, 'width', self.width)
+        self.write_datum(f, 'height', self.height)
+        self.write_datum(f, 'fps', self.fps)
+        self.write_datum(f, 'length', self.length)
+
+    def write_datum(self, f, key, value):
+        if value is not None:
+            print >>f,'%s %s' % (key, str(value))
+
+    def thumbs_dir(self):
+        t = os.path.join(self.data, 'thumbs')
+
+        try:
+            os.mkdir(t)
+        except OSError:
+            pass
+
+        return t
+
+    def thumb(self, n):
+        return os.path.join(self.thumbs_dir(), str('%08d.png' % (n + 1)))
+
+    def thumbs(self):
+        return len(os.listdir(self.thumbs_dir()))
+
+    def remove_thumbs(self):
+        shutil.rmtree(self.thumbs_dir())
+
+    def make_thumbs(self):
+        num_thumbs = 128
+        cl = self.player_command_line()
+        if self.length is not None:
+            sstep = self.length / num_thumbs
+        else:
+            sstep = 100
+        cl.extra = '-vo png -frames %d -sstep %d -nosound' % (num_thumbs, sstep)
+        os.chdir(self.thumbs_dir())
+        os.system(cl.get(True))
+
+    def set_dvd(self, d):
+        self.dvd = d
+        self.remove_thumbs()
+
+    def set_dvd_title(self, t):
+        self.dvd_title = t
+        self.remove_thumbs()
+
+    def set_content(self, c):
+        if c == self.content:
+            return
+
+        self.content = c
+        self.update_content_metadata()
+
+    def player_command_line(self):
+        cl = player.CommandLine()
+        cl.dvd = self.dvd
+        cl.dvd_title = self.dvd_title
+        cl.content = self.content
+        return cl
+    
+    def update_content_metadata(self):
+        if self.content is None:
+            return
+
+        self.width = None
+        self.height = None
+        self.fps = None
+        self.length = None
+
+        cl = self.player_command_line()
+        cl.extra = '-identify -vo null -ao null -frames 0'
+        text = subprocess.check_output(shlex.split(cl.get(True))).decode('utf-8')
+        lines = text.split('\n')
+        for l in lines:
+            s = l.strip()
+            b = s.split('=')
+            if len(b) == 2:
+                if b[0] == 'ID_VIDEO_WIDTH':
+                    self.width = int(b[1])
+                elif b[0] == 'ID_VIDEO_HEIGHT':
+                    self.height = int(b[1])
+                elif b[0] == 'ID_VIDEO_FPS':
+                    self.fps = float(b[1])
+                elif b[0] == 'ID_LENGTH':
+                    self.length = float(b[1])
diff --git a/hacks/python-playback/film_view.py b/hacks/python-playback/film_view.py
new file mode 100644 (file)
index 0000000..c11b2e6
--- /dev/null
@@ -0,0 +1,212 @@
+import os
+import pygtk
+pygtk.require('2.0')
+import gtk
+import ratio
+import util
+import thumbs
+
+class FilmView(gtk.HBox):
+    def __init__(self, parent):
+        gtk.HBox.__init__(self)
+
+        self.parent_window = parent
+
+        self.inhibit_save = True
+        
+        self.table = gtk.Table()
+        self.pack_start(self.table, True, True)
+
+        self.table.set_row_spacings(4)
+        self.table.set_col_spacings(12)
+        self.film_name = gtk.Entry()
+        self.ratio = gtk.combo_box_new_text()
+        for r in ratio.ratios:
+            self.ratio.append_text(r.name())
+        self.content_file_radio = gtk.RadioButton()
+        self.content_file_radio.set_label("File")
+        self.content_file_chooser = gtk.FileChooserDialog("Content", self.parent_window, gtk.FILE_CHOOSER_ACTION_OPEN, (("Select", gtk.RESPONSE_OK)))
+        self.content_file_button = gtk.FileChooserButton(self.content_file_chooser)
+        self.content_folder_radio = gtk.RadioButton()
+        self.content_folder_radio.set_label("Folder")
+        self.content_folder_radio.set_group(self.content_file_radio)
+        self.content_folder_chooser = gtk.FileChooserDialog("Content", self.parent_window, gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER, (("Select", gtk.RESPONSE_OK)))
+        self.content_folder_button = gtk.FileChooserButton(self.content_folder_chooser)
+        self.dvd = gtk.CheckButton("DVD")
+        self.dvd_title = gtk.SpinButton()
+        self.dvd_title.set_range(0, 32)
+        self.dvd_title.set_increments(1, 4)
+        self.deinterlace = gtk.CheckButton("Deinterlace")
+        self.left_crop = self.crop_spinbutton()
+        self.right_crop = self.crop_spinbutton()
+        self.top_crop = self.crop_spinbutton()
+        self.bottom_crop = self.crop_spinbutton()
+        
+        # Information about the content (immutable)
+        self.source_size = self.label()
+        self.fps = self.label()
+        self.length = self.label()
+
+        # Buttons
+        self.thumbs_button = gtk.Button("Show Thumbnails")
+
+        self.film_name.connect("changed", self.changed, self)
+        self.ratio.connect("changed", self.changed, self)
+        self.deinterlace.connect("toggled", self.changed, self)
+        self.thumbs_button.connect("clicked", self.thumbs_clicked, self)
+        self.content_file_radio.connect("toggled", self.content_radio_toggled, self)
+        self.content_folder_radio.connect("toggled", self.content_radio_toggled, self)
+        self.content_file_button.connect("file-set", self.content_file_chooser_file_set, self)
+        self.content_folder_button.connect("file-set", self.content_folder_chooser_file_set, self)
+        self.dvd.connect("toggled", self.changed, self)
+        self.dvd_title.connect("value-changed", self.changed, self)
+
+        n = 0
+        self.table.attach(self.label("Film"), 0, 1, n, n + 1)
+        self.table.attach(self.film_name, 1, 2, n, n + 1)
+        n += 1
+        self.table.attach(self.label("Ratio"), 0, 1, n, n + 1)
+        self.table.attach(self.ratio, 1, 2, n, n + 1)
+        n += 1
+        self.table.attach(self.label("Content"), 0, 1, n, n + 1)
+        b = gtk.HBox()
+        b.set_spacing(4)
+        b.pack_start(self.content_file_radio, False, False)
+        b.pack_start(self.content_file_button, True, True)
+        b.pack_start(self.content_folder_radio, False, False)
+        b.pack_start(self.content_folder_button, True, True)
+        self.table.attach(b, 1, 2, n, n + 1)
+        n += 1
+        self.table.attach(self.dvd, 0, 2, n, n + 1)
+        n += 1
+        self.table.attach(self.label("DVD title"), 0, 1, n, n + 1)
+        self.table.attach(self.dvd_title, 1, 2, n, n + 1)
+        n += 1
+        self.table.attach(self.deinterlace, 0, 2, n, n + 1)
+        n += 1
+        self.table.attach(self.label("Left Crop"), 0, 1, n, n + 1)
+        self.table.attach(self.left_crop, 1, 2, n, n + 1)
+        n += 1
+        self.table.attach(self.label("Right Crop"), 0, 1, n, n + 1)
+        self.table.attach(self.right_crop, 1, 2, n, n + 1)
+        n += 1
+        self.table.attach(self.label("Top Crop"), 0, 1, n, n + 1)
+        self.table.attach(self.top_crop, 1, 2, n, n + 1)
+        n += 1
+        self.table.attach(self.label("Bottom Crop"), 0, 1, n, n + 1)
+        self.table.attach(self.bottom_crop, 1, 2, n, n + 1)
+        n += 1
+        self.table.attach(self.label("Source size"), 0, 1, n, n + 1)
+        self.table.attach(self.source_size, 1, 2, n, n + 1)
+        n += 1
+        self.table.attach(self.label("Frames per second"), 0, 1, n, n + 1)
+        self.table.attach(self.fps, 1, 2, n, n + 1)
+        n += 1
+        self.table.attach(self.label("Length"), 0, 1, n, n + 1)
+        self.table.attach(self.length, 1, 2, n, n + 1)
+
+        self.right_vbox = gtk.VBox()
+        self.pack_start(self.right_vbox, False, False)
+
+        self.right_vbox.pack_start(self.thumbs_button, False, False)
+
+        self.inhibit_save = False
+
+    def set(self, film):
+        self.inhibit_save = True
+
+        self.film = film
+        self.film_name.set_text(film.name)
+        self.ratio.set_active(ratio.ratio_to_index(film.ratio))
+        if film.content is not None:
+            if os.path.isfile(film.content):
+                self.set_content_is_file(True)
+                self.content_file_button.set_filename(film.content)
+            else:
+                self.set_content_is_file(False)
+                self.content_folder_button.set_filename(film.content)
+        self.dvd.set_active(film.dvd)
+        self.dvd_title.set_value(film.dvd_title)
+        self.dvd_title.set_sensitive(film.dvd)
+        self.deinterlace.set_active(film.deinterlace)
+        self.left_crop.set_value(film.left_crop)
+        self.right_crop.set_value(film.right_crop)
+        self.top_crop.set_value(film.top_crop)
+        self.bottom_crop.set_value(film.bottom_crop)
+
+        # Content information
+        if film.width is not None and film.height is not None:
+            self.source_size.set_text("%dx%d" % (film.width, film.height))
+        else:
+            self.source_size.set_text("Unknown")
+        if film.fps is not None:
+            self.fps.set_text(str(film.fps))
+        if film.length is not None:
+            self.length.set_text("%d:%02d:%02d" % util.s_to_hms(film.length))
+
+        self.inhibit_save = False
+
+    def set_content_is_file(self, f):
+        self.content_file_button.set_sensitive(f)
+        self.content_folder_button.set_sensitive(not f)
+        self.content_file_radio.set_active(f)
+        self.content_folder_radio.set_active(not f)
+    
+    def label(self, text = ""):
+        l = gtk.Label(text)
+        l.set_alignment(0, 0.5)
+        return l
+
+    def changed(self, a, b):
+        self.dvd_title.set_sensitive(self.dvd.get_active())
+        self.save_film()
+
+    def crop_spinbutton(self):
+        s = gtk.SpinButton()
+        s.set_range(0, 1024)
+        s.set_increments(1, 16)
+        s.connect("value-changed", self.changed, self)
+        return s
+
+    def save_film(self):
+        if self.inhibit_save:
+            return
+
+        self.film.name = self.film_name.get_text()
+        self.film.ratio = ratio.index_to_ratio(self.ratio.get_active()).ratio
+
+        if self.content_file_radio.get_active():
+            self.film.set_content(self.content_file_button.get_filename())
+        else:
+            self.film.set_content(self.content_folder_button.get_filename())
+        self.film.set_dvd(self.dvd.get_active())
+        self.film.set_dvd_title(self.dvd_title.get_value_as_int())
+        self.film.deinterlace = self.deinterlace.get_active()
+        self.film.left_crop = self.left_crop.get_value_as_int()
+        self.film.right_crop = self.right_crop.get_value_as_int()
+        self.film.top_crop = self.top_crop.get_value_as_int()
+        self.film.bottom_crop = self.bottom_crop.get_value_as_int()
+        self.film.write()
+
+    def thumbs_clicked(self, a, b):
+        if self.film.thumbs() == 0:
+            self.film.make_thumbs()
+
+        t = thumbs.Thumbs(self.film)
+        t.run()
+        t.hide()
+        self.left_crop.set_value(t.left_crop())
+        self.right_crop.set_value(t.right_crop())
+        self.top_crop.set_value(t.top_crop())
+        self.bottom_crop.set_value(t.bottom_crop())
+
+    def content_file_chooser_file_set(self, a, b):
+        self.changed(a, b)
+
+    def content_folder_chooser_file_set(self, a, b):
+        self.changed(a, b)
+
+    def content_radio_toggled(self, a, b):
+        self.set_content_is_file(self.content_file_radio.get_active())
+        self.changed(a, b)
+
diff --git a/hacks/python-playback/player.py b/hacks/python-playback/player.py
new file mode 100644 (file)
index 0000000..5cc8da7
--- /dev/null
@@ -0,0 +1,112 @@
+import os
+import threading
+import subprocess
+import shlex
+import select
+import film
+import config
+import mplayer
+
+class CommandLine:
+    def __init__(self):
+        self.position_x = 0
+        self.position_y = 0
+        self.output_width = None
+        self.output_height = None
+        self.mov = False
+        self.delay = None
+        self.dvd = False
+        self.dvd_title = 1
+        self.content = None
+        self.extra = ''
+        self.crop_x = None
+        self.crop_y = None
+        self.crop_w = None
+        self.crop_h = None
+        self.deinterlace = False
+        self.aid = None
+        
+    def get(self, with_binary):
+        # hqdn3d?
+        # nr, unsharp?
+        # -vo x11 appears to be necessary to prevent unwanted hardware scaling
+        # -noaspect stops mplayer rescaling to the movie's specified aspect ratio
+        args = '-vo x11 -noaspect -ao pulse -noborder -noautosub -nosub -sws 10 '
+        args += '-geometry %d:%d ' % (self.position_x, self.position_y)
+
+        # Video filters (passed to -vf)
+
+        filters = []
+
+        if self.crop_x is not None or self.crop_y is not None or self.crop_w is not None or self.crop_h is not None:
+            crop = 'crop='
+            if self.crop_w is not None and self.crop_h is not None:
+                crop += '%d:%d' % (self.crop_w, self.crop_h)
+                if self.crop_x is not None and self.crop_x is not None:
+                    crop += ':%d:%d' % (self.crop_x, self.crop_y)
+                filters.append(crop)
+
+        if self.output_width is not None or self.output_height is not None:
+            filters.append('scale=%d:%d' % (self.output_width, self.output_height))
+
+        # Post processing
+        pp = []
+        if self.deinterlace:
+            pp.append('lb')
+
+        # Deringing filter
+        pp.append('dr')
+
+        if len(pp) > 0:
+            pp_string = 'pp='
+            for i in range(0, len(pp)):
+                pp_string += pp[i]
+                if i < len(pp) - 1:
+                    pp += ','
+
+            filters.append(pp_string)
+
+        if len(filters) > 0:
+            args += '-vf '
+            for i in range(0, len(filters)):
+                args += filters[i]
+                if i < len(filters) - 1:
+                    args += ','
+            args += ' '
+
+        if self.mov:
+            args += '-demuxer mov '
+        if self.delay is not None:
+            args += '-delay %f ' % float(args.delay)
+        if self.aid is not None:
+            args += '-aid %s ' % self.aid
+
+        args += self.extra
+        
+        if self.dvd:
+            data_specifier = 'dvd://%d -dvd-device \"%s\"' % (self.dvd_title, self.content)
+        else:
+            data_specifier = '\"%s\"' % self.content
+
+        if with_binary:
+            return 'mplayer %s %s' % (args, data_specifier)
+        
+        return '%s %s' % (args, data_specifier)
+  
+def get_player(film, format):
+    cl = CommandLine()
+    cl.dvd = film.dvd
+    cl.dvd_title = film.dvd_title
+    cl.content = film.content
+    cl.crop_w = film.width - film.left_crop - film.right_crop
+    cl.crop_h = film.height - film.top_crop - film.bottom_crop
+    cl.position_x = format.x
+    if format.external:
+        cl.position_x = format.x + config.LEFT_SCREEN_WIDTH
+        cl.position_y = format.y
+    cl.output_width = format.width
+    cl.output_height = format.height
+    cl.deinterlace = film.deinterlace
+    cl.aid = film.aid
+    return mplayer.Player(cl.get(False))
+
diff --git a/hacks/python-playback/ratio.py b/hacks/python-playback/ratio.py
new file mode 100644 (file)
index 0000000..62320dc
--- /dev/null
@@ -0,0 +1,56 @@
+# Class to describe a Ratio, and a collection of common
+# (and not-so-common) film ratios collected from Wikipedia.
+
+class Ratio:
+    def __init__(self, ratio, nickname = None):
+        self.nickname = nickname
+        self.ratio = ratio
+
+    # @return presentation name of this ratio
+    def name(self):
+        if self.nickname is not None:
+            return "%.2f (%s)" % (self.ratio, self.nickname)
+        
+        return "%.2f" % self.ratio
+
+ratios = []
+ratios.append(Ratio(1.33, '4:3'))
+ratios.append(Ratio(1.37, 'Academy'))
+ratios.append(Ratio(1.78, '16:9'))
+ratios.append(Ratio(1.85, 'Flat / widescreen'))
+ratios.append(Ratio(2.39, 'CinemaScope / Panavision'))
+ratios.append(Ratio(1.15, 'Movietone'))
+ratios.append(Ratio(1.43, 'IMAX'))
+ratios.append(Ratio(1.5))
+ratios.append(Ratio(1.56, '14:9'))
+ratios.append(Ratio(1.6, '16:10'))
+ratios.append(Ratio(1.67))
+ratios.append(Ratio(2, 'SuperScope'))
+ratios.append(Ratio(2.2, 'Todd-AO'))
+ratios.append(Ratio(2.35, 'Early CinemaScope / Panavision'))
+ratios.append(Ratio(2.37, '21:9'))
+ratios.append(Ratio(2.55, 'CinemaScope 55'))
+ratios.append(Ratio(2.59, 'Cinerama'))
+ratios.append(Ratio(2.76, 'Ultra Panavision'))
+ratios.append(Ratio(2.93, 'MGM Camera 65'))
+ratios.append(Ratio(4, 'Polyvision'))
+
+# Find a Ratio object from a fractional ratio
+def find(ratio):
+    for r in ratios:
+        if r.ratio == ratio:
+            return r
+
+    return None
+
+# @return the ith ratio
+def index_to_ratio(i):
+    return ratios[i]
+
+# @return the index within the ratios list of a given fractional ratio
+def ratio_to_index(r):
+    for i in range(0, len(ratios)):
+        if ratios[i].ratio == r:
+            return i
+
+    return None
diff --git a/hacks/python-playback/screens b/hacks/python-playback/screens
new file mode 100644 (file)
index 0000000..f389cb1
--- /dev/null
@@ -0,0 +1,62 @@
+# Screen 1 (untested)
+screen 1
+ratio 1.85
+x 175
+y 100
+width 1550
+height 950
+external 1
+ratio 2.39
+x 0
+y 200
+width 2000
+height 860
+external 1
+
+# Screen 2
+screen 2
+ratio 1.85
+x 175
+y 100
+width 1550
+height 950
+external 1
+ratio 2.39
+x 0
+y 200
+width 2000
+height 860
+external 1
+
+# Screen 3 (untested)
+screen 3
+ratio 1.85
+x 175
+y 100
+width 1550
+height 950
+external 1
+ratio 2.39
+x 0
+y 200
+width 2000
+height 860
+external 1
+
+# Carl's Laptop
+screen laptop
+ratio 1.85
+x 0
+y 0
+width 1366
+height 738
+ratio 2.39
+x 0
+y 0
+width 1366
+height 572
+ratio 1.78
+x 0
+y 0 
+width 1366
+height 767
diff --git a/hacks/python-playback/screens.py b/hacks/python-playback/screens.py
new file mode 100644 (file)
index 0000000..4230a4c
--- /dev/null
@@ -0,0 +1,85 @@
+#!/usr/bin/python
+
+class Screen:
+    def __init__(self):
+        self.name = None
+        self.formats = []
+
+class Format:
+    def __init__(self):
+        self.ratio = None
+        self.x = None
+        self.y = None
+        self.width = None
+        self.height = None
+        self.external = False
+
+class Screens:
+    def __init__(self, file):
+
+        self.screens = []
+
+        f = open(file, 'r')
+        current_screen = None
+        current_format = None
+        while 1:
+            l = f.readline()
+            if l == '':
+                break
+            if len(l) > 0 and l[0] == '#':
+                continue
+
+            s = l.strip()
+
+            if len(s) == 0:
+                continue
+
+            b = s.split()
+
+            if len(b) != 2:
+                print "WARNING: ignored line `%s' in screens file" % (s)
+                continue
+
+            if b[0] == 'screen':
+                if current_format is not None:
+                    current_screen.formats.append(current_format)
+                    current_format = None
+
+                if current_screen is not None:
+                    self.screens.append(current_screen)
+                    current_screen = None
+                
+                current_screen = Screen()
+                current_screen.name = b[1]
+            elif b[0] == 'ratio':
+                if current_format is not None:
+                    current_screen.formats.append(current_format)
+                    current_format = None
+                    
+                current_format = Format()
+                current_format.ratio = float(b[1])
+            elif b[0] == 'x':
+                current_format.x = int(b[1])
+            elif b[0] == 'y':
+                current_format.y = int(b[1])
+            elif b[0] == 'width':
+                current_format.width = int(b[1])
+            elif b[0] == 'height':
+                current_format.height = int(b[1])
+            elif b[0] == 'external':
+                current_format.external = int(b[1]) == 1
+
+        if current_format is not None:
+            current_screen.formats.append(current_format)
+
+        if current_screen is not None:
+            self.screens.append(current_screen)
+
+    def get_format(self, screen, ratio):
+        for s in self.screens:
+            if s.name == screen:
+                for f in s.formats:
+                    if f.ratio == ratio:
+                        return f
+
+        return None
diff --git a/hacks/python-playback/thumbs.py b/hacks/python-playback/thumbs.py
new file mode 100644 (file)
index 0000000..921f82f
--- /dev/null
@@ -0,0 +1,76 @@
+# GUI to display thumbnails and allow cropping
+# to be set up
+
+import os
+import sys
+import pygtk
+pygtk.require('2.0')
+import gtk
+import film
+import player
+
+class Thumbs(gtk.Dialog):
+    def __init__(self, film):
+        gtk.Dialog.__init__(self)
+        self.film = film
+        self.controls = gtk.Table()
+        self.controls.set_col_spacings(4)
+        self.thumb_adj = gtk.Adjustment(0, 0, self.film.thumbs() - 1, 1, 10)
+        self.add_control("Thumbnail", self.thumb_adj, 0)
+        self.left_crop_adj = gtk.Adjustment(self.film.left_crop, 0, 1024, 1, 128)
+        self.add_control("Left crop", self.left_crop_adj, 1)
+        self.right_crop_adj = gtk.Adjustment(self.film.right_crop, 0, 1024, 1, 128)
+        self.add_control("Right crop", self.right_crop_adj, 2)
+        self.top_crop_adj = gtk.Adjustment(self.film.top_crop, 0, 1024, 1, 128)
+        self.add_control("Top crop", self.top_crop_adj, 3)
+        self.bottom_crop_adj = gtk.Adjustment(self.film.bottom_crop, 0, 1024, 1, 128)
+        self.add_control("Bottom crop", self.bottom_crop_adj, 4)
+        self.display_image = gtk.Image()
+        self.update_display()
+        window_box = gtk.HBox()
+        window_box.set_spacing(12)
+
+        controls_vbox = gtk.VBox()
+        controls_vbox.set_spacing(4)
+        controls_vbox.pack_start(self.controls, False, False)
+        
+        window_box.pack_start(controls_vbox, True, True)
+        window_box.pack_start(self.display_image)
+        
+        self.set_title("%s Thumbnails" % film.name)
+        self.get_content_area().add(window_box)
+        self.add_button("Close", gtk.RESPONSE_ACCEPT)
+        self.show_all()
+
+        for a in [self.thumb_adj, self.left_crop_adj, self.right_crop_adj, self.top_crop_adj, self.bottom_crop_adj]:
+            a.connect('value-changed', self.update_display, self)
+
+    def add_control(self, name, adj, n):
+        l = gtk.Label(name)
+        l.set_alignment(1, 0.5)
+        self.controls.attach(l, 0, 1, n, n + 1)
+        s = gtk.SpinButton(adj)
+        self.controls.attach(s, 1, 2, n, n + 1)
+
+    def update_display(self, a = None, b = None):
+        thumb_pixbuf = gtk.gdk.pixbuf_new_from_file(self.film.thumb(self.thumb_adj.get_value()))
+        self.width = thumb_pixbuf.get_width()
+        self.height = thumb_pixbuf.get_height()
+        left = self.left_crop()
+        right = self.right_crop()
+        top = self.top_crop()
+        bottom = self.bottom_crop()
+        pixbuf = thumb_pixbuf.subpixbuf(left, top, self.width - left - right, self.height - top - bottom)
+        self.display_image.set_from_pixbuf(pixbuf)
+
+    def top_crop(self):
+        return int(self.top_crop_adj.get_value())
+
+    def bottom_crop(self):
+        return int(self.bottom_crop_adj.get_value())
+
+    def left_crop(self):
+        return int(self.left_crop_adj.get_value())
+    
+    def right_crop(self):
+        return int(self.right_crop_adj.get_value())
diff --git a/hacks/python-playback/util.py b/hacks/python-playback/util.py
new file mode 100644 (file)
index 0000000..d78abdd
--- /dev/null
@@ -0,0 +1,7 @@
+
+def s_to_hms(s):
+    m = int(s / 60)
+    s -= (m * 60)
+    h = int(m / 60)
+    m -= (h * 60)
+    return (h, m, s)
diff --git a/hacks/python-playback/xrandr-notes b/hacks/python-playback/xrandr-notes
new file mode 100644 (file)
index 0000000..eeabf14
--- /dev/null
@@ -0,0 +1,17 @@
+Recommended 1680 x 1050, 60 fps
+xrandr --output HDMI1 --mode 0xbc
+
+List modes
+xrandr --verbose -q
+
+2048 x 1024, 24 fps
+xrandr --output HDMI1 --mode 0xd1
+
+cvt <xres> <yres> <fps>
+to give modeline, then
+xrandr --newmode modeline
+then add
+xrandr --verbose --addmode HDMI1 modename
+then activate
+xrandr --output HDMI1 --mode foo
+