/usr/local/CPAN/dvdrip/Video/DVDRip/GUI/Main.pm
# $Id: Main.pm 2372 2009-02-22 18:30:38Z joern $
#-----------------------------------------------------------------------
# Copyright (C) 2001-2006 Jörn Reder <joern AT zyn.de>.
# All Rights Reserved. See file COPYRIGHT for details.
#
# This module is part of Video::DVDRip, which is free software; you can
# redistribute it and/or modify it under the same terms as Perl itself.
#-----------------------------------------------------------------------
package Video::DVDRip::GUI::Main;
use strict;
use base qw(Video::DVDRip::GUI::Base);
use Locale::TextDomain qw (video.dvdrip);
use Gtk2;
use Gtk2::Ex::FormFactory 0.65;
use File::Basename;
use Video::DVDRip::Project;
use Video::DVDRip::Logger;
use Video::DVDRip::JobPlanner;
use Video::DVDRip::GUI::Context;
use Video::DVDRip::GUI::Project::Storage;
use Video::DVDRip::GUI::Project::Title;
use Video::DVDRip::GUI::Project::ClipZoom;
use Video::DVDRip::GUI::Project::Subtitle;
use Video::DVDRip::GUI::Project::Transcode;
use Video::DVDRip::GUI::Project::Logging;
use Video::DVDRip::GUI::Progress;
use Video::DVDRip::GUI::ExecFlow;
use Video::DVDRip::GUI::Rules;
sub get_form_factory { shift->{form_factory} }
sub get_gtk_icon_factory { shift->{gtk_icon_factory} }
sub set_form_factory { shift->{form_factory} = $_[1] }
sub set_gtk_icon_factory { shift->{gtk_icon_factory} = $_[1] }
sub get_gui_state_file {
return $ENV{HOME}."/.dvdrip/main-gui.state";
}
sub save_gui_state {
my $self = shift;
my ($win_width, $win_height) =
$self->get_form_factory
->get_widget("main_window")
->get_gtk_parent_widget
->get_size;
my $file = $self->get_gui_state_file;
open (my $fh, "> $file") or die "can't write $file";
print $fh $win_width."\t".
$win_height."\n";
close $fh;
1;
}
sub load_gui_state {
my $self = shift;
my $file = $self->get_gui_state_file;
if ( ! -f $file ) {
return ( default_width => 600, default_height => 500 );
}
open (my $fh, $file) or die "can't read $file";
my $line = <$fh>;
close $fh;
chomp $line;
my @pos = split("\t", $line);
return ( default_width => $pos[0], default_height => $pos[1] );
}
sub window_name {
my $self = shift;
my $context = $self->get_context;
my $project = $context->get_object("project");
if ($project) {
return $self->config('program_name') . " - " . $project->name;
}
else {
return $self->config('program_name') . " - <no project>";
}
}
sub new {
my $class = shift;
my $self = $class->SUPER::new(@_);
my $logger = Video::DVDRip::Logger->new;
$self->set_logger($logger);
return $self;
}
sub start {
my $self = shift;
my %par = @_;
my ( $filename, $open_cluster_control, $function, $select_title )
= @par{ 'filename', 'open_cluster_control', 'function',
'select_title' };
Gtk2->init;
my $context = Video::DVDRip::GUI::Context->create;
$self->set_context($context);
if ( !$open_cluster_control ) {
$self->build;
}
$context->set_object( job_planner => Video::DVDRip::JobPlanner->new );
$context->set_object(
exec_flow_gui => Video::DVDRip::GUI::ExecFlow->new(
context => $context,
form_factory => $self->get_form_factory,
)
);
Glib->install_exception_handler(
sub {
my ($msg) = @_;
if ( $msg =~ /^msg:\s*(.*?)\s+at.*?line\s+\d+/s ) {
$self->error_window( message => $1, );
}
else {
my $error = __x(
"An internal exception was thrown!\n"
. "The error message was:\n\n{msg}",
msg => $msg
);
$self->long_message_window( message => $error );
}
1;
}
);
my $project;
if ( $self->dependencies_ok ) {
if ($filename) {
$self->open_project_file( filename => $filename );
$project = $self->get_context_object("project");
}
$self->log(
__x("Detected transcode version: {version}",
version => $self->version("transcode")
)
);
# Open Cluster Control window, if requested
$self->cluster_control( exit_on_close => 1 )
if $open_cluster_control;
# Error check
if ( ( $select_title or $function )
and not $project
and $function ne 'preferences' ) {
$self->error_dialog(
message => __ "Opening project file failed. Aborting." );
return 0;
}
$self->update_window_title;
Glib::Idle->add(
sub {
# Select a title, if requested
if ($select_title) {
$self->log("Selecting title $select_title");
$context->set_object_attr( "content.selected_title_nr",
$select_title );
$project->content->set_selected_title_nr($select_title);
}
# Execute a function, if requested
if ( $function eq 'preferences' ) {
$self->edit_preferences(1);
}
elsif ($function) {
my $title = $self->selected_title;
$title->set_tc_exit_afterwards('dont_save');
$title->set_tc_split(1) if $function eq 'transcode_split';
$self->get_context_object("transcode")->transcode;
}
return 0;
}
);
}
Gtk2->main;
1;
}
sub build {
my $self = shift;
$self->build_icon_factory;
my $context = $self->get_context;
$context->set_object( main => $self );
my $rule_checker = Video::DVDRip::GUI::Rules->new;
my $window;
my $ff = Gtk2::Ex::FormFactory->new(
context => $context,
rule_checker => $rule_checker,
sync => 1,
content => [
$window = Gtk2::Ex::FormFactory::Window->new(
attr => "main.window_name",
name => "main_window",
customize_hook => sub {
my ($gtk_window) = @_;
$_[0]->parent->set(
$self->load_gui_state,
);
1;
},
closed_hook => sub { $self->exit_program; 1 },
),
],
);
#-- FormFactory need to be set into the object
#-- before building the other dependent factories
$self->set_form_factory($ff);
$window->set_content(
[
$self->build_menu_factory,
$self->build_project_factory,
]
);
$ff->open;
$ff->update;
$self->set_wm_icon;
return 1;
}
sub set_wm_icon {
my $self = shift;
my $gtk_window
= $self->get_form_factory->get_widget("main_window")->get_gtk_widget;
my $icon_file
= $self->search_perl_inc( rel_path => "Video/DVDRip/icon.xpm" );
if ($icon_file) {
my ( $icon, $mask )
= Gtk2::Gdk::Pixmap->create_from_xpm( $gtk_window->window,
$gtk_window->style->white, $icon_file );
$gtk_window->window->set_icon( undef, $icon, $mask );
$gtk_window->window->set_icon_name( $self->config('program_name') );
}
1;
}
sub build_menu_factory {
my $self = shift;
my $context = $self->get_context;
return Gtk2::Ex::FormFactory::Menu->new(
menu_tree => [
__"_File" => {
item_type => '<Branch>',
children => [
__"_New Project" => {
item_type => '<StockItem>',
extra_data => 'gtk-new',
callback => sub { $self->new_project },
accelerator => '<ctrl>N',
active_cond => sub { !$self->progress_is_active },
active_depends => "progress.is_active",
},
__"_Open Project..." => {
item_type => '<StockItem>',
extra_data => 'gtk-open',
callback => sub { $self->open_project },
accelerator => '<ctrl>o',
active_cond => sub { !$self->progress_is_active },
active_depends => "progress.is_active",
},
__"_Save Project" => {
item_type => '<StockItem>',
extra_data => 'gtk-save',
callback => sub { $self->save_project },
accelerator => '<ctrl>s',
object => 'project',
},
__"Save _Project as..." => {
item_type => '<StockItem>',
extra_data => 'gtk-save-as',
callback => sub { $self->save_project_as },
accelerator => '<shift><ctrl>s',
object => 'project',
},
__"_Close Project" => {
item_type => '<StockItem>',
extra_data => 'gtk-close',
callback => sub { $self->close_project },
accelerator => '<ctrl>w',
object => 'project',
active_cond => sub {
$context->get_object("project") &&
!$self->progress_is_active
},
active_depends => [ "project", "progress.is_active"],
},
"sep_quit" => { item_type => '<Separator>', },
__"_Exit" => {
item_type => '<StockItem>',
extra_data => 'gtk-quit',
callback => sub { $self->exit_program },
accelerator => '<ctrl>q',
},
],
},
__"_Edit" => {
item_type => '<Branch>',
children => [
__ "Edit _Preferences..." => {
item_type => '<StockItem>',
extra_data => 'gtk-preferences',
callback => sub { $self->edit_preferences },
accelerator => '<ctrl>p',
},
],
},
__"_Title" => {
item_type => '<Branch>',
children => [
__"Transcode" => {
item_type => '<StockItem>',
extra_data => 'gtk-convert',
callback => sub {
$context->get_object("transcode")->transcode;
},
object => 'title',
active_cond => sub { $context->get_object("title") &&
!$self->progress_is_active },
active_depends => "progress.is_active",
},
__"View target file" => {
item_type => '<StockItem>',
extra_data => 'gtk-media-play',
callback => sub {
$context->get_object("transcode")->view_avi;
},
object => 'title',
active_cond => sub { $context->get_object("title") &&
!$self->progress_is_active },
active_depends => "progress.is_active",
},
__"Add project to cluster" => {
item_type => '<StockItem>',
extra_data => 'gtk-network',
callback => sub {
$context->get_object("transcode")->add_to_cluster;
},
object => 'title',
active_cond => sub {
my $title = $self->selected_title;
return 0 if not $title;
return $title->tc_container ne 'vcd';
},
active_depends => "title.tc_container",
},
"sep_vobsub" => { item_type => '<Separator>', },
__"Create vobsub" => {
callback => sub {
$context->get_object("subtitle_gui")
->create_vobsub_now;
},
object => 'subtitle',
active_cond => sub { $context->get_object("title") &&
!$self->progress_is_active },
active_depends => "progress.is_active",
},
"sep_info" => { item_type => '<Separator>', },
__"Create dvdrip-info file" => {
callback => sub {
my $title = $context->get_object("title");
return 1 if not $title;
require Video::DVDRip::InfoFile;
Video::DVDRip::InfoFile->new(
title => $title,
filename => $title->info_file,
)->write;
1;
},
object => 'title',
},
"sep_wav" => { item_type => '<Separator>', },
__"Create WAV from selected audio track" => {
callback => sub {
$context->get_object("transcode")->create_wav;
},
object => 'title',
active_cond => sub { $context->get_object("title") &&
!$self->progress_is_active },
active_depends => "progress.is_active",
},
],
},
__"_Cluster" => {
item_type => '<Branch>',
children => [
__ "Contro_l..." => {
item_type => '<StockItem>',
extra_data => 'gtk-network',
callback => sub { $self->cluster_control },
accelerator => '<ctrl>m',
},
],
},
__"_Debug" => {
item_type => '<Branch>',
children => [
__
"Show _Transcode commands..." => {
item_type => '<Item>',
callback => sub { $self->show_transcode_commands },
accelerator => '<ctrl>t',
object => 'title',
},
__ "Check _dependencies..." => {
item_type => '<Item>',
callback => sub { $self->show_dependencies },
accelerator => '<ctrl>d',
},
],
},
__"_Help" => {
item_type => '<LastBranch>',
children => [
__"_About dvd::rip" => {
callback => sub { $self->show_about_dialog },
item_type => '<StockItem>',
extra_data => 'gtk-about',
accelerator => '<ctrl>A',
},
]
},
],
);
}
sub build_project_factory {
my $self = shift;
my $context = $self->get_context;
my $storage = Video::DVDRip::GUI::Project::Storage->new(
form_factory => $self->get_form_factory, );
my $title = Video::DVDRip::GUI::Project::Title->new(
form_factory => $self->get_form_factory, );
my $clip_zoom = Video::DVDRip::GUI::Project::ClipZoom->new(
form_factory => $self->get_form_factory, );
my $subtitle = Video::DVDRip::GUI::Project::Subtitle->new(
form_factory => $self->get_form_factory, );
my $transcode = Video::DVDRip::GUI::Project::Transcode->new(
form_factory => $self->get_form_factory, );
my $logging = Video::DVDRip::GUI::Project::Logging->new(
form_factory => $self->get_form_factory, );
my $progress = Video::DVDRip::GUI::Progress->new(
form_factory => $self->get_form_factory, );
return Gtk2::Ex::FormFactory::VBox->new(
object => "project",
inactive => "invisible",
expand => 1,
content => [
Gtk2::Ex::FormFactory::Notebook->new(
name => "main_nb",
attr => "project.last_selected_nb_page",
expand => 1,
$self->get_optimum_screen_size_options("notebook"),
content => [
$storage->build_factory, $title->build_factory,
$clip_zoom->build_factory, $subtitle->build_factory,
$transcode->build_factory, $logging->build_factory,
],
),
$progress->build_factory,
],
);
}
sub build_selected_title_factory {
my $self = shift;
return Gtk2::Ex::FormFactory::HBox->new(
object => "title",
title => __ "Selected DVD title",
content => [
Gtk2::Ex::FormFactory::Popup->new(
attr => "content.selected_title_nr",
tip => __"Choose a DVD title at any time here",
),
Gtk2::Ex::FormFactory::Label->new(
attr => "title.get_title_info",
),
],
);
}
sub build_icon_factory {
my $self = shift;
my $icon_factory = Gtk2::IconFactory->new;
$icon_factory->add_default;
$self->set_gtk_icon_factory($icon_factory);
my $icon_dir
= $self->search_perl_inc( rel_path => "Video/DVDRip/GUI/Icons" );
return if not -d $icon_dir;
my ( $pixbuf, $icon_set, $name );
foreach my $icon_file ( glob("$icon_dir/*.png") ) {
$pixbuf = Gtk2::Gdk::Pixbuf->new_from_file($icon_file);
$icon_set = Gtk2::IconSet->new_from_pixbuf($pixbuf);
$name = basename($icon_file);
$name =~ s/\.[^.]+$//;
$icon_factory->add( $name, $icon_set );
}
1;
}
sub new_project {
my $self = shift;
return if not $self->dependencies_ok;
return if $self->unsaved_project_open( wants => "new_project" );
$self->close_project;
my $project = Video::DVDRip::Project->new;
$self->get_context->set_object( project => $project );
$self->get_context->set_object( "!project" => undef );
$self->get_context->get_object("job_planner")->set_project($project);
$self->update_window_title;
my $gtk_entry
= $self->get_form_factory->get_widget("project_name")->get_gtk_widget;
$gtk_entry->select_region( 0, -1 );
$gtk_entry->grab_focus;
1;
}
sub update_window_title {
my $self = shift;
$self->get_context->update_object_attr_widgets("main.window_name");
1;
}
sub open_project {
my $self = shift;
return if not $self->dependencies_ok;
return if $self->unsaved_project_open( wants => "open_project" );
$self->close_project;
$self->show_file_dialog(
dir => $self->config('dvdrip_files_dir'),
filename => "",
cb => sub {
$self->open_project_file( filename => $_[0] );
},
);
}
sub open_project_file {
my $self = shift;
my %par = @_;
my ($filename) = @par{'filename'};
return if not $self->dependencies_ok;
if ( not -r $filename ) {
$self->message_window(
message => __x(
"File '{filename}' not found or not readable.",
filename => $filename
)
);
return 1;
}
my $project
= Video::DVDRip::Project->new_from_file( filename => $filename );
my $context = $self->get_context;
$context->set_object( project => $project );
$context->set_object( "!project" => undef );
$self->get_context->get_object("job_planner")->set_project($project);
$self->logger->set_project($project);
$self->logger->insert_project_logfile;
$self->update_window_title;
if ( $project->convert_message ) {
$self->message_window( message => $project->convert_message );
$project->set_convert_message("");
}
1;
}
sub save_project {
my $self = shift;
my $context = $self->get_context;
my $project = $context->get_object("project");
my $created = $project->created;
if ( $project->filename ) {
# save
$project->save;
# reset changed flag
$self->get_context->get_proxy("project")->set_object_changed(0);
# greys out "Project name" widget
$context->update_object_attr_widgets("project.name");
return 1;
}
else {
$self->show_file_dialog(
title => __ "Save project: choose filename",
type => "save",
dir => $self->config('dvdrip_files_dir'),
filename => $project->name . ".rip",
confirm => 1,
cb => sub {
$project->set_filename( $_[0] );
$self->save_project;
unless ($created) {
$context->update_object_widgets("project");
$self->logger->set_project($project);
$self->log( __x "Project {name} created",
name => $project->name );
}
},
);
return 0;
}
}
sub save_project_as {
my $self = shift;
my $context = $self->get_context;
my $project = $context->get_object("project");
my $created = $project->created;
$self->show_file_dialog(
dir => $self->config('dvdrip_files_dir'),
filename => $project->name . ".rip",
confirm => 1,
cb => sub {
$project->set_filename( $_[0] );
$project->save;
$self->update_window_title;
unless ($created) {
$context->update_object_widgets("project");
$self->logger->set_project($project);
$self->log( __x "Project {name} created", name => $project->name );
}
},
);
return 1;
}
sub close_project {
my $self = shift;
my %par = @_;
my ($dont_ask) = @par{'dont_ask'};
return if not $dont_ask and $self->unsaved_project_open;
$self->get_context->set_object( project => undef );
$self->get_context->set_object( "!project" => 1 );
$self->get_context->get_object("job_planner")->set_project();
$self->update_window_title;
1;
}
sub project_changed {
return $_[0]->get_context->get_proxy("project")->get_object_changed;
}
sub unsaved_project_open {
my $self = shift;
my %par = @_;
my ($wants) = @par{'wants'};
return if ! $self->get_context->get_object("project");
return if ! $self->project_changed;
$self->confirm_window(
message => __ "Do you want to save this project first?",
yes_label => __ "Yes",
no_label => __ "No",
yes_callback => sub {
if ( $self->save_project ) {
$self->close_project( dont_ask => 1 );
$self->$wants() if $wants;
}
},
no_callback => sub {
$self->close_project( dont_ask => 1 );
$self->$wants() if $wants;
},
with_cancel => 1,
);
1;
}
sub exit_program {
my $self = shift;
my %par = @_;
my ($force) = @par{'force'};
$self->save_gui_state;
return 1
if not $force
and $self->unsaved_project_open( wants => "exit_program" );
$self->close_project( dont_ask => $force );
$self->get_form_factory->close;
Gtk2->main_quit;
}
sub edit_preferences {
my $self = shift;
my ($init) = @_;
require Video::DVDRip::GUI::Preferences;
my $pref = Video::DVDRip::GUI::Preferences->new(
form_factory => $self->get_form_factory, );
$pref->open_window;
$self->message_window (
modal => 1,
message =>
__"This is the first time you start dvd::rip. The Preferences ".
"dialog was opened automatically for you. Please review the settings, ".
"in particular the default DVD device and your data base directory. ".
"Point it to a directory with sufficient diskspace of at least ".
"10 GB for full DVD copies.",
) if $init;
1;
}
sub cluster_control {
my $self = shift;
my %par = @_;
my ($exit_on_close) = @par{'exit_on_close'};
if ( $self->get_context->get_object("cluster_gui") ) {
my $cluster = $self->get_context->get_object("cluster_gui");
$cluster->connect_master if !$cluster->master;
return;
}
return if not $self->dependencies_ok;
require Video::DVDRip::GUI::Cluster::Control;
my $cluster = Video::DVDRip::GUI::Cluster::Control->new(
context => $self->get_context,
form_factory => $self->get_form_factory,
);
$cluster->set_exit_on_close($exit_on_close);
$cluster->open_window;
if ( !$cluster->master ) {
return if !$cluster->connect_master;
}
1;
}
sub show_transcode_commands {
my $self = shift;
my $title = $self->selected_title;
return unless $title;
my $commands = "";
$commands .= __ "Probe Command:\n"
. "==============\n"
. $title->get_probe_command() . "\n";
$commands .= "\n\n";
if ( $title->project->rip_mode eq 'rip' ) {
my $rip_method =
$title->tc_use_chapter_mode
? "get_rip_command"
: "get_rip_and_scan_command";
$commands .= __ "Rip Command:\n"
. "============\n"
. $title->$rip_method() . "\n";
}
else {
$commands .= __ "Scan Command:\n"
. "============\n"
. $title->get_scan_command() . "\n";
}
$commands .= "\n\n";
$commands .= __ "Grab Preview Image Command:\n"
. "===========================\n";
eval {
$commands .= $title->get_take_snapshot_command(
frame => $title->preview_frame_nr )
. "\n";
};
if ($@) {
$commands .= __
"You must first rip the selected title to see this command.\n";
}
$commands .= "\n\n";
$commands .= __ "Transcode Command:\n" . "==================\n";
if ( $title->tc_multipass ) {
$commands
.= $title->get_transcode_command( pass => 1, split => 1 ) . "\n"
. $title->get_transcode_command( pass => 2, split => 1 ) . "\n";
}
else {
$commands .= $title->get_transcode_command( split => 1 ) . "\n";
}
if ( $title->tc_container eq 'vcd' ) {
$commands .= "\n" . $title->get_mplex_command( split => 1 ), "\n";
}
if ( $title->is_ogg ) {
$commands .= "\n"
. $title->get_merge_audio_command(
vob_nr => $title->get_first_audio_track,
avi_nr => 0,
),
"\n";
}
$commands .= "\n\n";
my $add_audio_tracks = $title->get_additional_audio_tracks;
if ( keys %{$add_audio_tracks} ) {
$commands .= __ "Additional audio tracks commands:\n"
. "============================\n";
my ( $avi_nr, $vob_nr );
while ( ( $avi_nr, $vob_nr ) = each %{$add_audio_tracks} ) {
$commands .= "\n"
. $title->get_transcode_audio_command(
vob_nr => $vob_nr,
target_nr => $avi_nr,
)
. "\n";
$commands .= "\n"
. $title->get_merge_audio_command(
vob_nr => $vob_nr,
target_nr => $avi_nr,
);
}
$commands .= "\n\n";
}
$commands .= __ "View DVD Command:\n"
. "=================\n"
. $title->get_view_dvd_command(
command_tmpl => $self->config('play_dvd_command') )
. "\n";
$commands .= "\n\n";
$commands .= __ "View Files Command:\n" . "===================\n"
. $title->get_view_avi_command(
command_tmpl => $self->config('play_file_command'),
file => "movie.avi",
)
. "\n";
my $create_image_command;
eval { $create_image_command = $title->get_create_image_command; };
$create_image_command = $self->stripped_exception if $@;
my $burn_command;
eval { $burn_command = $title->get_burn_command; };
$burn_command = $self->stripped_exception if $@;
$commands .= "\n\n";
$commands .= __ "CD image creation command:\n"
. "========================\n"
. $create_image_command;
$commands .= "\n\n";
$commands .= __ "CD burning command:\n"
. "==================\n"
. $burn_command;
$commands .= "\n\n";
my $wav_command = eval { $title->get_create_wav_command };
if ($wav_command) {
$commands .= __ "WAV creation command:\n"
. "====================\n"
. $wav_command;
}
$self->long_message_window(
title => __ "Commands executed by dvd::rip",
message => $commands
);
1;
}
sub show_dependencies {
my $self = shift;
require Video::DVDRip::GUI::Depend;
my $depend = Video::DVDRip::GUI::Depend->new(
form_factory => $self->get_form_factory );
$depend->open_window;
1;
}
sub dependencies_ok {
my $self = shift;
return 1 if $self->depend_object->ok;
$self->show_dependencies;
$self->message_window( message => __
"One or several mandatory tools are\nmissing or too old. You must install them\nbefore you can proceed with dvd::rip."
);
return 0;
}
sub show_about_dialog {
my $self = shift;
my $license = $self->read_text_file("license.txt");
my $translators = $self->read_text_file("translators.txt");
my $splash_lang_file = __"splash.en.png";
my $logo_image = Gtk2::Ex::FormFactory->get_image_path (
"Video/DVDRip/$splash_lang_file"
);
my $logo_pixbuf = $logo_image ?
Gtk2::Gdk::Pixbuf->new_from_file($logo_image) :
undef;
my $about = Gtk2::AboutDialog->new;
$about->set (
name => "dvd::rip",
version => $Video::DVDRip::VERSION,
logo => $logo_pixbuf,
license => $license,
translator_credits => $translators,
);
$about->run;
$about->destroy;
1;
}
sub read_text_file {
my $self = shift;
my ($name) = @_;
my $file = Gtk2::Ex::FormFactory->get_image_path("Video/DVDRip/$name");
open (my $fh, $file) or die "can't read $file [$name]";
my $text = do { local $/=undef; <$fh> };
close $fh;
return $text;
}
1;