The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package FFmpeg::Command;

use warnings;
use strict;
our $VERSION = '0.18';

use base qw( Class::Accessor::Fast Class::ErrorHandler );
__PACKAGE__->mk_accessors(qw/
    input_file
    output_file
    ffmpeg
    options
    global_options
    infile_options
    outfile_options
    timeout
    stdin
    stdout
    stderr
    command
 /);

use IPC::Run qw( start );
use Carp qw( carp );

our %option = (
    format              => '-f',
    video_codec         => '-vcodec',
    bitrate             => '-b',
    frame_rate          => '-r',
    frame_size          => '-s',
    audio_codec         => '-acodec',
    audio_sampling_rate => '-ar',
    audio_bit_rate      => '-ab',
    size                => '-s',
);

our %metadata = (
    title               => 'title=',
    author              => 'author=',
    comment             => 'comment=',
);

sub new {
    my $class = shift;
    my $self            =  {
        ffmpeg          => shift || 'ffmpeg',
        options         => [],
        global_options  => [],
        infile_options  => [],
        outfile_options => [],
        input_file      => [],
        output_file     => '',
        timeout         => 0,
    };

    system("$self->{ffmpeg} -version > /dev/null 2>&1");
    my $ret = $? >> 8;
    if ( $ret != 0 and $ret != 1 ) {
        carp "Can't find ffmpeg command.";
        exit 0;
    }

    bless $self, $class;
}

sub input_options {
    my ( $self, $args ) = @_;
    $self->input_file($args->{file} || $args->{'files'});
    return;
}

sub output_options {
    my ( $self, $args ) = @_;
    $self->output_file(delete $args->{file});
    my $device = delete $args->{device};

    my %device_option = (
        ipod => {
            format              => 'mp4',
            video_codec         => 'mpeg4',
            bitrate             => 600,
            frame_size          => '320x240',
            audio_codec         => 'libfaac',
            audio_sampling_rate => 48000,
            audio_bit_rate      => 64,
        },
        psp => {
            format              => 'psp',
            video_codec         => 'mpeg4',
            bitrate             => 600,
            frame_size          => '320x240',
            audio_codec         => 'libfaac',
            audio_sampling_rate => 48000,
            audio_bit_rate      => 64,
        },
    );

    my %output_option = (
        %$args,
    );

    # if a device name was supplied, add its output options
    if( $device ) {
        %output_option = (
            %output_option,
            %{ $device_option{$device} },
        );
    }

    for ( keys %output_option ){
        if( defined $option{$_} and defined $output_option{$_} ){
            push @{ $self->outfile_options }, $option{$_}, $output_option{$_};
        }
        elsif( defined $metadata{$_} and defined $output_option{$_} ){
            push @{ $self->outfile_options }, '-metadata', $metadata{$_} . $output_option{$_};
        }
        else {
            carp "$_ is not defined and ignored.";
        }
    }

    return;
}

sub execute {
    my $self = shift;

    my @opts = map { $self->{$_}  ? $self->{$_}  : \$self->{$_} } qw/stdin stdout stderr/;
    push @opts, IPC::Run::timeout($self->timeout) if $self->timeout;

    my $cmd = $self->_compose_command;

    # store the command line so we can debug it
    $self->command( join( ' ', @$cmd ) );

    my $h = eval {
        start( $cmd, @opts )
    };

    if( $@ ){
        $self->error($@);
        return;
    }
    else {
        finish $h or do {
            $self->error($self->stderr);
            return;
        };
    }

    return 1;
}

*exec = \&execute;

sub _compose_command {
    my $self = shift;

    my $files = $self->input_file;
    $files = [ $files ] unless ref $files eq 'ARRAY';

    my $cmd = [
        $self->ffmpeg,
        '-y',
        @{ $self->global_options },
        map ( { @{ $self->infile_options }, ( '-i', $_ ) } @$files ),
        @{ $self->options },
        @{ $self->outfile_options },
    ];

    # add output file only if we have one
    push @$cmd, $self->output_file
        if $self->output_file;

    return $cmd;
}

__END__

=head1 NAME

FFmpeg::Command - A wrapper class for ffmpeg command line utility.

=head1 DESCRIPTION

A simple interface for using ffmpeg command line utility.

=head1 SYNOPSIS

    use FFmpeg::Command;

    my $ffmpeg = FFmpeg::Command->new('/usr/local/bin/ffmpeg');

    # Set timeout
    $ffmpeg->timeout(300);

    # ga: global  option a, gb: global  option b
    # ia: infile  option a, ib: infile  option b
    # oa: outfile option a, ob: outfile option b

    # ffmpeg -y -ga -gb -ia -ib -i filename1 -oa -ob output_file
    $ffmpeg->global_options(qw/-ga -gb/);
    $ffmpeg->infile_options(qw/-ia -ib/);
    $ffmpeg->outfile_options(qw/-oa -ob/);
    $ffmpeg->input_file('filename1');
    $ffmpeg->output_file('output_file');

    my $result = $ffmpeg->exec();
    croak $ffmpeg->errstr unless $result;

    # ffmpeg -y -i filename1 -ga -gb -ia -ib -oa -ob output_file
    $ffmpeg->options(qw/-ga -gb -ia -ib -oa -ob/);
    $ffmpeg->input_file('filename1');
    $ffmpeg->output_file('output_file');

    $ffmpeg->exec();

    # ffmpeg -y -ga -gb -ia -ib -i filename1 -ia -ib -i filename2 -oa -ob output_file
    # Infile options are adopted for every input files
    $ffmpeg->global_options(qw/-ga -gb/);
    $ffmpeg->infile_options(qw/-ia -ib/);
    $ffmpeg->outfile_options(qw/-oa -ob/);
    $ffmpeg->input_file(['filename1','filename2']);
    $ffmpeg->output_file('output_file');

    $ffmpeg->exec()

    # ffmpeg -y -ga -gb -ia1 -ib1 -i filename1 -ia2 -ib2 -i filename2 -oa -ob output_file
    # Each input file has different infile options
    $ffmpeg->options(qw/-ga -gb -ia1 -ib1 -i filename1 -ia2 -ib2 -i filename2 -oa -ob output_file/);

    $ffmpeg->exec()

    # This sample code takes a screen shot.
    $ffmpeg->input_file($input_file);
    $ffmpeg->output_file($output_file);

    $ffmpeg->options(
        '-f'       => 'image2',
        '-pix_fmt' => 'jpg',
        '-vframes' => 1,
        '-ss'      => 30,
        '-s'       => '320x240',
        '-an',
    );

    $ffmpeg->exec()

    # Below are old style example(may be obsoleted at future release)
    $ffmpeg->input_options({
        file => $input_file,
    });

    # Convert a video file into iPod playable format.
    $ffmpeg->output_options({
        file   => $output_file,
        device => 'ipod',
    });

    # This is same as above.
    $ffmpeg->output_options({
        file                => $output_file,
        format              => 'mp4',
        video_codec         => 'mpeg4',
        bitrate             => 600,
        frame_size          => '320x240',
        audio_codec         => 'libaac',
        audio_sampling_rate => 48000,
        audio_bit_rate      => 64,
    });

    # Convert a video file into PSP playable format.
    $ffmpeg->output_options({
        file  => $output_file,
        device => 'psp',
    });

    # This is same as above.
    $ffmpeg->output_options({
        file                => $output_file,
        format              => 'psp',
        video_codec         => 'mpeg4',
        bitrate             => 600,
        frame_size          => '320x240',
        audio_codec         => 'libaac',
        audio_sampling_rate => 48000,
        audio_bit_rate      => 64,
    });



=head1 METHODS

=head2 new('/usr/bin/ffmpeg')

Contructs FFmpeg::Command object.It takes a path of ffmpeg command.
You can omit this argument and this module searches ffmpeg command within PATH environment variable.

=head2 timeout()

Set command timeout.Default is 0.

=head2 input_file( @files );

Specify names of input file(s).

=head2 output_file('/path/to/output_file')

Specify output file name.

=head2 global_options( @options )

Specify ffmpeg global options.

=head2 infile_options( @options )

Specify ffmpeg infile options.

=head2 outfile_options( @options )

Specify ffmpeg outfile options.


=head2 options( @options )

Specify ffmpeg command options directly including input files and and output file.

=head2 execute()

Executes ffmpeg command with specified options.

=head2 exec()

An alias of execute()

=head2 stdout()

Get ffmpeg command output to stdout.

=head2 stderr()

Get ffmpeg command output to stderr.

=head2 input_options({ %options })

Specify input file name and input options.(Now no options are available.)

=over

=item file

a file name of input file or an anonymous list of multiple input files
(useful for merging audio and video files together).

=back

=head2 output_options({ %options })

Specify output file name and output options.

Avaiable options are:

=over

=item file

a file name of output file.

=item format

Output video format.

=item video_codec

Output video codec.

=item bitrate

Output video bitrate.

=item frame_size

Output video screen size.

=item audio_codec

Output audio code.

=item audio_sampling_rate

Output audio sampling rate.

=item audio_bit_rate

Output audio bit rate.

=item title

Set the title.

=item author

Set the author.

=item comment

Set the comment.

=back

=head1 AUTHORS

Gosuke Miyashita, C<< <gosukenator at gmail.com> >>

Munechika Sumikawa, C<< <sumikawa at sumikawa.jp> >>

=head1 BUGS

Please report any bugs or feature requests to
C<bug-ffmpeg-command at rt.cpan.org>, or through the web interface at
L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=FFmpeg-Command>.
I will be notified, and then you'll automatically be notified of progress on
your bug as I make changes.

=head1 SUPPORT

You can find documentation for this module with the perldoc command.

    perldoc FFmpeg::Command

You can also look for information at:

=over 4

=item * AnnoCPAN: Annotated CPAN documentation

L<http://annocpan.org/dist/FFmpeg-Command>

=item * CPAN Ratings

L<http://cpanratings.perl.org/d/FFmpeg-Command>

=item * RT: CPAN's request tracker

L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=FFmpeg-Command>

=item * Search CPAN

L<http://search.cpan.org/dist/FFmpeg-Command>

=back

=head1 ACKNOWLEDGEMENTS

=head1 COPYRIGHT & LICENSE

Copyright 2006 Gosuke Miyashita, all rights reserved.

This program is free software; you can redistribute it and/or modify it
under the same terms as Perl itself.

=cut