#!/usr/bin/ruby # so my editor will like it...
=begin
Copyright 2010, Roger Pack
This file is part of Sensible Cinema.
Sensible Cinema is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Sensible Cinema is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Sensible Cinema. If not, see .
=end
print 'Starting Sensible Cinema...'
require File.expand_path(File.dirname(__FILE__) + "/../lib/add_any_bundled_gems_to_load_path.rb")
begin
require 'sane'
rescue LoadError
p 'please run $ rake rebundle_copy_in_dependencies first, as we dislike needing locally installed rubygems'
raise
end
raise 'need jruby' unless OS.java?
raise 'need newer jruby!' unless RUBY_DESCRIPTION >= 'jruby 1.6.0' # may not need this since we still have to accomodate for so many oddities :P
alias system_original system
require 'fileutils'
class String
def snake_case
self.gsub(/::/, '/').
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
gsub(/([a-z\d])([A-Z])/,'\1_\2').
tr("-", "_").
downcase
end
end
require_relative '../lib/swing_helpers'
for kls in [:MencoderWrapper, :EdlParser, :MplayerEdl, :PlayAudio, :SubtitleProfanityFinder, :ConvertThirtyFps, :RubyClip, :DriveInfo]
autoload kls, "./lib/#{kls.to_s.snake_case}"
end
# a few I'll need...
require_relative '../lib/storage'
require 'tmpdir'
require 'whichr'
require 'os'
if OS.doze?
autoload :WMI, 'ruby-wmi'
autoload :EightThree, './lib/eight_three'
end
vendor_cache = File.expand_path(File.dirname(__FILE__)) + '/../vendor/cache'
if OS.windows?
for name in ['.', 'mencoder', 'ffmpeg']
# put them all before the old path
ENV['PATH'] = (vendor_cache + '/' + name).to_filename + ';' + ENV['PATH']
end
installed_smplayer_folders = Dir['{c,d,e,f,g}:/program files*/MPlayer for Windows*'] # sometimes ends with UI? huh?
for folder in installed_smplayer_folders
ENV['PATH'] = ENV['PATH'] + ";#{folder.gsub('/', "\\")}"
end
else
ENV['PATH'] = ENV['PATH'] + ':' + '/opt/local/bin' # add macports' bin in, just in case...
end
import 'javax.swing.ImageIcon'
module SensibleSwing
VERSION = File.read(File.dirname(__FILE__) + "/../VERSION").strip
puts "v. " + VERSION
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()) # sigh
class MainWindow < JFrame
def we_are_in_create_mode
ARGV.index("--create-mode")
end
# currently advanced mode only *adds* buttons
def new_jbutton title, tooltip = nil
button = JButton.new title
button.tool_tip = tooltip
button.set_bounds(44, @starting_button_y, @button_width, 23)
@panel.add button
@buttons << button
if block_given? # allow for new_jbutton("xx") do ... end [this works through some miraculous means LOL]
button.on_clicked { yield }
end
increment_button_location
button
end
def add_text_line line
jlabel = JLabel.new line
happy = Font.new("Tahoma", Font::PLAIN, 11)
jlabel.setFont(happy)
jlabel.set_bounds(44,@starting_button_y ,460,14)
@panel.add jlabel
increment_button_location 18
jlabel
end
def increment_button_location how_much = 30
@starting_button_y += how_much
setSize @button_width+80, @starting_button_y + 50
end
def force_accept_license_first
if !(LocalStorage['main_license_accepted'] == VERSION)
require_blocking_license_accept_dialog 'Sensible Cinema', 'gplv3', 'http://www.gnu.org/licenses/gpl.html', 'Sensible Cinema license agreement',
"Sensible Cinema is distributed under the gplv3 (http://www.gnu.org/licenses/gpl.html).\nBY CLICKING \"accept\" YOU SIGNIFY THAT YOU HAVE READ, UNDERSTOOD AND AGREED TO ABIDE BY THE TERMS OF THIS AGREEMENT"
require_blocking_license_accept_dialog 'Sensible Cinema', 'is_it_legal_to_copy_dvds.txt file', File.expand_path(File.dirname(__FILE__) + "/../documentation/is_it_legal_to_copy_dvds.txt"),
'is_it_legal_to_copy_dvds.txt file', 'I acknowledge that I have read, understand, accept and agree to abide by the implications noted in the documentation/is_it_legal_to_copy_dvds.txt file'
LocalStorage['main_license_accepted'] = VERSION
end
end
LocalStorage = Storage.new("sensible_cinema_storage")
LocalStorage.set_default('screen_multiples', 1.5) # high compatibility :)
def setup_upconvert_buttons
@watch_file_upconvert = new_jbutton( "Watch a movie file upconverted (unedited)") do
warn_if_no_upconvert_options_currently_selected
filename_mpg = new_existing_file_selector_and_select_file( "pick movie file (like moviename.mpg)")
play_mplayer_edl [filename_mpg, nil]
end.tool_tip= "This plays back a movie file, like moviefile.mpg, or moviename.vob using your current upconverter settings.\nTo playback a file edited upconverted, set upconvert options here first, then run them using sensible cinema main--it will automatically use your new upconverting options." # LODO
@watch_dvd_upconvert = new_jbutton( "Watch a DVD upconverted (unedited)") do
warn_if_no_upconvert_options_currently_selected
play_dvd_smplayer_unedited false, false
end
@watch_dvd_upconvert.tool_tip = "Plays back the currently inserted DVD, using your current upconverter settings.\nIf it fails (dies immediately, blank screen, etc.), try setting upconvert options to a smaller screen resolution multiple.\nOr try playing the DVD with VLC first, then it might work.\nTo playback a DVD edited upconverted, set upconvert options here first, then run them using sensible cinema main--it will automatically use your new upconverting options."
@upconv_line = add_text_line ''
change_upconvert_line_to_current
add_text_line ''
add_change_upconvert_options_button
add_text_line "Note that sensible cinema will also upconvert when it plays back edited DVD's/files."
add_text_line ''
end
def add_change_upconvert_options_button
@show_upconvert_options = new_jbutton("Set Upconvert Options") do
add_setup_upconvert_buttons
end
@show_upconvert_options.tool_tip= "Allows you to set your upconvert options.\nUpconverting attempts to playback your movie with higher quality on high resolution monitors."
end
def warn_if_no_upconvert_options_currently_selected
if !LocalStorage[UpConvertEnglish]
show_blocking_message_dialog "Warning: upconvert options have not been set yet--set upconvert options first and restart, if desired."
end
end
def change_upconvert_line_to_current
@upconv_line.set_text get_current_upconvert_as_phrase
end
def select_new_sxs_style
answer = show_select_buttons_prompt 'Select EDL file style creation for this program', :yes => 'Smplayer style', :no => 'XBMC style'
if answer == 0
LocalStorage[SideBySide] = 'smplayer'
elsif answer == 1
LocalStorage[SideBySide] = 'xbmc'
else
show_blocking_message_dialog 'please choose one--smplayer if you don\'t know'
select_new_sxs_style
end
end
SideBySide = 'side_by_side' # 'xbmc' or 'smplayer'
UpConvertKey = 'upconvert_setting'
UpConvertKeyExtra = 'upconvert_setting_extra'
UpConvertEnglish = 'upconvert_english_name'
def add_setup_upconvert_buttons
none = new_jbutton("reset upconvert options to default (none)")
none.tool_tip = "Having no upconvert options is reasonably good, might use directx for scaling, nice for slow cpu's"
none.on_clicked {
LocalStorage[UpConvertKey] = nil
LocalStorage[UpConvertKeyExtra] = nil
LocalStorage[UpConvertEnglish] = nil
display_current_upconvert_setting
}
medium_dvd = new_jbutton("Change upconvert options to DVD-style video") {
LocalStorage[UpConvertKey] = "hqdn3d=0:1:4:4,scale=SCREEN_X:-10:0:0:2"
# hqdn3d[=luma_spatial:chroma_spatial:luma_tmp:chroma_tmp]
LocalStorage[UpConvertKeyExtra] = "-sws 9 -ssf ls=75.0 -ssf cs=7.0"
LocalStorage[UpConvertEnglish] = "DVD"
display_current_upconvert_setting
}
high_compression = new_jbutton("Change upconvert options for playing back highly compressed video") {
# -autoq 6 -vf pp [?]
LocalStorage[UpConvertEnglish] = "high compressed"
LocalStorage[UpConvertKey] = "hqdn3d=0:1:4:4,pp=hb:y/vb:y,scale=SCREEN_X:-10:0:0:3" # ordering?
LocalStorage[UpConvertKeyExtra] = "-sws 9 -ssf ls=75.0 -ssf cs=25.0"
display_current_upconvert_setting
# -Processing method: mplayer with accurate deblocking ???
}
new_jbutton("Change upconvert options to experimental style playback") {
LocalStorage[UpConvertKey] = "hqdn3d=7:7:5,scale=SCREEN_X:-10:0:0:10"
LocalStorage[UpConvertKeyExtra] = "-sws 9 -ssf ls=100.0 -ssf cs=75.0"
LocalStorage[UpConvertEnglish] = "experimental"
display_current_upconvert_setting
}
add_text_line "Multiple factor screen widths (higher is better, but uses more cpu, 2x is typically good)."
add_text_line "If mplayer just dies or if it displays only a blank screen then lower this."
slider = JSlider.new
slider.setBorder(BorderFactory.createTitledBorder("Screen resolution multiple"));
# I want tick for 1x, 1.5x, 2x, 2.5x, 3x
# so let's do 10 -> 30 ...
labelTable = java.util.Hashtable.new
i = java.lang.Integer
l = JLabel
# allow for 0.75 too, if you have a large monitor, slower cpu...
local_minimum = (720.0/get_current_max_width_resolution)*100 # allow 1024 to use upscaling to 860 LOL
# => 70 for 1024 monitor, so we'll then allow for the 75 below...
label_minimum = nil
(0..300).step(25) do |n|
if n > local_minimum
label_minimum ||= n
if (n % 100 == 0)
labelTable.put(i.new(n), l.new("#{n/100}x")) # 1x
elsif n == label_minimum # just for the bottom label, rest too chatty
labelTable.put(i.new(n), l.new("#{n/100.0}x")) # 1.5x
end
end
end
slider.setLabelTable( labelTable )
slider.maximum=300
slider.minimum=label_minimum
slider.setMajorTickSpacing(100)
slider.setMinorTickSpacing(25)
slider.setPaintTicks(true)
slider.setPaintLabels(true)
slider.snap_to_ticks=true
slider.set_value LocalStorage['screen_multiples'] * 100
slider.add_change_listener { |event|
if !slider.value_is_adjusting
# they released their hold on it...
old_value = LocalStorage['screen_multiples']
new_value = slider.value/100.0
LocalStorage['screen_multiples'] = new_value
if new_value != old_value
display_current_upconvert_setting
if slider.value == label_minimum
show_blocking_message_dialog "Setting it too low like that might make it not really do much upconverting!"
end
end
end
}
slider.set_bounds(44, @starting_button_y, @button_width, 66)
2.times {increment_button_location}
@panel.add(slider)
end
def display_current_upconvert_setting
change_upconvert_line_to_current
@display_current_upconvert_setting_dialog
@display_current_upconvert_setting_dialog = show_non_blocking_message_dialog get_current_upconvert_as_phrase
p get_current_upconvert_as_phrase
end
def get_current_upconvert_as_phrase
settings = LocalStorage[UpConvertEnglish]
out = "Upconvert options currently #{ settings ? "are set to #{settings} style" : "are NOT SET"}"
if settings
out += " (screen multiplier #{LocalStorage['screen_multiples']})."
end
out
end
def get_current_max_width_resolution
java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment.getScreenDevices.map{|gd| gd.display_mode.width}.max.to_i
end
def get_upconvert_vf_settings
template = LocalStorage[UpConvertKey]
if template
# choose width of widest monitor (why would they display it on the other?)
screen_multiple = LocalStorage['screen_multiples']
upc = template.gsub('SCREEN_X', (get_current_max_width_resolution*screen_multiple).to_i.to_s) # has to be an integer...
p 'using upconvert settings ' + upc
'pullup,softskip,' + upc
else
p 'not using any upconversion'
"pullup,softskip"
end
end
def get_upconvert_secondary_settings
LocalStorage[UpConvertKeyExtra]
end
def setup_advanced_buttons
add_text_line 'Realtime create Options:'
@create_new_edl_for_current_dvd = new_jbutton("Create new Edit List for a DVD",
"If your DVD doesn't have an EDL created for it, this will be your first step--create an EDL file for it.")
@create_new_edl_for_current_dvd.on_clicked do
create_brand_new_edl
@display_dvd_info.simulate_click # for now...
end
@open_list = new_jbutton("Open/Edit a previously created EDL file", "If your DVD has a previously existing EDL for it, you can open it to edit it with this button.")
@open_list.on_clicked {
filename = new_existing_file_selector_and_select_file( "Pick any file to open in editor", EDL_DIR)
open_file_to_edit_it filename
}
@create_dot_edl = new_jbutton( "Create a side-by-side moviefilename.edl file")
@create_dot_edl.tool_tip = <<-EOL
Create a moviefilename.edl file (corresponding to some moviefilename.some_ext file already existing)
XBMC/smplayer (smplayer can be used by WMC plugins, etc.) "automagically detect",
if it exists, and automatically use it .edl to show that file edited played back.
If you use smplayer, note that you'll need to download the "lord mulder mplayer"
version (which includes an updated version of mplayer that fixes some bugs in EDL playback)
EOL
@create_dot_edl.on_clicked {
choose_file_and_edl_and_create_sxs_or_play true
}
new_jbutton("Select side by side EDL file style (smplayer vs. XBMC)") do
select_new_sxs_style
end
@play_smplayer = new_jbutton( "Watch full DVD unedited (realtime smplayer)")
@play_smplayer.tool_tip = <<-EOL
This will play the DVD unedited within smplayer.
NB it will default to title 1, so updated your EDL file that matches this DVD with the proper title if this doesn't work for you
i.e. if it just plays a single preview title or what not, and not the main title, you need to change this value.
This is useful if you want to just kind of watch the movie to enjoy it, and look for scenes to cut out.
You can use the built-in OSD (on-screen-display) to see what time frame the questionable scenes are at
(type "o" to toggle it). However, just realize that the OSD is in 30 fps, and our time stamps are all in 29.97
fps, so you'll need to convert it (the convert timestamp button) to be able to use it in a file.
EOL
@play_smplayer.on_clicked {
play_dvd_smplayer_unedited
}
@play_mplayer_raw = new_jbutton( "Watch full DVD unedited (realtime mplayer)")
@play_mplayer_raw.tool_tip = <<-EOL
This is also useful for comparing subtitle files to see if they have accurate timings.
If you turn on subtitles (use the v button), then compare your srt file at say, the 1 hour mark, or 2 hour mark,
with the subtitles that mplayer displays, it *should* match exactly with the output in the command line,
like "V: 3600.0" should match your subtitle line "01:00:00,000 --> ..."
EOL
@play_mplayer_raw.on_clicked {
play_dvd_smplayer_unedited true
}
@display_dvd_info = new_jbutton( "Display information about current DVD (ID, etc.)" )
@display_dvd_info.tool_tip = "This is useful to setup a DVD's 'unique ID' within an EDL for it. \nIf your EDL doesn't have a line like disk_unique_id => \"...\" then you will want to run this to be able to add that line in."
@display_dvd_info.on_clicked {
drive, volume_name, dvd_id = choose_dvd_drive_or_file true # real DVD disk
# display it, allow them to copy and paste it out
title_lengths = nil
t = Thread.new { title_lengths= `mplayer dvdnav:// -nocache -dvd-device #{drive} -identify -frames 0 2>&1| grep LENGTH` }
id_string = "\"disk_unique_id\" => \"#{dvd_id}\", # #{volume_name}"
show_copy_pastable_string "#{drive} #{volume_name} for your copying+pasting pleasure (highlight, then ctrl+c to copy)\n
This is USED eventually to identify a disk to match it to its EDL, later.", id_string
t.join
File.write EdlTempFile, id_string + "\n" + title_lengths
open_file_to_edit_it EdlTempFile
id_string
}
@convert_seconds_to_ts = new_jbutton( "Convert 3600 <-> 1:00:00 style timestamps" )
@convert_seconds_to_ts.on_clicked {
input = get_user_input("Enter \"from\" timestamps, like 3600 or 1:40:00:", "1:00:00.1 or 3600.1")
if input =~ /:/
output = EdlParser.translate_string_to_seconds input
else
output = EdlParser.translate_time_to_human_readable input.to_f, true
end
show_copy_pastable_string("Converted:", output)
}
@convert_timestamp = new_jbutton( "Convert timestamp from DVD player time (30 fps) to EDL player time (29.97 fps)" )
@convert_timestamp.tool_tip=<<-EOL
Our EDL's assume 29.97 fps (which is what a DVD is). Unfortunately most hardware/commercial DVD players
assume that the DVD is 30 fps, which means that if you watch a movie with them, then use those timestamps
for your EDL file, you will be off, at the end of a 2 hour film, by 8 seconds. So all your edits will be wrong.
How to fix: convert your times from "DVD player" time to "EDL accurate" time by using this button.
This is necessary for hardware DVD player timestamps, PowerDVD, Windows Media Player (playing a DVD),
but not necessary for smplayer timestamps (or mplayer's "V: 3600" in the command line), which are already 29.97.
smplayer's on-screen-display (the 'o' key) is accurate (and doesn't suffer from dvd_mplayer_splits)
but is 30 fps, so timestamps would need to be converted.
Dont use VLC for DVD timestamps at all--it can get up to 30s off! VLC playing back a file is usually pretty accurate to 29.97.
In general, GUI's like VLC or smplayer are always a tidge off (maybe 0.3s) from the right timestamp, so take that into consideration.
Mplayers "V: 3600" is usually right on, however.
EOL
@convert_timestamp.on_clicked {
thirty_fps = get_user_input("Enter your DVD (30 fps) timestamp, I'll convert it to 29.97 (usable in EDL's):", "1:00:00.1")
thirty_fps_in_seconds = EdlParser.translate_string_to_seconds thirty_fps
twenty_nine_seven_fps = ConvertThirtyFps.from_thirty(thirty_fps_in_seconds)
human_twenty_nine_seven = EdlParser.translate_time_to_human_readable twenty_nine_seven_fps, true
show_copy_pastable_string("Sensible cinema usable value (29.97 fps) for #{thirty_fps} would be: ", human_twenty_nine_seven)
}
@parse_srt = new_jbutton("Scan a subtitle file (.srt) to detect profanity times automatically" )
@parse_srt.tool_tip = <<-EOL
You can download a .srt file and use it to automatically search for profanities.
Basically download it from opensubtitles.org (possibly from other sites, too),
(enter dvd name in the search box, click a result, click one from the list with an English flag, then click 'Download(zip)')
Once you download the zip, unzip it, and then compare the timestamps in it with those on the DVD (see the button "Watch DVD unedited (realtime mplayer)")
NB that you'll first want/need to *carefully* double check your subtitle
file with the actual DVD. (ex: smplayer, hit the 'o' button to display
the current timestamp, then go to the end, stop at some point with text
and hit the '.' key until a subtitle very first displays.
Next convert that number to 29.97 fps (using the button given).
The numbers should match precisely. If they don't, edit this file
so that it will have some offsets given.
EOL
@parse_srt.on_clicked do
filename = new_existing_file_selector_and_select_file("Pick srt file to scan for profanity:")
add_to_beginning = get_user_input("How much time to subtract from the beginning of each subtitle entry (ex: 1:00 -> 1:01 becomes 0:59 -> 1:01)", "0.0")
add_to_end = get_user_input("How much time to add to the end of each subtitle entry (ex: 1:00 -> 1:01 becomes 1:00 -> 1:02)", "0.0")
parsed = SubtitleProfanityFinder.edl_output filename, {}, add_to_beginning.to_f, add_to_end.to_f # flight: TODO necessary typically ??? 0.35, 0.25
File.write(EdlTempFile, "# add these into your mute section if you deem them mute-worthy\n" + parsed)
open_file_to_edit_it filename, true
sleep 0.3 if OS.mac? # add delay...
open_file_to_edit_it EdlTempFile
end
add_text_line 'Create Options that first create/use a local intermediary file:'
@preview_section = new_jbutton( "Preview a certain time frame from fulli file (edited)" )
@preview_section.tool_tip = <<-EOL
This allows you to preview an edit easily.
It is the equivalent of saying \"watch this file edited from exactly minute x second y to minute z second q"
Typically if you want to test an edit, you can start a few seconds before, and end a few seconds after it, to test it precisely.
EOL
@preview_section.on_clicked {
do_create_edited_copy_via_file true
}
@preview_section_unedited = new_jbutton("Preview a certain time frame from fulli file (unedited)" )
@preview_section.tool_tip = "Allows you to view a certain time frame unedited (ex: 10:00 to 10:05), so you can narrow down to pinpoint where questionable scenes are, etc. This is the only way to view a specific scene if there are not cuts within that scene yet."
@preview_section_unedited.on_clicked {
do_create_edited_copy_via_file true, false, true
}
@rerun_preview = new_jbutton( "Re-run most recently watched preview time frame from fulli file" )
@rerun_preview.tool_tip = "This will re-run the preview that you most recently performed. Great for checking to see if you last edits were successful or not."
@rerun_preview.on_clicked {
repeat_last_copy_dvd_to_hard_drive
}
# Maybe this button should go too...
@fast_preview = new_jbutton("fast preview all from fulli file (smplayer EDL)")
@fast_preview.tool_tip = <<-EOL
Plays smplayer on a file with an EDL.
This gives you a rough estimate to see if your edits are accurate, and is really fast to seek, etc.
This is useful because you can't use mplayer on a DVD for accurate timestamps if it has any
timestamp splits in it [because some DVD's are buggy]
EOL
@fast_preview.on_clicked {
success, wrote_to_here_fulli = do_create_edited_copy_via_file false, true
sleep 0.5 # lodo take out ???
background_thread.join if background_thread # let it write out the original fulli, if necessary [?]
nice_file = wrote_to_here_fulli
run_smplayer_blocking nice_file, nil, "-edl #{normalize_path EdlTempFile}", false
}
@reload = new_jbutton("reload bin/sensible-cinema code") do
load $0
end
end # advanced buttons
# converts to full path, 8.3 if on doze
def normalize_path path
path = File.expand_path path
path = EightThree.convert_path_to_8_3 path if OS.doze?
end
def setup_normal_buttons
@mplayer_edl = new_jbutton( "Watch DVD edited (realtime)")
@mplayer_edl.tool_tip = "This will watch your DVD in realtime from your computer while skipping/muting questionable scenes."
@mplayer_edl.on_clicked {
play_mplayer_edl
}
@create = new_jbutton( "Create edited copy of DVD/file on Your Hard Drive" )
@create.tool_tip = <<-EOL
This takes a file and creates a new file on your hard disk like dvd_name_edited.mpg that you can watch when it's done.
The file you create will contain the whole movie edited.
It takes quite awhile maybe 2 hours. Sometimes the progress bar will look paused--it typically continues eventually.
EOL
@create.on_clicked {
do_create_edited_copy_via_file false
}
@watch_file_edl = new_jbutton( "Watch movie file edited (realtime)" ) do
choose_file_and_edl_and_create_sxs_or_play false
end
if LocalStorage[UpConvertEnglish]
add_text_line ''
add_open_documentation_button
@upconv_line = add_text_line " #{get_current_upconvert_as_phrase}"
else
@upconv_line = add_text_line ''
add_open_documentation_button
end
add_change_upconvert_options_button
end
def add_open_documentation_button
@open_help_file = new_jbutton("View Sensible Cinema Documentation") do
show_in_explorer __DIR__ + "/../documentation" # TODO mac :)
end
end
def choose_file_and_edl_and_create_sxs_or_play just_create_dot_edl_file_instead_of_play
filename_mpg = new_existing_file_selector_and_select_file( "Pick moviefile (like moviename.mpg or VIDEO_TS/vts_01_0.vob)")
edl_filename = new_existing_file_selector_and_select_file( "Pick an EDL file to use with it", EDL_DIR)
assert_ownership_dialog
if just_create_dot_edl_file_instead_of_play
descriptors = EdlParser.parse_file edl_filename
# LODO these timings...DRY up...plus is XBMC the same? what about on a slower computer?
# NB these are just for the SxS EDL!
#add_secs_end = 0
#add_secs_beginning = 0.5 # LODO are these accurate for file based, as well? slow cpu versus fast?
edl_contents = MplayerEdl.convert_to_edl descriptors, add_secs_end = MplayerEndBuffer, MplayerBeginingBuffer, splits = []
output_file = filename_mpg.gsub(/\.[^\.]+$/, '') + '.edl' # sanitize...
File.write(output_file, edl_contents)
raise unless File.exist?(output_file)
show_blocking_message_dialog("created #{output_file}")
else
play_mplayer_edl [filename_mpg, edl_filename]
end
end
def we_are_in_upconvert_mode
ARGV.index("--upconvert-mode")
end
def initialize
super "Sensible-Cinema #{VERSION} (GPL)"
force_accept_license_first
setDefaultCloseOperation JFrame::EXIT_ON_CLOSE
panel = JPanel.new
@panel = panel
@buttons = []
panel.set_layout nil
add panel # why can't I just slap these down? panel? huh?
@starting_button_y = 40
@button_width = 400
add_text_line "Welcome to Sensible Cinema!"
@starting_button_y += 10 # kinder ugly...
add_text_line " Rest mouse over buttons for \"help\" type descriptions (tooltips)."
add_text_line ""
add_text_line ""
if we_are_in_upconvert_mode
setup_upconvert_buttons
else
setup_normal_buttons
if we_are_in_create_mode
setup_advanced_buttons
add_text_line 'Contact:'
end
@upload = new_jbutton("Submit feedback/Upload/Request Help") # keep this one last! :)
@upload.tool_tip = "We welcome all feedback!\nQuestion, comments, request help.\nAlso if you create a new EDL, please submit it back to us so that others can benefit from it later!"
@upload.on_clicked {
system_non_blocking("start mailto:sensible-cinema@googlegroups.com")
system_non_blocking("start http://groups.google.com/group/sensible-cinema")
}
@progress_bar = JProgressBar.new(0, 100)
@progress_bar.set_bounds(44,@starting_button_y,@button_width,23)
@progress_bar.visible = false
panel.add @progress_bar
increment_button_location
end
@exit = new_jbutton("Exit", "Exits the application and kills any background processes that are running at all--don't exit unless you are done processing all the way!")
@exit.on_clicked {
Thread.new { self.close } # don't waste the time to close it :P
puts 'Thank you for using Sensible Cinema. Come again!'
System.exit 0
}
increment_button_location
increment_button_location
setIconImage(ImageIcon.new(__DIR__ + "/../vendor/profs.png").getImage())
check_for_various_dependencies
end
def run_smplayer_non_blocking *args
@background_thread = Thread.new {
run_smplayer_blocking *args
}
end
def run_smplayer_blocking play_this, title_track_maybe_nil, extra_options, force_use_mplayer
unless File.exist?(File.expand_path(play_this))
_dbg
raise play_this
end
parent_parent = File.basename(File.dirname(play_this))
force_use_mplayer ||= OS.mac?
if parent_parent == 'VIDEO_TS'
# case d:\yo\VIDEO_TS\title0.vob
dvd_device_dir = normalize_path(File.dirname(play_this))
if force_use_mplayer
extra_options += " -dvd-device \"#{dvd_device_dir}/..\""
else
# smplayer
raise if dvd_device_dir =~ / / && OS.mac? # not accomodated
extra_options += " -dvd-device #{dvd_device_dir}/.."
end
play_this = "dvdnav://#{title_track_maybe_nil}"
elsif File.exist?(play_this + '/VIDEO_TS')
# case d:\ where d:\VIDEO_TS exists [DVD mounted in drive] or mac's /Volumes/YO
raise if play_this =~ / / # unexpected
extra_options += " -nocache -dvd-device #{play_this}"
play_this = "dvdnav://#{title_track_maybe_nil}"
else
# case g:\video\filename.mpg
# leave same...
end
if play_this =~ /dvdnav/ && title_track_maybe_nil
extra_options << " -msglevel identify=4 " # prevent smplayer from using *forever* to look up info on DVD's 4
end
extra_options += " -alang en " # for those that put french first...though this doesn't help...
extra_options += " -mouse-movements #{get_upconvert_secondary_settings} " # just in case smplayer also needs -mouse-movements... :) LODO
extra_options += " -lavdopts threads=#{OS.cpu_count} " # just in case this helps [supposed to with h.264] # fast *crashes* doze...
if force_use_mplayer
show_mplayer_instructions_once
conf_file = File.expand_path './mplayer_input_conf'
File.write conf_file, "MOUSE_BTN0_DBL vo_fullscreen\nMOUSE_BTN2 vo_fullscreen\nKP_ENTER dvdnav select\n" # dvdnav doesn't work here...
extra_options += " -font #{File.expand_path('subfont.ttf')} "
extra_options += " -volume 100 " # why start low? mplayer why oh why
if OS.windows?
# direct3d for windows 7 old nvidia cards' sake [yipes] and also dvdnav sake
extra_options += " -vo direct3d "
extra_options += " -slang en "
conf_file = conf_file[2..-1] # strip off drive letter, which it doesn't seem to like no sir
end
extra_options += " -fs " # full screen
c = "mplayer #{extra_options} -vf #{get_upconvert_vf_settings} -input conf=\"#{conf_file}\" \"#{play_this}\" "
else
if OS.windows?
extra_options += " -vo direct3d " # more light nvidia...should be ok...
end
config_path = set_smplayer_opts extra_options
c = "smplayer_portable \"#{play_this}\" -fullscreen -config-path \"#{File.dirname config_path}\" "
if !we_are_in_create_mode
#c += " -close-at-end "
end
end
puts c
system_blocking c
end
def set_smplayer_opts to_this, show_subs = false
p 'set smplayer extra opts to this:' + to_this
smplayer_prefs_file = File.expand_path("~/.smplayer/smplayer.ini")
old_prefs = File.read(smplayer_prefs_file) rescue ''
unless old_prefs.contain? 'mplayer_additional_options='
old_prefs = "[advanced]\nmplayer_additional_options=\nmplayer_additional_video_filters=\n[subtitles]\nautoload_sub=false\n[performance]\npriority=3"
end
raise to_this if to_this =~ /"/ # unexpected, unfortunately...
new_prefs = old_prefs.gsub(/mplayer_additional_options=.*/, "mplayer_additional_options=#{to_this}")
new_prefs.gsub!(/autoload_sub=.*$/, "autoload_sub=#{show_subs.to_s}")
upconvert_settings =
new_prefs.gsub!(/mplayer_additional_video_filters=.*$/, "mplayer_additional_video_filters=\"#{get_upconvert_vf_settings}\"")
new_prefs.gsub!(/priority=.*$/, "priority=3") # normal priority...scary otherwise! lodo tell smplayer...
# enable dvdnav navigation, just for kicks I guess.
new_prefs.gsub!(/use_dvdnav=.*$/, "use_dvdnav=true")
FileUtils.mkdir_p File.dirname(smplayer_prefs_file) # case it doesn't exist'
File.write(smplayer_prefs_file, new_prefs)
smplayer_prefs_file
end
def create_brand_new_edl
drive, volume, dvd_id = choose_dvd_drive_or_file true
english_name = get_user_input("Enter a human readable DVD description for #{volume}", volume.gsub('_', ' ').downcase)
input = <<-EOL
# comments can go after a # on any line, for example this one.
"name" => "#{english_name}",
"mutes" => [
# an example line, uncomment the leading "#" to make it active
# "0:00:01.0", "0:00:02.0", "profanity", "da..",
],
"blank_outs" => [
# an example line, uncomment the leading "#" to make it active
# "00:03:00.0" , "00:04:00.0", "violence", "of some sort",
],
"volume_name" => "#{volume}",
"disk_unique_id" => "#{dvd_id}",
"dvd_title_track" => "1", # the "show DVD info" button will tell you title lengths (typically longest title is the title track)
# "dvd_title_track_length" => "6095", # sum length, on the DVD, use the show DVD info button to retrieve it.
# "opensubtitles_number" => "1234567",
# "not edited out stuff" => "some violence left in...",
# "closing thoughts" => "only did the...",
# "mplayer_dvd_splits" => ["59:59", "1:04:59"], # or [] if there are none. Additive currently. 12345.6 ok. In mplayer, the DVD timestamp "resets" to zero for some reason, so you need to specify when if you want to use mplayer DVD realtime playback, or use mencoder -edl to split your file. See http://goo.gl/yMfqX
EOL
# TODO auto-ify above, move docs to a file in documentation.
filename = EDL_DIR + "/edls_being_edited/" + english_name.gsub(' ', '_') + '.txt'
filename.downcase!
File.write(filename, input) unless File.exist?(filename) # lodo let them choose name (?)
open_file_to_edit_it filename
end
def system_blocking command, low_prio = false
return true if command =~ /^@rem/ # JRUBY-5890 bug
if low_prio
out = IO.popen(command) # + " 2>&1"
low_prio = 64 # from msdn
if command =~ /(ffmpeg|mencoder)/
# XXXX not sure if there's a better way...because some *are* complex and have ampersands...
# unfortunately have to check for nil because it could exit too early [?]
exe_name = $1 + '.exe'
begin
p = proc{ ole = WMI::Win32_Process.find(:first, :conditions => {'Name' => exe_name}); sleep 1 unless ole; ole }
piddy = p.call || p.call || p.call # we actually do need this to loop...guess we're too quick
# but the first time through this still inexplicably fails all 3... LODO
if piddy
# piddy.SetPriority low_prio # this call can seg fault at times...yikes...JRUBY-5422
pid = piddy.ProcessId # this doesn't seg fault, tho
system_original("vendor\\setpriority -lowest #{pid}") # use the PID on the command line
else
# XXXX first one always fails [?] huh?
p 'unable to find to set priority ' + exe_name
end
rescue Exception => e
p 'warning, got exception trying to set priority [jruby prob? ...]', e
end
end
print out.read # let it finish
out.close
$?.exitstatus == 0 # 0 means success
else
raise command + ' failed' unless system_original command
end
end
def system_non_blocking command
@background_thread = Thread.new { system_original command }
end
if respond_to?(:system)
# make them choose which system call to use explicitly
undef system
end
def download full_url, to_here
require 'open-uri'
writeOut = open(to_here, "wb")
writeOut.write(open(full_url).read)
writeOut.close
end
def play_dvd_smplayer_unedited use_mplayer_instead = false, show_instructions = true
drive_or_file, dvd_volume_name, dvd_id, edl_path_maybe_nil, descriptors_maybe_nil = choose_dvd_or_file_and_edl_for_it false
if descriptors_maybe_nil
title_track_maybe_nil = get_title_track(descriptors_maybe_nil, false)
end
if show_instructions
# want this even with smplayer sometimes I guess...
show_mplayer_instructions_once
end
run_smplayer_non_blocking drive_or_file, title_track_maybe_nil, "-osd-fractions 2", use_mplayer_instead
end
EdlTempFile = Dir.tmpdir + '/mplayer.temp.edl'
def show_mplayer_instructions_once
@_show_mplayer_instructions_once ||= show_non_blocking_message_dialog <<-EOL
About to run mplayer. To control it, use
spacebar : pause,
double clicky/right click : toggle full screen,
arrow keys (left, right, up down, pg up, pg dn) to seek/scan
/ and * : inc/dec volume.
'o' key: turn on on-screen-display timestamps (note: the OSD timestamps [upper left] are 30 fps so will need to be converted to use).
'v' key: turn off subtitles.
'.' key: step one frame.
# key: change audio language track
EOL
end
MplayerBeginingBuffer = 1.0
MplayerEndBuffer = 0.0
def play_mplayer_edl optional_file_with_edl_path = nil
extra_mplayer_commands = []
if optional_file_with_edl_path
drive_or_file, edl_path = optional_file_with_edl_path
dvd_id = NonDvd # fake it out...LODO a bit smelly
else
drive_or_file, dvd_volume_name, dvd_id, edl_path, descriptors = choose_dvd_or_file_and_edl_for_it
end
start_add_this_to_all_ts = 0
descriptors = EdlParser.parse_file edl_path
title_track = get_title_track(descriptors)
if dvd_id == NonDvd
# check if starts offset...
all = `ffmpeg -i "#{drive_or_file}" 2>&1`
# Duration: 01:35:49.59, start: 600.000000
all =~ /Duration.*start: ([\d\.]+)/
start = $1.to_f
if start > 1 # LODO huh? dvd's themselves start at 0.3 [sintel]?
show_non_blocking_message_dialog "Warning: file seems to start at an extra offset, adding it to the timestamps... #{start}
maybe not compatible with XBMC, if that's what you use, and you probably don't" # TODO test it XBMC...
start_add_this_to_all_ts = start
end
else
using_dvd = true
end
if using_dvd
splits = descriptors['mplayer_dvd_splits']
if splits == nil
show_blocking_message_dialog("warning: edit list does not contain mplayer replay information [mplayer_dvd_splits] so edits past a certain time period might not won't work ( http://goo.gl/yMfqX ).")
splits = []
end
splits.map!{|s| EdlParser.translate_string_to_seconds(s) }
else
splits = []
end
edl_contents = MplayerEdl.convert_to_edl descriptors, add_secs_end = MplayerEndBuffer, MplayerBeginingBuffer, splits, start_add_this_to_all_ts # add a sec to mutes to accomodate for mplayer's oddness..
File.write(EdlTempFile, edl_contents)
# -framedrop is for slow CPU's
# same with -autosync to try and help it stay in sync... -mc 0.03 is to A/V correct 1s audio per 2s video
# -hardframedrop might help but hurts just too much
extra_mplayer_commands << "-framedrop"
# ?? extra_mplayer_commands << "-mc 0.016" ??
extra_mplayer_commands << "-autosync 15"
# prefer english, attempt to disable subtitles, pass in edl...
extra_mplayer_commands << "-alang en -nosub -noautosub -forcedsubsonly -sid 1000 -edl #{File.expand_path EdlTempFile}"
run_smplayer_non_blocking drive_or_file, title_track, extra_mplayer_commands.join(' '), false
end
def assert_ownership_dialog
message = "Do you certify you own the DVD this came of and have it in your possession?"
title = "Verify ownership"
returned = JOptionPane.showConfirmDialog self, message, title, JOptionPane::YES_NO_CANCEL_OPTION
assert_confirmed_dialog returned, nil
end
# returns 0,1,2 for yes, no, cancel equivs
def show_select_buttons_prompt message, names_hash
old = ['no', 'yes', 'ok'].map{|name| 'OptionPane.' + name + 'ButtonText'}.map{|name| [name, UIManager.get(name)]}
if names_hash[:yes]
UIManager.put("OptionPane.yesButtonText", names_hash[:yes])
end
if names_hash[:no]
UIManager.put("OptionPane.noButtonText", names_hash[:no])
end
# if names_hash[:ok] # ???
# UIManager.put("OptionPane.okButtonText", names_hash[:ok])
# end
if names_hash[:cancel]
UIManager.put("OptionPane.noButtonText", names_hash[:cancel])
end
returned = JOptionPane.showConfirmDialog self, message, title, JOptionPane::YES_NO_CANCEL_OPTION
old.each{|name, old_setting| UIManager.put(name, old_setting)}
returned
end
def require_blocking_license_accept_dialog program, license_name, license_url_should_also_be_embedded_by_you_in_message,
title = 'Confirm Acceptance of License Agreement', message = nil
puts 'Please confirm license agreement in open window.'
# cancel button stays the same...
message ||= "Sensible Cinema requires a separately installed program (#{program}). You can install this program
separately to the vendor/cache directory, or Sensible Cinema can download it for you.
By clicking accept, below, you are confirming that you have read and agree to be bound by the
terms of its license (the #{license_name}), located at #{license_url_should_also_be_embedded_by_you_in_message}.
Click 'View License' to view it. If you do not agree to these terms, click 'Cancel'. You also agree that this is a
separate program, with its own distribution, license, ownership and copyright.
You agree that you are responsible for the download and use of this program, within sensible cinema or otherwise."
returned = show_select_buttons_prompt message, :yes => 'Accept', :no => "View #{license_name}"
assert_confirmed_dialog returned, license_url_should_also_be_embedded_by_you_in_message
p 'confirmation of sensible cinema related license saved of: ' + license_name
throw unless returned == 0
end
def assert_confirmed_dialog returned, license_url_should_also_be_embedded_by_you_in_message
# 1 is view button was clicked
# 0 is accept
# 2 is cancel
if returned == 1
if license_url_should_also_be_embedded_by_you_in_message
system_non_blocking("start #{license_url_should_also_be_embedded_by_you_in_message}")
puts "Please restart after reading license agreement, to be able to then accept it."
end
System.exit 0
end
if returned == 2
p 'license not accepted...exiting'
System.exit 1
end
if returned == -1
p 'license exited early...exiting'
System.exit 1
end
end
def print *args
Kernel.print *args # avoid bin\sensible-cinema.rb:83:in `system_blocking': cannot convert instance of class org.jruby.RubyString to class java.awt.Graphics (TypeError)
end
def download_7zip
Dir.mkdir('./vendor/cache') unless File.directory? 'vendor/cache' # development may not have it created yet... [?]
unless File.exist? 'vendor/cache/7za.exe'
Dir.chdir('vendor/cache') do
print 'downloading unzipper (400K) ...'
download("http://downloads.sourceforge.net/project/sevenzip/7-Zip/9.20/7za920.zip", "7za920.zip")
system_blocking("../unzip.exe -o 7za920.zip") # -o means "overwrite" without prompting
end
end
end
def download_zip_file_and_extract english_name, url, to_this
download_7zip
Dir.chdir('vendor/cache') do
file_name = url.split('/')[-1]
print "downloading #{english_name} ..."
download(url, file_name)
system_blocking("7za e #{file_name} -y -o#{to_this}")
puts 'done ' + english_name
# creates vendor/cache/mencoder/mencoder.exe...
end
end
def check_for_exe windows_loc, unix_name
# in windows, that exe *at that location* must exist...
if OS.windows?
File.exist?(windows_loc)
else
require 'lib/check_installed_mac.rb'
if !CheckInstalledMac.check_for_installed(unix_name)
exit 1 # it'll have already displayed a message...
else
true
end
end
end
def check_for_various_dependencies
if we_are_in_create_mode
if !check_for_exe('vendor/cache/mencoder/mencoder.exe', 'mencoder')
require_blocking_license_accept_dialog 'mplayer', 'gplv2', 'http://www.gnu.org/licenses/gpl-2.0.html', "Appears that you need to install a dependency: mplayer with mencoder."
download_zip_file_and_extract "Mplayer/mencoder (6MB)", "http://downloads.sourceforge.net/project/mplayer-win32/MPlayer%20and%20MEncoder/revision%2033574/MPlayer-rtm-svn-33574.7z", "mencoder"
end
end
# runtime dependencies, at least today...
ffmpeg_exe_loc = File.expand_path('vendor/cache/ffmpeg/ffmpeg.exe')
if !check_for_exe(ffmpeg_exe_loc, 'ffmpeg')
require_blocking_license_accept_dialog 'ffmpeg', 'gplv2', 'http://www.gnu.org/licenses/gpl-2.0.html', "Appears that you need to install a dependency: ffmpeg."
download_zip_file_and_extract "ffmpeg (5MB)", "http://ffmpeg.zeranoe.com/builds/win32/shared/ffmpeg-git-1929807-win32-shared.7z", "ffmpeg"
end
if OS.mac?
check_for_exe("mplayer", "mplayer") # mencoder and mplayer are separate for mac... [this checks for mac's mplayerx, too]
else
path = RubyWhich.new.which('smplayer_portable')
if(path.length == 0)
# this one has its own installer...
show_blocking_message_dialog("It appears that you need to install a pre-requisite dependency: MPlayer for Windows (MPUI).
Click ok to be directed to its download website, where you can download and install it (recommend: MPUI Full-complete),
then restart sensible cinema. NB that it takes awhile to install MPUI. Sorry about that.",
"Lacking dependency", JOptionPane::ERROR_MESSAGE)
open_url_to_view_it_non_blocking "http://code.google.com/p/mulder/downloads/detail?name=MPUI.2011-06-09.Full-Package.exe"
System.exit 0
end
end
end
def open_url_to_view_it_non_blocking url
if OS.windows?
system_non_blocking(c = "start #{url.gsub('&', '^&')}") # LODO would launchy help/work here with the full url?
p c
else
system_non_blocking "#{OS.open_file_command} \"#{url}\"" # LODO test
end
end
def open_file_to_edit_it filename, start_minimized=false
if OS.windows?
if start_minimized
system_non_blocking "start /min notepad \"#{filename}\""
else
system_non_blocking "notepad \"#{filename}\""
end
else
system_non_blocking "open -a TextEdit \"#{filename}\""
end
end
def single_edit_list_matches_dvd dvd_id
matching = Dir[EDL_DIR + '/**/*.txt'].select{|file|
begin
parse_edl(file)["disk_unique_id"] == dvd_id
rescue SyntaxError => e
# ignore poorly formed edit lists for the auto choose phase...
p 'warning, unable to parse a file:' + file + " " + e.to_s
false
end
}
if matching.length == 1
file = matching[0]
p "selecting the one only matching EDL #{file} for this DVD ID: #{dvd_id}"
file
else
nil
end
end
EDL_DIR = File.expand_path(__DIR__ + "/../zamples/edit_decision_lists/dvds").to_filename
def repeat_last_copy_dvd_to_hard_drive
generate_and_run_bat_file *LocalStorage['last_params']
end
def new_nonexisting_filechooser title = nil, default_dir = nil
out = JFileChooser.new
out.set_title title
if default_dir
out.set_current_directory JFile.new(default_dir)
end
out
end
def show_blocking_message_dialog(message, title = message.split("\n")[0], style= JOptionPane::INFORMATION_MESSAGE)
JOptionPane.showMessageDialog(nil, message, title, style)
true
end
# call dispose on this to close it if it hasn't been canceled yet...
def show_non_blocking_message_dialog message, close_button_text = 'Close'
# lodo NonBlockingDialog it can get to the top instead of being so buried...
NonBlockingDialog.new(message, close_button_text)
end
include_class javax.swing.UIManager
def get_user_input(message, default = '', cancel_ok = false)
received = JOptionPane.showInputDialog(message, default)
raise 'user cancelled' unless received unless cancel_ok
received
end
def show_copy_pastable_string(message, value)
RubyClip.set_clipboard value
get_user_input message + " (has been copied to clipboard)", value, true
end
def parse_edl path
EdlParser.parse_file path
end
def get_freespace path
JFile.new(File.dirname(path)).get_usable_space
end
def new_existing_file_selector_and_select_file title, dir=nil
out = FileDialog.new(self, title, FileDialog::LOAD)
out.set_title title
dir ||= LocalStorage[caller.inspect]
out.set_directory dir.to_filename if dir
got = out.go
raise 'cancelled choosing existing file method' unless got # I think we always want to raise...
LocalStorage[caller.inspect] = File.dirname(got)
got
end
def choose_dvd_or_file_and_edl_for_it force_choose_edl_file_if_no_easy_match = true
drive_or_file, dvd_volume_name, dvd_id = choose_dvd_drive_or_file false
unless @_edit_list_path # cache file selection...
edit_list_path = single_edit_list_matches_dvd(dvd_id)
if !edit_list_path && force_choose_edl_file_if_no_easy_match
edit_list_path = new_existing_file_selector_and_select_file("Please pick a DVD Edit List File (none or more than one were found that seem to match #{dvd_volume_name})--may need to create one for it", EDL_DIR)
raise 'cancelled choosing an EDL' unless edit_list_path
end
@_edit_list_path = edit_list_path
end
if @_edit_list_path
# reload it every time just in case it has changed on disk
descriptors = nil
while(!descriptors)
begin
descriptors = parse_edl @_edit_list_path
rescue SyntaxError => e
puts e
show_blocking_message_dialog("this file has an error--please fix then hit ok: \n" + @_edit_list_path + "\n " + e)
end
end
end
[drive_or_file, dvd_volume_name, dvd_id, @_edit_list_path, descriptors]
end
def get_title_track descriptors, use_default_of_one = true
given = descriptors["dvd_title_track"]
given ||= "1" if use_default_of_one
given
end
def get_import_from_filename dvd_title, dvd_title_track
@_get_import_from_filename ||=
begin
new_existing_file_selector_and_select_file "Please choose the file that is your ripped equivalent of #{dvd_title} (title track #{dvd_title_track}) (.mpg or .ts--see file documentation/how_to_get_files_from_dvd.txt)"
end
end
def get_save_to_filename dvd_title
@_get_save_to_filename ||=
begin
fc = new_nonexisting_filechooser "Pick where to save #{dvd_title} edited version to"
save_to_file_name = dvd_title + ' edited version'
save_to_file_name = save_to_file_name.gsub(' ', '_').gsub( /\W/, '') + ".avi" # no punctuation or spaces for now, to not complicate...
fc.set_file(get_drive_with_most_space_with_slash + save_to_file_name)
save_to = fc.go
raise 'no spaces allowed yet' if save_to =~ / /
begin
a = File.open(File.dirname(save_to) + "/test_file_to_see_if_we_have_permission_to_write_to_this_folder", "w")
a.close
File.delete a.path
rescue Errno::EACCES => e
show_blocking_message_dialog "unable to write to that directory, please pick again: " + e.to_s
raise 'pick again!'
end
freespace = get_freespace(save_to)
if freespace < 8_000_000_000
show_blocking_message_dialog("Warning: there may not be enough space on the disk for #{save_to}
(depending on DVD size, you may need around 10G free--you have #{freespace/1_000_000_000}GB free). Click OK to continue.")
end
save_to.gsub(/\.avi$/, '')
end
end
def do_create_edited_copy_via_file should_prompt_for_start_and_end_times, exit_early_if_fulli_exists = false, watch_unedited = false
drive_or_file, dvd_volume_name, dvd_id, edit_list_path, descriptors = choose_dvd_or_file_and_edl_for_it
descriptors = parse_edl(edit_list_path)
if watch_unedited
descriptors['mutes'] = descriptors['blank_outs'] = []
end
# LODO allow for spaces in the save_to filename
if should_prompt_for_start_and_end_times
# only show this message once :)
@show_block ||= show_blocking_message_dialog(<<-EOL, "Preview")
Ok, let's preview just a portion of it.
Note that you'll want to preview a section that wholly includes a deleted section in it.
For example, if it mutes from second 1 to second 10, you'll want to play from 00:00 to 00:12 or what not.
Also note that the first time you preview a section of a video, it will take a long time (like an hour) as it sets up the entire video for processing.
Subsequent previews will be faster, though, as long as you use the same filename, as it won't have to re-set it up for processing.
Also note that if you change your edit list, you'll need to close, and restart the video to be able to see it with your new settings.
EOL
old_start = LocalStorage['start_time']
start_time = get_user_input("At what point in the video would you like to start your preview? (like 01:00 for starting at 1 minute)", LocalStorage['start_time'])
default_end = LocalStorage['end_time']
if start_time and start_time != old_start
default_end = EdlParser.translate_string_to_seconds(start_time) + 10
default_end = EdlParser.translate_time_to_human_readable(default_end)
end
end_time = get_user_input("At what point in the video would you like to finish your preview? (like 02:00 for ending at the 2 minute mark)", default_end)
unless start_time and end_time
JOptionPane.showMessageDialog(nil, " Please choose start and end", "Failed", JOptionPane::ERROR_MESSAGE)
return
end
LocalStorage['start_time'] = start_time
LocalStorage['end_time'] = end_time
end
dvd_friendly_name = descriptors['name']
raise 'no dvd name in EDL?' unless dvd_friendly_name
dvd_title_track = get_title_track(descriptors)
if dvd_id == NonDvd
file_from = drive_or_file
else
file_from = get_import_from_filename dvd_friendly_name, dvd_title_track # we don't even care about the drive letter anymore...
end
show_blocking_message_dialog("warning: file #{file_from} is not a .mpg or .ts file--it may not work properly all the way--if it's mkv and fails consider first converting to ts by using tsmuxer.") unless file_from =~ /\.(ts|mpg|mpeg)$/i
save_to_edited = get_save_to_filename dvd_friendly_name
fulli = MencoderWrapper.calculate_fulli_filename save_to_edited
if exit_early_if_fulli_exists
if fulli_dot_done_file_exists? save_to_edited
return [true, fulli]
end
# make it create a dummy response file for us :)
start_time = "00:00"
end_time = "00:01"
end
should_run_mplayer = should_prompt_for_start_and_end_times || exit_early_if_fulli_exists
require_deletion_entry = true unless watch_unedited
generate_and_run_bat_file save_to_edited, edit_list_path, descriptors, file_from, dvd_friendly_name, start_time, end_time, dvd_title_track, should_run_mplayer, require_deletion_entry
[false, fulli] # false means it's running in a background thread :P
end
def get_drive_with_most_space_with_slash
DriveInfo.get_drive_with_most_space_with_slash
end
def fulli_dot_done_file_exists? save_to_edited
fulli = MencoderWrapper.calculate_fulli_filename save_to_edited
File.exist?(fulli + ".done") # stinky!
end
# to make it stubbable :)
def get_mencoder_commands descriptors, file_from, save_to, start_time, end_time, dvd_title_track, require_deletion_entry
delete_partials = true unless start_time # in case anybody wants to look really really close [?]
MencoderWrapper.get_bat_commands descriptors, file_from, save_to, start_time, end_time, dvd_title_track, delete_partials, require_deletion_entry
end
def generate_and_run_bat_file save_to, edit_list_path, descriptors, file_from, dvd_title, start_time, end_time, dvd_title_track, run_mplayer, require_deletion_entry
LocalStorage['last_params'] = [save_to, edit_list_path, descriptors, file_from, dvd_title, start_time, end_time, dvd_title_track, run_mplayer, require_deletion_entry]
begin
commands = get_mencoder_commands descriptors, file_from, save_to, start_time, end_time, dvd_title_track, require_deletion_entry
rescue MencoderWrapper::TimingError => e
show_blocking_message_dialog("Appears you chose a time frame with no deletion segment in it--please try again:" + e)
return
rescue Errno::EACCES => e
show_blocking_message_dialog("Appears a file on the system is locked: perhaps you need to close down some instance of mplayer?" + e)
return
end
temp_dir = Dir.tmpdir
temp_file = temp_dir + '/vlc.temp.bat'
File.write(temp_file, commands)
popup_message = <<-EOL
Applying #{File.basename edit_list_path}
to #{file_from} (#{dvd_title}).
Copying to #{save_to}.
EOL
if !fulli_dot_done_file_exists?(save_to)
popup_message += "This could take quite awhile (several hours), and will prompt you with a chime noise when it is done.\n
You can close this window and minimize sensible cinema and continue using your computer while it runs in the background.\n"
end
if !start_time
# assume a full run..
popup_message += <<-EOL
NB that the created file will be playable only with VLC (possibly also with smplayer),
but probably not with windows media player.
EOL
end
popup = show_non_blocking_message_dialog(popup_message, "OK")
# allow our popups to still be serviced while it is running
@background_thread = Thread.new {
run_batch_file_commands_and_use_output_somehow commands, save_to, file_from, run_mplayer
popup.dispose
}
# LODO warn if they will overwrite a file in the end...
end
attr_accessor :background_thread, :buttons
def run_batch_file_commands_and_use_output_somehow batch_commands, save_to, file_from, run_mplayer_after_done
@buttons.each{|b| b.set_enabled false}
success = true
lines = batch_commands.lines.to_a
total_size = lines.length.to_f
@progress_bar.visible=true
@progress_bar.set_value(10) # start at 10% always, so they can see something.
lines.each_with_index{|line, idx|
if success
puts "running #{line}"
success = system_blocking(line, true)
if !success
puts "\n", 'line failed: ' + line + "\n" + ' see troubleshooting section in README.txt file! ignoring further processing commands...'
end
end
@progress_bar.set_value(10 + idx/total_size*90)
}
@progress_bar.visible=false
@buttons.each{|b| b.set_enabled true}
if success
saved_to = save_to + '.avi'
if run_mplayer_after_done
run_smplayer_non_blocking saved_to, nil, '', false
else
if File.exist?(saved_to) && (File.size(saved_to).to_f/File.size(file_from) < 0.5) # less than 50% size is suspicious...indeed...check if exists for unit tests.
show_blocking_message_dialog("Warning: file size differs by more than 50%--it's possible that transcoding failed somehow")
end
show_in_explorer saved_to
PlayAudio.play(File.expand_path(File.dirname(__FILE__)) + "/../vendor/music.wav")
msg = "Done--you may now watch file\n #{saved_to}\n in VLC player (or possibly smplayer)"
puts msg # for being able to tell it's done on the command line
show_blocking_message_dialog msg
show_in_explorer saved_to # and again, just for kicks [?]
end
else
show_blocking_message_dialog("Failed--please examine console output and report back!\nAlso consult the documentation/troubleshooting file.", "Failed", JOptionPane::ERROR_MESSAGE)
end
end
def show_in_explorer filename
begin
system_blocking "explorer /e,/select,\"#{File.expand_path(filename).to_filename}\"" # command returns immediately...
rescue => why_does_this_happen_ignore_this_exception_it_probably_actually_succeeded
end
end
def get_disk_chooser_window names
GetDisk.new(self, names)
end
NonDvd = 'non dvd has no dvdid' # we need it for convenience, say you want to go through your indexed vids and convert them all?
# returns e:\, volume_name, dvd_id
# or full_path.mkv, filename, ''
def choose_dvd_drive_or_file force_choose_only_dvd_drive
opticals = DriveInfo.get_dvd_drives_as_openstruct
if @saved_opticals == opticals && @_choose_dvd_drive_or_file
# memoize...if disks haven't changed :)
return @_choose_dvd_drive_or_file
else
@saved_opticals = opticals # save currently mounted disk list, so we know if we should re-select later...
# is this ok for os x?
end
has_at_least_one_dvd_inserted = opticals.find{|d| d.VolumeName }
if !has_at_least_one_dvd_inserted && force_choose_only_dvd_drive
show_blocking_message_dialog 'insert a dvd first'
raise 'no dvd found'
end
names = opticals.map{|d| d.Name + "\\" + " (" + (d.VolumeName || 'Insert DVD to use') + ")"}
if !force_choose_only_dvd_drive && !has_at_least_one_dvd_inserted
names += ['No DVD mounted so choose Local File (or insert DVD, re-try)'] # LODO cannot read it...
used_local_file_option = true
end
count = 0
opticals.each{|d| count += 1 if d.VolumeName}
if count == 1 && !used_local_file_option
# just choose it if there's only one disk available..
p 'selecting only disk present in the various DVD drives'
selected_idx = opticals.index{|d| d.VolumeName}
unless selected_idx
show_blocking_message_dialog "Please insert a disk first"
raise 'inset disk'
end
else
dialog = get_disk_chooser_window names
dialog.setSize 200, 125
dialog.show
selected_idx = dialog.selected_idx
end
if selected_idx
if used_local_file_option
raise unless selected_idx == 0 # it was our only option...
filename = new_existing_file_selector_and_select_file("Select yer previously grabbed from DVD file")
assert_ownership_dialog
return [filename, File.basename(filename), NonDvd]
else
disk = opticals[selected_idx]
out = show_non_blocking_message_dialog "calculating disk's unique id..." # useful, believe it or not
dvd_id = DriveInfo.md5sum_disk(disk.MountPoint)
out.dispose
@_choose_dvd_drive_or_file = [disk.MountPoint, opticals[selected_idx].VolumeName, dvd_id]
return @_choose_dvd_drive_or_file
end
else
raise 'did not select a drive...'
end
end
end
class GetDisk < JDialog
attr_reader :selected_idx
def initialize parent, options_array
super parent, true
box = JComboBox.new
box.add_action_listener do |e|
idx = box.get_selected_index
if idx != 0
# don't count choosing the first as a real entry
@selected_idx = box.get_selected_index - 1
dispose
end
end
box.add_item "Click to select DVD drive" # put something in index 0
options_array.each{|drive|
box.add_item drive
}
add box
pack # how do you get this arbitrary size? what the...
end
end
end
# LODO move to sane :) also remove the andand dep.
class String
def present?
length > 0
end
end
class NilClass
def present?
false
end
end
class Object
def present?
true
end
end
class Array
def present?
length > 0
end
end
if $0 == __FILE__
if ARGV.index('-h') || ARGV.index('--help')
puts <<-EOL
# jruby bin/sensible-cinema # DVD/file EDL player
$ jruby bin/sensible-cinema --create-mode # DVD output "parser/cutter" create mode [advanced]
$ jruby bin/sensible-cinema --upconvert-mode # DVD/video upconverting player
$ jruby bin/sensible-cinema-cli # the experimental OCR tracker for arbitrary online/local/internet players.
See "Advanced" section in the developer_how_to.txt file.
--exit-immediately [don't show the GUI, for benchmarking startup times]
EOL
else
a = SensibleSwing::MainWindow.new
a.set_visible true
puts 'Please use the Sensible Cinema GUI window popup...'
if ARGV.index('--exit-immediately')
a.dispose
end
end
end
# icon derived from: http://www.threes.com/index.php?option=com_content&view=article&id=1800:three-wise-monkeys&catid=82:mythology&Itemid=62