--- /dev/null
+
+LEFT_SCREEN_WIDTH = 1366
--- /dev/null
+#!/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()
--- /dev/null
+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])
--- /dev/null
+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)
+
--- /dev/null
+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))
+
--- /dev/null
+# 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
--- /dev/null
+# 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
--- /dev/null
+#!/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
--- /dev/null
+# 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())
--- /dev/null
+
+def s_to_hms(s):
+ m = int(s / 60)
+ s -= (m * 60)
+ h = int(m / 60)
+ m -= (h * 60)
+ return (h, m, s)
--- /dev/null
+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
+