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;