package GCExtract;

###################################################
#
#  Copyright 2005 Tian
#
#  This file is part of GCfilms.
#
#  GCfilms 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.
#
#  GCfilms 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 GCfilms; if not, write to the Free Software
#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
###################################################

use strict;
use Gtk2;

{
    package GCExtractDialog;

    use base qw 'Gtk2::Dialog';

    sub show
    {
        my $self = shift;
        $self->show_all;
        my $code = $self->run;
        if ($code eq 'ok')
        {
            foreach (@{$self->{extractedArray}})
            {
                if (! $self->{$_.'Cb'}->get_active)
                {
                    if ($_ ne 'size')
                    {
                        $self->{info}->{$_} = '';
                    }
                    else
                    {
                        $self->{info}->{width} = '';
                    }
                }
            }
            $self->{panel}->time($self->{info}->{length})
                if $self->{info}->{length};
            $self->{panel}->video($self->{info}->{type})
                if $self->{info}->{type};
            my $currentAudio = $self->{panel}->audio;
            if ($self->{info}->{audioEncoding})
            {
                if ($currentAudio =~ /;/)
                {
                    $currentAudio =~ s/^([^;]*);[^,]*(,?.*)$/$1;$self->{info}->{audioEncoding}$2/;
                }
                else
                {
                    $currentAudio = ';'.$self->{info}->{audioEncoding};
                }
                $self->{panel}->audio($currentAudio);
            }
            if ($self->{info}->{width} && $self->{info}->{height})
            {
                my $comment = $self->{panel}->comment;
                $comment .= "\n" if $comment && ($comment !~ /\n$/m);
                $self->{panel}->comment($comment.
                    $self->{parent}->{lang}->{ExtractSize}.$self->{parent}->{lang}->{Separator}.
                    $self->{info}->{width}.'*'.$self->{info}->{height})
            }
        }
        $self->hide;
    }
    
    sub setInfo
    {
        my ($self, $info, $panel) = @_;
        ($self->{info}, $self->{panel}) = ($info, $panel);
        foreach (@{$self->{extractedArray}})
        {
            next if ($_ eq 'width') || ($_ eq 'height');
            if ($info->{$_})
            {
                $self->{$_}->set_text($info->{$_});
                $self->{$_}->set_selectable(1);
            }
            else
            {
                $self->{$_}->set_text('-');
                $self->{$_}->set_selectable(0);
            }
        }
        if ($info->{width} && $info->{height})
        {
            $self->{size}->set_text($info->{width}.' * '.$info->{height});
            $self->{size}->set_selectable(1);
        }
        else
        {
            $self->{size}->set_text('-');
            $self->{size}->set_selectable(0);
        }
    }
    
    sub new
    {
        my ($proto, $parent) = @_;
        my $class = ref($proto) || $proto;
        my $self  = $class->SUPER::new($parent->{lang}->{ExtractTitle},
                              $parent,
                              [qw/modal destroy-with-parent/],
                              'gtk-cancel' => 'cancel'
                              );
        bless($self, $class);

        $self->{extractedArray} = ['length', 'size', 'type', 'audioEncoding'];

        my $table = new Gtk2::Table(4,2);
        $table->set_col_spacings(10);
        $table->set_row_spacings(10);
        $table->set_border_width(10);
        
        my $i = 0;
        foreach (@{$self->{extractedArray}})
        {
            (my $capsField = $_) =~ s/^(.)/\U$1\E/;
            $self->{$_.'Cb'} = new Gtk2::CheckButton($parent->{lang}->{'Extract'.$capsField}.$parent->{lang}->{Separator});
            $self->{$_.'Cb'}->set_active(1);
            $self->{$_} = new Gtk2::Label;
            $table->attach($self->{$_.'Cb'}, 0, 1, $i, $i+1, 'fill', 'fill', 0, 0);
            $table->attach($self->{$_}, 1, 2, $i, $i+1, 'fill', 'fill', 0, 0);
            $i++;
        }

        $self->vbox->pack_start($table,1,1,0);

        $self->{importButton} = new Gtk2::Button($parent->{lang}->{ExtractImport});
        $self->add_action_widget($self->{importButton}, 'ok');

        $self->{parent} = $parent;
        return $self;
}

{
    package GCVideoInfo;
    
    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 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;
        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;
            }
            last if $gotAudio && $gotVideo;
        }
        
        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;
        }
        else
        {
            my $magic2;
            read FILE,$magic2,4;
            if ($magic2 =~ /(moov|notp|wide|ftyp)/)
            {
                $info = $self->getMovInfo;
            }
        }
        
        close FILE;
        return $info;
    }
    
    sub new
    {
        my ($proto, $fileName) = @_;
        my $class = ref($proto) || $proto;
        
        my $fileSize = -s $fileName;
        
        my $self = {fileName => $fileName,
                    fileSize => $fileSize};
        
        bless($self, $class);
        
        return $self;
    }
}

   
}

1;
