1# $Id: Project.pm 2273 2007-03-10 09:55:35Z 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::Project;
12use Locale::TextDomain qw (video.dvdrip);
13
14use base Video::DVDRip::Base;
15
16use Carp;
17use strict;
18
19use FileHandle;
20use Data::Dumper;
21use File::Basename;
22use File::Path;
23use File::Copy;
24
25use Video::DVDRip::Content;
26
27sub name			{ shift->{name}				}
28sub version			{ shift->{version}			}
29sub filename			{ shift->{filename}			}
30sub dvd_device			{ shift->{dvd_device}			}
31sub vob_dir			{ shift->{vob_dir}  			}
32sub avi_dir			{ shift->{avi_dir}  			}
33sub snap_dir			{ shift->{snap_dir}  			}
34sub content			{ shift->{content}			}
35sub selected_title_nr		{ shift->{selected_title_nr}		}
36sub rip_mode			{ shift->{rip_mode} 	       || 'rip'	}
37sub convert_message		{ shift->{convert_message}		}
38sub last_selected_nb_page	{ shift->{last_selected_nb_page}	}
39sub created			{ my $created = shift->{created};
40				  !defined($created) ? 1 : $created	}
41sub selected_dvd_device         { shift->{selected_dvd_device}          }
42
43sub set_version			{ shift->{version}		= $_[1] }
44sub set_filename		{ shift->{filename}		= $_[1] }
45sub set_dvd_device		{ shift->{dvd_device}		= $_[1]	}
46sub set_vob_dir			{ shift->{vob_dir}  		= $_[1]	}
47sub set_avi_dir			{ shift->{avi_dir}  		= $_[1]	}
48sub set_snap_dir		{ shift->{snap_dir}		= $_[1]	}
49sub set_content			{ shift->{content}		= $_[1] }
50sub set_selected_title_nr	{ shift->{selected_title_nr}	= $_[1] }
51sub set_rip_mode		{ shift->{rip_mode}		= $_[1]	}
52sub set_convert_message		{ shift->{convert_message}	= $_[1]	}
53sub set_last_selected_nb_page	{ shift->{last_selected_nb_page}= $_[1]	}
54sub set_created			{ shift->{created}		= $_[1]	}
55sub set_selected_dvd_device     { shift->{selected_dvd_device}  = $_[1] }
56
57sub logfile {
58    my $self = shift;
59    return $self->snap_dir . "/logfile.txt";
60}
61
62sub ifo_dir {
63    my $self = shift;
64
65    return sprintf( "%s/ifo", $self->snap_dir );
66}
67
68sub set_name {
69    my $self = shift;
70    my ($new_name) = @_;
71
72    my $old_name = $self->name;
73
74    my $project_dir = $self->config('base_project_dir');
75
76    if ( $self->vob_dir eq "$project_dir/$old_name/vob" ) {
77        $self->set_vob_dir("$project_dir/$new_name/vob");
78    }
79
80    if ( $self->avi_dir eq "$project_dir/$old_name/avi" ) {
81        $self->set_avi_dir("$project_dir/$new_name/avi");
82    }
83
84    if ( $self->snap_dir eq "$project_dir/$old_name/tmp" ) {
85        $self->set_snap_dir("$project_dir/$new_name/tmp");
86    }
87
88    $self->{name} = $new_name;
89
90    1;
91}
92
93sub new {
94    my $class = shift;
95
96    my $base_project_dir = $class->config('base_project_dir');
97
98    my $self = bless {
99        name              => "unnamed",
100        filename          => "",
101        dvd_device        => $class->config('dvd_device'),
102        selected_dvd_device => $class->config('dvd_device'),
103        rip_mode          => "rip",
104        vob_dir           => "$base_project_dir/unnamed/vob",
105        avi_dir           => "$base_project_dir/unnamed/avi",
106        snap_dir          => "$base_project_dir/unnamed/tmp",
107        content           => undef,
108        selected_title_nr => undef,
109        version           => $Video::DVDRip::VERSION,
110        created           => 0,
111    }, $class;
112
113    my $content = Video::DVDRip::Content->new( project => $self );
114
115    $self->set_content($content);
116
117    if ( !$self->dvd_device ) {
118        my $dvd_device = $self->config_object->get_first_dvd_device;
119        $self->set_dvd_device($dvd_device);
120    }
121
122    return $self;
123}
124
125sub new_from_file {
126    my $class      = shift;
127    my %par        = @_;
128    my ($filename) = @par{'filename'};
129
130    confess "missing filename" if not $filename;
131
132    my $self = bless { filename => $filename, }, $class;
133
134    $self->load;
135
136    $self->set_filename($filename);
137    $self->set_version($Video::DVDRip::VERSION);
138
139    return $self;
140}
141
142sub save {
143    my $self = shift;
144    my ($filename) = @_;
145
146    $self->set_created(1);
147
148    $filename ||= $self->filename;
149    confess "not filename set" if not $filename;
150
151    my $dir = dirname($filename);
152    mkpath( [$dir], 0, 0775 ) unless -d $dir;
153
154    my $data_sref = $self->get_save_data_text;
155
156    my $fh = FileHandle->new;
157
158    open( $fh, "> $filename" ) or confess "can't write $filename";
159    print $fh q{# $Id: Project.pm 2273 2007-03-10 09:55:35Z joern $},
160        "\n";
161    print $fh
162        "# This file was generated by Video::DVDRip Version $Video::DVDRip::VERSION\n\n";
163
164    print $fh ${$data_sref};
165    close $fh;
166
167    $self->log(
168        __x( "Project file saved to '{filename}'", filename => $filename ) );
169
170    my $dir = $self->snap_dir;
171    if ( !-d $dir ) {
172        mkpath( [$dir], 0, 0775 ) or die "can't create directory $dir";
173        $self->log(
174            __x( "Project temporary dir '{dir}' created", dir => $dir ) );
175    }
176
177    1;
178}
179
180sub backup_copy {
181    my $self = shift;
182
183    $self->save( $self->snap_dir . "/backup.rip" );
184
185    1;
186}
187
188sub get_save_data_text {
189    my $self = shift;
190
191    my $filename = $self->filename;
192    $self->set_filename(undef);
193
194    my $dd = Data::Dumper->new( [$self], ['project'] );
195    $dd->Indent(1);
196    $dd->Purity(1);
197    $dd->Sortkeys(1);
198    my $data = $dd->Dump;
199
200    my $end_marker        = "}, 'Video::DVDRip::Project' );\n";
201    my $end_marker_quoted = quotemeta($end_marker);
202    $data =~ s/$end_marker_quoted.*/$end_marker/so;
203
204    $self->set_filename($filename);
205
206    return \$data;
207}
208
209sub get_save_data_binary {
210    my $self = shift;
211
212    my $filename = $self->filename;
213    $self->set_filename(undef);
214
215    require Storable;
216
217    my $data = "BINFMT\n".Storable::nfreeze($self);
218
219    $self->set_filename($filename);
220
221    return \$data;
222}
223
224
225sub load {
226    my $self = shift;
227
228    my $filename = $self->filename;
229    croak __ "no filename set" if not $filename;
230    croak __x( "can't read {filename}", filename => $filename )
231        if not -r $filename;
232
233    my $fh = FileHandle->new;
234    open( $fh, $filename )
235        or croak __x( "can't read {filename}", filename => $filename );
236
237    my $data;
238    my $head;
239    my $line = 0;
240    my $bin_fmt = 0;
241    while (<$fh>) {
242        ++$line;
243        if ( $line == 2 ) {
244            die __ "File is no dvd::rip file"
245                unless /This file was generated by Video::DVDRip/;
246        }
247        if ( $line == 4 && /BINFMT/) {
248            $bin_fmt = 1;
249            next;
250        }
251        if ( $line <= 3 ) {
252            $head .= $_;
253        }
254        if ( $line > 3 ) {
255            $data .= $_;
256        }
257        last if !$bin_fmt && /Video::DVDRip::Project/;
258    }
259    close $fh;
260
261    my ( $version, undef, $pre )
262        = $head =~ /DVDRip Version (\d+\.\d+)(_(\d+))?/;
263    my ( $major, $minor, $patch )
264        = $head =~ /DVDRip Version (\d+)\.(\d+)\.(\d+)/;
265
266    my $project;
267
268    if ( $bin_fmt ) {
269        require Storable;
270        $project = Storable::thaw($data)
271            or die __"Can't load {filename}, wrong data format";
272    }
273    else {
274        eval($data);
275        croak __x(
276            "can't load {filename}. Perl error: {error}",
277            filename => $filename,
278            error    => $@
279            )
280            if $@;
281    }
282
283    bless $project, ref($self);
284
285    $self->convert_from_old_version(
286        project => $project,
287        version => $version,
288        pre     => $pre,
289        major   => $major,
290        minor   => $minor,
291        patch   => $patch,
292    );
293
294    %{$self} = %{$project};
295
296    $self->content->set_project($self);
297    $self->check_for_deleted_filters;
298
299    1;
300}
301
302sub get_free_diskspace {
303    my $self = shift;
304    my %par  = @_;
305    my ($kb) = @par{'kb'};
306
307    my $dir = $self->avi_dir;
308
309    if ( not -d $dir ) {
310        mkpath( [$dir], 0, 0775 );
311    }
312
313    my $df = qx[ df -Pk $dir ];
314    my ($free) = $df =~ /\s+\d+\s+\d+\s+(\d+)/;
315    $free = int( $free / 1024 ) if not $kb;
316
317    return $free;
318}
319
320sub rip_data_source {
321    my $self = shift;
322    return $self->dvd_device;
323}
324
325sub resolve_symlinks {
326    my $self = shift;
327    my ($file) = @_;
328
329    require File::Spec;
330
331    my %symlinks = ( $file => 1 );
332
333    while ( -l $file ) {
334        my $link_target = readlink($file);
335        if ( !File::Spec->file_name_is_absolute($link_target) ) {
336            $file =~ s!/[^/]+$!!;
337            $file = File::Spec->rel2abs( $link_target, $file );
338        }
339        else {
340            $file = $link_target;
341        }
342        $symlinks{$file} = 1;
343    }
344
345    return \%symlinks;
346}
347
348sub get_mount_dir_from_fstab {
349    my $self = shift;
350    my ( $dvd_device, $fstab_file ) = @_;
351
352    my $symlinks_href = $self->resolve_symlinks($dvd_device);
353
354    open(FSTABINPUT, $fstab_file )
355	or die "can't read $fstab_file";
356
357    my $mount_dir;
358    while (<FSTABINPUT>) {
359	my ($dev, $mnt, $fstyp, $opt, $dump, $pass) = split;
360	next if $dev =~ /^#/;
361	if (( $dev eq $symlinks_href->{$dev} ) ||
362	   ( $mnt eq $symlinks_href->{$dev} )  ||
363	   ( $fstyp eq 'cd9660' )) {
364	    $mount_dir = $mnt;
365	    last;
366	}
367    }
368    close (FSTABINPUT);
369
370    return $mount_dir;
371}
372
373sub dvd_mount_point {
374    my $self = shift;
375
376    my $dvd_device = $self->dvd_device;
377
378    my $dvd_mount_point
379        = $self->get_mount_dir_from_fstab( $dvd_device, "/etc/fstab" );
380
381    return $dvd_mount_point;
382}
383
384sub dvd_mount_dir {
385    my $self = shift;
386
387    return $self->dvd_mount_point;
388}
389
390sub copy_ifo_files {
391    my $self = shift;
392
393    mkpath( [ $self->ifo_dir ], 0, 0775 );
394
395    my $mounted = $self->dvd_is_mounted;
396    $self->mount_dvd if not $mounted;
397
398    my $dvd_mount_dir = $self->dvd_mount_dir;
399
400    my @files
401        = glob( $dvd_mount_dir . "/{video_ts,VIDEO_TS}/{vts,VTS}*{ifo,IFO}" );
402
403    if ( not @files ) {
404        $self->log(
405            __ "WARNING: no IFO files found - vobsub feature disabled." );
406    }
407
408    $self->log(
409        __x("Copying IFO files from {src_dir} to {dir}",
410            src_dir => $dvd_mount_dir,
411            dir     => $self->ifo_dir
412        )
413    );
414
415    copy( $_, $self->ifo_dir . "/" . lc( basename($_) ) ) for @files;
416
417    $self->umount_dvd if not $mounted;
418
419    1;
420}
421
422sub selected_dvd_device_list {
423    my $self = shift;
424
425    return $self->config_object->selected_dvd_device_list;
426}
427
428sub dvd_is_mounted {
429    my $self = shift;
430
431    my $dvd_mount_point = $self->dvd_mount_point;
432
433    return 1 if -d "$dvd_mount_point/video_ts";
434    return 1 if -d "$dvd_mount_point/VIDEO_TS";
435    return;
436}
437
438sub mount_dvd {
439    my $self = shift;
440
441    return 1 if -d $self->dvd_device;
442
443    my $dvd_mount_point = $self->dvd_mount_point;
444
445    $self->log(
446        __x("Mounting DVD at {mount_point}",
447            mount_point => $dvd_mount_point
448        )
449    );
450
451    my $mount = qx[ mount $dvd_mount_point 2>&1 && echo EXECFLOW_OK ];
452
453    $mount =~ s/\s$//;
454
455    croak "msg:"
456        . __x(
457        "Failed to mount DVD at {mount_point} ({mount_error})",
458        mount_point => $dvd_mount_point,
459        mount_error => $mount
460        )
461        if $mount !~ /EXECFLOW_OK/;
462
463    1;
464}
465
466sub umount_dvd {
467    my $self = shift;
468
469    return 1 if -d $self->dvd_device;
470
471    my $dvd_mount_point = $self->dvd_mount_point;
472
473    my $mount = qx[ umount $dvd_mount_point 2>&1 ];
474
475    $mount ||= "Ok";
476
477    $self->log(
478        __x( "Umount {mount_point}: ", mount_point => $dvd_mount_point )
479            . $mount );
480
481    1;
482}
483
484sub convert_from_old_version {
485    my $self = shift;
486    my %par  = @_;
487    my ( $project, $version, $pre, $major, $minor, $patch )
488        = @par{ 'project', 'version', 'pre', 'major', 'minor', 'patch' };
489
490    if ($version < 0.45
491        or (    $version == 0.45
492            and defined $pre
493            and $pre < 4 )
494        ) {
495        require Video::DVDRip::Convert;
496        Video::DVDRip::Convert->convert_audio_tracks_0_45_04(
497            project => $project, );
498    }
499
500    if ($version < 0.47
501        or (    $version == 0.47
502            and defined $pre
503            and $pre < 2 )
504        ) {
505        require Video::DVDRip::Convert;
506        Video::DVDRip::Convert->set_audio_bitrates_0_47_02(
507            project => $project, );
508    }
509
510    $version = $major * 10000 + $minor * 100 + $patch;
511
512    if ( $version < 4900 ) {
513        require Video::DVDRip::Convert;
514        Video::DVDRip::Convert->convert_container_0_49_1( project => $project,
515        );
516    }
517
518    if ( $version < 4902 ) {
519        require Video::DVDRip::Convert;
520        Video::DVDRip::Convert->convert_0_49_2( project => $project, );
521    }
522
523    1;
524}
525
526sub check_for_deleted_filters {
527    my $self = shift;
528
529    return if not $self->content->titles;
530
531    foreach my $title ( values %{ $self->content->titles } ) {
532        my $selected_filters = $title->tc_filter_settings->filters;
533        my @remove_filters;
534        my $i = 0;
535        foreach my $filter_instance ( @{$selected_filters} ) {
536            eval { $filter_instance->get_filter };
537            if ( $@ ) {
538                print __x(
539                     "Warning: filter {filter} removed from title #{nr}"
540                    ." because this transcode installation doesn't"
541                    ." provide it anymore",
542                    filter => $filter_instance->filter_name,
543                    nr     => $title->nr,
544                ),"\n";
545                push @remove_filters, $i;
546            }
547            ++$i;
548        }
549        delete $selected_filters->[$_] for reverse @remove_filters;
550    }
551
552    1;
553}
554
5551;
556