package GCExtract::GCExtractFilms; ################################################### # # Copyright 2005-2010 Christian Jodar # # This file is part of GCstar. # # GCstar is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # GCstar is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with GCstar; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # ################################################### use strict; use GCExtract; use Image::ExifTool; { package GCExtract::GCfilmsExtracter; use base 'GCItemExtracter'; sub new { my $proto = shift; my $class = ref($proto) || $proto; my $self = $class->SUPER::new(@_); bless ($self, $class); return $self; } sub readInt { my ($self, $size) = @_; my $buf; $size = 4 if !$size; read $self->{file},$buf,$size; return unpack "i",$buf; } sub getAviInfo { my $self = shift; my $info = {}; my @audioCodecs; $audioCodecs[0x0001] = 'PCM'; $audioCodecs[0x0002] = 'ADPCM'; $audioCodecs[0x0030] = 'Dolby AC2'; $audioCodecs[0x0050] = 'MPEG'; $audioCodecs[0x0055] = 'MP3'; $audioCodecs[0x0092] = 'Dolby AC3 SPDIF'; $audioCodecs[0x2000] = 'Dolby AC3'; $audioCodecs[0x2001] = 'Dolby DTS'; $audioCodecs[0x2002] = 'WAVE'; $audioCodecs[0x2003] = 'WAVE'; $audioCodecs[0x2004] = 'WAVE'; $audioCodecs[0x2005] = 'WAVE'; $audioCodecs[0x674F] = 'Ogg Vorbis', $audioCodecs[0x6750] = 'Ogg Vorbis', $audioCodecs[0x6751] = 'Ogg Vorbis', $audioCodecs[0x676F] = 'Ogg Vorbis', $audioCodecs[0x6770] = 'Ogg Vorbis', $audioCodecs[0x6771] = 'Ogg Vorbis', my $chunkName; seek $self->{file},8,0; read $self->{file},$chunkName,8; return $info if ($chunkName ne 'AVI LIST'); seek $self->{file},4,1; read $self->{file},$chunkName,8; $self->readInt; my $dwMicroSecPerFrame = $self->readInt; my $dwMaxBytesPerSec = $self->readInt; my $dwReserved1 = $self->readInt; my $dwFlags = $self->readInt; my $dwTotalFrames = $self->readInt; my $dwInitialFrames = $self->readInt; my $dwStreams = $self->readInt; my $dwSuggestedBufferSize = $self->readInt; $info->{width} = $self->readInt; $info->{height} = $self->readInt; my $dwScale = $self->readInt; my $dwRate = $self->readInt; my $dwStart = $self->readInt; my $dwLength = $self->readInt; $info->{length} = ($dwTotalFrames * $dwMicroSecPerFrame) / 60000000; $info->{length} = GCUtils::round($info->{length}); my $buff; my ($gotVids, $gotAuds) = (0,0); while (! eof($self->{file})) { read $self->{file},$chunkName,4; if ($chunkName eq 'strl') { seek $self->{file},8,1; read $self->{file},$buff,4; if ($buff eq 'vids') { read $self->{file},$info->{type},4; $gotVids = 1; } elsif ($buff eq 'auds') { read $self->{file},$info->{audioEncoding},4; $info->{audioEncoding} =~ s/^.*?\w*\W*?$/$1/g; if (!$info->{audioEncoding}) { read $self->{file},$chunkName,4 while ($chunkName ne 'strf'); seek $self->{file},4,1; my $codec; read $self->{file}, $codec, 2; $codec = unpack "v",$codec; $codec = $audioCodecs[$codec]; seek $self->{file}, 2, 1; my $hz = $self->readInt; $info->{audioEncoding} = $codec if $codec; $info->{audioEncoding} .= " ($hz Hz)" if $hz; } $gotAuds = 1; } last if $gotVids && $gotAuds; } last if ($chunkName eq 'movi'); } return {} if ($buff ne 'vids') && ($buff ne 'auds'); return $info; } sub getMovAtom { my ($self, $wanted, $subAtom) = @_; my $copy = $subAtom; my ($header, $type, $length); my $atom = 0; if ($subAtom) { while ($copy) { $header = substr($copy, 0, 8, ''); ($length, $type) = unpack("Na4", $header); last if $type eq $wanted; substr($copy, 0 , $length - 8, ''); } if ($copy) { $atom = substr($copy, 0 , $length - 8, ''); } } else { while (!eof ($self->{file})) { read $self->{file}, $header, 8; ($length, $type) = unpack("Na4", $header); last if $type eq $wanted; seek $self->{file},$length - 8, 1; } if ($self->{file}) { read $self->{file}, $atom, $length - 8; } } return $atom; } sub getMovInfo { #Inspired from Video::Info::Quicktime_PL my $self = shift; my $info = {}; seek $self->{file},0,0; my $header; my $atom = $self->getMovAtom('moov'); if ($atom) { while (length($atom) > 0) { my ($sublen) = unpack("Na4", substr( $atom, 0, 4, '') ); my ($subatom) = substr($atom, 0, $sublen-4, ''); my($type) = substr($subatom, 0, 4, ''); if ($type eq 'mvhd') { my $timeScale = unpack( "Na4", substr($subatom,12,4)); my $duration = unpack( "Na4", substr($subatom,16,4)); $info->{length} = GCUtils::round($duration / ($timeScale * 60)); } elsif ($type eq 'trak') { my $tkhd = $self->getMovAtom('tkhd', $subatom); my $mdia = $self->getMovAtom('mdia', $subatom); next if !$mdia; my $minf = $self->getMovAtom('minf', $mdia); next if !$minf; my $vmhd = $self->getMovAtom('vmhd', $minf); my $smhd = $self->getMovAtom('smhd', $minf); if ($vmhd || $smhd) { my $stbl = $self->getMovAtom('stbl', $minf); my $stsd = $self->getMovAtom('stsd', $stbl); if ($vmhd) { my $width = unpack("Na4", substr($tkhd,74,4)); my $height = unpack("Na4", substr($tkhd,78,4)); ($info->{width}, $info->{height}) = ($width, $height); ($info->{type} = substr($stsd,12,8)) =~ s/\W(.*?)\W/$1/g; } else { ($info->{audioEncoding}= substr($stsd,12,8)) =~ s/\W(.*?)\W/$1/g; } } } } } return $info; } sub getMpgInfo { #Inspired from MPEG::Info my $self = shift; my @frameRates = ( 0, 24000/1001, 24, 25, 30000/1001, 30, 50, 60000/1001, 60, ); my $info = {}; $info->{type} = 'MPEG'; $info->{audioEncoding} = 'MPEG'; my $magic; my $numMagic = unpack("N",$self->{magic}); while (!eof($self->{file}) && $numMagic != 0x000001b3) { read $self->{file},$magic,4; $numMagic = unpack("N",$magic); seek $self->{file},-3, 1; } seek $self->{file},3, 1; my $size; read $self->{file},$size,3; $info->{width} = ((unpack "n",substr($size,0,2)) >> 4); $info->{height} = ((unpack "n",substr($size,1,2)) & 0x0fff); my $fps; read $self->{file},$fps,1; $fps = $frameRates[ord($fps) & 0x0f]; my ($buff1, $buff2); read $self->{file}, $buff1, 2; $buff1 = unpack 'n', $buff1; $buff1 <<= 2; read $self->{file}, $buff2, 1; $buff2 = unpack 'C', $buff2; $buff2 >>=6; my $bitRate = ( ( $buff1 | $buff2 ) * 400); $info->{length} = GCUtils::round((($self->{fileSize} * 8 ) / $bitRate) / 60) if $bitRate; return $info; } sub getExifInfo { my $self = shift; my $fln = $self->{fileName}; my $exifTool = new Image::ExifTool; my $exifinfo = $exifTool->ImageInfo($fln); my $info = {}; $info->{type} = $exifinfo->{FileType}.':'.$exifinfo->{VideoCodecID}; #$info->{audioEncoding} = substr $exifinfo->{AudioCodecID}, 2; my $audCount=0; my $subCount=0; my $tracki=0; my $tc=""; my $audInfo=""; my $subInfo=""; while ($exifinfo->{"TrackType".$tc}){ my $trt=$exifinfo->{"TrackType".$tc}; my $trl=$exifinfo->{"TrackLanguage".$tc}; my $trn=$exifinfo->{"TrackName".$tc}; my $succcpc=utf8::decode($trn); if ($trt eq 'Audio'){ my $atc=$audCount>0?" (".$audCount.")":""; my $trc=$exifinfo->{"AudioCodecID".$atc}; $info->{audioAll}->[$audCount]->[0] = $trl.":".$trn; $info->{audioAll}->[$audCount]->[1] = substr $trc, 2; $audInfo=$audInfo.($audInfo?";":"").$trl.":".$trc; $audCount++; }elsif($trt eq 'Subtitle'){ $info->{subtitle}->[$subCount]->[0] = $trl.":".$trn; $subInfo=$subInfo.($subInfo?";":"").$trl; $subCount++; } $tracki++; $tc=" (".$tracki.")"; } $info->{audioEncoding} = $audInfo; $info->{subtitleInfo} = $subInfo; $info->{width} = $exifinfo->{ImageWidth}; $info->{height} = $exifinfo->{ImageHeight}; $info->{length} = $exifinfo->{Duration}; return $info; } sub findOgmPage { #Inspired from Ogg::Vorbis::Header::PurePerl my $self = shift; my $char; my $curStr = ''; my $i = 0; while (read($self->{file}, $char, 1)) { $curStr = $char . $curStr; $curStr = substr($curStr, 0, 4); if ($curStr eq 'SggO') { seek $self->{file}, 8, 1; my $serial = $self->readInt(4); return $serial; } } return -1; } sub findLastOgmPage { my $self = shift; my $buff; my $curStr = ''; seek $self->{file}, -5, 2; my $i = 0; while (read($self->{file}, $buff, 4)) { if ($buff eq 'OggS') { seek $self->{file}, 2, 1; my $granulePos = $self->readInt; return $granulePos; } seek $self->{file}, -5, 1; } return -1; } sub getOgmInfo { my $info = {}; my $self = shift; my $buff; my ($gotAudio, $gotVideo) = (0,0); seek $self->{file}, 0, 0; my $serial = 0; my $videoSerial = -1; my $fps; my $iteration = 0; while ($serial != -1) { $serial = $self->findOgmPage; seek $self->{file}, 13, 1; read $self->{file}, $buff, 8; if ($buff =~ /^video/) { read $self->{file}, $info->{type}, 4; my $size = $self->readInt; my $timeUnit = $self->readInt(8); my $spu = $self->readInt(8); $fps = (10000000.0 * $spu) / $timeUnit; my $defaultLen = $self->readInt; my $bufferSize = $self->readInt; my $bbp = $self->readInt; $info->{width} = $self->readInt; $info->{height} = $self->readInt; $gotVideo = 1; $videoSerial = $serial; } elsif ($buff =~ /vorbis/) { $info->{audioEncoding} = 'Vorbis'; seek $self->{file}, 3, 1; my $hz = $self->readInt; $info->{audioEncoding} .= " ($hz Hz)" if $hz; $gotAudio = 1; } else { last if $iteration > 5; } last if $gotAudio && $gotVideo; $iteration++; } if ($gotVideo) { my $biggestGranulePos = $self->findLastOgmPage; $info->{length} = GCUtils::round(($biggestGranulePos / $fps) / 60); } return $info; } sub getInfo { my $self = shift; open FILE, '<'.$self->{fileName}; binmode FILE; my $info = {}; $self->{file} = \*FILE; my $magic; $self->{magic} = $magic; read FILE,$magic,4; my $numMagic = unpack("N",$magic); if ($magic eq 'RIFF') { $info = $self->getAviInfo; } elsif ($magic eq 'OggS') { $info = $self->getOgmInfo; } elsif (($numMagic == 0x000001ba) || ($numMagic == 0x000001b3)) { $info = $self->getMpgInfo; } elsif ($numMagic == 0x1a45dfa3) { close FILE; $info = $self->getExifInfo; } else { my $magic2; read FILE,$magic2,4; if ($magic2 =~ /(moov|notp|wide|ftyp)/) { $info = $self->getMovInfo; } } close FILE; my $result; $result->{time} = {displayed => $info->{length}, value => $info->{length}}; $result->{video} = {displayed => $info->{type}, value => $info->{type}}; my $currentAudio = $self->{panel}->audio; if ($info->{audioAll}) { $result->{audio}->{value} = $info->{audioAll}; $result->{audio}->{displayed} = $info->{audioEncoding}; } elsif ($info->{audioEncoding}) { $currentAudio->[0]->[1] = $info->{audioEncoding}; $result->{audio}->{value} = $currentAudio; $result->{audio}->{displayed} = $info->{audioEncoding}; } if ($info->{width} && $info->{height}) { my $comment = $self->{panel}->comment; $comment .= "\n" if $comment && ($comment !~ /\n$/m); $result->{comment}->{displayed} = $self->{model}->getDisplayedText('ExtractSize').$self->{parent}->{lang}->{Separator}. $info->{width}.'*'.$info->{height}; $result->{comment}->{value} = $comment . $result->{comment}->{displayed}; } if ($info->{subtitle}){ $result->{subt}->{value} = $info->{subtitle}; $result->{subt}->{displayed} = $info->{subtitleInfo}; } return $result; } sub getFields { return ['time', 'video', 'audio', 'comment', 'subt']; } } 1;