1# $Id: Title.pm 2374 2009-02-22 18:33:07Z 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::Title; 12use Locale::TextDomain qw (video.dvdrip); 13 14use base Video::DVDRip::Base; 15 16use Video::DVDRip::Probe; 17use Video::DVDRip::PSU; 18use Video::DVDRip::Audio; 19use Video::DVDRip::Subtitle; 20use Video::DVDRip::BitrateCalc; 21use Video::DVDRip::FilterSettings; 22 23use Carp; 24use strict; 25 26use FileHandle; 27use File::Path; 28use File::Basename; 29use File::Copy; 30 31# Back reference to the project of this title 32 33sub project { shift->{project} } 34sub set_project { shift->{project} = $_[1] } 35 36#------------------------------------------------------------------------ 37# These attributes are probed from the DVD 38#------------------------------------------------------------------------ 39 40sub width { shift->{width} } 41sub height { shift->{height} } 42sub aspect_ratio { shift->{aspect_ratio} } 43sub video_mode { shift->{video_mode} } 44sub letterboxed { shift->{letterboxed} } 45sub frames { shift->{frames} } 46sub runtime { shift->{runtime} } 47sub frame_rate { 48 my $self = shift; 49 my $frame_rate = $self->{frame_rate}; 50 $frame_rate =~ tr/,/./; 51 return $frame_rate; 52} 53sub bitrates { shift->{bitrates} } 54sub audio_tracks { shift->{audio_tracks} } 55sub chapters { shift->{chapters} } 56sub viewing_angles { shift->{viewing_angles} } 57sub dvd_probe_output { shift->{dvd_probe_output} } 58sub vob_probe_output { shift->{vob_probe_output} } 59 60sub set_width { shift->{width} = $_[1] } 61sub set_height { shift->{height} = $_[1] } 62sub set_aspect_ratio { shift->{aspect_ratio} = $_[1] } 63sub set_video_mode { shift->{video_mode} = $_[1] } 64sub set_letterboxed { shift->{letterboxed} = $_[1] } 65sub set_frames { shift->{frames} = $_[1] } 66sub set_runtime { shift->{runtime} = $_[1] } 67sub set_frame_rate { shift->{frame_rate} = $_[1] } 68sub set_bitrates { shift->{bitrates} = $_[1] } 69sub set_audio_tracks { shift->{audio_tracks} = $_[1] } 70sub set_chapters { shift->{chapters} = $_[1] } 71sub set_viewing_angles { shift->{viewing_angles} = $_[1] } 72sub set_dvd_probe_output { shift->{dvd_probe_output} = $_[1] } 73sub set_vob_probe_output { shift->{vob_probe_output} = $_[1] } 74 75#------------------------------------------------------------------------ 76# Some calculated attributes 77#------------------------------------------------------------------------ 78 79sub nr { shift->{nr} } 80sub size { shift->{size} } 81sub audio_channel { shift->{audio_channel} } 82sub preset { shift->{preset} } 83sub last_applied_preset { shift->{last_applied_preset} } 84sub preview_frame_nr { shift->{preview_frame_nr} } 85sub files { shift->{files} } 86sub actual_chapter { 87 # if no actual chapter is set, this method returns the first 88 # chapter, so all functions that are not aware of chapter 89 # mode should do something senseful. 90 # If you want to have the *real* actual chapter, use the 91 # methode ->real_actual_chapter! 92 my $self = shift; 93 $self->{actual_chapter} || 94 $self->get_first_chapter; 95} 96sub real_actual_chapter { shift->{actual_chapter} } 97sub program_stream_units { shift->{program_stream_units} } 98sub bbox_min_x { shift->{bbox_min_x} } 99sub bbox_min_y { shift->{bbox_min_y} } 100sub bbox_max_x { shift->{bbox_max_x} } 101sub bbox_max_y { shift->{bbox_max_y} } 102sub chapter_frames { shift->{chapter_frames} ||= {} } 103 104sub set_nr { shift->{nr} = $_[1] } 105sub set_size { shift->{size} = $_[1] } 106sub set_audio_channel { shift->{audio_channel} = $_[1] } 107sub set_preset { shift->{preset} = $_[1] } 108sub set_last_applied_preset { shift->{last_applied_preset} = $_[1] } 109sub set_preview_frame_nr { shift->{preview_frame_nr} = $_[1] } 110sub set_actual_chapter { shift->{actual_chapter} = $_[1] } 111sub set_program_stream_units { shift->{program_stream_units} = $_[1] } 112sub set_bbox_min_x { shift->{bbox_min_x} = $_[1] } 113sub set_bbox_min_y { shift->{bbox_min_y} = $_[1] } 114sub set_bbox_max_x { shift->{bbox_max_x} = $_[1] } 115sub set_bbox_max_y { shift->{bbox_max_y} = $_[1] } 116sub set_chapter_frames { shift->{chapter_frames} = $_[1] } 117 118#------------------------------------------------------------------------ 119# These attributes must be specified by the user and are 120# input parameters for the transcode process. 121#------------------------------------------------------------------------ 122 123sub tc_container { shift->{tc_container} } 124sub tc_viewing_angle { shift->{tc_viewing_angle} } 125sub tc_deinterlace { shift->{tc_deinterlace} || 0 } 126sub tc_anti_alias { shift->{tc_anti_alias} || 0 } 127sub tc_clip1_top { shift->{tc_clip1_top} } 128sub tc_clip1_bottom { shift->{tc_clip1_bottom} } 129sub tc_clip1_left { shift->{tc_clip1_left} } 130sub tc_clip1_right { shift->{tc_clip1_right} } 131sub tc_zoom_width { shift->{tc_zoom_width} } 132sub tc_zoom_height { shift->{tc_zoom_height} } 133sub tc_clip2_top { shift->{tc_clip2_top} } 134sub tc_clip2_bottom { shift->{tc_clip2_bottom} } 135sub tc_clip2_left { shift->{tc_clip2_left} } 136sub tc_clip2_right { shift->{tc_clip2_right} } 137sub tc_video_codec { shift->{tc_video_codec} } 138sub tc_video_af6_codec { shift->{tc_video_af6_codec} } 139sub tc_video_bitrate { shift->{tc_video_bitrate} } 140sub tc_video_bitrate_manual { shift->{tc_video_bitrate_manual} } 141sub tc_video_bpp { shift->{tc_video_bpp} } 142sub tc_video_bpp_manual { shift->{tc_video_bpp_manual} } 143sub tc_video_bitrate_mode { shift->{tc_video_bitrate_mode} } 144sub tc_video_bitrate_range { shift->{tc_video_bitrate_range} } 145sub tc_video_framerate { shift->{tc_video_framerate} } 146sub tc_fast_bisection { shift->{tc_fast_bisection} } 147sub tc_psu_core { shift->{tc_psu_core} } 148sub tc_keyframe_interval { shift->{tc_keyframe_interval} || 250 } 149sub tc_split { shift->{tc_split} } 150sub tc_force_slow_grabbing { shift->{tc_force_slow_grabbing} } 151 152sub tc_target_size { shift->{tc_target_size} } 153sub tc_disc_cnt { shift->{tc_disc_cnt} } 154sub tc_disc_size { shift->{tc_disc_size} } 155sub tc_start_frame { shift->{tc_start_frame} } 156sub tc_end_frame { shift->{tc_end_frame} } 157sub tc_fast_resize { shift->{tc_fast_resize} } 158sub tc_multipass { shift->{tc_multipass} } 159sub tc_multipass_reuse_log { shift->{tc_multipass_reuse_log} } 160sub tc_title_nr { $_[0]->{tc_title_nr} || $_[0]->{nr} } 161sub tc_use_chapter_mode { shift->{tc_use_chapter_mode} || 0 } 162sub tc_selected_chapters { shift->{tc_selected_chapters} } 163sub tc_options { shift->{tc_options} } 164sub tc_nice { shift->{tc_nice} } 165sub tc_preview { shift->{tc_preview} } 166sub tc_execute_afterwards { shift->{tc_execute_afterwards} } 167sub tc_exit_afterwards { shift->{tc_exit_afterwards} } 168 169sub set_tc_viewing_angle { shift->{tc_viewing_angle} = $_[1] } 170sub set_tc_deinterlace { shift->{tc_deinterlace} = $_[1] } 171sub set_tc_anti_alias { shift->{tc_anti_alias} = $_[1] } 172# implemented below : sub set_tc_clip1_top {} 173# implemented below : sub set_tc_clip1_bottom {} 174# implemented below : sub set_tc_clip1_left {} 175# implemented below : sub set_tc_clip1_right {} 176# implemented below : sub set_tc_zoom_width {} 177# implemented below : sub set_tc_zoom_height {} 178# implemented below : sub set_tc_clip2_top {} 179# implemented below : sub set_tc_clip2_bottom {} 180# implemented below : sub set_tc_clip2_left {} 181# implemented below : sub set_tc_clip2_right {} 182# implemented below : sub set_tc_video_codec {} 183# implemented below : sub set_tc_video_af6_codec {} 184sub set_tc_video_bitrate { shift->{tc_video_bitrate} = $_[1] } 185# implemented below : sub set_tc_video_bitrate_manual {} 186sub set_tc_video_bpp { shift->{tc_video_bpp} = $_[1] } 187# implemented below : sub set_tc_video_bpp_manual {} 188# implemented below : sub set_tc_video_bitrate_mode {} 189# implemented below : sub set_tc_video_bitrate_range {} 190sub set_tc_video_framerate { shift->{tc_video_framerate} = $_[1] } 191sub set_tc_fast_bisection { shift->{tc_fast_bisection} = $_[1] } 192sub set_tc_psu_core { shift->{tc_psu_core} = $_[1] } 193sub set_tc_keyframe_interval { shift->{tc_keyframe_interval} = $_[1] } 194sub set_tc_split { shift->{tc_split} = $_[1] } 195sub set_tc_force_slow_grabbing { shift->{tc_force_slow_grabbing}= $_[1]} 196 197# implemented below : sub set_tc_disc_cnt 198# implemented below : sub set_tc_disc_size 199# implemented below : sub set_tc_target_size 200# implemented below : sub set_tc_start_frame 201# implemented below : sub set_tc_end_frame 202sub set_tc_fast_resize { shift->{tc_fast_resize} = $_[1] } 203sub set_tc_multipass { shift->{tc_multipass} = $_[1] } 204sub set_tc_multipass_reuse_log { shift->{tc_multipass_reuse_log}= $_[1]} 205sub set_tc_title_nr { shift->{tc_title_nr} = $_[1] } 206sub set_tc_use_chapter_mode { shift->{tc_use_chapter_mode} = $_[1] } 207sub set_tc_selected_chapters { shift->{tc_selected_chapters} = $_[1] } 208sub set_tc_options { shift->{tc_options} = $_[1] } 209sub set_tc_nice { shift->{tc_nice} = $_[1] } 210sub set_tc_preview { shift->{tc_preview} = $_[1] } 211sub set_tc_execute_afterwards { shift->{tc_execute_afterwards}= $_[1] } 212sub set_tc_exit_afterwards { shift->{tc_exit_afterwards} = $_[1] } 213 214#-- Attributes for storage ---------------------------------------------- 215 216sub storage_video_size { shift->{storage_video_size} } 217sub storage_audio_size { shift->{storage_audio_size} } 218sub storage_other_size { shift->{storage_other_size} } 219sub storage_total_size { shift->{storage_total_size} } 220 221sub set_storage_video_size { shift->{storage_video_size} = $_[1] } 222sub set_storage_audio_size { shift->{storage_audio_size} = $_[1] } 223sub set_storage_other_size { shift->{storage_other_size} = $_[1] } 224sub set_storage_total_size { shift->{storage_total_size} = $_[1] } 225 226sub bitrate_calc { shift->{bitrate_calc} } 227sub set_bitrate_calc { shift->{bitrate_calc} = $_[1] } 228 229#-- Attributes for CD burning ------------------------------------------- 230 231sub burn_cd_type { shift->{burn_cd_type} || 'iso' } 232sub burn_label { shift->{burn_label} } 233sub burn_abstract { shift->{burn_abstract} } 234sub burn_number { shift->{burn_number} } 235sub burn_abstract_sticky { shift->{burn_abstract_sticky} } 236sub burn_files_selected { shift->{burn_files_selected} } 237 238sub set_burn_cd_type { shift->{burn_cd_type} = $_[1] } 239sub set_burn_label { shift->{burn_label} = $_[1] } 240sub set_burn_abstract { shift->{burn_abstract} = $_[1] } 241sub set_burn_number { shift->{burn_number} = $_[1] } 242sub set_burn_abstract_sticky { shift->{burn_abstract_sticky} = $_[1] } 243sub set_burn_files_selected { shift->{burn_files_selected} = $_[1] } 244 245#-- Attributes for subtitles -------------------------------------------- 246 247sub subtitles { shift->{subtitles} } 248sub selected_subtitle_id { shift->{selected_subtitle_id} } 249sub subtitle_test { shift->{subtitle_test} } 250sub tc_rip_subtitle_mode { shift->{tc_rip_subtitle_mode} } 251sub tc_rip_subtitle_lang { shift->{tc_rip_subtitle_lang} } 252 253sub set_subtitles { shift->{subtitles} = $_[1] } 254sub set_selected_subtitle_id { shift->{selected_subtitle_id} = $_[1] } 255sub set_subtitle_test { shift->{subtitle_test} = $_[1] } 256sub set_tc_rip_subtitle_mode { shift->{tc_rip_subtitle_mode} = $_[1] } 257sub set_tc_rip_subtitle_lang { shift->{tc_rip_subtitle_lang} = $_[1] } 258 259#-- Filter Settings ----------------------------------------------------- 260 261sub tc_filter_settings { 262 my $self = shift; 263 if ( not $self->{tc_filter_settings} ) { 264 return $self->{tc_filter_settings} 265 = Video::DVDRip::FilterSettings->new; 266 } 267 return $self->{tc_filter_settings}; 268} 269 270sub tc_filter_setting_id { shift->{tc_filter_setting_id} } 271sub set_tc_filter_setting_id { shift->{tc_filter_setting_id} = $_[1] } 272 273sub tc_selected_filter_setting { 274 my $self = shift; 275 return if not $self->tc_filter_setting_id; 276 return $self->tc_filter_settings->get_filter_instance( 277 id => $self->tc_filter_setting_id ); 278} 279 280sub tc_preview_start_frame { shift->{tc_preview_start_frame} } 281sub tc_preview_end_frame { shift->{tc_preview_end_frame} } 282sub tc_preview_buffer_frames { shift->{tc_preview_buffer_frames}||20 } 283 284sub set_tc_preview_start_frame { shift->{tc_preview_start_frame} = $_[1] } 285sub set_tc_preview_end_frame { shift->{tc_preview_end_frame} = $_[1] } 286sub set_tc_preview_buffer_frames{ shift->{tc_preview_buffer_frames} = $_[1] } 287 288sub tc_use_yuv_internal { 289 my $self = shift; 290 291 # enabled only if all selected filters support YUV 292 # and we have no odd clipping / resizing 293 294 return 0 295 if $self->tc_clip1_left % 2 296 or $self->tc_clip1_right % 2 297 or $self->tc_clip1_top % 2 298 or $self->tc_clip1_bottom % 2 299 or $self->tc_clip2_left % 2 300 or $self->tc_clip2_right % 2 301 or $self->tc_clip2_top % 2 302 or $self->tc_clip2_bottom % 2 303 or $self->tc_zoom_width % 2 304 or $self->tc_zoom_height % 2; 305 306 foreach my $filter_instance ( @{ $self->tc_filter_settings->filters } ) { 307 return 0 308 if $filter_instance->get_filter->can_video 309 and not $filter_instance->get_filter->can_yuv; 310 } 311 312 return 1; 313} 314 315#-- Constructor --------------------------------------------------------- 316 317sub new { 318 my $class = shift; 319 my %par = @_; 320 my ( $nr, $project ) = @par{ 'nr', 'project' }; 321 322 my $default_subtitle_grab = $class->config('default_subtitle_grab'); 323 324 my $self = { 325 project => $project, 326 nr => $nr, 327 size => 0, 328 files => [], 329 audio_channel => 0, 330 scan_result => undef, 331 tc_clip1_top => 0, 332 tc_clip1_bottom => 0, 333 tc_clip1_left => 0, 334 tc_clip1_right => 0, 335 tc_clip2_top => 0, 336 tc_clip2_bottom => 0, 337 tc_clip2_left => 0, 338 tc_clip2_right => 0, 339 tc_rip_subtitle_mode => $default_subtitle_grab, 340 tc_selected_chapters => [], 341 tc_preview_buffer_frames => 20, 342 program_stream_units => [], 343 chapter_frames => {}, 344 tc_filter_settings => Video::DVDRip::FilterSettings->new, 345 }; 346 347 return bless $self, $class; 348} 349 350sub set_tc_video_codec { 351 my $self = shift; 352 my ($value) = @_; 353 354 $self->{tc_video_codec} = $value; 355 356 $self->set_tc_video_af6_codec('mpeg4') if $value eq 'ffmpeg'; 357 $self->set_tc_video_af6_codec('') if $value ne 'ffmpeg'; 358 359 if ( $value eq 'VCD' ) { 360 $self->audio_track->set_tc_bitrate(224); 361 $self->audio_track->set_tc_audio_codec('mp2'); 362 $self->set_tc_multipass(0); 363 $self->set_tc_video_bitrate_manual(1152); 364 $self->set_tc_video_bitrate_mode("manual"); 365 366 } 367 elsif ( $value =~ /^(X?S?VCD|CVD)$/ ) { 368 $self->audio_track->set_tc_audio_codec('mp2'); 369 $self->set_tc_multipass(0); 370 } 371 372 if ( $value =~ /^X?S?VCD$/ ) { 373 foreach my $audio ( @{ $self->audio_tracks } ) { 374 $audio->set_tc_mp2_samplerate(44100); 375 } 376 } 377 378 return $value; 379} 380 381sub set_tc_video_af6_codec { 382 my $self = shift; 383 my ($value) = @_; 384 385 $self->{tc_video_af6_codec} = $value; 386 387# $self->set_tc_multipass(0) if $value eq 'h264'; 388 389 return $value; 390} 391 392#-- get actually selected audio (or a dummy object, if no track is selected) 393 394sub audio_track { 395 my $self = shift; 396 if ( $self->audio_channel == -1 ) { 397 # no audio track selected. create a dummy object. 398 # (probably this title has no audio at all) 399 return Video::DVDRip::Audio->new( title => $self ); 400 } 401 return $self->audio_tracks->[ $self->audio_channel ]; 402} 403 404sub has_target_audio_tracks { 405 my $self = shift; 406 407 foreach my $audio ( @{ $self->audio_tracks } ) { 408 return 1 if $audio->tc_target_track != -1; 409 } 410 411 return 0; 412} 413 414sub set_tc_container { 415 my $self = shift; 416 my ($container) = @_; 417 418 return $container if $container eq $self->tc_container; 419 420 $self->{tc_container} = $container; 421 422 return if not defined $self->audio_tracks; 423 424 my @messages; 425 426 if ( $container eq 'avi' ) { 427 428 # no vorbis and mp2 audio here 429 foreach my $audio ( @{ $self->audio_tracks } ) { 430 next if $audio->tc_target_track == -1; 431 if ( $audio->tc_audio_codec eq 'vorbis' ) { 432 push @messages, 433 __x( 434 "Set codec of audio track #{nr} to 'mp3', " 435 . "'vorbis' not supported by AVI container", 436 nr => $audio->tc_nr 437 ); 438 $audio->set_tc_audio_codec('mp3'); 439 } 440 elsif ( $audio->tc_audio_codec eq 'mp2' ) { 441 push @messages, 442 __x( 443 "Set codec of audio track #{nr} to 'mp3', " 444 . "'mp2' not supported by AVI container", 445 nr => $audio->tc_nr 446 ); 447 $audio->set_tc_audio_codec('mp3'); 448 } 449 } 450 451 # no (S)VCD here 452 if ( $self->tc_video_codec =~ /^(X?S?VCD|CVD)$/ ) { 453 push @messages, 454 __ "Set video codec to 'xvid', '" 455 . $self->tc_video_codec 456 . __ "' not supported by AVI container"; 457 $self->set_tc_video_codec("xvid"); 458 } 459 460 } 461 elsif ( $container eq 'vcd' ) { 462 463 # only mp2 audio here 464 foreach my $audio ( @{ $self->audio_tracks } ) { 465 next if $audio->tc_target_track == -1; 466 if ( $audio->tc_audio_codec ne 'mp2' ) { 467 push @messages, 468 __x( "Set codec of audio track #{nr} to 'mp2', '", 469 nr => $audio->tc_nr ) 470 . $audio->tc_audio_codec 471 . __ "' not supported by MPEG container"; 472 $audio->set_tc_audio_codec('mp2'); 473 } 474 } 475 476 # only (S)VCD here 477 if ( $self->tc_video_codec !~ /^(X?S?VCD|CVD)$/ ) { 478 push @messages, 479 __ "Set video codec to 'SVCD', '" 480 . $self->tc_video_codec 481 . __ "' not supported by MPEG container"; 482 $self->set_tc_video_codec("SVCD"); 483 } 484 485 } 486 elsif ( $container eq 'ogg' ) { 487 488 # no mp2 and pcm audio here 489 foreach my $audio ( @{ $self->audio_tracks } ) { 490 next if $audio->tc_target_track == -1; 491 if ( $audio->tc_audio_codec eq 'mp2' 492 or $audio->tc_audio_codec eq 'pcm' ) { 493 push @messages, 494 __x( "Set codec of audio track #{nr} to 'vorbis', '", 495 nr => $audio->tc_nr ) 496 . $audio->tc_audio_codec 497 . __ "' not supported by OGG container"; 498 $audio->set_tc_audio_codec('vorbis'); 499 } 500 } 501 502 # no (S)VCD here 503 if ( $self->tc_video_codec =~ /^(X?S?VCD|CVD)$/ ) { 504 $self->set_tc_video_codec("xvid"); 505 push @messages, __ 506 "Set video codec to 'xvid', MPEG not supported by OGG container"; 507 } 508 } 509 510 foreach my $msg (@messages) { 511 $self->log($msg); 512 } 513 514 $self->calc_video_bitrate; 515 516 return $container; 517} 518 519sub set_tc_disc_cnt { 520 my $self = shift; 521 my ($cnt) = @_; 522 $self->{tc_disc_cnt} = $cnt; 523 $self->set_tc_target_size( $cnt * $self->tc_disc_size ); 524 return $cnt; 525} 526 527sub set_tc_disc_size { 528 my $self = shift; 529 my ($size) = @_; 530 $self->{tc_disc_size} = $size; 531 $self->set_tc_target_size( $self->tc_disc_cnt * $size ); 532 return $size; 533} 534 535sub set_tc_target_size { 536 my $self = shift; 537 my ($value) = @_; 538 $self->{tc_target_size} = $value; 539 $self->calc_video_bitrate; 540 return $value; 541} 542 543sub set_tc_video_bitrate_manual { 544 my $self = shift; 545 my ($size) = @_; 546 $self->{tc_video_bitrate_manual} = $size; 547 $self->calc_video_bitrate; 548 return $size; 549} 550 551sub set_tc_video_bpp_manual { 552 my $self = shift; 553 my ($value) = @_; 554 $self->{tc_video_bpp_manual} = $value; 555 $self->calc_video_bitrate; 556 return $value; 557} 558 559sub set_tc_video_bitrate_mode { 560 my $self = shift; 561 my ($value) = @_; 562 $self->{tc_video_bitrate_mode} = $value; 563 $self->calc_video_bitrate; 564 return $value; 565} 566 567sub set_tc_video_bitrate_range { 568 my $self = shift; 569 my ($value) = @_; 570 $self->{tc_video_bitrate_range} = $value; 571 $self->calc_video_bitrate; 572 return $value; 573} 574 575sub set_tc_start_frame { 576 my $self = shift; 577 my ($value) = @_; 578 $self->{tc_start_frame} = $value; 579 $self->calc_video_bitrate; 580 return $value; 581} 582 583sub set_tc_end_frame { 584 my $self = shift; 585 my ($value) = @_; 586 $self->{tc_end_frame} = $value; 587 $self->calc_video_bitrate; 588 return $value; 589} 590 591#--------------------- 592 593sub set_tc_clip1_top { 594 my $self = shift; 595 my ($value) = @_; 596 $self->{tc_clip1_top} = $value; 597 $self->calc_video_bitrate; 598 return $value; 599} 600 601sub set_tc_clip1_bottom { 602 my $self = shift; 603 my ($value) = @_; 604 $self->{tc_clip1_bottom} = $value; 605 $self->calc_video_bitrate; 606 return $value; 607} 608 609sub set_tc_clip1_left { 610 my $self = shift; 611 my ($value) = @_; 612 $self->{tc_clip1_left} = $value; 613 $self->calc_video_bitrate; 614 return $value; 615} 616 617sub set_tc_clip1_right { 618 my $self = shift; 619 my ($value) = @_; 620 $self->{tc_clip1_right} = $value; 621 $self->calc_video_bitrate; 622 return $value; 623} 624 625sub set_tc_zoom_width { 626 my $self = shift; 627 my ($value) = @_; 628 $self->{tc_zoom_width} = $value; 629 $self->calc_video_bitrate; 630 return $value; 631} 632 633sub set_tc_zoom_height { 634 my $self = shift; 635 my ($value) = @_; 636 $self->{tc_zoom_height} = $value; 637 $self->calc_video_bitrate; 638 return $value; 639} 640 641sub set_tc_clip2_top { 642 my $self = shift; 643 my ($value) = @_; 644 $self->{tc_clip2_top} = $value; 645 $self->calc_video_bitrate; 646 return $value; 647} 648 649sub set_tc_clip2_bottom { 650 my $self = shift; 651 my ($value) = @_; 652 $self->{tc_clip2_bottom} = $value; 653 $self->calc_video_bitrate; 654 return $value; 655} 656 657sub set_tc_clip2_left { 658 my $self = shift; 659 my ($value) = @_; 660 $self->{tc_clip2_left} = $value; 661 $self->calc_video_bitrate; 662 return $value; 663} 664 665sub set_tc_clip2_right { 666 my $self = shift; 667 my ($value) = @_; 668 $self->{tc_clip2_right} = $value; 669 $self->calc_video_bitrate; 670 return $value; 671} 672 673sub is_ogg { 674 my $self = shift; 675 return $self->tc_container eq 'ogg'; 676} 677 678sub is_mpeg { 679 my $self = shift; 680 return $self->tc_container eq 'vcd'; 681} 682 683sub is_resized { 684 my $self = shift; 685 686 my $clip_size = $self->preview_label(type => "clip1", size_only => 1); 687 my $zoom_size = $self->preview_label(type => "zoom", size_only => 1); 688 689 return $clip_size ne $zoom_size; 690} 691 692sub has_vbr_audio { 693 my $self = shift; 694 695 return 0 if $self->tc_video_bitrate_mode eq 'manual'; 696 697 foreach my $audio ( @{ $self->audio_tracks } ) { 698 next if $audio->tc_target_track == -1; 699 return 1 if $audio->tc_audio_codec eq 'vorbis'; 700 } 701 702 return 0; 703} 704 705sub vob_dir { 706 my $self = shift; 707 708 my $vob_dir; 709 710 if ( $self->tc_use_chapter_mode ) { 711 $vob_dir = sprintf( "%s/%03d-C%03d/", 712 $self->project->vob_dir, $self->nr, 713 ( $self->actual_chapter || $self->get_first_chapter || 1 ) ); 714 715 } 716 else { 717 $vob_dir = sprintf( "%s/%03d/", $self->project->vob_dir, $self->nr ); 718 } 719 720 return $vob_dir; 721} 722 723sub get_vob_size { 724 my $self = shift; 725 726 return 1 if $self->project->rip_mode ne 'rip'; 727 728 my $vob_dir = $self->vob_dir; 729 730 my $vob_size = 0; 731 $vob_size += -s for <$vob_dir/*>; 732 $vob_size = int( $vob_size / 1024 / 1024 ); 733 734 return $vob_size; 735} 736 737sub get_title_info { 738 my $self = shift; 739 740 my $fps = $self->frame_rate; 741 $fps =~ s/\.0+$//; 742 743 my $length = $self->runtime - 1; 744 my $h = int( $length / 3600 ); 745 my $m = int( ( $length - $h * 3600 ) / 60 ); 746 my $s = $length - $h * 3600 - $m * 60; 747 748 $length = sprintf( "%02d:%02d:%02d", $h, $m, $s ); 749 750 return $length . ", " 751 . uc( $self->video_mode ) . ", " 752 . $self->chapters . " " 753 . __("Chp") . ", " 754 . scalar( @{ $self->audio_tracks } ) . " " 755 . __("Aud") . ", " 756 . "$fps fps, " 757 . $self->aspect_ratio . ", " 758 . $self->frames . " " 759 . __("frames") . ", " 760 . $self->width . "x" 761 . $self->height 762 763} 764 765sub transcode_data_source { 766 my $self = shift; 767 768 my $project = $self->project; 769 my $mode = $project->rip_mode; 770 771 my $source; 772 773 if ( $mode eq 'rip' ) { 774 $source = $self->vob_dir; 775 776 } 777 else { 778 $source = $project->rip_data_source; 779 780 } 781 782 return quotemeta($source); 783} 784 785sub data_source_options { 786 my $self = shift; 787 my %par = @_; 788 my ($audio_channel) = @par{'audio_channel'}; 789 790 $audio_channel = $self->audio_channel 791 if not defined $audio_channel; 792 793 my $mode = $self->project->rip_mode; 794 my $source = $self->transcode_data_source; 795 796 my ( $input_filter, $need_title ); 797 798 if ( $mode eq 'rip' ) { 799 $input_filter = "vob"; 800 $need_title = 0; 801 802 } 803 else { 804 $input_filter = "dvd"; 805 $need_title = 1; 806 807 } 808 809 $input_filter .= ",null" if $audio_channel == -1; 810 811 my %options = ( 812 i => $source, 813 x => $input_filter 814 ); 815 816 if ($need_title) { 817 my $chapter = $self->actual_chapter || -1; 818 $options{T} = $self->nr . ",$chapter," . $self->tc_viewing_angle; 819 } 820 821 return \%options; 822} 823 824sub create_vob_dir { 825 my $self = shift; 826 827 my $vob_dir = $self->vob_dir; 828 829 if ( not -d $vob_dir ) { 830 mkpath( [$vob_dir], 0, 0755 ) 831 or croak __x( "Can't mkpath directory '{dir}'", dir => $vob_dir ); 832 } 833 834 1; 835} 836 837sub avi_dir { 838 my $self = shift; 839 840 return sprintf( "%s/%03d", $self->project->avi_dir, $self->nr, ); 841} 842 843sub get_target_ext { 844 my $self = shift; 845 846 my $video_codec = $self->tc_video_codec; 847 my $ext = ( $video_codec =~ /^(X?S?VCD|CVD)$/ ) ? "" : ".avi"; 848 849 $ext = "." . $self->config('ogg_file_ext') if $self->is_ogg; 850 851 return $ext; 852} 853 854sub avi_file { 855 my $self = shift; 856 857 my $ext = $self->get_target_ext; 858 859 my $target_dir = 860 $self->subtitle_test 861 ? $self->get_subtitle_preview_dir 862 : $self->avi_dir; 863 864 if ( $self->tc_use_chapter_mode ) { 865 return sprintf( 866 "%s/%s-%03d-C%03d$ext", 867 $target_dir, $self->project->name, 868 $self->nr, $self->actual_chapter 869 ); 870 } 871 else { 872 return sprintf( "%s/%s-%03d$ext", 873 $target_dir, $self->project->name, $self->nr ); 874 } 875} 876 877sub target_avi_file { 878 my $self = shift; 879 return $self->avi_file; 880} 881 882sub target_avi_audio_file { 883 my $self = shift; 884 my %par = @_; 885 my ( $vob_nr, $avi_nr ) = @par{ 'vob_nr', 'avi_nr' }; 886 887 my $ext = $self->is_ogg ? "." . $self->config('ogg_file_ext') : '.avi'; 888 $ext = "" if $self->tc_container eq 'vcd'; 889 890 my $audio_file = $self->target_avi_file; 891 $audio_file =~ s/\.[^.]+$//; 892 $audio_file = sprintf( "%s-%02d$ext", $audio_file, $avi_nr ); 893 894 return $audio_file; 895} 896 897sub multipass_log_dir { 898 my $self = shift; 899 return dirname( $self->preview_filename ); 900} 901 902sub create_avi_dir { 903 my $self = shift; 904 905 my $avi_dir = dirname $self->avi_file; 906 907 if ( not -d $avi_dir ) { 908 mkpath( [$avi_dir], 0, 0755 ) 909 or croak __x( "Can't mkpath directory '{dir}'", dir => $avi_dir ); 910 } 911 912 1; 913} 914 915sub preview_filename { 916 my $self = shift; 917 my %par = @_; 918 my ($type) = @par{'type'}; 919 920 return sprintf( "%s/%s-%03d-preview-%s.jpg", 921 $self->project->snap_dir, $self->project->name, $self->nr, $type ); 922} 923 924sub preview_filename_orig { 925 shift->preview_filename( type => "orig" ); 926} 927 928sub preview_filename_clip1 { 929 shift->preview_filename( type => "clip1" ); 930} 931 932sub preview_filename_zoom { 933 shift->preview_filename( type => "zoom" ); 934} 935 936sub preview_filename_clip2 { 937 shift->preview_filename( type => "clip2" ); 938} 939 940sub preview_scratch_filename { 941 my $self = shift; 942 my %par = @_; 943 my ($type) = @par{'type'}; 944 945 return sprintf( "%s/%s-%03d-preview-scratch-%s.jpg", 946 $self->project->snap_dir, $self->project->name, $self->nr, $type ); 947} 948 949sub preview_label { 950 my $self = shift; 951 my %par = @_; 952 my ($type, $details, $size_only) = @par{'type','details','size_only'}; 953 954 my ( $width, $height, $warn_width, $warn_height, $text, $ratio, 955 $phys_ratio ); 956 ( $width, $height, $ratio ) = $self->get_effective_ratio( type => $type ); 957 958 $ratio = "4:3" if $ratio >= 1.32 and $ratio <= 1.34; 959 $ratio = "16:9" if $ratio >= 1.76 and $ratio <= 1.78; 960 961 ($ratio) = $ratio =~ /(\d+[.,]\d{1,2})/ if $ratio !~ /:/; 962 963 $phys_ratio = $width / $height; 964 ($phys_ratio) = $phys_ratio =~ /(\d+[.,]\d{1,2})/; 965 966 $warn_width = ( $type eq 'clip2' and $width % 16 ) ? "!16" : ""; 967 $warn_height = ( $type eq 'clip2' and $height % 16 ) ? "!16" : ""; 968 969 $warn_width ||= ( $width % 2 ) ? "!2" : ""; 970 $warn_height ||= ( $height % 2 ) ? "!2" : ""; 971 972 if ( $type eq 'clip1' ) { 973 $warn_height ||= "!" 974 if $self->tc_clip1_top % 2 975 or $self->tc_clip1_bottom % 2; 976 $warn_width ||= "!" 977 if $self->tc_clip1_left % 2 978 or $self->tc_clip1_right % 2; 979 } 980 981 if ( $type eq 'clip2' ) { 982 $warn_height ||= "!" 983 if $self->tc_clip2_top % 2 984 or $self->tc_clip2_bottom % 2; 985 $warn_width ||= "!" 986 if $self->tc_clip2_left % 2 987 or $self->tc_clip2_right % 2; 988 } 989 990 if ( $details ) { 991 my @status; 992 if ( $warn_width =~ /2/ ) { 993 push @status, __"Width isn't even."; 994 } 995 elsif ( $warn_width =~ /16/ ) { 996 push @status, __"Width is not divisible by 16."; 997 } 998 if ( $warn_height =~ /2/ ) { 999 push @status, __"Height isn't even."; 1000 } 1001 elsif ( $warn_height =~ /16/ ) { 1002 push @status, __"Height is not divisible by 16."; 1003 } 1004 $text = join (" ", @status); 1005 if ( $text ) { 1006 $text = qq[<span foreground="red"><b>$text</b></span>]; 1007 } 1008 else { 1009 $text = qq[<span foreground="#007700"><b>]. 1010 __("Settings Ok"). 1011 qq[</b></span>]; 1012 } 1013 } 1014 else { 1015 my $type_text = 1016 $type eq 'clip1' ? __ "After 1st clipping" 1017 : $type eq 'clip2' ? __ "After 2nd clipping" 1018 : __ "After zoom"; 1019 1020 $text = sprintf( 1021 "<u>$type_text</u>: <b>%d%sx%d%s</b>\n" 1022 . __x( 1023 "Eff. ratio: <b>{eff}</b>, phys. ratio: <b>{phys}</b>", 1024 eff => $ratio, 1025 phys => $phys_ratio 1026 ), 1027 $width, 1028 qq[<span foreground="red">$warn_width</span>], 1029 $height, 1030 qq[<span foreground="red">$warn_height</span>], 1031 ); 1032 } 1033 1034 return "${width}x${height}" if $size_only; 1035 return $text; 1036} 1037 1038sub preview_label_clip1 { 1039 shift->preview_label( type => "clip1" ); 1040} 1041 1042sub preview_label_zoom { 1043 shift->preview_label( type => "zoom" ); 1044} 1045 1046sub preview_label_clip2 { 1047 shift->preview_label( type => "clip2" ); 1048} 1049 1050sub vob_nav_file { 1051 my $self = shift; 1052 1053 my $file; 1054 if ( $self->tc_use_chapter_mode ) { 1055 $file = sprintf( "%s/%s-%03d-C%03d-nav.log", 1056 $self->project->snap_dir, $self->project->name, $self->nr, 1057 $self->actual_chapter ); 1058 } 1059 else { 1060 $file = sprintf( "%s/%s-%03d-nav.log", 1061 $self->project->snap_dir, $self->project->name, $self->nr ); 1062 } 1063 1064 return $file; 1065} 1066 1067sub has_vob_nav_file { 1068 my $self = shift; 1069 1070 my $old_chapter = $self->actual_chapter; 1071 1072 $self->set_actual_chapter( $self->get_first_chapter ) 1073 if $self->tc_use_chapter_mode; 1074 1075 my $vob_nav_file = $self->vob_nav_file; 1076 1077 $self->set_actual_chapter($old_chapter) 1078 if $self->tc_use_chapter_mode; 1079 1080 return -f $vob_nav_file; 1081} 1082 1083sub audio_wav_file { 1084 my $self = shift; 1085 1086 my $chap; 1087 if ( $self->actual_chapter ) { 1088 $chap = sprintf( "-C%02d", $self->actual_chapter ); 1089 } 1090 1091 return sprintf( 1092 "%s/%s-%03d-%02d$chap.wav", 1093 $self->avi_dir, $self->project->name, 1094 $self->nr, $self->audio_track->tc_nr, 1095 ); 1096} 1097 1098sub add_vob { 1099 my $self = shift; 1100 my %par = @_; 1101 my ($file) = @par{'file'}; 1102 1103 $self->set_size( $self->size + ( -s $file ) ); 1104 push @{ $self->files }, $file; 1105 1106 1; 1107} 1108 1109sub apply_preset { 1110 my $self = shift; 1111 my %par = @_; 1112 my ($preset) = @par{'preset'}; 1113 1114 $preset ||= $self->config_object->get_preset( name => $self->preset ); 1115 1116 return 1 if not $preset; 1117 1118 $self->set_last_applied_preset( $preset->name ); 1119 1120 if ( $preset->auto_clip ) { 1121 $self->auto_adjust_clip_only; 1122 } 1123 elsif ( $preset->auto ) { 1124 $self->auto_adjust_clip_zoom( 1125 frame_size => $preset->frame_size, 1126 fast_resize => $preset->tc_fast_resize, 1127 ); 1128 } 1129 else { 1130 my $attributes = $preset->attributes; 1131 my $set_method; 1132 foreach my $attr ( @{$attributes} ) { 1133 $set_method = "set_$attr"; 1134 $self->$set_method( $preset->$attr() ); 1135 } 1136 } 1137 1138 1; 1139} 1140 1141sub get_chapters { 1142 my $self = shift; 1143 1144 my @chapters; 1145 if ( $self->tc_use_chapter_mode eq 'select' ) { 1146 @chapters = sort { $a <=> $b } @{ $self->tc_selected_chapters || [] }; 1147 } 1148 else { 1149 @chapters = ( 1 .. $self->chapters ); 1150 } 1151 1152 return \@chapters; 1153} 1154 1155sub get_first_chapter { 1156 my $self = shift; 1157 1158 my $chapter_mode = $self->tc_use_chapter_mode; 1159 return if not $chapter_mode; 1160 1161 if ( $chapter_mode eq 'select' ) { 1162 my $chapters = $self->get_chapters; 1163 return $chapters->[0]; 1164 } 1165 else { 1166 return 1; 1167 } 1168} 1169 1170sub get_last_chapter { 1171 my $self = shift; 1172 1173 my $chapter_mode = $self->tc_use_chapter_mode; 1174 return if not $chapter_mode; 1175 1176 my $chapters = $self->get_chapters; 1177 return $chapters->[ @{$chapters} - 1 ]; 1178} 1179 1180sub calc_program_stream_units { 1181 my $self = shift; 1182 1183 my $vob_nav_file = $self->vob_nav_file; 1184 1185 my $fh = FileHandle->new; 1186 open( $fh, $vob_nav_file ) 1187 or croak __x( "Can't read VOB navigation file '{filename}'", 1188 filename => $vob_nav_file ); 1189 1190 my $current_unit = 0; 1191 my ( @program_stream_units, $unit, $frame, $last_frame ); 1192 1193 while (<$fh>) { 1194 ( $unit, $frame ) = /(\d+)\s+(\d+)/; 1195 if ( $unit != $current_unit ) { 1196 push @program_stream_units, 1197 Video::DVDRip::PSU->new( 1198 nr => $current_unit, 1199 frames => $last_frame, 1200 ); 1201 $current_unit = $unit; 1202 } 1203 $last_frame = $frame; 1204 } 1205 1206 if ( $last_frame != 0 ) { 1207 push @program_stream_units, 1208 Video::DVDRip::PSU->new( 1209 nr => $current_unit, 1210 frames => $last_frame, 1211 ); 1212 } 1213 1214 close $fh; 1215 1216 $self->set_program_stream_units( \@program_stream_units ); 1217 1218 $self->log( __ "Program stream units calculated" ); 1219 1220 1; 1221} 1222 1223sub get_effective_ratio { 1224 my $self = shift; 1225 my %par = @_; 1226 my ($type) = @par{'type'}; # clip1, zoom, clip2 1227 1228 my $width = $self->width; 1229 my $height = $self->height || 1; 1230 my $clip1_ratio = $width / $height; 1231 1232 my $from_width = $width - $self->tc_clip1_left - $self->tc_clip1_right; 1233 my $from_height = $height - $self->tc_clip1_top - $self->tc_clip1_bottom; 1234 1235 return ( $from_width, $from_height, $clip1_ratio ) if $type eq 'clip1'; 1236 1237 my $zoom_width = $self->tc_zoom_width || $from_width; 1238 my $zoom_height = $self->tc_zoom_height || $from_height; 1239 my $zoom_ratio = ( $zoom_width / $zoom_height ) * ( $width / $height ) 1240 / ( $from_width / $from_height ); 1241 1242 return ( $zoom_width, $zoom_height, $zoom_ratio ) if $type eq 'zoom'; 1243 1244 my $clip2_width 1245 = $zoom_width - $self->tc_clip2_left - $self->tc_clip2_right; 1246 my $clip2_height 1247 = $zoom_height - $self->tc_clip2_top - $self->tc_clip2_bottom; 1248 1249 return ( $clip2_width, $clip2_height, $zoom_ratio ); 1250} 1251 1252sub calc_export_par { 1253 my $self = shift; 1254 1255 my $width = $self->width; 1256 my $height = $self->height; 1257 1258 my $source_aspect = $width/$height; 1259 my $target_aspect = $self->aspect_ratio; 1260 1261 my ($w, $h) = split(":", $target_aspect); 1262 $target_aspect = $w/$h; 1263 1264 return sprintf("%d,100", 100 * $target_aspect / $source_aspect); 1265} 1266 1267sub auto_adjust_clip_only { 1268 my $self = shift; 1269 1270 $self->set_tc_fast_resize(1); 1271 1272 my $result = $self->get_zoom_parameters( 1273 target_width => undef, 1274 target_height => undef, 1275 fast_resize_align => 16, 1276 result_align => 16, 1277 result_align_clip2 => 1, 1278 auto_clip => 1, 1279 use_clip1 => 0, 1280 ); 1281 1282 $self->set_tc_zoom_width( undef ); 1283 $self->set_tc_zoom_height( undef ); 1284 $self->set_tc_clip1_left( 0 ); 1285 $self->set_tc_clip1_right( 0 ); 1286 $self->set_tc_clip1_top( 0 ); 1287 $self->set_tc_clip1_bottom( 0 ); 1288 $self->set_tc_clip2_left( $result->{clip2_left} ); 1289 $self->set_tc_clip2_right( $result->{clip2_right} ); 1290 $self->set_tc_clip2_top( $result->{clip2_top} ); 1291 $self->set_tc_clip2_bottom( $result->{clip2_bottom} ); 1292 1293 1; 1294} 1295 1296sub auto_adjust_clip_zoom { 1297 my $self = shift; 1298 my %par = @_; 1299 my ( $frame_size, $fast_resize ) = @par{ 'frame_size', 'fast_resize' }; 1300 1301 croak __x( "invalid parameter for frame_size ('{frame_size}')", 1302 frame_size => $frame_size ) 1303 if not $frame_size =~ /^(big|medium|small)$/; 1304 1305 my %width_presets; 1306 if ($fast_resize) { 1307 %width_presets = ( 1308 small => 496, 1309 medium => 640, 1310 big => 720, 1311 ); 1312 } 1313 else { 1314 %width_presets = ( 1315 small => 496, 1316 medium => 640, 1317 big => 768, 1318 ); 1319 } 1320 1321 $self->set_tc_fast_resize($fast_resize); 1322 1323 my $results = $self->calculator; 1324 1325 my $target_width = $width_presets{$frame_size}; 1326 1327 my %result_by_ar_err; 1328 my $range = 16; 1329 while ( keys(%result_by_ar_err) == 0 and $range < 1024 ) { 1330 foreach my $result ( @{$results} ) { 1331 next if abs( $target_width - $result->{clip2_width} ) > $range; 1332 $result_by_ar_err{ abs( $result->{ar_err} ) } 1333 ->{ abs( $target_width - $result->{clip2_width} ) } = $result; 1334 } 1335 $range += 16; 1336 } 1337 1338 my ($min_err) = sort { $a <=> $b } keys %result_by_ar_err; 1339 my ($min_width_diff) 1340 = sort { $a <=> $b } keys %{ $result_by_ar_err{$min_err} }; 1341 my $result = $result_by_ar_err{$min_err}->{$min_width_diff}; 1342 1343 $self->set_tc_zoom_width( $result->{zoom_width} ); 1344 $self->set_tc_zoom_height( $result->{zoom_height} ); 1345 $self->set_tc_clip1_left( $result->{clip1_left} ); 1346 $self->set_tc_clip1_right( $result->{clip1_right} ); 1347 $self->set_tc_clip1_top( $result->{clip1_top} ); 1348 $self->set_tc_clip1_bottom( $result->{clip1_bottom} ); 1349 $self->set_tc_clip2_left( $result->{clip2_left} ); 1350 $self->set_tc_clip2_right( $result->{clip2_right} ); 1351 $self->set_tc_clip2_top( $result->{clip2_top} ); 1352 $self->set_tc_clip2_bottom( $result->{clip2_bottom} ); 1353 1354 1; 1355} 1356 1357sub calc_zoom { 1358 my $self = shift; 1359 my %par = @_; 1360 my ( $width, $height ) = @par{ 'width', 'height' }; 1361 1362 my $result = $self->get_zoom_parameters( 1363 target_width => ( $height ? $self->tc_zoom_width : undef ), 1364 target_height => ( $width ? $self->tc_zoom_height : undef ), 1365 fast_resize_align => ( $self->tc_fast_resize ? 8 : 0 ), 1366 result_align => 16, 1367 result_align_clip2 => 1, 1368 auto_clip => 0, 1369 use_clip1 => 1, 1370 ); 1371 1372 $self->set_tc_zoom_width( $result->{zoom_width} ); 1373 $self->set_tc_zoom_height( $result->{zoom_height} ); 1374 $self->set_tc_clip1_left( $result->{clip1_left} ); 1375 $self->set_tc_clip1_right( $result->{clip1_right} ); 1376 $self->set_tc_clip1_top( $result->{clip1_top} ); 1377 $self->set_tc_clip1_bottom( $result->{clip1_bottom} ); 1378 $self->set_tc_clip2_left( $result->{clip2_left} ); 1379 $self->set_tc_clip2_right( $result->{clip2_right} ); 1380 $self->set_tc_clip2_top( $result->{clip2_top} ); 1381 $self->set_tc_clip2_bottom( $result->{clip2_bottom} ); 1382 1383 1; 1384} 1385 1386sub calculator { 1387 my $self = shift; 1388 my %par = @_; 1389 my ( $fast_resize_align, $result_align, $result_align_clip2 ) 1390 = @par{ 'fast_resize_align', 'result_align', 'result_align_clip2' }; 1391 my ( $auto_clip, $use_clip1, $video_bitrate ) 1392 = @par{ 'auto_clip', 'use_clip1', 'video_bitrate' }; 1393 1394 $fast_resize_align = $self->tc_fast_resize * 8 1395 if not defined $fast_resize_align; 1396 $result_align = 16 if not defined $result_align; 1397 $result_align_clip2 = 1 if not defined $result_align_clip2; 1398 $auto_clip = 1 if not defined $auto_clip; 1399 $use_clip1 = 0 if not defined $use_clip1; 1400 1401 my ( $width, $height ) = ( $self->width, $self->height ); 1402 1403 my @result; 1404 my $last_result; 1405 my ( $actual_width, $actual_height, $best_result ); 1406 1407 for ( my $i = 0;; ++$i ) { 1408 my $result = $self->get_zoom_parameters( 1409 step => $i, 1410 step_size => 1, 1411 auto_clip => $auto_clip, 1412 use_clip1 => $use_clip1, 1413 fast_resize_align => $fast_resize_align, 1414 result_align => $result_align, 1415 result_align_clip2 => $result_align_clip2, 1416 video_bitrate => $video_bitrate, 1417 ); 1418 1419 last if $result->{clip2_width} < 200; 1420 next if $result->{ar_err} > 1; 1421 next 1422 if $fast_resize_align 1423 and ( ( $result->{clip1_width} > $result->{zoom_width} ) 1424 xor( $result->{clip1_height} > $result->{zoom_height} ) ); 1425 1426 if ($i != 0 1427 and ( $actual_width != $result->{clip2_width} 1428 or $actual_height != $result->{clip2_height} ) 1429 ) { 1430 push @result, $best_result; 1431 $best_result = undef; 1432 } 1433 1434 if ( not $best_result 1435 or $best_result->{ar_err} > $result->{ar_err} ) { 1436 $best_result = $result; 1437 } 1438 1439 $actual_width = $result->{clip2_width}; 1440 $actual_height = $result->{clip2_height}; 1441 } 1442 1443 push @result, $best_result if $best_result; 1444 1445 return \@result; 1446} 1447 1448sub get_zoom_parameters { 1449 my $self = shift; 1450 my %par = @_; 1451 my ($target_width, $target_height, $fast_resize_align) = 1452 @par{'target_width','target_height','fast_resize_align'}; 1453 my ($result_align, $result_align_clip2, $auto_clip, $step) = 1454 @par{'result_align','result_align_clip2','auto_clip','step'}; 1455 my ($step_size, $use_clip1, $video_bitrate) = 1456 @par{'step_size','use_clip1','video_bitrate'}; 1457 1458 #use Data::Dumper; print Dumper(\%par); 1459 1460 my ( $clip1_top, $clip1_bottom, $clip1_left, $clip1_right ); 1461 my ( $clip_top, $clip_bottom, $clip_left, $clip_right ); 1462 1463 my ( $width, $height ) = ( $self->width, $self->height ); 1464 $height ||= 1; 1465 my $ar = $self->aspect_ratio eq '16:9' ? 16 / 9 : 4 / 3; 1466 my $ar_width_factor = $ar / ( $width / $height ); 1467 my $zoom_align = $fast_resize_align ? $fast_resize_align : 2; 1468 $zoom_align ||= $result_align if not $result_align_clip2; 1469 $use_clip1 = 1 if not $auto_clip; 1470 $video_bitrate ||= $self->tc_video_bitrate; 1471 1472 #print "width=$width height=$height\n"; 1473 1474 # clip image 1475 if ($auto_clip) { 1476 $clip_top = $self->bbox_min_y || 0; 1477 $clip_bottom 1478 = defined $self->bbox_max_y ? $height - $self->bbox_max_y : 0; 1479 $clip_left = $self->bbox_min_x || 0; 1480 $clip_right 1481 = defined $self->bbox_max_x ? $width - $self->bbox_max_x : 0; 1482 } 1483 else { 1484 $clip_top = $self->tc_clip1_top; 1485 $clip_bottom = $self->tc_clip1_bottom; 1486 $clip_left = $self->tc_clip1_left; 1487 $clip_right = $self->tc_clip1_right; 1488 } 1489 1490 if ($use_clip1) { 1491 $clip1_top = $clip_top; 1492 $clip1_bottom = $clip_bottom; 1493 $clip1_left = $clip_left; 1494 $clip1_right = $clip_right; 1495 } 1496 else { 1497 $clip1_top = 0; 1498 $clip1_bottom = 0; 1499 $clip1_left = 0; 1500 $clip1_right = 0; 1501 } 1502 1503 # align clip1 values when fast resizing is enabled 1504 if ($fast_resize_align) { 1505 $clip1_left = int( $clip1_left / $zoom_align ) * $zoom_align; 1506 $clip1_right = int( $clip1_right / $zoom_align ) * $zoom_align; 1507 $clip1_top = int( $clip1_top / $zoom_align ) * $zoom_align; 1508 $clip1_bottom = int( $clip1_bottom / $zoom_align ) * $zoom_align; 1509 } 1510 1511 # no odd clip values 1512 --$clip1_left if $clip1_left % 2; 1513 --$clip1_right if $clip1_right % 2; 1514 --$clip1_top if $clip1_top % 2; 1515 --$clip1_bottom if $clip1_bottom % 2; 1516 1517 # calculate start width and height 1518 my $clip_width = $width - $clip1_left - $clip1_right; 1519 my $clip_height = $height - $clip1_top - $clip1_bottom; 1520 1521 #print "clip_width=$clip_width clip_height=$clip_height\n"; 1522 1523 if ( not $target_height ) { 1524 $target_width 1525 ||= int( $clip_width * $ar_width_factor - $step * $step_size ); 1526 } 1527 1528 my ( $actual_width, $actual_height ); 1529 my ( $zoom_width, $zoom_height ); 1530 my ( $clip2_width, $clip2_height ); 1531 my ( $clip2_top, $clip2_bottom, $clip2_left, $clip2_right ); 1532 1533 if ($target_width) { 1534 $actual_width = $target_width; 1535 $actual_height = int( 1536 $clip_height - ( $clip_width * $ar_width_factor - $target_width ) 1537 / ( $ar * $height / $clip_height ) ); 1538 } 1539 else { 1540 $actual_height = $target_height; 1541 $actual_width = int( 1542 $clip_width * $ar_width_factor - ( $clip_height - $actual_height ) 1543 * ( $ar * $height / $clip_height ) ); 1544 } 1545 1546 my $zoom_width = $actual_width; 1547 my $zoom_height = $actual_height; 1548 1549 #print "actual_width=$actual_width actual_height=$actual_height\n"; 1550 1551 if ( $zoom_width % $zoom_align ) { 1552 $zoom_width = int( $zoom_width / $zoom_align + 1 ) * $zoom_align 1553 if $zoom_width % $zoom_align >= $zoom_align / 2; 1554 $zoom_width = int( $zoom_width / $zoom_align ) * $zoom_align 1555 if $zoom_width % $zoom_align < $zoom_align / 2; 1556 } 1557 1558 if ( $zoom_height % $zoom_align ) { 1559 $zoom_height = int( $zoom_height / $zoom_align + 1 ) * $zoom_align 1560 if $zoom_height % $zoom_align >= $zoom_align / 2; 1561 $zoom_height = int( $zoom_height / $zoom_align ) * $zoom_align 1562 if $zoom_height % $zoom_align < $zoom_align / 2; 1563 } 1564 1565 #print "zoom_width=$zoom_width zoom_height=$zoom_height\n"; 1566 1567 my $eff_ar = ( $zoom_width / $zoom_height ) * ( $width / $height ) 1568 / ( $clip_width / $clip_height ); 1569 my $ar_err = abs( 100 - $eff_ar / $ar * 100 ); 1570 1571#print "clip_left=$clip_left clip_right=$clip_right clip_top=$clip_top clip_bottom=$clip_bottom\n"; 1572 1573 if ( not $use_clip1 ) { 1574 $clip2_left = int( $clip_left * $zoom_width / $clip_width / 2 ) * 2; 1575 $clip2_right = int( $clip_right * $zoom_width / $clip_width / 2 ) * 2; 1576 $clip2_top = int( $clip_top * $zoom_height / $clip_height / 2 ) * 2; 1577 $clip2_bottom 1578 = int( $clip_bottom * $zoom_height / $clip_height / 2 ) * 2; 1579 $result_align_clip2 = 1; 1580 $result_align = 16 if not defined $result_align; 1581 } 1582 1583 $clip2_width = $zoom_width - $clip2_left - $clip2_right; 1584 $clip2_height = $zoom_height - $clip2_top - $clip2_bottom; 1585 1586 #print "clip2_width=$clip2_width clip2_height=$clip2_height\n"; 1587 1588 if ($result_align_clip2) { 1589 $result_align ||= 16; # fail safe -> prevent division by zero 1590 my $rest; 1591 if ( $rest = $clip2_width % $result_align ) { 1592 $clip2_left += $rest / 2; 1593 $clip2_right += $rest / 2; 1594 $clip2_width -= $rest; 1595 if ( $clip2_left % 2 and $clip2_left > $clip2_right ) { 1596 --$clip2_left; 1597 ++$clip2_right; 1598 } 1599 elsif ( $clip2_left % 2 ) { 1600 ++$clip2_left; 1601 --$clip2_right; 1602 } 1603 } 1604 if ( $rest = $clip2_height % $result_align ) { 1605 $clip2_top += $rest / 2; 1606 $clip2_bottom += $rest / 2; 1607 $clip2_height -= $rest; 1608 if ( $clip2_top % 2 and $clip2_top > $clip2_bottom ) { 1609 --$clip2_top; 1610 ++$clip2_bottom; 1611 } 1612 elsif ( $clip2_top % 2 ) { 1613 ++$clip2_top; 1614 --$clip2_bottom; 1615 } 1616 } 1617 } 1618 1619 my $phys_ar = 0; 1620 $phys_ar = $clip2_width / $clip2_height if $clip2_height != 0; 1621 1622 # pixels per second 1623 my $pps = $self->frame_rate * $clip2_width * $clip2_height; 1624 1625 # bits per pixel 1626 my $bpp = 0; 1627 $bpp = $video_bitrate * 1000 / $pps if $pps != 0; 1628 1629 return { 1630 zoom_width => $zoom_width, 1631 zoom_height => $zoom_height, 1632 eff_ar => $eff_ar, 1633 ar_err => $ar_err, 1634 clip1_left => ( $clip1_left || 0 ), 1635 clip1_right => ( $clip1_right || 0 ), 1636 clip1_top => ( $clip1_top || 0 ), 1637 clip1_bottom => ( $clip1_bottom || 0 ), 1638 clip1_width => $width - $clip1_left - $clip1_right, 1639 clip1_height => $height - $clip1_top - $clip1_bottom, 1640 clip2_left => ( $clip2_left || 0 ), 1641 clip2_right => ( $clip2_right || 0 ), 1642 clip2_top => ( $clip2_top || 0 ), 1643 clip2_bottom => ( $clip2_bottom || 0 ), 1644 clip2_width => $clip2_width, 1645 clip2_height => $clip2_height, 1646 phys_ar => $phys_ar, 1647 bpp => $bpp, 1648 exact_width => $actual_width, 1649 exact_height => $actual_height, 1650 }; 1651} 1652 1653#--------------------------------------------------------------------- 1654# Methods for Ripping 1655#--------------------------------------------------------------------- 1656 1657sub is_ripped { 1658 my $self = shift; 1659 1660 my $project = $self->project; 1661 return 1 if $project->rip_mode ne 'rip'; 1662 1663 my $name = $project->name; 1664 1665 if ( not $self->tc_use_chapter_mode ) { 1666 my $vob_dir = $self->vob_dir; 1667 return -f "$vob_dir/$name-001.vob"; 1668 } 1669 1670 my $chapters = $self->get_chapters; 1671 1672 my $vob_dir; 1673 foreach my $chapter ( @{$chapters} ) { 1674 $self->set_actual_chapter($chapter); 1675 $vob_dir = $self->vob_dir; 1676 $self->set_actual_chapter(undef); 1677 return if not -f "$vob_dir/$name-001.vob"; 1678 } 1679 1680 return 1; 1681} 1682 1683sub get_rip_command { 1684 my $self = shift; 1685 1686 my $nr = $self->tc_title_nr; 1687 my $name = $self->project->name; 1688 my $dvd_device = quotemeta($self->project->dvd_device); 1689 my $vob_dir = $self->vob_dir; 1690 my $vob_nav_file = $self->vob_nav_file; 1691 1692 $self->create_vob_dir; 1693 1694 my $chapter = $self->tc_use_chapter_mode ? $self->actual_chapter : "-1"; 1695 1696 my $angle = $self->tc_viewing_angle || 1; 1697 1698 my ( $setup_subtitle_grabbing, $subtitle_grabbing_pipe ) 1699 = $self->get_subtitle_rip_commands; 1700 1701 my $tc_nice = $self->tc_nice || 0; 1702 1703 my $command = $setup_subtitle_grabbing 1704 . "rm -f $vob_dir/$name-???.vob && " 1705 . "execflow -n $tc_nice tccat -t dvd -T $nr,$chapter,$angle -i $dvd_device " 1706 . $subtitle_grabbing_pipe 1707 . "| dvdrip-splitpipe -f $vob_nav_file 1024 " 1708 . " $vob_dir/$name vob >/dev/null && echo EXECFLOW_OK"; 1709 1710 return $command; 1711} 1712 1713sub set_chapter_length { 1714 my $self = shift; 1715 1716 my $chapter = $self->actual_chapter; 1717 my $vob_nav_file = $self->vob_nav_file; 1718 1719 my $fh = FileHandle->new; 1720 open( $fh, $vob_nav_file ) 1721 or croak __x( "Can't read VOB navigation file '{vob_nav_file}'", 1722 vob_nav_file => $vob_nav_file ); 1723 1724 my ( $frames, $block_offset, $frame_offset ); 1725 ++$frames while <$fh>; 1726 close $fh; 1727 1728 $self->chapter_frames->{$chapter} = $frames; 1729 1730 1; 1731} 1732 1733#--------------------------------------------------------------------- 1734# Methods for Volume Scanning 1735#--------------------------------------------------------------------- 1736 1737sub get_tc_scan_command_pipe { 1738 my $self = shift; 1739 1740 my $audio_channel = $self->audio_channel; 1741 my $codec = $self->audio_track->type =~ /pcm/ ? 'pcm' : 'ac3'; 1742 my $tcdecode = $codec eq 'ac3' ? "| tcdecode -x ac3 " : ""; 1743 1744 my $tc_nice = $self->tc_nice || 0; 1745 1746 my $command .= "tcextract -a $audio_channel -x $codec -t vob " 1747 . $tcdecode 1748 . "| tcscan -x pcm"; 1749 1750 return $command; 1751} 1752 1753sub get_scan_command { 1754 my $self = shift; 1755 1756 my $nr = $self->tc_title_nr; 1757 my $name = $self->project->name; 1758 my $data_source = $self->transcode_data_source; 1759 my $vob_dir = $self->vob_dir; 1760 my $source_options = $self->data_source_options; 1761 my $rip_mode = $self->project->rip_mode; 1762 my $tc_nice = $self->tc_nice || 0; 1763 1764 $self->create_vob_dir; 1765 1766 my $command; 1767 1768 if ( $rip_mode eq 'rip' ) { 1769 my $vob_size = $self->get_vob_size; 1770 $command 1771 = "execflow -n $tc_nice cat $vob_dir/* | dvdrip-progress -m $vob_size -i 5 | tccat -t vob"; 1772 1773 } 1774 else { 1775 $command = "execflow -n $tc_nice tccat "; 1776 delete $source_options->{x}; 1777 $command .= " -" . $_ . " " . $source_options->{$_} 1778 for keys %{$source_options}; 1779 $command .= "| dvdrip-splitpipe -f /dev/null 0 - -"; 1780 } 1781 1782 my $scan_command = $self->get_tc_scan_command_pipe; 1783 1784 $command .= " | $scan_command"; 1785 $command .= " && echo EXECFLOW_OK"; 1786 1787 return $command; 1788} 1789 1790sub get_subtitle_languages { 1791 my $self = shift; 1792 1793 my %lang_list; 1794 foreach my $subtitle ( values %{ $self->subtitles } ) { 1795 $lang_list{ $subtitle->lang } = 1; 1796 } 1797 1798 return \%lang_list; 1799} 1800 1801sub has_subtitles { 1802 my $self = shift; 1803 return scalar( keys %{ $self->subtitles } ); 1804} 1805 1806sub get_subtitle_rip_commands_spuunmux { 1807 my $self = shift; 1808 1809 return if $self->version("spuunmux") < 611; 1810 1811 my $mode = $self->tc_rip_subtitle_mode; 1812 return if !$mode; 1813 1814 my $setup_subtitle_grabbing; 1815 my $subtitle_grabbing = " | dvdrip-multitee 1 "; 1816 1817 my $lang = $self->tc_rip_subtitle_lang || []; 1818 my %lang; 1819 @lang{ @{$lang} } = (1) x @{$lang}; 1820 1821 my $lang_match; 1822 foreach my $subtitle ( sort { $a->id <=> $b->id } 1823 values %{ $self->subtitles } ) { 1824 next if $mode eq 'lang' && !$lang{ $subtitle->lang }; 1825 $lang_match = 1; 1826 my $sub_dir = $self->get_subtitle_preview_dir( $subtitle->id ); 1827 my $sid = $subtitle->id; 1828 $setup_subtitle_grabbing .= "rm -rf $sub_dir; mkdir -p $sub_dir; " 1829 . "touch $sub_dir/.ripped; "; 1830 $subtitle_grabbing .= "'spuunmux -s $sid -o $sub_dir/pic -' "; 1831 } 1832 1833 return unless $lang_match; 1834 return ( $setup_subtitle_grabbing, $subtitle_grabbing ); 1835} 1836 1837sub get_subtitle_rip_commands { 1838 my $self = shift; 1839 1840 my $mode = $self->tc_rip_subtitle_mode; 1841 return if !$mode; 1842 1843 my $setup_subtitle_grabbing; 1844 my $subtitle_grabbing = " | dvdrip-multitee 1 "; 1845 1846 my $lang = $self->tc_rip_subtitle_lang || []; 1847 my %lang; 1848 @lang{ @{$lang} } = (1) x @{$lang}; 1849 1850 my $lang_match; 1851 foreach my $subtitle ( sort { $a->id <=> $b->id } 1852 values %{ $self->subtitles } ) { 1853 next if $mode eq 'lang' && !$lang{ $subtitle->lang }; 1854 $lang_match = 1; 1855 my $sub_dir = $self->get_subtitle_preview_dir( $subtitle->id ); 1856 my $sid = sprintf( "0x%x", 32 + $subtitle->id ); 1857 $setup_subtitle_grabbing .= "rm -rf $sub_dir; mkdir -p $sub_dir; " 1858 . "touch $sub_dir/.ripped; "; 1859 $subtitle_grabbing .= "'tcextract -x ps1 -t vob -a $sid |" 1860 . " subtitle2pgm -P -C 0 -o $sub_dir/pic -e 00:00:00,50000 2>&1 |" 1861 . " dvdrip-subpng' "; 1862 } 1863 1864 return unless $lang_match; 1865 return ( $setup_subtitle_grabbing, $subtitle_grabbing ); 1866} 1867 1868#--------------------------------------------------------------------- 1869# Methods for Ripping and Scanning 1870#--------------------------------------------------------------------- 1871 1872sub get_rip_and_scan_command { 1873 my $self = shift; 1874 1875 my $nr = $self->tc_title_nr; 1876 my $audio_channel = $self->audio_channel; 1877 my $name = $self->project->name; 1878 my $dvd_device = quotemeta($self->project->dvd_device); 1879 my $vob_dir = $self->vob_dir; 1880 my $vob_nav_file = $self->vob_nav_file; 1881 my $tc_nice = $self->tc_nice || 0; 1882 1883 $self->create_vob_dir; 1884 1885 my $chapter = $self->tc_use_chapter_mode ? $self->actual_chapter : "-1"; 1886 1887 my $angle = $self->tc_viewing_angle || 1; 1888 1889 my ( $setup_subtitle_grabbing, $subtitle_grabbing_pipe ) 1890 = $self->get_subtitle_rip_commands; 1891 1892 my $command = $setup_subtitle_grabbing 1893 . "rm -f $vob_dir/$name-???.vob && " 1894 . "execflow -n $tc_nice tccat -t dvd -T $nr,$chapter,$angle -i $dvd_device " 1895 . $subtitle_grabbing_pipe 1896 . "| dvdrip-splitpipe -f $vob_nav_file 1024 $vob_dir/$name vob "; 1897 1898 if ( $audio_channel != -1 ) { 1899 my $scan_command = $self->get_tc_scan_command_pipe; 1900 $command .= " | $scan_command && echo EXECFLOW_OK"; 1901 1902 } 1903 else { 1904 $command .= ">/dev/null && echo EXECFLOW_OK"; 1905 } 1906 1907 return $command; 1908} 1909 1910sub analyze_scan_output { 1911 my $self = shift; 1912 my %par = @_; 1913 my ( $output, $count ) = @par{ 'output', 'count' }; 1914 1915 return 1 if $self->audio_channel == -1; 1916 1917 $output =~ s/^.*?--splitpipe-finished--\n//s; 1918 1919 Video::DVDRip::Probe->analyze_scan( 1920 scan_output => $output, 1921 audio => $self->audio_track, 1922 count => $count, 1923 ); 1924 1925 1; 1926} 1927 1928#--------------------------------------------------------------------- 1929# Methods for Probing DVD 1930#--------------------------------------------------------------------- 1931 1932sub get_probe_command { 1933 my $self = shift; 1934 1935 my $nr = $self->tc_title_nr; 1936 my $data_source = $self->project->rip_data_source; 1937 1938 my $command = "execflow tcprobe -H 10 -i $data_source -T $nr && " 1939 . "echo EXECFLOW_OK; " 1940 . "execflow dvdxchap -t $nr $data_source 2>/dev/null"; 1941 1942 return $command; 1943} 1944 1945sub analyze_probe_output { 1946 my $self = shift; 1947 my %par = @_; 1948 my ($output) = @par{'output'}; 1949 1950 Video::DVDRip::Probe->analyze( 1951 probe_output => $output, 1952 title => $self, 1953 ); 1954 1955 1; 1956} 1957 1958#--------------------------------------------------------------------- 1959# Methods for probing detailed audio information 1960#--------------------------------------------------------------------- 1961 1962sub get_probe_audio_command { 1963 my $self = shift; 1964 1965 my $nr = $self->tc_title_nr; 1966 my $vob_dir = $self->vob_dir; 1967 1968 my $probe_mb = 25; 1969 my $vob_size_mb = $self->get_vob_size; 1970 1971 $probe_mb = $vob_size_mb - 1 if $probe_mb > $vob_size_mb; 1972 1973 my $h_option = $probe_mb <= 0 ? "" : "-H $probe_mb"; 1974 1975 return "execflow tcprobe $h_option -i $vob_dir && echo EXECFLOW_OK"; 1976} 1977 1978sub get_detect_audio_bitrate_command { 1979 my $self = shift; 1980 1981 my $nr = $self->tc_title_nr; 1982 my $tmp_file = $self->project->snap_dir."/dvdrip.audioprobe.$$.vob"; 1983 my $data_source = $self->project->rip_data_source; 1984 1985 return 1986 "execflow tccat -i $data_source -T $nr | ". 1987 "dvdrip-progress -m 25 -i 1 | ". 1988 "head -c 25m > $tmp_file && ". 1989 "tcprobe -i $tmp_file && ". 1990 "echo EXECFLOW_OK; ". 1991 "rm -f $tmp_file"; 1992} 1993 1994sub probe_audio { 1995 my $self = shift; 1996 1997 return 1 if $self->audio_channel == -1; 1998 1999 my $output = $self->system( command => $self->get_probe_audio_command ); 2000 2001 $self->analyze_probe_audio_output( output => $output, ); 2002 2003 1; 2004} 2005 2006sub analyze_probe_audio_output { 2007 my $self = shift; 2008 my %par = @_; 2009 my ($output) = @par{'output'}; 2010 2011 Video::DVDRip::Probe->analyze_audio( 2012 probe_output => $output, 2013 title => $self, 2014 ); 2015 2016 1; 2017} 2018 2019#--------------------------------------------------------------------- 2020# Methods for Transcoding 2021#--------------------------------------------------------------------- 2022 2023sub suggest_transcode_options { 2024 my $self = shift; 2025 my ($mode) = @_; 2026 2027 $mode ||= "all"; # or "update", called after ripping, probing VOB 2028 2029 my $rip_mode = $self->project->rip_mode; 2030 2031 if ( $self->video_mode eq 'ntsc' 2032 and $rip_mode eq 'rip' 2033 and @{ $self->program_stream_units } > 1 ) { 2034 $self->set_tc_psu_core(1); 2035 $self->log( 2036 __ "Enabled PSU core. Movie is NTSC and has more than one PSU." ); 2037 2038 } 2039 elsif ( $self->video_mode eq 'ntsc' and $rip_mode eq 'rip' ) { 2040 $self->log( 2041 __ "Not enabling PSU core, because this movie has only one PSU." 2042 ); 2043 } 2044 2045 if ( $rip_mode eq 'rip' ) { 2046 if ( $self->tc_use_chapter_mode ) { 2047 my $chapter = $self->get_first_chapter; 2048 $self->set_preview_frame_nr( 2049 int( $self->chapter_frames->{$chapter} / 2 ) ); 2050 } 2051 else { 2052 $self->set_preview_frame_nr( int( $self->frames / 2 ) ); 2053 } 2054 } 2055 else { 2056 $self->set_preview_frame_nr(200); 2057 } 2058 2059 my $pref_lang = $self->config('preferred_lang'); 2060 if ( $pref_lang =~ /^\s*([a-z]{2})/ ) { 2061 $pref_lang = $1; 2062 } 2063 else { 2064 $pref_lang = ""; 2065 } 2066 2067 if ( $pref_lang ) { 2068 #-- select the subtitle stream of the preferred language 2069 #-- with the minumum number of images, because it's likely 2070 #-- that this is a forced subtitle. 2071 my $min_image_cnt = 1_000_000; 2072 my $min_image_subtitle_id; 2073 foreach my $sid ( sort { $a <=> $b } keys %{ $self->subtitles } ) { 2074 my $subtitle = $self->subtitles->{$sid}; 2075 if ( $subtitle->lang eq $pref_lang ) { 2076 if ( $subtitle->ripped_images_cnt < $min_image_cnt ) { 2077 $min_image_subtitle_id = $subtitle->id; 2078 $min_image_cnt = $subtitle->ripped_images_cnt; 2079 } 2080 } 2081 } 2082 if ( defined $min_image_subtitle_id ) { 2083 $self->set_selected_subtitle_id($min_image_subtitle_id); 2084 } 2085 } 2086 2087 $self->set_tc_video_framerate( $self->frame_rate ); 2088 2089 return if $mode ne 'all'; 2090 2091 if ( $pref_lang ) { 2092 foreach my $audio ( @{ $self->audio_tracks } ) { 2093 if ( $audio->lang eq $pref_lang ) { 2094 $self->set_audio_channel( $audio->tc_nr ); 2095 last; 2096 } 2097 } 2098 } 2099 2100 $self->set_skip_video_bitrate_calc(1); 2101 2102 $self->set_tc_viewing_angle(1) if !$self->tc_viewing_angle; 2103 $self->set_tc_multipass(1); 2104 $self->set_tc_keyframe_interval(50); 2105 2106 my $container = $self->config('default_container'); 2107 2108 # Internal value for MPEG/X*S*VCD/CVD container is 'vcd', 2109 # but in the Config dialog the more convenient 'mpeg' 2110 # is used, so this is translated here. 2111 $container = 'vcd' if $container eq 'mpeg'; 2112 2113 $self->set_tc_container( $self->config('default_container') ); 2114 $self->set_tc_video_codec( $self->config('default_video_codec') ); 2115 2116 if ( $self->tc_video_codec =~ /^(X?S?VCD|CVD)$/ ) { 2117 $self->set_tc_container('vcd'); 2118 } 2119 2120 $self->set_preset($self->config("default_preset")) 2121 unless $self->last_applied_preset; 2122 2123 my $subtitle_langs = $self->get_subtitle_languages; 2124 2125 if ( $subtitle_langs->{$pref_lang} ) { 2126 $self->set_tc_rip_subtitle_lang( [$pref_lang] ); 2127 } 2128 2129 $self->set_tc_video_bitrate_mode("size"); 2130 $self->set_tc_target_size(1400); 2131 $self->set_tc_disc_size(700); 2132 $self->set_tc_disc_cnt(2); 2133 $self->set_tc_video_bitrate_manual(1800); 2134 $self->set_tc_nice(19); 2135 2136 if ( $self->config('default_bpp') ne '<none>' ) { 2137 $self->set_tc_video_bitrate_mode('bpp'); 2138 $self->set_tc_video_bpp_manual( $self->config('default_bpp') ); 2139 } 2140 2141 $self->set_skip_video_bitrate_calc(0); 2142 2143 $self->calc_video_bitrate; 2144 2145 1; 2146} 2147 2148sub skip_video_bitrate_calc { shift->{skip_video_bitrate_calc} } 2149sub set_skip_video_bitrate_calc { shift->{skip_video_bitrate_calc} = $_[1] } 2150 2151sub calc_video_bitrate { 2152 my $self = shift; 2153 2154 return if $self->skip_video_bitrate_calc; 2155 2156 my $bc = Video::DVDRip::BitrateCalc->new( 2157 title => $self, 2158 with_sheet => 1, 2159 ); 2160 $bc->calculate; 2161 2162 $self->set_tc_video_bpp( $bc->video_bpp ); 2163 $self->set_tc_video_bitrate( $bc->video_bitrate ); 2164 $self->set_storage_video_size( int( $bc->video_size ) ); 2165 $self->set_storage_audio_size( int( $bc->audio_size ) ); 2166 $self->set_storage_other_size( int( $bc->other_size ) ); 2167 $self->set_storage_total_size( int( $bc->file_size ) ); 2168 2169 $self->set_bitrate_calc($bc); 2170 2171 return $bc->video_bitrate; 2172} 2173 2174sub get_first_audio_track { 2175 my $self = shift; 2176 2177 return -1 if $self->audio_channel == -1; 2178 return -1 if not $self->audio_tracks; 2179 2180 foreach my $audio ( @{ $self->audio_tracks } ) { 2181 return $audio->tc_nr if $audio->tc_target_track == 0; 2182 } 2183 2184 return -1; 2185} 2186 2187sub get_last_audio_track { 2188 my $self = shift; 2189 2190 return -1 if $self->audio_channel == -1; 2191 return -1 if not $self->audio_tracks; 2192 2193 my $tc_nr = -1; 2194 foreach my $audio ( @{ $self->audio_tracks } ) { 2195 $tc_nr = $audio->tc_nr if $audio->tc_target_track > $tc_nr; 2196 } 2197 2198 return $tc_nr; 2199} 2200 2201sub get_additional_audio_tracks { 2202 my $self = shift; 2203 2204 my %avi2vob; 2205 foreach my $audio ( @{ $self->audio_tracks } ) { 2206 next if $audio->tc_target_track == -1; 2207 next if $audio->tc_target_track == 0; 2208 $avi2vob{ $audio->tc_target_track } = $audio->tc_nr; 2209 } 2210 2211 return \%avi2vob; 2212} 2213 2214sub get_transcode_frame_cnt { 2215 my $self = shift; 2216 my %par = @_; 2217 my ($chapter) = @par{'chapter'}; 2218 2219 my $frames_cnt; 2220 if ( not $chapter ) { 2221 $frames_cnt = $self->frames; 2222 } 2223 else { 2224 $frames_cnt = $self->chapter_frames->{$chapter}; 2225 } 2226 2227 my $frames = $frames_cnt; 2228 2229 if ( $self->tc_start_frame ne '' 2230 or $self->tc_end_frame ne '' ) { 2231 $frames = $self->tc_end_frame || $frames_cnt; 2232 $frames = $frames - $self->tc_start_frame 2233 if $self->has_vob_nav_file; 2234 $frames ||= $frames_cnt; 2235 } 2236 2237 return $frames; 2238} 2239 2240sub get_transcode_progress_max { 2241 my $self = shift; 2242 2243 my $subtitle_test = $self->subtitle_test; 2244 2245 my $chapter = $self->actual_chapter; 2246 2247 my $progress_max; 2248 2249 if ($subtitle_test) { 2250 my ( $from, $to ) = $self->get_subtitle_test_frame_range; 2251 $progress_max = $to - $from; 2252 } 2253 else { 2254 $progress_max = $self->get_transcode_frame_cnt( chapter => $chapter ); 2255 } 2256 2257 return $progress_max; 2258} 2259 2260sub multipass_log_is_reused { 2261 my $self = shift; 2262 2263 return $self->tc_multipass_reuse_log 2264 && -f $self->multipass_log_dir . "/divx4.log"; 2265} 2266 2267sub get_transcode_status_option { 2268 my $self = shift; 2269 my ($rate) = @_; 2270 2271 $rate ||= 25; 2272 2273 if ( $self->version("transcode") >= 10100) { 2274 return "--progress_meter 2 --progress_rate $rate"; 2275 } 2276 else { 2277 return "--print_status $rate"; 2278 } 2279} 2280 2281sub get_transcode_command { 2282 my $self = shift; 2283 my %par = @_; 2284 my ( $pass, $split, $no_audio, $output_file ) 2285 = @par{ 'pass', 'split', 'no_audio', 'output_file' }; 2286 2287 my $bc = Video::DVDRip::BitrateCalc->new( title => $self ); 2288 $bc->calculate; 2289 2290 my $nr = $self->nr; 2291 my $avi_file = $output_file || $self->avi_file; 2292 my $audio_channel = $self->get_first_audio_track; 2293 2294 $audio_channel = -1 if $no_audio; 2295 2296 my $source_options 2297 = $self->data_source_options( audio_channel => $audio_channel ); 2298 2299 my ($audio_info); 2300 2301 if ( $audio_channel != -1 ) { 2302 $audio_info = $self->audio_tracks->[$audio_channel]; 2303 } 2304 2305 my $mpeg = 0; 2306 $mpeg = "svcd" if $self->tc_video_codec =~ /^(X?SVCD|CVD)$/; 2307 $mpeg = "vcd" if $self->tc_video_codec =~ /^X?VCD$/; 2308 2309 my $dir = dirname($avi_file); 2310 2311 my $tc_nice = $self->tc_nice || 0; 2312 my $command = "mkdir -p $dir && execflow -n $tc_nice transcode -H 10"; 2313 2314 $command .= " -a $audio_channel" if $audio_channel != -1; 2315 2316 $command .= " -" . $_ . " " . $source_options->{$_} 2317 for keys %{$source_options}; 2318 2319 if ( $self->tc_video_bitrate ) { 2320 $command .= " -w " 2321 . int( $self->tc_video_bitrate ) . "," 2322 . $self->tc_keyframe_interval; 2323 } 2324 2325 # if ( not $mpeg ) { 2326 # $command .= 2327 # " -w ".int($self->tc_video_bitrate); 2328 # } elsif ( $mpeg eq 'svcd' and $self->tc_video_bitrate ) { 2329 # $command .= 2330 # " -w ".int($self->tc_video_bitrate); 2331 # } 2332 2333 if ( $self->tc_start_frame ne '' 2334 or $self->tc_end_frame ne '' ) { 2335 my $start_frame = $self->tc_start_frame; 2336 my $end_frame = $self->tc_end_frame; 2337 $start_frame ||= 0; 2338 $end_frame ||= $self->frames; 2339 2340 if ( $start_frame != 0 ) { 2341 my $options 2342 = $self->get_frame_grab_options( frame => $start_frame ); 2343 $options->{c} =~ /(\d+)/; 2344 my $c1 = $1; 2345 my $c2 = $c1 + $end_frame - $start_frame; 2346 $command .= " -c $c1-$c2"; 2347 $command .= " -L $options->{L}" 2348 if $options->{L} ne ''; 2349 2350 } 2351 else { 2352 $command .= " -c $start_frame-$end_frame"; 2353 } 2354 } 2355 2356 if ($mpeg) { 2357 my $size = $bc->disc_size || 1; 2358 my $reserve_bitrate = $bc->vcd_reserve_bitrate; 2359 my $mpeg2enc_opts = "-B $reserve_bitrate -I 0 "; 2360 if ($split) { 2361 $mpeg2enc_opts .= "-S $size "; 2362 } 2363 else { 2364 $mpeg2enc_opts .= "-S 10000 "; 2365 } 2366 2367 if ( $mpeg eq 'svcd' ) { 2368 if ( $self->video_mode eq 'pal' ) { 2369 $mpeg2enc_opts .= " -g 6 -G 15"; 2370 } 2371 else { 2372 $mpeg2enc_opts .= " -g 9 -G 18"; 2373 if ( $self->frame_rate == 23.976 ) { 2374 $mpeg2enc_opts .= " -p"; 2375 } 2376 } 2377 2378 $mpeg2enc_opts = ",'$mpeg2enc_opts'" if $mpeg2enc_opts; 2379 2380 $command .= " -F 5$mpeg2enc_opts"; 2381 2382 if ( $self->aspect_ratio eq '16:9' ) { 2383 2384 # 16:9 2385 if ( $self->last_applied_preset =~ /4_3/ ) { 2386 2387 # 4:3 with black bars 2388 $command .= " --export_asr 2"; 2389 } 2390 else { 2391 $command .= " --export_asr 3"; 2392 } 2393 } 2394 else { 2395 2396 # 4:3 2397 $command .= " --export_asr 2"; 2398 } 2399 } 2400 else { 2401 $mpeg2enc_opts = ",'$mpeg2enc_opts'" if $mpeg2enc_opts; 2402 2403 if ( $self->tc_video_codec eq 'XVCD' ) { 2404 $command .= " -F 2$mpeg2enc_opts --export_asr 2"; 2405 } 2406 else { 2407 $command .= " -F 1$mpeg2enc_opts --export_asr 2"; 2408 } 2409 } 2410 2411 } 2412 else { 2413 $command .= " -F " . $self->tc_video_af6_codec 2414 if $self->tc_video_af6_codec ne ''; 2415 } 2416 2417 if ( $audio_channel != -1 ) { 2418 $command .= " -d" 2419 if $audio_info->type eq 'lpcm' 2420 and $self->version("transcode") < 613; 2421 2422 if ($mpeg) { 2423 $command .= " -b " . $audio_info->tc_bitrate; 2424 } 2425 elsif ( $audio_info->tc_audio_codec =~ /^mp\d/ ) { 2426 $command .= " -b " 2427 . $audio_info->tc_bitrate . ",0," 2428 . $audio_info->tc_mp3_quality; 2429 } 2430 elsif ( $audio_info->tc_audio_codec eq 'vorbis' ) { 2431 if ( $audio_info->tc_vorbis_quality_enable ) { 2432 $command .= " -b 0,1," . $audio_info->tc_vorbis_quality; 2433 } 2434 else { 2435 $command .= " -b " . $audio_info->tc_bitrate; 2436 } 2437 } 2438 2439 if ( $audio_info->tc_audio_codec eq 'ac3' ) { 2440 $command .= " -A -N " . $audio_info->tc_option_n; 2441 2442 } 2443 elsif ( $audio_info->tc_audio_codec eq 'pcm' ) { 2444 $command .= " -N 0x1"; 2445 2446 } 2447 else { 2448 $command .= " -s " . $audio_info->tc_volume_rescale 2449 if $audio_info->tc_volume_rescale != 0 2450 and $audio_info->type ne 'lpcm'; 2451 $command .= " --a52_drc_off" 2452 if $audio_info->tc_audio_filter ne 'a52drc'; 2453 $command .= " -J normalize" 2454 if $audio_info->tc_audio_filter eq 'normalize'; 2455 } 2456 } 2457 2458 if ( $self->version("transcode") >= 613 ) { 2459 $command .= " --use_rgb -k " 2460 if not $self->tc_use_yuv_internal; 2461 2462 } 2463 elsif ( $self->version("transcode") >= 608 ) { 2464 $command .= " -V " 2465 if $self->tc_use_yuv_internal 2466 and $self->tc_deinterlace ne 'smart' 2467 2468 } 2469 else { 2470 $command .= " -V " 2471 if $self->tc_use_yuv_internal 2472 and $self->version("transcode") >= 603; 2473 } 2474 2475 $command .= " -C " . $self->tc_anti_alias 2476 if $self->tc_anti_alias; 2477 2478 my $fr = $self->tc_video_framerate; 2479 2480 if ( $self->tc_deinterlace eq '32detect' ) { 2481 $command .= " -J 32detect=force_mode=3"; 2482 2483 } 2484 elsif ( $self->tc_deinterlace eq 'smart' ) { 2485 if ( $self->version("transcode") >= 608 ) { 2486 $command 2487 .= " -J smartyuv=threshold=10:Blend=1:diffmode=2:highq=1"; 2488 } 2489 else { 2490 $command 2491 .= " -J smartdeinter=threshold=10:Blend=1:diffmode=2:highq=1"; 2492 } 2493 2494 } 2495 elsif ( $self->tc_deinterlace eq 'ivtc' ) { 2496 $fr = 23.976; 2497 $command .= " -J ivtc,32detect=force_mode=3,decimate"; 2498 2499 } 2500 elsif ( $self->tc_deinterlace ) { 2501 $command .= " -I " . $self->tc_deinterlace; 2502 } 2503 2504 if ( $self->tc_video_framerate ) { 2505 $fr = "24,1" if $fr == 23.976; 2506 $fr = "30,4" if $fr == 29.97; 2507 $command .= " -f $fr"; 2508 } 2509 2510 if ( $self->video_mode eq 'ntsc' and $self->tc_options !~ /-M/ ) { 2511 my $m = " -M 2"; 2512 $m = " -M 0" if $self->tc_deinterlace eq 'ivtc'; 2513 $command .= $m; 2514 } 2515 2516 $command .= " -J preview=xv" if $self->tc_preview; 2517 2518 my $clip1 = ( $self->tc_clip1_top || 0 ) . "," 2519 . ( $self->tc_clip1_left || 0 ) . "," 2520 . ( $self->tc_clip1_bottom || 0 ) . "," 2521 . ( $self->tc_clip1_right || 0 ); 2522 2523 $command .= " -j $clip1" 2524 if $clip1 =~ /^-?\d+,-?\d+,-?\d+,-?\d+$/ 2525 and $clip1 ne '0,0,0,0'; 2526 2527 my $clip2 = ( $self->tc_clip2_top || 0 ) . "," 2528 . ( $self->tc_clip2_left || 0 ) . "," 2529 . ( $self->tc_clip2_bottom || 0 ) . "," 2530 . ( $self->tc_clip2_right || 0 ); 2531 2532 $command .= " -Y $clip2" 2533 if $clip2 =~ /^-?\d+,-?\d+,-?\d+,-?\d+$/ 2534 and $clip2 ne '0,0,0,0'; 2535 2536 if ( not $self->is_resized ) { 2537 my $export_par = $self->calc_export_par; 2538 $command .= " --export_par $export_par"; 2539 } 2540 else { 2541 if ( $self->tc_fast_bisection ) { 2542 $command .= " -r 2,2"; 2543 2544 } 2545 elsif ( not $self->tc_fast_resize ) { 2546 my $zoom = $self->tc_zoom_width . "x" . $self->tc_zoom_height; 2547 $command .= " -Z $zoom" 2548 if $zoom =~ /^\d+x\d+$/; 2549 2550 } 2551 else { 2552 my $multiple_of = 8; 2553 2554 my ( $width_n, $height_n, $err_div32, $err_shrink_expand ) 2555 = $self->get_fast_resize_options; 2556 2557 if ($err_div32) { 2558 croak __x( 2559 "When using fast resize: Clip1 and Zoom size must be divisible by {multiple_of}", 2560 multiple_of => $multiple_of 2561 ); 2562 } 2563 2564 if ($err_shrink_expand) { 2565 croak __ 2566 "When using fast resize: Width and height must both shrink or expand"; 2567 } 2568 2569 if ( $width_n * $height_n >= 0 ) { 2570 if ( $width_n > 0 or $height_n > 0 ) { 2571 $command .= " -X $height_n,$width_n"; 2572 $command .= ",$multiple_of" if $multiple_of != 32; 2573 } 2574 elsif ( $width_n < 0 or $height_n < 0 ) { 2575 $width_n = abs($width_n); 2576 $height_n = abs($height_n); 2577 $command .= " -B $height_n,$width_n"; 2578 $command .= ",$multiple_of" if $multiple_of != 32; 2579 } 2580 } 2581 } 2582 } 2583 2584 my $dir = $self->multipass_log_dir; 2585 $command = "mkdir -m 0775 -p '$dir' && cd $dir && $command"; 2586 2587 if ( $self->tc_multipass ) { 2588 $command .= " -R $pass"; 2589 2590 $avi_file = "/dev/null" if $pass == 1; 2591 2592 if ($pass == 1 and not $self->has_vbr_audio 2593 or ( $pass == 2 2594 and $self->has_vbr_audio 2595 and not $self->multipass_log_is_reused ) 2596 ) { 2597 $command =~ s/(-x\s+[^\s]+)/$1,null/; 2598 $command =~ s/-x\s+([^,]+),null,null/-x $1,null/; 2599 $command .= " -y " . $self->tc_video_codec; 2600 $command .= ",null" if not $self->has_vbr_audio or $pass == 2; 2601 } 2602 2603 if ( $pass == 1 and $self->video_mode eq 'ntsc' ) { 2604 2605 # Don't use -x vob,null with NTSC, because this may 2606 # cause out-of-sync audio. 2607 $command =~ s/(-x\s+[^,]+),null/$1/; 2608 } 2609 } 2610 2611 if ( not $self->tc_multipass 2612 or ( $pass == 2 xor $self->has_vbr_audio ) 2613 or ( $pass == 2 and $self->multipass_log_is_reused ) ) { 2614 if ($mpeg) { 2615 $command .= " -y mpeg2enc,mp2enc"; 2616 $command .= " -E " . $audio_info->tc_samplerate 2617 if $audio_info->tc_samplerate; 2618 } 2619 else { 2620 $command .= " -y " . $self->tc_video_codec; 2621 if ( $self->tc_container eq 'ogg' 2622 and $audio_channel != -1 ) { 2623 $command .= ",ogg" 2624 if $audio_info->tc_audio_codec eq 'vorbis'; 2625 $command .= " -m " 2626 . $self->target_avi_audio_file( 2627 vob_nr => $audio_channel, 2628 avi_nr => 0 2629 ); 2630 } 2631 if ( $audio_channel == -1 ) { 2632 $command .= ",null"; 2633 2634 } 2635 else { 2636 if ( not $audio_info->is_passthrough 2637 and $audio_info->tc_samplerate != $audio_info->sample_rate 2638 and $audio_info->tc_samplerate ) { 2639 $command .= " -E " . $audio_info->tc_samplerate 2640 if $audio_info->tc_samplerate; 2641 $command .= " -J resample" 2642 if $audio_info->tc_audio_codec eq 'vorbis'; 2643 } 2644 } 2645 } 2646 } 2647 2648 if ( $self->tc_psu_core ) { 2649 $command .= " --psu_mode --nav_seek " 2650 . $self->vob_nav_file 2651 . " --no_split "; 2652 } 2653 2654 $command .= " -o $avi_file"; 2655 2656 $command .= " ".$self->get_transcode_status_option; 2657 2658 if ( $self->tc_container eq 'avi' and $self->tc_target_size > 2048 ) { 2659 $command .= " --avi_limit 9999"; 2660 } 2661 2662 # Filters 2663 my $config_strings = $self->tc_filter_settings->get_filter_config_strings; 2664 2665 foreach my $config ( @{$config_strings} ) { 2666 next if not $config->{enabled}; 2667 $command .= " -J $config->{filter}"; 2668 $command .= "=$config->{options}" if $config->{options}; 2669 } 2670 2671 $self->create_avi_dir; 2672 2673 $command = $self->combine_command_options( 2674 cmd => "transcode", 2675 cmd_line => $command, 2676 options => $self->tc_options, 2677 ) 2678 if $self->tc_options =~ /\S/; 2679 2680 $command .= $self->get_subtitle_transcode_options; 2681 2682 if ( $self->tc_video_af6_codec eq 'h264' and 2683 $self->tc_multipass and $pass == 1 ) { 2684 $command .= " && cp x264_2pass.log divx4.log"; 2685 } 2686 2687 2688 $command = "$command && echo EXECFLOW_OK "; 2689 2690 return $command; 2691} 2692 2693sub get_transcode_audio_command { 2694 my $self = shift; 2695 my %par = @_; 2696 my ( $vob_nr, $target_nr ) = @par{ 'vob_nr', 'target_nr' }; 2697 2698 my $source_options 2699 = $self->data_source_options( audio_channel => $vob_nr ); 2700 2701 $source_options->{x} = "null,$source_options->{x}"; 2702 2703 my $audio_info = $self->audio_tracks->[$vob_nr]; 2704 2705 my $audio_file = $self->target_avi_audio_file( 2706 vob_nr => $vob_nr, 2707 avi_nr => $target_nr 2708 ); 2709 2710 my $dir = dirname($audio_file); 2711 2712 my $tc_nice = $self->tc_nice || 0; 2713 2714 my $command = "mkdir -p $dir && " 2715 . "execflow -n $tc_nice transcode " 2716 . " -H 10" 2717 . " -u 50" 2718 . " -a $vob_nr" 2719 . " -y raw"; 2720 2721 if ( $self->is_ogg ) { 2722 if ( $audio_info->tc_audio_codec eq 'vorbis' ) { 2723 $command .= ",ogg -m $audio_file -o /dev/null"; 2724 } 2725 else { 2726 $command .= " -m $audio_file -o /dev/null"; 2727 } 2728 2729 } 2730 elsif ( $self->tc_container eq 'vcd' ) { 2731 $command .= ",mp2enc -o $audio_file"; 2732 2733 } 2734 else { 2735 $command .= " -o " . $audio_file; 2736 } 2737 2738 my ( $k, $v ); 2739 while ( ( $k, $v ) = each %{$source_options} ) { 2740 $command .= " -$k $v"; 2741 } 2742 2743 if ( $self->tc_video_framerate ) { 2744 my $fr = $self->tc_video_framerate; 2745 $fr = "24,1" if $fr == 23.976; 2746 $fr = "30,4" if $fr == 29.97; 2747 $command .= " -f $fr"; 2748 } 2749 2750 if ( $audio_info->tc_audio_codec eq 'ac3' ) { 2751 $command .= " -A -N " . $audio_info->tc_option_n; 2752 2753 } 2754 elsif ( $audio_info->tc_audio_codec eq 'pcm' ) { 2755 $command .= " -N 0x1"; 2756 2757 } 2758 else { 2759 2760 if ( $audio_info->tc_audio_codec =~ /^mp\d/ ) { 2761 $command .= " -b " 2762 . $audio_info->tc_bitrate . ",0," 2763 . $audio_info->tc_mp3_quality; 2764 2765 } 2766 elsif ( $audio_info->tc_audio_codec eq 'vorbis' ) { 2767 if ( $audio_info->tc_vorbis_quality_enable ) { 2768 $command .= " -b 0,1," . $audio_info->tc_vorbis_quality; 2769 } 2770 else { 2771 $command .= " -b " . $audio_info->tc_bitrate; 2772 } 2773 } 2774 2775 $command .= " -s " . $audio_info->tc_volume_rescale 2776 if $audio_info->tc_volume_rescale != 0; 2777 2778 $command .= " --a52_drc_off " 2779 if $audio_info->tc_audio_filter ne 'a52drc'; 2780 $command .= " -J normalize" 2781 if $audio_info->tc_audio_filter eq 'normalize'; 2782 2783 if ( $audio_info->tc_samplerate != $audio_info->sample_rate 2784 and $audio_info->tc_samplerate ) { 2785 $command .= " -E " . $audio_info->tc_samplerate 2786 if $audio_info->tc_samplerate; 2787 $command .= " -J resample" 2788 if $audio_info->tc_audio_codec eq 'vorbis'; 2789 } 2790 } 2791 2792 if ( $self->tc_start_frame ne '' 2793 or $self->tc_end_frame ne '' ) { 2794 my $start_frame = $self->tc_start_frame; 2795 my $end_frame = $self->tc_end_frame; 2796 $start_frame ||= 0; 2797 $end_frame ||= $self->frames; 2798 2799 if ( $start_frame != 0 ) { 2800 my $options 2801 = $self->get_frame_grab_options( frame => $start_frame ); 2802 $options->{c} =~ /(\d+)/; 2803 my $c1 = $1; 2804 my $c2 = $c1 + $end_frame - $start_frame; 2805 $command .= " -c $c1-$c2"; 2806 $command .= " -L $options->{L}" 2807 if $options->{L} ne ''; 2808 2809 } 2810 else { 2811 $command .= " -c $start_frame-$end_frame"; 2812 } 2813 } 2814 2815 if ( $self->tc_psu_core ) { 2816 $command .= " --psu_mode --nav_seek " 2817 . $self->vob_nav_file 2818 . " --no_split"; 2819 } 2820 2821 $command .= " ".$self->get_transcode_status_option; 2822 2823 $command = $self->combine_command_options( 2824 cmd => "transcode", 2825 cmd_line => $command, 2826 options => $self->tc_options, 2827 ) 2828 if $self->tc_options =~ /\S/; 2829 2830 if ( $self->tc_container eq 'vcd' ) { 2831 $command .= " && rm -f " . $self->target_avi_file; 2832 } 2833 2834 $command .= " && echo EXECFLOW_OK"; 2835 2836 return $command; 2837} 2838 2839sub get_merge_audio_command { 2840 my $self = shift; 2841 my %par = @_; 2842 my ( $vob_nr, $target_nr ) = @par{ 'vob_nr', 'target_nr' }; 2843 2844 my $avi_file = $self->target_avi_file; 2845 my $audio_file; 2846 $audio_file = $self->target_avi_audio_file( 2847 vob_nr => $vob_nr, 2848 avi_nr => $target_nr 2849 ) 2850 if $vob_nr != -1; 2851 2852 my $command; 2853 2854 my $tc_nice = $self->tc_nice || 0; 2855 2856 if ( $self->is_ogg ) { 2857 $command .= "execflow -n $tc_nice ogmmerge -o $avi_file.merged " 2858 . " $avi_file" 2859 . " $audio_file &&" 2860 . " mv $avi_file.merged $avi_file &&" 2861 . " rm -f $audio_file &&" 2862 . " echo EXECFLOW_OK"; 2863 2864 } 2865 else { 2866 die "avimerge without audio isn't possible" 2867 if not $audio_file; 2868 2869 $command .= "execflow -n $tc_nice avimerge" 2870 . " -p $audio_file" 2871 . " -a $target_nr" 2872 . " -o $avi_file.merged" 2873 . " -i $avi_file &&" 2874 . " mv $avi_file.merged $avi_file &&" 2875 . " rm $audio_file &&" 2876 . " echo EXECFLOW_OK"; 2877 } 2878 2879 return $command; 2880} 2881 2882sub get_fast_resize_options { 2883 my $self = shift; 2884 2885 my $multiple_of = 8; 2886 2887 my $width = $self->width - $self->tc_clip1_left - $self->tc_clip1_right; 2888 my $height = $self->height - $self->tc_clip1_top - $self->tc_clip1_bottom; 2889 2890 my $zoom_width = $self->tc_zoom_width; 2891 my $zoom_height = $self->tc_zoom_height; 2892 2893 $zoom_width ||= $width; 2894 $zoom_height ||= $height; 2895 2896 my $width_n = ( $zoom_width - $width ) / $multiple_of; 2897 my $height_n = ( $zoom_height - $height ) / $multiple_of; 2898 2899 my ( $err_div32, $err_shrink_expand ); 2900 2901 if (( $width_n != 0 2902 and 2903 ( $zoom_width % $multiple_of != 0 or $width % $multiple_of != 0 ) 2904 ) 2905 or ($height_n != 0 2906 and ( $zoom_height % $multiple_of != 0 2907 or $height % $multiple_of != 0 ) 2908 ) 2909 ) { 2910 $err_div32 = 1; 2911 } 2912 2913 if ( $width_n * $height_n < 0 ) { 2914 $err_shrink_expand = 1; 2915 } 2916 2917 return ( $width_n, $height_n, $err_div32, $err_shrink_expand ); 2918} 2919 2920sub fast_resize_possible { 2921 my $self = shift; 2922 my ( undef, undef, $err1, $err2 ) = $self->get_fast_resize_options; 2923 my $ok = !( $err1 || $err2 ); 2924 $self->set_tc_fast_resize(0) unless $ok; 2925 return $ok; 2926} 2927 2928#--------------------------------------------------------------------- 2929# Methods for MPEG multiplexing 2930#--------------------------------------------------------------------- 2931 2932sub get_mplex_command { 2933 my $self = shift; 2934 2935 my $video_codec = $self->tc_video_codec; 2936 2937 my $avi_file = $self->target_avi_file; 2938 my $size = $self->tc_disc_size; 2939 2940 my %mplex_f = ( 2941 XSVCD => 5, 2942 SVCD => 4, 2943 CVD => 4, 2944 XVCD => 2, 2945 VCD => 1, 2946 ); 2947 2948 my %mplex_v = ( 2949 XSVCD => "-V", 2950 SVCD => "-V", 2951 CVD => "-V", 2952 XVCD => "-V", 2953 VCD => "", 2954 ); 2955 2956 my %vext = ( 2957 XSVCD => "m2v", 2958 SVCD => "m2v", 2959 CVD => "m2v", 2960 XVCD => "m1v", 2961 VCD => "m1v", 2962 ); 2963 2964 my $mplex_f = $mplex_f{$video_codec}; 2965 my $mplex_v = $mplex_v{$video_codec}; 2966 my $vext = $vext{$video_codec}; 2967 2968 my $target_file = "$avi_file-%d.mpg"; 2969 2970 my $add_audio_tracks; 2971 my $add_audio_tracks_href = $self->get_additional_audio_tracks; 2972 2973 if ( keys %{$add_audio_tracks_href} ) { 2974 my ( $avi_nr, $vob_nr ); 2975 foreach $avi_nr ( sort keys %{$add_audio_tracks_href} ) { 2976 $vob_nr = $add_audio_tracks_href->{$avi_nr}; 2977 $add_audio_tracks .= " " 2978 . $self->target_avi_audio_file( 2979 vob_nr => $vob_nr, 2980 avi_nr => $avi_nr, 2981 ) 2982 . ".mpa"; 2983 } 2984 } 2985 2986 my $opt_r; 2987 if ( $video_codec =~ /^(XS?VCD|CVD)$/ ) { 2988 2989 #-- get overall bitrate, needed for X(S)VCD. 2990 my $bc = Video::DVDRip::BitrateCalc->new( title => $self ); 2991 $bc->calculate; 2992 my $bitrate = $bc->video_bitrate + $bc->audio_bitrate 2993 + $bc->vcd_reserve_bitrate; 2994 $opt_r = "-r $bitrate"; 2995 } 2996 2997 my $tc_nice = $self->tc_nice || 0; 2998 2999 my $command = "execflow -n $tc_nice mplex -f $mplex_f $opt_r $mplex_v " 3000 . "-o $target_file $avi_file.$vext $avi_file.mpa " 3001 . "$add_audio_tracks && echo EXECFLOW_OK"; 3002 3003 return $command; 3004} 3005 3006#--------------------------------------------------------------------- 3007# Methods for AVI Splitting 3008#--------------------------------------------------------------------- 3009 3010sub get_split_command { 3011 my $self = shift; 3012 3013 my $avi_file = $self->target_avi_file; 3014 my $size = $self->tc_disc_size; 3015 3016 my $avi_dir = dirname $avi_file; 3017 $avi_file = basename $avi_file; 3018 3019 my $split_mask = sprintf( "%s-%03d", $self->project->name, $self->nr, ); 3020 3021 my $command; 3022 3023 if ( -s "$avi_dir/$avi_file" > 0 3024 and -s "$avi_dir/$avi_file" <= $size * 1024 * 1024 ) { 3025 $command = "echo File is smaller than one disc, no need to split. " 3026 . "&& echo EXECFLOW_OK"; 3027 return $command; 3028 } 3029 3030 my $tc_nice = $self->tc_nice || 0; 3031 3032 if ( $self->is_ogg ) { 3033 $split_mask .= $self->config('ogg_file_ext'); 3034 3035 $command .= "cd $avi_dir && ls -l && " 3036 . "execflow -n $tc_nice ogmsplit -s $size $avi_file && " 3037 . "echo EXECFLOW_OK"; 3038 } 3039 else { 3040 $command .= "cd $avi_dir && " 3041 . "execflow -n $tc_nice avisplit -s $size -i $avi_file -o $split_mask && " 3042 . "echo EXECFLOW_OK"; 3043 } 3044 3045 return $command; 3046} 3047 3048#--------------------------------------------------------------------- 3049# Methods for taking Snapshots 3050#--------------------------------------------------------------------- 3051 3052sub snapshot_filename { 3053 my $self = shift; 3054 3055 return $self->preview_filename( type => 'orig' ); 3056} 3057 3058sub raw_snapshot_filename { 3059 my $self = shift; 3060 3061 my $raw_filename = $self->snapshot_filename; 3062 $raw_filename =~ s/\.jpg$/.raw/; 3063 3064 return $raw_filename; 3065} 3066 3067sub get_frame_grab_options { 3068 my $self = shift; 3069 my %par = @_; 3070 my ($frame) = @par{'frame'}; 3071 3072 if ( $self->project->rip_mode ne 'rip' 3073 || !$self->has_vob_nav_file 3074 || $self->tc_force_slow_grabbing ) { 3075 $self->log( __ "Fast VOB navigation only available for ripped DVD's, " 3076 . "falling back to slow method." ) 3077 if $self->project->rip_mode ne 'rip'; 3078 $self->log( 3079 __ "VOB navigation file is missing. Slow navigation method used." 3080 ) 3081 if $self->project->rip_mode eq 'rip' 3082 and not $self->has_vob_nav_file; 3083 $self->log( __ "Using slow preview grabbing as adviced by user" ) 3084 if $self->tc_force_slow_grabbing; 3085 return { c => $frame . "-" . ( $frame + 1 ), }; 3086 } 3087 3088 my $old_chapter = $self->actual_chapter; 3089 3090 $self->set_actual_chapter( $self->get_first_chapter ) 3091 if $self->tc_use_chapter_mode; 3092 3093 my $vob_nav_file = $self->vob_nav_file; 3094 3095 $self->set_actual_chapter($old_chapter) 3096 if $self->tc_use_chapter_mode; 3097 3098 my $fh = FileHandle->new; 3099 open( $fh, $vob_nav_file ) 3100 or croak "msg:" 3101 . __x( "Can't read VOB navigation file '{vob_nav_file}'", 3102 vob_nav_file => $vob_nav_file ); 3103 3104 my ( $found, $block_offset, $frame_offset, $psu ); 3105 3106 my $frames = 0; 3107 3108 while (<$fh>) { 3109 if ( $frames == $frame ) { 3110 s/^\s+//; 3111 s/\s+$//; 3112 croak "msg:" 3113 . __x( "VOB navigation file '{vob_nav_file}' is corrupted.", 3114 vob_nav_file => $vob_nav_file ) 3115 if !/^\d+\s+\d+\s+\d+\s+\d+\s+\d+\s+\d+$/; 3116 ( $psu, $block_offset, $frame_offset ) 3117 = ( split( /\s+/, $_ ) )[ 0, 4, 5 ]; 3118 $found = 1; 3119 last; 3120 } 3121 ++$frames; 3122 } 3123 3124 close $fh; 3125 3126 croak "msg:" 3127 . __x( 3128 "Can't find frame {frame} in VOB navigation file " 3129 . "'{vob_nav_file}' (which has only {frames} frames). ", 3130 frame => $frame, 3131 vob_nav_file => $vob_nav_file, 3132 frames => $frames 3133 ) 3134 if not $found; 3135 3136 my @psu; 3137 if ($psu) { 3138 @psu = ( S => --$psu ); 3139 } 3140 3141 return { 3142 @psu, 3143 L => $block_offset, 3144 c => $frame_offset . "-" . ( $frame_offset + 1 ) 3145 }; 3146} 3147 3148sub get_take_snapshot_command { 3149 my $self = shift; 3150 3151 return $self->get_take_snapshot_command_transcode 3152 if not $self->has("ffmpeg"); 3153 3154 my $nr = $self->nr; 3155 my $frame = $self->preview_frame_nr; 3156 my $tmp_dir = $self->project->snap_dir."/dvdrip$$.snap"; 3157 my $filename = $self->preview_filename( type => 'orig' ); 3158 my $raw_filename = $self->raw_snapshot_filename; 3159 my $frame_rate = $self->frame_rate; 3160 3161 my $source_options = $self->data_source_options; 3162 my $grab_options = $self->get_frame_grab_options( frame => $frame ); 3163 3164 $grab_options->{S} ||= "0"; 3165 $grab_options->{L} ||= "0"; 3166 3167 my ($start_frame) = $grab_options->{c} =~ /(\d+)/; 3168 my $start = sprintf("%.3f", $start_frame / $frame_rate); 3169 3170 my $T; 3171 $T = "-T $source_options->{T}" if $source_options->{T}; 3172 3173 my $command = "mkdir -m 0775 $tmp_dir; " 3174 . "cd $tmp_dir; " 3175 . "execflow " 3176 . "tccat -i $source_options->{i} $T " 3177 . "-t $source_options->{x} " 3178 . "-S $grab_options->{L} -d 0 | " 3179 . "tcdemux -s 0x80 -x mpeg2 -S $grab_options->{S} " 3180 . "-M 0 -d 0 -P /dev/null | " 3181 . "tcextract -t vob -a 0 -x mpeg2 -d 0 | " 3182 . "ffmpeg -r $frame_rate -i - -an -r 1 -ss '$start' -vframes 1 snapshot%03d.png "; 3183 3184 $command .= " && " 3185 . "execflow convert" 3186 . " -size " 3187 . $self->width . "x" 3188 . $self->height 3189 . " $tmp_dir/snapshot*.png $filename && " 3190 . "execflow convert" 3191 . " -size " 3192 . $self->width . "x" 3193 . $self->height 3194 . " $tmp_dir/snapshot*.png gray:$raw_filename &&" 3195 . " rm -r $tmp_dir && " 3196 . "echo EXECFLOW_OK"; 3197 3198 return $command; 3199} 3200 3201sub get_take_snapshot_command_transcode { 3202 my $self = shift; 3203 3204 my $nr = $self->nr; 3205 my $frame = $self->preview_frame_nr; 3206 my $tmp_dir = $self->project->snap_dir."/dvdrip$$.ppm"; 3207 my $filename = $self->preview_filename( type => 'orig' ); 3208 my $raw_filename = $self->raw_snapshot_filename; 3209 3210 my $source_options = $self->data_source_options; 3211 3212 $source_options->{x} .= ",null"; 3213 3214 my $command = "mkdir -m 0775 $tmp_dir; " 3215 . "cd $tmp_dir; " 3216 . "execflow transcode " 3217 . " -H 10 " 3218 . $self->get_transcode_status_option 3219 . ( $self->version("transcode") < 613 ? " -z -k" : "" ) 3220 . " -o snapshot" 3221 . " -y ppm,null"; 3222 3223 $command .= " -" . $_ . " " . $source_options->{$_} 3224 for keys %{$source_options}; 3225 3226 my $grab_options = $self->get_frame_grab_options( frame => $frame ); 3227 3228 $command .= " -" . $_ . " " . $grab_options->{$_} 3229 for keys %{$grab_options}; 3230 3231 $command .= " && " 3232 . "execflow convert" 3233 . " -size " 3234 . $self->width . "x" 3235 . $self->height 3236 . " $tmp_dir/snapshot*.ppm $filename && " 3237 . "execflow convert" 3238 . " -size " 3239 . $self->width . "x" 3240 . $self->height 3241 . " $tmp_dir/snapshot*.ppm gray:$raw_filename &&" 3242 . " rm -r $tmp_dir && " 3243 . "echo EXECFLOW_OK"; 3244 3245 $command =~ s/-x\s+([^,]+),null,null/-x $1,null/; 3246 3247 return $command; 3248} 3249 3250sub calc_snapshot_bounding_box { 3251 my $self = shift; 3252 3253 my $filename = $self->raw_snapshot_filename; 3254 3255 open( IN, $filename ) 3256 or die "can't read '$filename'"; 3257 my $blob = ""; 3258 while (<IN>) { 3259 $blob .= $_; 3260 } 3261 close IN; 3262 3263 my ( $min_x, $min_y, $max_x, $max_y, $x, $y ); 3264 my $width = $min_x = $self->width; 3265 my $height = $min_y = $self->height; 3266 my $thres = 12; 3267 3268 # search min_y 3269 for ( $x = 0; $x < $width; ++$x ) { 3270 for ( $y = 0; $y < $height; ++$y ) { 3271 if (unpack( "C", substr( $blob, $y * $width + $x, 1 ) ) > $thres ) 3272 { 3273 $min_y = $y if $y < $min_y; 3274 last; 3275 } 3276 } 3277 } 3278 3279 # search max_y 3280 for ( $x = 0; $x < $width; ++$x ) { 3281 for ( $y = $height - 1; $y >= 0; --$y ) { 3282 if (unpack( "C", substr( $blob, $y * $width + $x, 1 ) ) > $thres ) 3283 { 3284 $max_y = $y if $y > $max_y; 3285 last; 3286 } 3287 } 3288 } 3289 3290 # search min_x 3291 for ( $y = 0; $y < $height; ++$y ) { 3292 for ( $x = 0; $x < $width; ++$x ) { 3293 3294# print "x=$x y=$y min_x=$min_x c=".unpack("C", substr($blob, $y*$width+$x, 1)),"\n"; 3295 if (unpack( "C", substr( $blob, $y * $width + $x, 1 ) ) > $thres ) 3296 { 3297 $min_x = $x if $x < $min_x; 3298 last; 3299 } 3300 } 3301 } 3302 3303 # search max_y 3304 for ( $y = 0; $y < $height; ++$y ) { 3305 for ( $x = $width - 1; $x >= 0; --$x ) { 3306 if (unpack( "C", substr( $blob, $y * $width + $x, 1 ) ) > $thres ) 3307 { 3308 $max_x = $x if $x > $max_x; 3309 last; 3310 } 3311 } 3312 } 3313 3314 # height clipping must not be odd 3315 --$min_y if $min_y % 2; 3316 ++$max_y if $max_y % 2; 3317 3318 $self->set_bbox_min_x($min_x); 3319 $self->set_bbox_min_y($min_y); 3320 $self->set_bbox_max_x($max_x); 3321 $self->set_bbox_max_y($max_y); 3322 3323 1; 3324} 3325 3326#--------------------------------------------------------------------- 3327# Methods for making clip and zoom images 3328#--------------------------------------------------------------------- 3329 3330sub make_preview_clip1 { 3331 my $self = shift; 3332 3333 return $self->make_preview_clip( type => "clip1", ); 3334} 3335 3336sub make_preview_clip2 { 3337 my $self = shift; 3338 3339 return $self->make_preview_clip( type => "clip2", ); 3340} 3341 3342sub make_preview_clip { 3343 my $self = shift; 3344 my %par = @_; 3345 my ($type) = @par{'type'}; 3346 3347 my $source_file; 3348 if ( $type eq 'clip1' ) { 3349 $source_file = $self->preview_filename( type => 'orig' ); 3350 } 3351 else { 3352 $source_file = $self->preview_filename( type => 'zoom' ); 3353 } 3354 3355 return if not -f $source_file; 3356 3357 my $target_file = $self->preview_filename( type => $type ); 3358 3359 my $catch = $self->system( command => "identify $source_file" ); 3360 my ( $width, $height ); 3361 ( $width, $height ) = ( $catch =~ /\s+(\d+)x(\d+)\s+/ ); 3362 3363 my ( $top, $bottom, $left, $right ); 3364 if ( $type eq 'clip1' ) { 3365 $top = $self->tc_clip1_top; 3366 $bottom = $self->tc_clip1_bottom; 3367 $left = $self->tc_clip1_left; 3368 $right = $self->tc_clip1_right; 3369 } 3370 else { 3371 $top = $self->tc_clip2_top; 3372 $bottom = $self->tc_clip2_bottom; 3373 $left = $self->tc_clip2_left; 3374 $right = $self->tc_clip2_right; 3375 } 3376 3377 my $new_width = $width - $left - $right; 3378 my $new_height = $height - $top - $bottom; 3379 3380 my $command = "convert $source_file -crop " 3381 . "${new_width}x${new_height}+$left+$top " 3382 . $target_file; 3383 3384 $self->system( command => "convert $source_file -crop " 3385 . "${new_width}x${new_height}+$left+$top " 3386 . $target_file ); 3387 3388 1; 3389} 3390 3391sub make_preview_zoom { 3392 my $self = shift; 3393 my %par = @_; 3394 3395 my $source_file = $self->preview_filename( type => 'clip1' ); 3396 my $target_file = $self->preview_filename( type => 'zoom' ); 3397 3398 my $new_width = $self->tc_zoom_width; 3399 my $new_height = $self->tc_zoom_height; 3400 3401 if ( not $new_width or not $new_height ) { 3402 copy( $source_file, $target_file ); 3403 return 1; 3404 } 3405 3406 my $catch = $self->system( command => "identify $source_file" ); 3407 3408 $self->system( command => "convert $source_file -geometry " 3409 . "'${new_width}!x${new_height}!' " 3410 . $target_file ); 3411 3412 1; 3413} 3414 3415sub get_make_preview_command { 3416 my $self = shift; 3417 my %par = @_; 3418 my ($type) = @par{'type'}; 3419 3420 my $command; 3421 if ( $type =~ /clip/ ) { 3422 my ( $top, $bottom, $left, $right, $source_file ); 3423 if ( $type eq 'clip1' ) { 3424 $source_file = $self->preview_filename( type => 'orig' ); 3425 $top = $self->tc_clip1_top; 3426 $bottom = $self->tc_clip1_bottom; 3427 $left = $self->tc_clip1_left; 3428 $right = $self->tc_clip1_right; 3429 } 3430 else { 3431 $source_file = $self->preview_filename( type => 'zoom' ); 3432 $top = $self->tc_clip2_top; 3433 $bottom = $self->tc_clip2_bottom; 3434 $left = $self->tc_clip2_left; 3435 $right = $self->tc_clip2_right; 3436 } 3437 3438 $top ||= "0"; 3439 $bottom ||= "0"; 3440 $left ||= "0"; 3441 $right ||= "0"; 3442 3443 my $target_file = $self->preview_filename( type => $type ); 3444 return "execflow dvdrip-thumb $source_file $target_file " 3445 . "$top $right $bottom $left"; 3446 } 3447 elsif ( $type eq 'zoom' ) { 3448 my $source_file = $self->preview_filename( type => 'clip1' ); 3449 my $target_file = $self->preview_filename( type => 'zoom' ); 3450 my $new_width = $self->tc_zoom_width || $self->width; 3451 my $new_height = $self->tc_zoom_height || $self->height; 3452 return "execflow dvdrip-thumb $source_file $target_file " 3453 . "$new_width $new_height"; 3454 } 3455} 3456 3457sub get_make_previews_command { 3458 my $self = shift; 3459 3460 return $self->get_make_preview_command( type => 'clip1' ) . " && " 3461 . $self->get_make_preview_command( type => 'zoom' ) . " && " 3462 . $self->get_make_preview_command( type => 'clip2' ); 3463} 3464 3465#--------------------------------------------------------------------- 3466 3467sub remove_vob_files { 3468 my $self = shift; 3469 3470 my $vob_dir = $self->vob_dir; 3471 3472 unlink(<$vob_dir/*>); 3473 3474 1; 3475} 3476 3477sub get_remove_vobs_command { 3478 my $self = shift; 3479 3480 my $vob_dir = $self->vob_dir; 3481 3482 my $command = "rm $vob_dir/* && echo EXECFLOW_OK"; 3483 3484 return $command; 3485} 3486 3487sub get_view_dvd_command { 3488 my $self = shift; 3489 my %par = @_; 3490 my ($command_tmpl) = @par{command_tmpl}; 3491 3492 my $nr = $self->nr; 3493 my $audio_channel = $self->audio_channel; 3494 my $base_audio_code; 3495 3496 if ( $self->audio_track->type eq 'lpcm' ) { 3497 $base_audio_code = 160; 3498 3499 } 3500 elsif ( $self->audio_track->type eq 'mpeg1' ) { 3501 $base_audio_code = 0; 3502 3503 } 3504 else { 3505 $base_audio_code = 128; 3506 } 3507 3508 my @opts = ( 3509 { t => $self->nr, 3510 a => $self->audio_channel, 3511 m => $self->tc_viewing_angle, 3512 b => $base_audio_code, 3513 d => quotemeta($self->project->dvd_device), 3514 } 3515 ); 3516 3517 if ( $self->tc_use_chapter_mode eq 'select' ) { 3518 my $chapters = $self->tc_selected_chapters; 3519 use Data::Dumper; 3520 print Dumper($chapters); 3521 if ( not $chapters or not @{$chapters} ) { 3522 return "echo 'no chapters selected'"; 3523 } 3524 push @opts, { c => $_ } foreach @{$chapters}; 3525 } 3526 else { 3527 push @opts, { c => 1 }; 3528 } 3529 3530 my $command = $self->apply_command_template( 3531 template => $command_tmpl, 3532 opts => \@opts, 3533 ); 3534 3535 return $command; 3536} 3537 3538sub get_view_avi_command { 3539 my $self = shift; 3540 my %par = @_; 3541 my ( $command_tmpl, $file ) = @par{ 'command_tmpl', 'file' }; 3542 3543 my @filenames; 3544 if ($file) { 3545 @filenames = ($file); 3546 3547 } 3548 elsif ( $self->tc_use_chapter_mode ) { 3549 my $chapters = $self->get_chapters; 3550 my $filename; 3551 foreach my $chapter ( @{$chapters} ) { 3552 $self->set_actual_chapter($chapter); 3553 $filename = $self->avi_file; 3554 push @filenames, $filename if -f $filename; 3555 } 3556 $self->set_actual_chapter(undef); 3557 3558 } 3559 else { 3560 my $filename = $self->avi_file; 3561 my $ext = $self->get_target_ext; 3562 $filename =~ s/\.[^.]+$//; 3563 push @filenames, grep !/dvdrip-info/, glob( "${filename}*" . $ext ); 3564 } 3565 3566 croak "msg:" . __ "You first have to transcode this title." 3567 if not @filenames; 3568 3569 my @opts = ( {} ); 3570 push @opts, { f => $_ } for @filenames; 3571 3572 my $command = $self->apply_command_template( 3573 template => $command_tmpl, 3574 opts => \@opts, 3575 ); 3576 3577 return $command; 3578} 3579 3580sub get_view_stdin_command { 3581 my $self = shift; 3582 my %par = @_; 3583 my ($command_tmpl) = @par{'command_tmpl'}; 3584 3585 my $audio_channel = $self->audio_channel; 3586 3587 my @opts = ( { a => 0, } ); 3588 3589 my $command = $self->apply_command_template( 3590 template => $command_tmpl, 3591 opts => \@opts, 3592 ); 3593 3594 my $opts 3595 = $self->get_frame_grab_options( frame => $self->preview_frame_nr, ); 3596 3597 my $source_options = $self->data_source_options; 3598 3599 my $T; 3600 $T = "-T $source_options->{T}" if $source_options->{T}; 3601 3602 $command = "tccat -i $source_options->{i}" . " $T" 3603 . " -a $audio_channel -S $opts->{L} | $command"; 3604 3605 return $command; 3606} 3607 3608sub get_view_vob_image_command { 3609 my $self = shift; 3610 my %par = @_; 3611 my ($command_tmpl) = @par{'command_tmpl'}; 3612 3613 my $nr = $self->nr; 3614 my $audio_channel = $self->audio_channel; 3615 my $angle = $self->tc_viewing_angle; 3616 3617 my $command = "execflow tccat -i " 3618 . quotemeta($self->project->dvd_device) 3619 . " -a $audio_channel -L " 3620 . " -T $nr,1,$angle | $command_tmpl"; 3621 3622 return $command; 3623} 3624 3625#--------------------------------------------------------------------- 3626# CD burning stuff 3627#--------------------------------------------------------------------- 3628 3629sub get_burn_files { 3630 my $self = shift; 3631 3632 my $cd_type = $self->burn_cd_type || 'iso'; 3633 3634 my $ogg_ext = $self->config('ogg_file_ext'); 3635 3636 my $mask = 3637 $cd_type eq 'iso' ? "*.{avi,$ogg_ext,iso,dvdrip-info,sub,ifo,idx,rar}" 3638 : $cd_type eq 'vcd' ? "*.{mpg,vcd}" 3639 : "*.{mpg,svcd}"; 3640 3641 $mask = $self->avi_dir . "/" . $mask; 3642 3643 my @files = glob($mask); 3644 3645 my @burn_files; 3646 my %files_per_group; 3647 my ($label, $abstract, $base, $group, 3648 $index, $is_image, $ext, $chapter 3649 ); 3650 3651 foreach my $file ( sort @files ) { 3652 $base = basename($file); 3653 3654 $base =~ /^(.*?)([_-]\d+)([_-](C?)\d+)?\.([^\.]+)$/; 3655 $index = $3; 3656 $chapter = $4; 3657 $group = "$1:$5"; 3658 3659 $base =~ /([^\.]+)$/; 3660 $ext = $1; 3661 3662 $index =~ s/C//g; 3663 $index = $index * -1 if $index < 0; 3664 ++$files_per_group{$group}; 3665 3666 $is_image = $ext =~ /^(iso|vcd|svcd)$/; 3667 ++$index 3668 if $cd_type eq 'iso' 3669 and not $chapter; # avi counting begins with 0 3670 3671 $label = $base; 3672 $label =~ s/(-C?\d+)*\.[^\.]+$//; 3673 3674 $abstract = $label; 3675 $abstract =~ s/_/ /g; 3676 $abstract =~ s/\b(.)/uc($1)/eg; 3677 3678 $label .= "_$index" if not $is_image; 3679 3680 push @burn_files, 3681 { 3682 name => $base, 3683 label => $label, 3684 abstract => $abstract, 3685 size => ( int( ( -s $file ) / 1024 / 1024 ) || 1 ), 3686 group => $group, 3687 index => $index, 3688 path => $file, 3689 is_image => $is_image 3690 }; 3691 } 3692 3693 foreach my $file (@burn_files) { 3694 $file->{number} 3695 = "$file->{index} of " . $files_per_group{ $file->{group} }; 3696 } 3697 3698 return \@burn_files; 3699} 3700 3701sub cd_image_file { 3702 my $self = shift; 3703 3704 my $cd_type = $self->burn_cd_type; 3705 3706 my @labels = map { $_->{label} } 3707 sort { $a->{label} cmp $b->{label} } 3708 values %{ $self->burn_files_selected }; 3709 3710 return $self->avi_dir . "/" . $labels[0] . ".$cd_type"; 3711} 3712 3713sub burning_an_image { 3714 my $self = shift; 3715 3716 my $is_image; 3717 map { $is_image = 1 if $_->{is_image} } 3718 sort { $a->{path} cmp $b->{path} } 3719 values %{ $self->burn_files_selected }; 3720 3721 return $is_image; 3722} 3723 3724sub get_create_image_command { 3725 my $self = shift; 3726 my %par = @_; 3727 my ($on_the_fly) = @par{'on_the_fly'}; 3728 3729 croak "msg:" . __ "No files for image creation selected." 3730 if not $self->burn_files_selected 3731 or not keys %{ $self->burn_files_selected }; 3732 3733 my $is_image; 3734 my @files = map { $is_image = 1 if $_->{is_image}; $_->{path} } 3735 sort { $a->{path} cmp $b->{path} } 3736 values %{ $self->burn_files_selected }; 3737 3738 die __ "No burn files selected." if not @files; 3739 die __ "File is already an CD image." if $is_image; 3740 3741 my $cd_type = $self->burn_cd_type; 3742 3743 if ( $cd_type ne 'iso' and $on_the_fly ) { 3744 croak __ "Can't burn (S)VCD on the fly"; 3745 } 3746 3747 my $image_file = $self->cd_image_file; 3748 3749 my $command; 3750 if ( $cd_type eq 'iso' ) { 3751 if ( $on_the_fly and $self->config('burn_estimate_size') ) { 3752 $command = 'SIZE=$('; 3753 $command .= $self->config('burn_mkisofs_cmd'); 3754 $command .= " -quiet -print-size" 3755 . " -r -J -jcharset default -l -D -L" . " -V '" 3756 . $self->burn_label . "'" 3757 . " -abstract '" 3758 . $self->burn_abstract . " " 3759 . $self->burn_number . "'" . " " 3760 . join( " ", @files ); 3761 $command .= ") && "; 3762 $command .= "execflow " . $self->config('burn_mkisofs_cmd'); 3763 $command .= " -quiet"; 3764 $command .= " -r -J -jcharset default -l -D -L" . " -V '" 3765 . $self->burn_label . "'" 3766 . " -abstract '" 3767 . $self->burn_abstract . " " 3768 . $self->burn_number . "'" . " " 3769 . join( " ", @files ); 3770 } 3771 else { 3772 $command = "execflow " . $self->config('burn_mkisofs_cmd'); 3773 $command .= " -quiet" if $on_the_fly; 3774 $command .= " -o $image_file" if not $on_the_fly; 3775 $command .= " -r -J -jcharset default -l -D -L" . " -V '" 3776 . $self->burn_label . "'" 3777 . " -abstract '" 3778 . $self->burn_abstract . " " 3779 . $self->burn_number . "'" . " " 3780 . join( " ", @files ); 3781 } 3782 } 3783 else { 3784 $command = "execflow " 3785 . $self->config('burn_vcdimager_cmd') 3786 . ( $cd_type eq 'svcd' ? ' --type=svcd' : ' --type=vcd2' ) 3787 . " --iso-volume-label='" 3788 . uc( $self->burn_label ) . "'" 3789 . " --info-album-id='" 3790 . uc( $self->burn_abstract . " " . $self->burn_number ) . "'" 3791 . " --cue-file=$image_file.cue" 3792 . " --bin-file=$image_file" . " " 3793 . join( " ", @files ); 3794 } 3795 3796 $command .= " && echo EXECFLOW_OK" if not $on_the_fly; 3797 3798 return $command; 3799} 3800 3801sub get_burn_command { 3802 my $self = shift; 3803 3804 croak "msg:" . __ "No files for burning selected." 3805 if not $self->burn_files_selected 3806 or not keys %{ $self->burn_files_selected }; 3807 3808 my $cd_type = $self->burn_cd_type; 3809 3810 my $is_image; 3811 my @files = map { $is_image = 1 if $_->{is_image}; $_->{path} } 3812 sort { $a->{path} cmp $b->{path} } 3813 values %{ $self->burn_files_selected }; 3814 3815 die "msg:" . __ "No burn files selected." if not @files; 3816 3817 my $command; 3818 if ( $cd_type eq 'iso' ) { 3819 if ( not $is_image ) { 3820 $command = $self->get_create_image_command( on_the_fly => 1 ); 3821 $command .= " | " . $self->config('burn_cdrecord_cmd'); 3822 } 3823 else { 3824 $command = "execflow " . $self->config('burn_cdrecord_cmd'); 3825 } 3826 3827 my $gracetime = $self->config('burn_cdrecord_cmd') =~ /cdrecord/ 3828 ? 'gracetime=5' 3829 : ''; 3830 3831 $command .= " dev=" 3832 . $self->config('burn_cdrecord_device') 3833 . " fs=4096k -v -overburn $gracetime" 3834 . " speed=" 3835 . $self->config('burn_writing_speed') 3836 . " -eject -pad -overburn"; 3837 3838 $command .= " -dummy" if $self->config('burn_test_mode'); 3839 3840 $command .= ' tsize=${SIZE}s' 3841 if ( ( not $is_image ) and $self->config('burn_estimate_size') ); 3842 3843 if ( not $is_image ) { 3844 $command .= " -"; 3845 } 3846 else { 3847 $command .= " $files[0]"; 3848 } 3849 } 3850 else { 3851 $command = "rm -f $files[0].bin; ln -s $files[0] $files[0].bin && "; 3852 3853 $command .= "execflow " . $self->config('burn_cdrdao_cmd'); 3854 3855 if ( $command !~ /\bwrite\b/ ) { 3856 $command .= " write"; 3857 } 3858 3859 $command .= " --device " 3860 . $self->config('burn_cdrecord_device') 3861 . " --speed " 3862 . $self->config('burn_writing_speed'); 3863 3864 $command .= " --driver " . $self->config('burn_cdrdao_driver') 3865 if $self->config('burn_cdrdao_driver'); 3866 3867 $command .= " --buffers " . $self->config('burn_cdrdao_buffers') 3868 if $self->config('burn_cdrdao_buffers'); 3869 3870 $command .= " --eject" if $self->config('burn_cdrdao_eject'); 3871 $command .= " --overburn" if $self->config('burn_cdrdao_overburn'); 3872 $command .= " --simulate" if $self->config('burn_test_mode'); 3873 3874 $command .= " $files[0].cue" . " && rm $files[0].bin"; 3875 } 3876 3877 $command .= " && echo EXECFLOW_OK"; 3878 3879 return $command; 3880} 3881 3882sub get_erase_cdrw_command { 3883 my $self = shift; 3884 3885 my $blank_method = $self->config('burn_blank_method'); 3886 ($blank_method) = $blank_method =~ /^\s*([^\s]+)/; 3887 3888 my $command = $self->config('burn_cdrecord_cmd') . " dev=" 3889 . $self->config('burn_cdrecord_device') 3890 . " blank=$blank_method"; 3891 3892 $command .= " -dummy" if $self->config('burn_test_mode'); 3893 3894 $command .= " && echo EXECFLOW_OK"; 3895 3896 return $command; 3897} 3898 3899sub selected_subtitle { 3900 my $self = shift; 3901 return undef if not $self->subtitles; 3902 return undef if not defined $self->selected_subtitle_id; 3903 return $self->subtitles->{ $self->selected_subtitle_id }; 3904} 3905 3906sub get_cat_vob_command { 3907 my $self = shift; 3908 3909 my $rip_mode = $self->project->rip_mode; 3910 3911 my $cat; 3912 if ( $rip_mode eq 'rip' ) { 3913 $cat = "cat " . $self->vob_dir . "/*"; 3914 3915 } 3916 else { 3917 $cat = "execflow tccat -i " 3918 . $self->project->rip_data_source . " -T " 3919 . $self->tc_title_nr; 3920 } 3921 3922 return $cat; 3923} 3924 3925sub get_subtitle_grab_images_command { 3926 my $self = shift; 3927 3928 my $subtitle = $self->selected_subtitle; 3929 3930 my $timecode = $subtitle->tc_preview_timecode; 3931 my $cnt = $subtitle->tc_preview_img_cnt; 3932 my $sid = sprintf( "0x%02x", $subtitle->id + 32 ); 3933 3934 my $sub_dir = $self->get_subtitle_preview_dir; 3935 my $vob_dir = $self->vob_dir; 3936 3937 if ( $timecode !~ /^\d\d:\d\d:\d\d$/ ) { 3938 my $frames = $timecode + 0; 3939 my $seconds = int( $frames / $self->tc_video_framerate ); 3940 $timecode = $self->format_time( time => $seconds ); 3941 } 3942 3943 $cnt = 0 + $cnt; 3944 $cnt ||= 1; 3945 3946 my $cat = $self->get_cat_vob_command; 3947 3948 my $command = "mkdir -p $sub_dir && rm -f $sub_dir/*.{pgm,srtx} && " 3949 . " $cat | tcextract -x ps1 -t vob -a $sid |" 3950 . " subtitle2pgm -P -C 0 -o $sub_dir/pic -v -e $timecode,$cnt" 3951 . " 2>&1 | dvdrip-subpng && echo EXECFLOW_OK"; 3952 3953 return $command; 3954} 3955 3956sub get_frame_of_sec { 3957 my $self = shift; 3958 my ($sec) = @_; 3959 3960 my $frame = int( $sec * $self->frame_rate ); 3961 3962 $frame = 0 if $frame < 0; 3963 $frame = $self->frames - 1 if $frame >= $self->frames; 3964 3965 return $frame; 3966} 3967 3968sub get_subtitle_test_frame_range { 3969 my $self = shift; 3970 3971 my $subtitle = $self->selected_subtitle; 3972 my $image_cnt = $subtitle->tc_test_image_cnt; 3973 my $first_entry = $subtitle->get_first_entry; 3974 my $nth_entry = $subtitle->get_nth_entry($image_cnt); 3975 3976 my $time_sec_from = $first_entry->get_time_sec; 3977 my $time_sec_to = $nth_entry->get_time_sec; 3978 3979 my $frame_from = $self->get_frame_of_sec( $time_sec_from - 15 ); 3980 my $frame_to = $self->get_frame_of_sec( $time_sec_to + 15 ); 3981 3982 $frame_to = $frame_from if $frame_to < $frame_from; 3983 3984 return ( $frame_from, $frame_to ); 3985 3986} 3987 3988sub get_subtitle_transcode_options { 3989 my $self = shift; 3990 3991 my $subtitle = $self->get_render_subtitle; 3992 3993 return "" if not $subtitle; 3994 3995 my $command = " -J extsub=" 3996 . $subtitle->id . ":" 3997 . ( $subtitle->tc_vertical_offset || 0 ) . ":" 3998 . ( $subtitle->tc_time_shift || 0 ) . ":" 3999 . ( $subtitle->tc_antialias ? "0" : "1" ) . ":" 4000 . ( $subtitle->tc_postprocess ? "1" : "0" ); 4001 4002 if ( $subtitle->tc_color_manip ) { 4003 $command .= ":" 4004 . ( $subtitle->tc_color_a || 0 ) . ":" 4005 . ( $subtitle->tc_color_b || 0 ) . ":" 4006 . ( $subtitle->tc_assign_color_a || 0 ) . ":" 4007 . ( $subtitle->tc_assign_color_b || 0 ); 4008 } 4009 4010 return $command; 4011} 4012 4013sub get_subtitle_preview_dir { 4014 my $self = shift; 4015 my ($subtitle_id) = @_; 4016 4017 $subtitle_id = $self->selected_subtitle_id if !defined $subtitle_id; 4018 4019 if ( $self->tc_use_chapter_mode ) { 4020 return sprintf( "%s/subtitles/%03d-C%03d/%02d", 4021 $self->project->snap_dir, $self->nr, 4022 ( $self->actual_chapter || $self->get_first_chapter || 1 ), 4023 $subtitle_id ); 4024 } 4025 else { 4026 return sprintf( "%s/subtitles/%03d/%02d", 4027 $self->project->snap_dir, $self->nr, $subtitle_id ); 4028 } 4029} 4030 4031sub get_render_subtitle { 4032 my $self = shift; 4033 4034 return undef if not $self->subtitles; 4035 4036 foreach my $subtitle ( values %{ $self->subtitles } ) { 4037 return $subtitle if $subtitle->tc_render; 4038 } 4039 4040 return undef; 4041} 4042 4043sub info_file { 4044 my $self = shift; 4045 4046 my $info_file = $self->avi_file; 4047 4048 $info_file =~ s/\.[^.]+$/.dvdrip-info/; 4049 $info_file .= ".dvdrip-info" if $info_file !~ /\./; 4050 4051 return $info_file; 4052} 4053 4054sub get_transcoded_video_width_height { 4055 my $self = shift; 4056 4057 my $width = $self->tc_zoom_width; 4058 my $height = $self->tc_zoom_height; 4059 4060 $width -= $self->tc_clip2_left + $self->tc_clip2_right; 4061 $height -= $self->tc_clip2_top + $self->tc_clip2_bottom; 4062 4063 return ( $width, $height ); 4064} 4065 4066sub suggest_subtitle_on_black_bars { 4067 my $self = shift; 4068 4069 my $subtitle = $self->get_render_subtitle; 4070 return 1 if not $subtitle; 4071 4072 croak "msg:" . __ "No subtitle selected" if not $subtitle; 4073 4074 my $clip2_top = 0; 4075 my $clip2_bottom = 0; 4076 4077 my $width = $self->tc_zoom_width; 4078 my $height = $self->tc_zoom_height; 4079 4080 my $rest = ( $height - $clip2_bottom - $clip2_top ) % 16; 4081 4082 if ($rest) { 4083 if ( $rest % 2 ) { 4084 $clip2_bottom -= int( $rest / 2 ) + 1; 4085 $clip2_top -= int( $rest / 2 ); 4086 } 4087 else { 4088 $clip2_bottom -= $rest / 2; 4089 $clip2_top -= $rest / 2; 4090 } 4091 } 4092 4093 $self->set_tc_clip2_bottom($clip2_bottom); 4094 $self->set_tc_clip2_top($clip2_top); 4095 4096 $subtitle->set_tc_vertical_offset(0); 4097 4098 return 1; 4099} 4100 4101sub suggest_subtitle_on_movie { 4102 my $self = shift; 4103 4104 my $subtitle = $self->get_render_subtitle; 4105 return 1 if not $subtitle; 4106 4107 croak "msg:" . __ "No subtitle selected" if not $subtitle; 4108 4109 my $clip2_bottom = $self->tc_clip2_bottom; 4110 my $zoom_height = $self->tc_zoom_height || $self->height; 4111 my $pre_zoom_height 4112 = $self->height - $self->tc_clip1_top - $self->tc_clip1_bottom; 4113 my $scale = $pre_zoom_height / $zoom_height; 4114 4115 my $shift = int( $clip2_bottom * $scale ); 4116 4117 $shift = 0 if $shift < 0; 4118 4119 $subtitle->set_tc_vertical_offset( $shift + 4 ); 4120 4121 return 1; 4122} 4123 4124sub get_extract_ps1_stream_command { 4125 my $self = shift; 4126 my %par = @_; 4127 my ($subtitle) = @par{'subtitle'}; 4128 4129 my $vob_size = $self->get_vob_size; 4130 my $vob_dir = $self->vob_dir; 4131 4132 my $sid = sprintf( "0x%x", 32 + $subtitle->id ); 4133 my $vobsub_ps1_file = $subtitle->ps1_file; 4134 my $ifo_file = $subtitle->ifo_file( nr => 0 ); 4135 4136 my $cat = $self->get_cat_vob_command; 4137 4138 my $command = "$cat | " 4139 . "dvdrip-progress -m $vob_size -i 5 | " 4140 . "tcextract -x ps1 -t vob -a $sid > $vobsub_ps1_file && " 4141 . "echo EXECFLOW_OK"; 4142 4143 return $command; 4144} 4145 4146sub get_create_vobsub_command { 4147 my $self = shift; 4148 my %par = @_; 4149 my ( $subtitle, $start, $end, $file_nr ) 4150 = @par{ 'subtitle', 'start', 'end', 'file_nr' }; 4151 4152 my $avi_dir = $self->avi_dir; 4153 4154 my $sid = sprintf( "0x%x", 32 + $subtitle->id ); 4155 my $vobsub_prefix = $subtitle->vobsub_prefix( file_nr => $file_nr ); 4156 my $vobsub_ifo_file = "$vobsub_prefix.ifo"; 4157 my $vobsub_ps1_file = $subtitle->ps1_file; 4158 my $ifo_file = $subtitle->ifo_file( nr => 0 ); 4159 4160 my $ps1_size = int( ( -s $vobsub_ps1_file ) / 1024 / 1024 + 1 ); 4161 4162 my $range = ""; 4163 if ( defined $start and defined $end ) { 4164 $range = "-e $start,$end,0"; 4165 $ps1_size = int( ( $end - $start ) / $self->runtime * $ps1_size + 1 ); 4166 $vobsub_ifo_file = "$vobsub_prefix.ifo"; 4167 } 4168 4169 my $lang = $subtitle->lang; 4170 4171 my $command = "mkdir -p $avi_dir && " 4172 . "cp $ifo_file $avi_dir/$vobsub_ifo_file && " 4173 . "cd $avi_dir && " 4174 . "chmod 644 $vobsub_ifo_file && " 4175 . "execflow cat $vobsub_ps1_file | " 4176 . "dvdrip-progress -m $ps1_size -i 1 | " 4177 . "subtitle2vobsub $range" 4178 . " -i $vobsub_ifo_file " 4179 . " -o $vobsub_prefix &&" 4180 . "sed 's/^id: /id: $lang/' < $vobsub_prefix.idx > vobsub$$.tmp && " 4181 . "mv vobsub$$.tmp $vobsub_prefix.idx && " 4182 . "echo EXECFLOW_OK"; 4183 4184 if ( $self->has("rar") ) { 4185 my $rar = $self->config('rar_command'); 4186 $command 4187 .= " && $rar a $vobsub_prefix $vobsub_prefix.{idx,ifo,sub} && " 4188 . "rm $vobsub_prefix.{idx,ifo,sub}"; 4189 } 4190 4191 return $command; 4192} 4193 4194sub get_view_vobsub_command { 4195 my $self = shift; 4196 my %par = @_; 4197 my ($subtitle) = @par{'subtitle'}; 4198 4199 my $avi_dir = $self->avi_dir; 4200 my $vob_dir = $self->vob_dir; 4201 4202 my $vobsub_prefix = $subtitle->vobsub_prefix; 4203 4204 my $command = "cd $avi_dir && " 4205 . "mplayer -vobsub $vobsub_prefix -vobsubid 0 $vob_dir/*"; 4206 4207 return $command; 4208} 4209 4210sub get_split_files { 4211 my $self = shift; 4212 4213 my $mask = $self->avi_file; 4214 $mask =~ s/\.([^\.]+)$//; 4215 my $ext = $1; 4216 $mask .= "-*.$ext"; 4217 4218 my @files = glob($mask); 4219 4220 return \@files; 4221} 4222 4223sub get_count_frames_in_files_command { 4224 my $self = shift; 4225 4226 my $files = $self->get_split_files; 4227 4228 my $command = "echo START"; 4229 4230 foreach my $file ( @{$files} ) { 4231 if ( $self->is_ogg ) { 4232 $command .= " && echo 'DVDRIP:OGG:$file' frames=\$("; 4233 $command .= " ogminfo -v -v $file 2>&1 |" 4234 . " grep 'v1.*granulepos' | wc -l )"; 4235 } 4236 else { 4237 $command .= " && echo 'DVDRIP:AVI:$file' \$("; 4238 $command .= " tcprobe -H 10 -i $file 2>&1 | grep frames= )"; 4239 } 4240 } 4241 4242 $command .= " && echo EXECFLOW_OK"; 4243 4244 return $command; 4245} 4246 4247sub has_vobsub_subtitles { 4248 my $self = shift; 4249 4250 return 0 if not $self->subtitles; 4251 4252 foreach my $subtitle ( values %{ $self->subtitles } ) { 4253 return 1 if $subtitle->tc_vobsub; 4254 } 4255 4256 return 0; 4257} 4258 4259sub get_create_wav_command { 4260 my $self = shift; 4261 4262 return "echo 'No audio channel selected'" 4263 if $self->audio_channel == -1; 4264 4265 my $audio_wav_file = $self->audio_wav_file; 4266 my $dir = dirname($audio_wav_file); 4267 my $nr = $self->nr; 4268 my $source = $self->transcode_data_source; 4269 my $audio_nr = $self->audio_track->tc_nr; 4270 4271 my $source_options = $self->data_source_options; 4272 $source_options->{x} = "null"; 4273 4274 my $tc_nice = $self->tc_nice || 0; 4275 4276 my $command = "mkdir -p $dir &&" 4277 . " execflow -n $tc_nice transcode -a $audio_nr " 4278 . $self->get_transcode_status_option(200) 4279 . " -y null,wav -u 100 -o $audio_wav_file"; 4280 4281 $command .= " -$_ $source_options->{$_}" for keys %{$source_options}; 4282 4283 $command .= " -d" 4284 if $self->audio_track->type eq 'lpcm' 4285 and $self->version("transcode") < 613; 4286 4287 if ( $self->tc_start_frame ne '' 4288 or $self->tc_end_frame ne '' ) { 4289 my $start_frame = $self->tc_start_frame; 4290 my $end_frame = $self->tc_end_frame; 4291 $start_frame ||= 0; 4292 $end_frame ||= $self->frames; 4293 4294 if ( $start_frame != 0 ) { 4295 my $options 4296 = $self->get_frame_grab_options( frame => $start_frame ); 4297 $options->{c} =~ /(\d+)/; 4298 my $c1 = $1; 4299 my $c2 = $c1 + $end_frame - $start_frame; 4300 $command .= " -c $c1-$c2"; 4301 $command .= " -L $options->{L}" 4302 if $options->{L} ne ''; 4303 4304 } 4305 else { 4306 $command .= " -c $start_frame-$end_frame"; 4307 } 4308 } 4309 4310 $command .= " && echo EXECFLOW_OK"; 4311 4312 return $command; 4313} 4314 4315sub check_svcd_geometry { 4316 my $self = shift; 4317 4318 return if not $self->tc_container eq 'vcd'; 4319 4320 my $codec = $self->tc_video_codec; 4321 my $mode = $self->video_mode; 4322 4323 return if $codec =~ /^XS?VCD$/; 4324 4325 my $width 4326 = ( $self->tc_zoom_width || $self->width ) - $self->tc_clip2_left 4327 - $self->tc_clip2_right; 4328 4329 my $height 4330 = ( $self->tc_zoom_height || $self->height ) - $self->tc_clip2_top 4331 - $self->tc_clip2_bottom; 4332 4333 my %valid_values = ( 4334 "VCD:pal:width" => 352, 4335 "VCD:pal:height" => 288, 4336 4337 "VCD:ntsc:width" => 352, 4338 "VCD:ntsc:height" => 240, 4339 4340 "SVCD:pal:width" => 480, 4341 "SVCD:pal:height" => 576, 4342 4343 "SVCD:ntsc:width" => 480, 4344 "SVCD:ntsc:height" => 480, 4345 4346 "CVD:pal:width" => 352, 4347 "CVD:pal:height" => 576, 4348 4349 "CVD:ntsc:width" => 352, 4350 "CVD:ntsc:height" => 480, 4351 ); 4352 4353 my $should_width = $valid_values{"$codec:$mode:width"}; 4354 my $should_height = $valid_values{"$codec:$mode:height"}; 4355 4356 $mode = uc($mode); 4357 4358 if ( $width != $should_width or $height != $should_height ) { 4359 return __x( 4360 "Your frame size isn't conform to the standard,\n" 4361 . "which is {should_width}x{should_height} for {codec}/{mode}, " 4362 . "but you configured {width}x{height}.", 4363 should_width => $should_width, 4364 should_height => $should_height, 4365 codec => $codec, 4366 mode => $mode, 4367 width => $width, 4368 height => $height 4369 ); 4370 } 4371 4372 return; 4373} 4374 4375sub move_clip2_to_clip1 { 4376 my $self = shift; 4377 4378 my $clip1_top = $self->tc_clip1_top; 4379 my $clip1_bottom = $self->tc_clip1_bottom; 4380 my $clip1_left = $self->tc_clip1_left; 4381 my $clip1_right = $self->tc_clip1_right; 4382 4383 if ( $clip1_top or $clip1_bottom or $clip1_left or $clip1_right ) { 4384 die "msg:" 4385 . __ "2nd clipping parameters can only be\nmoved to 1st " 4386 . "clipping parameters, if\n1st clipping is not defined."; 4387 return 1; 4388 } 4389 4390 my $width = $self->width; 4391 my $height = $self->height || 1; 4392 4393 my $zoom_width = $self->tc_zoom_width || $self->width; 4394 my $zoom_height = $self->tc_zoom_height || $self->height; 4395 4396 my $x_factor = $zoom_width / $width; 4397 my $y_factor = $zoom_height / $height; 4398 4399 my $clip2_top = $self->tc_clip2_top; 4400 my $clip2_bottom = $self->tc_clip2_bottom; 4401 my $clip2_left = $self->tc_clip2_left; 4402 my $clip2_right = $self->tc_clip2_right; 4403 4404 my $clip1_top = $clip2_top / $y_factor; 4405 my $clip1_bottom = $clip2_bottom / $y_factor; 4406 my $clip1_left = $clip2_left / $x_factor; 4407 my $clip1_right = $clip2_right / $x_factor; 4408 4409 $width = $width - $clip1_left - $clip1_right; 4410 $height = $height - $clip1_top - $clip1_bottom; 4411 4412 $zoom_width = $width * $x_factor; 4413 $zoom_height = $height * $y_factor; 4414 4415 # no odd clip values 4416 if ( $clip1_left % 2 and $clip1_right % 2 ) { 4417 if ( $clip1_left > $clip1_right ) { 4418 --$clip1_left; 4419 ++$clip1_right; 4420 } 4421 else { 4422 ++$clip1_left; 4423 --$clip1_right; 4424 } 4425 } 4426 else { 4427 --$clip1_left if $clip1_left % 2; 4428 --$clip1_right if $clip1_right % 2; 4429 } 4430 4431 if ( $clip1_top % 2 and $clip1_bottom % 2 ) { 4432 if ( $clip1_left > $clip1_bottom ) { 4433 --$clip1_top; 4434 ++$clip1_bottom; 4435 } 4436 else { 4437 ++$clip1_top; 4438 --$clip1_bottom; 4439 } 4440 } 4441 else { 4442 --$clip1_top if $clip1_top % 2; 4443 --$clip1_bottom if $clip1_bottom % 2; 4444 } 4445 4446 $self->set_tc_clip1_top( int($clip1_top) ); 4447 $self->set_tc_clip1_bottom( int($clip1_bottom) ); 4448 $self->set_tc_clip1_left( int($clip1_left) ); 4449 $self->set_tc_clip1_right( int($clip1_right) ); 4450 $self->set_tc_zoom_width( int($zoom_width) ); 4451 $self->set_tc_zoom_height( int($zoom_height) ); 4452 $self->set_tc_clip2_top(0); 4453 $self->set_tc_clip2_bottom(0); 4454 $self->set_tc_clip2_left(0); 4455 $self->set_tc_clip2_right(0); 4456 4457 1; 4458} 4459 44601; 4461