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