1# $Id: Subtitle.pm 2344 2007-08-09 21:37:41Z joern $ 2 3#----------------------------------------------------------------------- 4# Copyright (C) 2001-2006 J�rn Reder <joern AT zyn.de>. 5# All Rights Reserved. See file COPYRIGHT for details. 6# 7# This module is part of Video::DVDRip, which is free software; you can 8# redistribute it and/or modify it under the same terms as Perl itself. 9#----------------------------------------------------------------------- 10 11package Video::DVDRip::GUI::Project::Subtitle; 12 13use base qw( Video::DVDRip::GUI::Base ); 14 15use Locale::TextDomain qw (video.dvdrip); 16 17use Carp; 18use strict; 19 20use Video::DVDRip::GUI::FormFactory::SubtitlePreviews; 21 22sub new { 23 my $class = shift; 24 25 my $self = $class->SUPER::new(@_); 26 27 return $self; 28} 29 30sub build_factory { 31 my $self = shift; 32 33 my $context = $self->get_context; 34 35 $context->set_object( subtitle_gui => $self ); 36 37 return Gtk2::Ex::FormFactory::VBox->new( 38 $self->get_optimum_screen_size_options("page"), 39 title => '[gtk-underline]'.__ "Subtitles", 40 object => "title", 41 active_cond => sub { 42 $self->selected_title 43 && $self->project 44 && $self->project->created; 45 }, 46 active_depends => "project.created", 47 no_frame => 1, 48 content => [ 49 Video::DVDRip::GUI::Main->build_selected_title_factory, 50 Gtk2::Ex::FormFactory::VBox->new( 51 object => "subtitle", 52 inactive => "invisible", 53 expand => 1, 54 content => [ 55 $self->build_select_box, $self->build_preview_box, 56 $self->build_render_box, $self->build_vobsub_box, 57 ], 58 ), 59 Gtk2::Ex::FormFactory::Label->new( 60 label => "\n<b>" . __("This title has no subtitles") . "</b>", 61 with_markup => 1, 62 inactive => "invisible", 63 active_cond => sub { 64 !( $self->selected_title && $self->selected_title->has_subtitles ) 65 }, 66 active_depends => "title", 67 ), 68 ], 69 ); 70} 71 72sub build_select_box { 73 my $self = shift; 74 75 return Gtk2::Ex::FormFactory::HBox->new( 76 title => __ "Subtitle selection", 77 content => [ 78 Gtk2::Ex::FormFactory::Popup->new( 79 attr => "title.selected_subtitle_id", 80 label => __ "Select subtitle", 81 ), 82 Gtk2::Ex::FormFactory::Label->new( 83 attr => "title.subtitles_activated", 84 label => __ "Activated:", 85 ), 86 ], 87 ); 88} 89 90sub build_preview_box { 91 my $self = shift; 92 93 my $preview_event_box = Gtk2::EventBox->new; 94 $preview_event_box->modify_bg( "normal", 95 Gtk2::Gdk::Color->parse("#ffffff") ); 96 97 return Gtk2::Ex::FormFactory::VBox->new( 98 title => __ "Preview", 99 expand => 1, 100 content => [ 101 Gtk2::Ex::FormFactory::HBox->new( 102 content => [ 103 Gtk2::Ex::FormFactory::Button->new( 104 name => "subtitle_grab_button", 105 object => "subtitle", 106 label => __ "Grab", 107 stock => "gtk-copy", 108 clicked_hook => sub { 109 $self->grab_subtitle_preview_images; 110 }, 111 inactive => "invisible", 112 active_cond => sub { 113 my ($subtitle) = @_; 114 return 0 unless $subtitle; 115 return 0 if $subtitle->is_ripped; 116 return $self->progress_is_active ? 117 'insensitive' : 'sensitive'; 118 }, 119 active_depends => ["subtitle.is_ripped", "progress.is_active" ], 120 ), 121 Gtk2::Ex::FormFactory::Combo->new( 122 attr => "subtitle.tc_preview_img_cnt", 123 presets => [ 1, 3, 5, 10, 20, 30, 50, 100 ], 124 width => 60, 125 rules => "positive-integer", 126 ), 127 Gtk2::Ex::FormFactory::Label->new( 128 label => __ "image(s)", 129 ), 130 Gtk2::Ex::FormFactory::Entry->new( 131 attr => "subtitle.tc_preview_timecode", 132 label => __ "starting at", 133 width => 60, 134 rules => sub { 135 my ($value) = @_; 136 my $frames = $self->selected_title->frames; 137 return 138 if $value =~ /^\d+$/ 139 and $value <= $frames; 140 return if $value =~ /^\d\d:\d\d:\d\d$/; 141 return __x( "Movie has only {cnt} frames", 142 cnt => $frames ) 143 if $value =~ /^\d+$/ 144 and $value > $frames; 145 return __ "Invalid time/frame number format"; 146 }, 147 ), 148 Gtk2::Ex::FormFactory::Label->new( 149 label => __ "(timecode nn:nn:nn or frame number)", 150 ), 151 ], 152 ), 153 Video::DVDRip::GUI::FormFactory::SubtitlePreviews->new( 154 attr => "subtitle.preview_dir", 155 attr_image_cnt => "subtitle.tc_preview_img_cnt", 156 attr_start_time => "subtitle.tc_preview_timecode", 157 expand => 1, 158 ), 159 ], 160 ); 161} 162 163sub build_render_box { 164 my $self = shift; 165 166 my $context = $self->get_context; 167 168 return Gtk2::Ex::FormFactory::Table->new( 169 object => "subtitle", 170 active_cond => 171 sub { $context->get_object_attr("title.subtitle_render_ok") }, 172 active_depends => "subtitle.tc_vobsub", 173 title => __ "Render subtitle on movie", 174 layout => " 175 +--------+------------+>>>+--------+-----------+>>>+-----------+-------+ 176 | ActLab | RendCheckb | S | Colors | ColCheckB | S | PrevImCnt | Entry | 177 +--------+-------+----+ +--------+-----+-----+ +-----------+-------+ 178 | VOff | Entry | Un | | Gray | Ent | Ent | | PrevWin | Open | 179 +--------+-------+----+ +--------+-----+-----+ +-----------+-------+ 180 | TimeS | Entry | Un | | Index | Pop | Pop | | | 181 +--------+-------+----+ +--------+-----+-----+---+-------------------+ 182 | PostPr | Antiaalias | | Suggst | Letterbox | | Full Size Movie | 183 +--------+-------+----+---+--------+-----------+---+-------------------+ 184 ", 185 content => [ 186 187 #-- Row #1 188 Gtk2::Ex::FormFactory::Label->new( 189 label => __ "Activate this subtitle" . " ", 190 label_group => "subtitle1", 191 ), 192 Gtk2::Ex::FormFactory::CheckButton->new( 193 attr => "subtitle.tc_render", 194 label => __ "for rendering", 195 ), 196 Gtk2::Ex::FormFactory::VSeparator->new, 197 Gtk2::Ex::FormFactory::Label->new( 198 label => __ "Colors" . " ", 199 for => "subtitle.tc_color_manip", 200 ), 201 Gtk2::Ex::FormFactory::CheckButton->new( 202 attr => "subtitle.tc_color_manip", 203 label => __ "Enable manipulation", 204 ), 205 Gtk2::Ex::FormFactory::VSeparator->new, 206 Gtk2::Ex::FormFactory::Label->new( 207 label => __ "Preview image count" . " ", 208 for => "subtitle.tc_test_image_cnt", 209 ), 210 Gtk2::Ex::FormFactory::Entry->new( 211 attr => "subtitle.tc_test_image_cnt", 212 width => 50, 213 rules => "positive-integer", 214 ), 215 216 #-- Row #2 217 Gtk2::Ex::FormFactory::Label->new( 218 label => __ "Vertical offset" . " ", 219 for => "subtitle.tc_vertical_offset", 220 ), 221 Gtk2::Ex::FormFactory::Entry->new( 222 attr => "subtitle.tc_vertical_offset", 223 width => 50, 224 rules => "integer", 225 ), 226 Gtk2::Ex::FormFactory::Label->new( 227 label => __ "rows", 228 active_cond => 229 sub { $context->get_object_attr("subtitle.tc_render") }, 230 ), 231 Gtk2::Ex::FormFactory::Label->new( 232 label => __ "Gray A/B" . " ", 233 for => "subtitle.tc_color_a", 234 ), 235 Gtk2::Ex::FormFactory::Entry->new( 236 attr => "subtitle.tc_color_a", 237 width => 50, 238 rules => sub { 239 $_[0] =~ /^\d+$/ && $_[0] >= 0 && $_[0] <= 255 240 ? undef 241 : __ "Value not between 0 and 255"; 242 }, 243 ), 244 Gtk2::Ex::FormFactory::Entry->new( 245 attr => "subtitle.tc_color_b", 246 width => 50, 247 rules => sub { 248 $_[0] =~ /^\d+$/ && $_[0] >= 0 && $_[0] <= 255 249 ? undef 250 : __ "Value not between 0 and 255"; 251 }, 252 ), 253 Gtk2::Ex::FormFactory::Label->new( 254 label => __ "Preview window" . " ", 255 active_cond => sub { 256 return 0 unless $context->get_object_attr("subtitle.tc_render"); 257 return !$self->progress_is_active; 258 }, 259 active_depends => [ "subtitle.tc_render", "progress.is_active" ], 260 ), 261 Gtk2::Ex::FormFactory::Button->new( 262 stock => "gtk-media-play", 263 label => __ "Open", 264 object => "subtitle", 265 clicked_hook => sub { $self->subtitle_preview_window }, 266 active_cond => sub { 267 return 0 unless $context->get_object_attr("subtitle.tc_render"); 268 return !$self->progress_is_active; 269 }, 270 active_depends => [ "subtitle.tc_render", "progress.is_active" ], 271 ), 272 273 #-- Row #3 274 Gtk2::Ex::FormFactory::Label->new( 275 label => __ "Time shift" . " ", 276 for => "subtitle.tc_time_shift", 277 ), 278 Gtk2::Ex::FormFactory::Entry->new( 279 attr => "subtitle.tc_time_shift", 280 width => 50, 281 rules => "positive-zero-integer", 282 ), 283 Gtk2::Ex::FormFactory::Label->new( 284 label => __ "ms", 285 active_cond => 286 sub { $context->get_object_attr("subtitle.tc_render") }, 287 active_depends => "subtitle.tc_render", 288 ), 289 Gtk2::Ex::FormFactory::Label->new( 290 label => __ "Index A/B" . " ", 291 for => "subtitle.tc_assign_color_a", 292 ), 293 Gtk2::Ex::FormFactory::Popup->new( 294 attr => "subtitle.tc_assign_color_a", 295 width => 50, 296 ), 297 Gtk2::Ex::FormFactory::Popup->new( 298 attr => "subtitle.tc_assign_color_b", 299 width => 50, 300 ), 301 302 #-- Row #4 303 Gtk2::Ex::FormFactory::CheckButton->new( 304 attr => "subtitle.tc_postprocess", 305 label => __ "Postprocessing" . " ", 306 ), 307 Gtk2::Ex::FormFactory::CheckButton->new( 308 attr => "subtitle.tc_antialias", 309 label => __ "Antialiasing", 310 ), 311 Gtk2::Ex::FormFactory::Label->new( 312 label => __ "Suggest" . " ", 313 active_cond => 314 sub { $context->get_object_attr("subtitle.tc_render") }, 315 active_depends => "subtitle.tc_render", 316 ), 317 Gtk2::Ex::FormFactory::Button->new( 318 label => __ "Values for letterbox", 319 object => "subtitle", 320 active_cond => 321 sub { $context->get_object_attr("subtitle.tc_render") }, 322 active_depends => "subtitle.tc_render", 323 clicked_hook => sub { 324 $self->suggest_render_black_bars; 325 }, 326 ), 327 Gtk2::Ex::FormFactory::Button->new( 328 label => __ "Values for full size movie", 329 object => "subtitle", 330 active_cond => 331 sub { $context->get_object_attr("subtitle.tc_render") }, 332 active_depends => "subtitle.tc_render", 333 clicked_hook => sub { 334 $self->suggest_render_full_size; 335 }, 336 ), 337 ], 338 ); 339} 340 341sub build_vobsub_box { 342 my $self = shift; 343 344 my $context = $self->get_context; 345 346 return Gtk2::Ex::FormFactory::Table->new( 347 object => "subtitle", 348 active_cond => 349 sub { !$context->get_object_attr("subtitle.tc_render") }, 350 active_depends => "subtitle.tc_render", 351 title => __ "Create vobsub file", 352 layout => " 353 +---------+---------+---------+---------------+ 354 | Now | Button | Button | Info | 355 +---------+---------+---------+---------------+ 356 | Later | after transcoding | Info | 357 +---------+-------------------+---------------+ 358 ", 359 content => [ 360 361 #-- Row #1 362 Gtk2::Ex::FormFactory::Label->new( 363 label => __ "Create now", 364 label_group => "subtitle1", 365 ), 366 Gtk2::Ex::FormFactory::Button->new( 367 label => __ "Create now", 368 clicked_hook => sub { $self->create_vobsub_now }, 369 active_cond => sub { !$self->progress_is_active }, 370 active_depends => "progress.is_active", 371 ), 372 Gtk2::Ex::FormFactory::Button->new( 373 label => __ "View vobsub", 374 clicked_hook => sub { $self->view_vobsub }, 375 ), 376 Gtk2::Ex::FormFactory::Label->new( 377 label => " " . __ "Only useful for single-CD-rips or after transcoding", 378 ), 379 380 #-- Row #2 381 Gtk2::Ex::FormFactory::Label->new( label => __ "Create later", ), 382 Gtk2::Ex::FormFactory::CheckButton->new( 383 attr => "subtitle.tc_vobsub", 384 label => __ "after transcoding", 385 ), 386 Gtk2::Ex::FormFactory::Label->new( 387 label => " " 388 . __ "This considers splitted files automaticly", 389 ), 390 ], 391 ); 392} 393 394sub suggest_render_black_bars { 395 my $self = shift; 396 397 my $title = $self->selected_title; 398 return 1 if not $title; 399 400 $title->suggest_subtitle_on_black_bars; 401 402 $self->get_context->get_object("clip_zoom")->make_previews; 403 $self->get_context->update_object_widgets("title"); 404 $self->get_context->update_object_widgets("bitrate_calc"); 405 $self->get_context->update_object_attr_widgets( 406 "subtitle.tc_vertical_offset"); 407 408 1; 409} 410 411sub suggest_render_full_size { 412 my $self = shift; 413 414 my $title = $self->selected_title; 415 return 1 if not $title; 416 417 $title->suggest_subtitle_on_movie; 418 419 $self->get_context->get_object("clip_zoom")->make_previews; 420 $self->get_context->update_object_widgets("title"); 421 $self->get_context->update_object_widgets("bitrate_calc"); 422 $self->get_context->update_object_attr_widgets( 423 "subtitle.tc_vertical_offset"); 424 425 1; 426} 427 428sub create_vobsub_now { 429 my $self = shift; 430 431 my $title = $self->selected_title; 432 return 1 if not $title; 433 434 my $subtitle = $title->selected_subtitle; 435 return 1 if not $subtitle; 436 437 my $job_planner = $self->get_context->get_object("job_planner"); 438 my $exec_flow_gui = $self->get_context->get_object("exec_flow_gui"); 439 my $job = $job_planner->build_vobsub_job($title, $subtitle); 440 441 $exec_flow_gui->start_job($job); 442 443 1; 444} 445 446sub view_vobsub { 447 my $self = shift; 448 449 my $title = $self->selected_title; 450 return 1 if not $title; 451 452 my $subtitle = $title->selected_subtitle; 453 return 1 if not $subtitle; 454 455 if ( not $self->has("mplayer") ) { 456 $self->message_window( 457 message => __ "You need Mplayer to view vobsub files." ); 458 return 1; 459 } 460 461 if ( $title->project->rip_mode ne 'rip' ) { 462 $self->message_window( 463 message => __ "This is only supported for ripped movies." ); 464 return 1; 465 } 466 467 if ( not $subtitle->vobsub_file_exists ) { 468 $self->message_window( 469 message => __ "What about creating the vobsub file first?" ); 470 return 1; 471 } 472 473 my $command = $title->get_view_vobsub_command( subtitle => $subtitle ); 474 475 $self->log( __ "Executing command: " . $command ); 476 477 system("$command &"); 478 479 1; 480} 481 482sub subtitle_preview_window { 483 my $self = shift; 484 485 my $title = $self->selected_title; 486 487 my $orig_preview_start_frame = $title->tc_preview_start_frame; 488 my $orig_preview_end_frame = $title->tc_preview_end_frame; 489 490 my ( $from, $to ) = $title->get_subtitle_test_frame_range; 491 492 $title->set_tc_preview_start_frame($from); 493 $title->set_tc_preview_end_frame($to); 494 495 my $restore_cb = sub { 496 $title->set_tc_preview_start_frame($orig_preview_start_frame); 497 $title->set_tc_preview_end_frame($orig_preview_end_frame); 498 }; 499 500 require Video::DVDRip::GUI::Preview; 501 502 my $preview = Video::DVDRip::GUI::Preview->new( 503 form_factory => $self->get_form_factory, 504 closed_cb => $restore_cb, 505 eof_cb => $restore_cb, 506 ); 507 508 $preview->open; 509 510 1; 511} 512 513sub grab_subtitle_preview_images { 514 my $self = shift; 515 my %par = @_; 516 my ($force) = @par{'force'}; 517 518 my $title = $self->selected_title; 519 return 1 if not $title; 520 return 1 if $self->progress_is_active; 521 my $selected_subtitle = $title->selected_subtitle; 522 return 1 if not $selected_subtitle; 523 524 if ( not $title->is_ripped ) { 525 $self->message_window( 526 message => __ "You first have to rip this title." ); 527 return 1; 528 } 529 530 if ( not $self->has("subtitle2pgm") ) { 531 $self->message_window( 532 message => __ "Sorry, you need subtitle2pgm for this to work." ); 533 return 1; 534 } 535 536 my $job_planner = $self->get_context->get_object("job_planner"); 537 my $exec_flow_gui = $self->get_context->get_object("exec_flow_gui"); 538 my $job = $job_planner->build_grab_subtitle_images_job($title); 539 540 $job->get_post_callbacks->add( 541 sub { 542 $self->get_context->update_object_attr_widgets( 543 "title.selected_subtitle_id"); 544 $self->get_context->update_object_attr_widgets( 545 "subtitle.preview_dir"); 546 } 547 ); 548 549 $exec_flow_gui->start_job($job); 550 551 1; 552} 553 5541; 555