The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.
#!/usr/bin/perl -w

package main;

use warnings;
use strict;
use 5.008;
use Getopt::Long;
use Pod::Usage;
use English qw(-no_match_vars);
use FLV::Cut;

our $VERSION = '0.24';

my %opts = (
   keyframes => 0,
   verbose   => 0,
   help      => 0,
   version   => 0,
);

Getopt::Long::Configure('bundling');
GetOptions(
   'k|keyframes' => \$opts{keyframes},
   'v|verbose'   => \$opts{verbose},
   'h|help'      => \$opts{help},
   'V|version'   => \$opts{version},
) or pod2usage(1);
if ($opts{help})
{
   pod2usage(-exitstatus => 0, -verbose => 2);
}
if ($opts{version})
{
   print "v$VERSION\n";
   exit 0;
}

if (3 > @ARGV)
{
   pod2usage(1);
}
if (1 != (@ARGV % 2))
{
   pod2usage(1);
}

my $infile = shift;

my $flv = FLV::File->new;
$flv->parse($infile);
my @keyframe_times;
if ($opts{keyframes})
{
   $flv->populate_meta;
   @keyframe_times = @{ $flv->get_meta('keyframes')->{millis} };
}

my $converter = FLV::Cut->new;
while (@ARGV)
{
   my $outfile = shift;
   my $times   = shift;
   if ($times =~ m/\A (\d+|) : (\d+|) \z/xms)
   {
      my $cutin  = $1 || undef;
      my $cutout = $2 || undef;
      if (@keyframe_times)
      {
         $cutin  = _round_to_keyframe($cutin, \@keyframe_times, $opts{verbose});
         $cutout = _round_to_keyframe($cutout, \@keyframe_times, $opts{verbose});
      }
      $converter->add_output($outfile, $cutin, $cutout);
   }
   else
   {
      pod2usage(1);
   }
}
$converter->parse_flv($flv);
$converter->save_all;

sub _round_to_keyframe
{
   my $cut     = shift; # millisecs
   my $times   = shift; # arrayref of millisecs
   my $verbose = shift; # bool

   return $cut if !defined $cut;
   my $best_time = -1;
   my $best_diff = 1_000_000_000;
   for my $time_ms (@{ $times })
   {
      my $diff = abs($cut - $time_ms);
      if ($diff < $best_diff)
      {
         $best_diff = $diff;
         $best_time = $time_ms;
      }
   }
   $best_time = $best_time < 0 ? $cut : $best_time;
   if ($verbose && $cut != $best_time)
   {
      print "Rounding time from $cut to $best_time (milliseconds)\n";
   }
   return $best_time < 0 ? $cut : $best_time;
}

__END__

=for stopwords FLV flv2flv flvsplice in.flv out.flv inms:outms flvcut

=head1 NAME

flvcut - Extract one or more time segments from an FLV file

=head1 SYNOPSIS

flvcut [options] in.flv out.flv inms:outms [out.flv inms:outms ...]

 Options:
   -k --keyframes      Round specified times to the nearest keyframe time
   -v --verbose        Print diagnostic messages
   -h --help           Verbose help message
   -V --version        Print version

Either of the in or out files can be C<->, meaning STDIN or STDOUT.

=head1 DESCRIPTION

Sometimes you have an FLV that is too long, or has extraneous content.
This tool lets you efficiently split the FLV into one or more
segments.

The in and out times are in milliseconds.  Either (or both!) can be
omitted, which implies the ends of the input file.

You should probably always use the C<-k> option.  Otherwise, if your
times are wrong, the video is going to look terrible at the start of
the clip.

=head1 SEE ALSO

flv2flv

flvsplice

L<FLV::Cut>

=head1 AUTHOR

Chris Dolan, I<cdolan@cpan.org>

=cut