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