| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package App::fftrim; | 
| 2 | 2 |  |  | 2 |  | 133288 | use 5.006; | 
|  | 2 |  |  |  |  | 17 |  | 
| 3 | 2 |  |  | 2 |  | 11 | use strict; | 
|  | 2 |  |  |  |  | 3 |  | 
|  | 2 |  |  |  |  | 53 |  | 
| 4 | 2 |  |  | 2 |  | 10 | use warnings; | 
|  | 2 |  |  |  |  | 3 |  | 
|  | 2 |  |  |  |  | 68 |  | 
| 5 | 2 |  |  | 2 |  | 12 | no warnings 'once'; | 
|  | 2 |  |  |  |  | 2 |  | 
|  | 2 |  |  |  |  | 72 |  | 
| 6 | 2 |  |  | 2 |  | 11 | no warnings 'uninitialized'; | 
|  | 2 |  |  |  |  | 4 |  | 
|  | 2 |  |  |  |  | 83 |  | 
| 7 | 2 |  |  | 2 |  | 14 | use feature 'say'; | 
|  | 2 |  |  |  |  | 2 |  | 
|  | 2 |  |  |  |  | 192 |  | 
| 8 | 2 |  |  | 2 |  | 14 | use Cwd; | 
|  | 2 |  |  |  |  | 3 |  | 
|  | 2 |  |  |  |  | 2752 |  | 
| 9 |  |  |  |  |  |  | our ($opt, $usage, | 
| 10 |  |  |  |  |  |  | $current_dir, | 
| 11 |  |  |  |  |  |  | $control, | 
| 12 |  |  |  |  |  |  | $control_file, | 
| 13 |  |  |  |  |  |  | $dotdir , | 
| 14 |  |  |  |  |  |  | $profile, | 
| 15 |  |  |  |  |  |  | $fh, | 
| 16 |  |  |  |  |  |  | $encoding_params, | 
| 17 |  |  |  |  |  |  | %length, | 
| 18 |  |  |  |  |  |  | $framerate, | 
| 19 |  |  |  |  |  |  | $finaldir, | 
| 20 |  |  |  |  |  |  | @lines, | 
| 21 |  |  |  |  |  |  | $is_error, | 
| 22 |  |  |  |  |  |  | @source_files, | 
| 23 |  |  |  |  |  |  | $concat_target, | 
| 24 |  |  |  |  |  |  | ); | 
| 25 |  |  |  |  |  |  | sub initial_setup { | 
| 26 |  |  |  |  |  |  |  | 
| 27 | 0 |  |  | 0 | 0 | 0 | $dotdir = join_path($ENV{HOME}, '.fftrim'); | 
| 28 | 0 |  |  |  |  | 0 | my $did_something; | 
| 29 | 0 | 0 |  |  |  | 0 | if ( ! -d $dotdir) | 
| 30 | 0 |  |  |  |  | 0 | {	mkdir $dotdir; | 
| 31 | 0 |  |  |  |  | 0 | $did_something++; | 
| 32 | 0 |  |  |  |  | 0 | my $default = join_path($dotdir, 'default'); | 
| 33 | 0 |  |  |  |  | 0 | say STDERR qq(\n$default contains ffmpeg options to merge by default.); | 
| 34 | 0 |  |  |  |  | 0 | print STDERR qq(\nShall I populate this file with sample compression settings ? [y/n] ); | 
| 35 | 0 |  |  |  |  | 0 | my $answer = ; | 
| 36 | 0 |  |  |  |  | 0 | open my $fh, '>', $default; | 
| 37 | 0 | 0 |  |  |  | 0 | if ($answer =~ /YyJj/) | 
| 38 |  |  |  |  |  |  | { | 
| 39 | 0 |  |  |  |  | 0 | my @lines = ; | 
| 40 | 0 |  |  |  |  | 0 | print $fh @lines; | 
| 41 | 0 |  |  |  |  | 0 | say STDERR "Done! Edit this file to suit your needs or create additional profiles"; | 
| 42 |  |  |  |  |  |  | } | 
| 43 | 0 |  |  |  |  | 0 | else {print $fh "\n"} | 
| 44 |  |  |  |  |  |  | } | 
| 45 | 0 |  |  |  |  | 0 | print "\n"; | 
| 46 | 0 |  |  |  |  | 0 | $did_something; | 
| 47 |  |  |  |  |  |  | } | 
| 48 |  |  |  |  |  |  |  | 
| 49 |  |  |  |  |  |  | sub process_args { | 
| 50 |  |  |  |  |  |  |  | 
| 51 |  |  |  |  |  |  |  | 
| 52 | 0 |  |  | 0 | 0 | 0 | $current_dir = getcwd; | 
| 53 |  |  |  |  |  |  |  | 
| 54 | 0 |  | 0 |  |  | 0 | $profile = join_path($dotdir, $opt->{profile} // 'default'); | 
| 55 | 0 | 0 |  |  |  | 0 | if (-r $profile) | 
| 56 |  |  |  |  |  |  | { | 
| 57 | 0 |  |  |  |  | 0 | open $fh, '<', $profile; | 
| 58 | 0 |  |  |  |  | 0 | $encoding_params = join '', grep {! /^#/} <$fh>; | 
|  | 0 |  |  |  |  | 0 |  | 
| 59 | 0 |  |  |  |  | 0 | $encoding_params =~ s/\n/ /g; | 
| 60 |  |  |  |  |  |  | } | 
| 61 |  |  |  |  |  |  |  | 
| 62 |  |  |  |  |  |  | # handle command line mode | 
| 63 | 0 | 0 | 0 |  |  | 0 | if ($opt->{in} and $opt->{out} ){ | 
| 64 | 0 | 0 |  |  |  | 0 | if ($opt->{in} =~ /\s/) | 
| 65 |  |  |  |  |  |  | { | 
| 66 | 0 |  |  |  |  | 0 | @source_files = split ' ', $opt->{in}; | 
| 67 | 0 |  |  |  |  | 0 | $framerate = video_framerate($source_files[0]); | 
| 68 | 0 |  |  |  |  | 0 | say "source files: ", join '|', @source_files; | 
| 69 | 0 | 0 |  |  |  | 0 | $concat_target = $opt->{concat_only} ? $opt->{out} : to_mp4($source_files[0]); | 
| 70 | 0 |  |  |  |  | 0 | say "concat target: $concat_target"; | 
| 71 | 0 |  |  |  |  | 0 | concatenate_video($concat_target, @source_files); | 
| 72 |  |  |  |  |  |  | } | 
| 73 |  |  |  |  |  |  | compress_and_trim_video($concat_target//$opt->{in}, $opt->{out}, $opt->{start} // 0, $opt->{end}) | 
| 74 | 0 | 0 | 0 |  |  | 0 | unless $opt->{concat_only}; | 
|  |  |  | 0 |  |  |  |  | 
| 75 |  |  |  |  |  |  | exit | 
| 76 | 0 |  |  |  |  | 0 | } | 
| 77 |  |  |  |  |  |  |  | 
| 78 |  |  |  |  |  |  | # batch mode | 
| 79 |  |  |  |  |  |  |  | 
| 80 |  |  |  |  |  |  | # support old filename | 
| 81 | 0 |  |  |  |  | 0 | ($control_file) = grep{ -e } map{ join_path($opt->{source_dir},$_) }  qw(CONTROL CONTENTS); | 
|  | 0 |  |  |  |  | 0 |  | 
|  | 0 |  |  |  |  | 0 |  | 
| 82 | 0 | 0 |  |  |  | 0 | -e $control_file or die "CONTROL file not found in $opt->{source_dir}"; | 
| 83 |  |  |  |  |  |  |  | 
| 84 | 0 |  |  |  |  | 0 | $finaldir = $opt->{target_dir}; | 
| 85 | 0 | 0 |  |  |  | 0 | mkdir $finaldir unless -e $finaldir; | 
| 86 | 0 | 0 |  |  |  | 0 | -d $finaldir or die "$finaldir is not a directory!"; | 
| 87 |  |  |  |  |  |  |  | 
| 88 | 0 |  |  |  |  | 0 | open my $fh, '<',$control_file; | 
| 89 | 0 |  | 0 |  |  | 0 | (@lines) = grep {! /^\s*$/ and ! /^\s*#/} map{ chomp; $_ } <$fh>; | 
|  | 0 |  |  |  |  | 0 |  | 
|  | 0 |  |  |  |  | 0 |  | 
|  | 0 |  |  |  |  | 0 |  | 
| 90 |  |  |  |  |  |  |  | 
| 91 | 0 |  |  |  |  | 0 | process_lines(); # check for errors; | 
| 92 | 0 | 0 |  |  |  | 0 | say(STDERR "Errors found. Fix $control_file and try again."), exit if $is_error; | 
| 93 | 0 |  |  |  |  | 0 | process_lines("really do it! (but still may be a test)"); | 
| 94 |  |  |  |  |  |  |  | 
| 95 |  |  |  |  |  |  | } | 
| 96 |  |  |  |  |  |  | sub get_lengths { | 
| 97 | 0 |  |  | 0 | 0 | 0 | my @source_files = @_; | 
| 98 | 0 |  |  |  |  | 0 | for (@source_files) | 
| 99 |  |  |  |  |  |  | { | 
| 100 | 0 | 0 |  |  |  | 0 | next if defined $length{$_}; | 
| 101 | 0 |  |  |  |  | 0 | my $len = video_length($_); | 
| 102 | 0 |  |  |  |  | 0 | $length{$_} = seconds($len); | 
| 103 |  |  |  |  |  |  | } | 
| 104 |  |  |  |  |  |  |  | 
| 105 |  |  |  |  |  |  | } | 
| 106 |  |  |  |  |  |  |  | 
| 107 |  |  |  |  |  |  | sub process_lines { | 
| 108 | 0 |  |  | 0 | 0 | 0 | my $do = shift; | 
| 109 | 0 |  |  |  |  | 0 | foreach my $line (@lines){ | 
| 110 | 0 |  |  |  |  | 0 | $line =~ s/\s+$//; | 
| 111 | 0 |  |  |  |  | 0 | say STDERR "line: $line"; | 
| 112 | 0 |  |  |  |  | 0 | my ($source_files, $target, $start, $end) = split /\s+:\s+/, $line; | 
| 113 | 0 |  |  |  |  | 0 | my @source_files = map{ join_path($opt->{source_dir}, $_)} split " ", $source_files; | 
|  | 0 |  |  |  |  | 0 |  | 
| 114 | 0 |  |  |  |  | 0 | $framerate = video_framerate($source_files[0]); | 
| 115 | 0 |  |  |  |  | 0 | get_lengths(@source_files); | 
| 116 | 0 | 0 |  |  |  | 0 | say STDERR qq(no target for source files "$source_files". Using source name.) if not $target; | 
| 117 | 0 | 0 |  |  |  | 0 | if ( ! $target ) { | 
| 118 | 0 |  |  |  |  | 0 | $target = to_mp4($source_files[0]); | 
| 119 |  |  |  |  |  |  | } | 
| 120 |  |  |  |  |  |  | else { | 
| 121 |  |  |  |  |  |  | # pass filenames with extension, otherwise append .mp4 | 
| 122 | 0 | 0 |  |  |  | 0 | $target = mp4($target) unless $target =~ /\.[a-zA-Z]{3}$/ | 
| 123 |  |  |  |  |  |  | } | 
| 124 |  |  |  |  |  |  | { | 
| 125 | 2 |  |  | 2 |  | 18 | no warnings 'uninitialized'; | 
|  | 2 |  |  |  |  | 4 |  | 
|  | 2 |  |  |  |  | 3329 |  | 
|  | 0 |  |  |  |  | 0 |  | 
| 126 | 0 |  |  |  |  | 0 | say STDERR "source files: @source_files"; | 
| 127 | 0 |  |  |  |  | 0 | say STDERR "target: $target"; | 
| 128 | 0 |  |  |  |  | 0 | say STDERR "start time: $start"; | 
| 129 | 0 |  |  |  |  | 0 | say STDERR "end time: $end"; | 
| 130 | 0 | 0 |  |  |  | 0 | say(STDERR qq(no source files in line!! $line)), $is_error++, if not @source_files; | 
| 131 | 0 |  |  |  |  | 0 | my @missing = grep { ! -r } @source_files; | 
|  | 0 |  |  |  |  | 0 |  | 
| 132 | 0 | 0 |  |  |  | 0 | say(STDERR qq(missing source files: @missing)), $is_error++, if @missing; | 
| 133 |  |  |  |  |  |  | } | 
| 134 |  |  |  |  |  |  |  | 
| 135 | 0 | 0 |  |  |  | 0 | next unless $do; | 
| 136 | 0 |  |  |  |  | 0 | my $compression_source; | 
| 137 | 0 | 0 |  |  |  | 0 | if (@source_files > 1) | 
| 138 |  |  |  |  |  |  | { | 
| 139 | 0 |  |  |  |  | 0 | my $concat_target = to_mp4($source_files[0]); | 
| 140 | 0 |  |  |  |  | 0 | say STDERR "concat target: $concat_target"; | 
| 141 | 0 |  |  |  |  | 0 | concatenate_video($concat_target, @source_files); | 
| 142 | 0 |  |  |  |  | 0 | $compression_source = $concat_target; | 
| 143 |  |  |  |  |  |  | } | 
| 144 |  |  |  |  |  |  | else | 
| 145 |  |  |  |  |  |  | { | 
| 146 | 0 |  |  |  |  | 0 | $compression_source = $source_files[0]; | 
| 147 |  |  |  |  |  |  | } | 
| 148 | 0 |  |  |  |  | 0 | my $final = trim_target($target); | 
| 149 | 0 |  |  |  |  | 0 | $start = decode_cutpoint($start, \@source_files); | 
| 150 | 0 |  |  |  |  | 0 | $end = decode_cutpoint($end, \@source_files); | 
| 151 | 0 |  |  |  |  | 0 | say STDERR "decoded start: $start, decoded end: $end"; | 
| 152 | 0 |  |  |  |  | 0 | compress_and_trim_video( | 
| 153 |  |  |  |  |  |  | $compression_source, | 
| 154 |  |  |  |  |  |  | $final, | 
| 155 |  |  |  |  |  |  | $start, | 
| 156 |  |  |  |  |  |  | $end | 
| 157 |  |  |  |  |  |  | ); | 
| 158 |  |  |  |  |  |  | } | 
| 159 |  |  |  |  |  |  | } | 
| 160 | 0 |  |  | 0 | 0 | 0 | sub name_part  { my ($name) = $_[0] =~ /(.+?)(\.[a-zA-Z]{1,3})$/} | 
| 161 | 0 |  |  | 0 | 0 | 0 | sub mp4 { $_[0] . '.mp4' } | 
| 162 | 0 | 0 |  | 0 | 0 | 0 | sub to_mp4 { mp4( $opt->{old_concat} ? name_part($_[0]): $_[0]) } | 
| 163 |  |  |  |  |  |  |  | 
| 164 | 0 |  |  | 0 | 0 | 0 | sub trim_target { "$finaldir/$_[0]" } | 
| 165 |  |  |  |  |  |  |  | 
| 166 |  |  |  |  |  |  | sub concatenate_video { | 
| 167 | 0 |  |  | 0 | 0 | 0 | my ($target, @sources) = @_; | 
| 168 | 0 |  |  |  |  | 0 | file_level_concat($target, @sources); | 
| 169 |  |  |  |  |  |  | } | 
| 170 |  |  |  |  |  |  |  | 
| 171 |  |  |  |  |  |  | sub file_level_concat { | 
| 172 | 0 |  |  | 0 | 0 | 0 | my ($target, @sources) = @_; | 
| 173 | 0 | 0 |  |  |  | 0 | $target .= ".mp4" unless $target =~ /mp4$/; | 
| 174 | 0 | 0 |  |  |  | 0 | say(STDERR "$target: file exists, skipping"), return if file_exists($target); | 
| 175 | 0 |  |  |  |  | 0 | my $parts = join '|', @sources; | 
| 176 | 0 |  |  |  |  | 0 | my $cmd = qq(ffmpeg -i concat:"$parts" -codec copy $target); | 
| 177 | 0 |  |  |  |  | 0 | say STDERR "concatenating: @sources -> $target"; | 
| 178 | 0 |  |  |  |  | 0 | say $cmd; | 
| 179 | 0 | 0 |  |  |  | 0 | system $cmd unless simulate(); | 
| 180 |  |  |  |  |  |  | } | 
| 181 |  |  |  |  |  |  |  | 
| 182 |  |  |  |  |  |  | sub compress_and_trim_video { | 
| 183 | 0 |  |  | 0 | 0 | 0 | my ($input, $output, $start, $end) = @_; | 
| 184 | 0 |  |  |  |  | 0 | say "compress and trim args: ",join " | ",$input, $output, $start, $end; | 
| 185 | 0 | 0 |  |  |  | 0 | say(STDERR "$output: file exists, skipping"), return if file_exists( $output ); | 
| 186 | 0 |  |  |  |  | 0 | my $target_framerate; | 
| 187 |  |  |  |  |  |  | $target_framerate = $opt->{auto_frame_rate} | 
| 188 |  |  |  |  |  |  | ? $framerate | 
| 189 | 0 | 0 |  |  |  | 0 | : $opt->{frame_rate}; | 
| 190 | 0 |  | 0 |  |  | 0 | $start //= 0; | 
| 191 | 0 |  |  |  |  | 0 | my @args = "ffmpeg"; | 
| 192 | 0 |  |  |  |  | 0 | push @args, "-i $input"; | 
| 193 | 0 | 0 |  |  |  | 0 | push @args, "-to $end" if $end; | 
| 194 | 0 |  |  |  |  | 0 | push @args, $encoding_params; | 
| 195 | 0 | 0 |  |  |  | 0 | push @args, "-ss $start" if $start; | 
| 196 | 0 | 0 |  |  |  | 0 | push @args, "-r $target_framerate" if $target_framerate; | 
| 197 | 0 |  |  |  |  | 0 | push @args, $output; | 
| 198 | 0 |  |  |  |  | 0 | my $cmd = join " ",@args; | 
| 199 | 0 |  |  |  |  | 0 | say $cmd; | 
| 200 | 0 | 0 |  |  |  | 0 | system $cmd unless simulate(); | 
| 201 |  |  |  |  |  |  | } | 
| 202 |  |  |  |  |  |  | sub seconds { | 
| 203 | 16 |  |  | 16 | 0 | 24 | my $hms = shift; | 
| 204 | 16 |  |  |  |  | 26 | my $count = $hms =~ tr/:/:/; | 
| 205 | 16 |  | 50 |  |  | 33 | $count //= 0; | 
| 206 |  |  |  |  |  |  | # case 1, seconds only | 
| 207 | 16 | 100 |  |  |  | 38 | if (! $count) | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
| 208 |  |  |  |  |  |  | { | 
| 209 | 6 |  |  |  |  | 23 | return $hms | 
| 210 |  |  |  |  |  |  | } | 
| 211 |  |  |  |  |  |  | elsif($count == 1) | 
| 212 |  |  |  |  |  |  | { | 
| 213 |  |  |  |  |  |  | # m:s | 
| 214 |  |  |  |  |  |  |  | 
| 215 | 6 |  |  |  |  | 19 | my ($m,$s) = split ':', $hms; | 
| 216 | 6 |  |  |  |  | 28 | return $m * 60 + $s | 
| 217 |  |  |  |  |  |  | } | 
| 218 |  |  |  |  |  |  | elsif($count == 2) | 
| 219 |  |  |  |  |  |  | { | 
| 220 | 4 |  |  |  |  | 15 | my ($h,$m,$s) = split ':', $hms; | 
| 221 | 4 |  |  |  |  | 21 | return $h * 3600 + $m * 60 + $s | 
| 222 |  |  |  |  |  |  | } | 
| 223 | 0 |  |  |  |  | 0 | else { die "$hms: something wrong, detected too many ':' characters" } | 
| 224 |  |  |  |  |  |  | } | 
| 225 |  |  |  |  |  |  | sub hms { | 
| 226 | 8 |  |  | 8 | 0 | 12 | my $seconds = shift; | 
| 227 | 8 |  |  |  |  | 16 | my $whole_hours = int( $seconds  / 3600 ); | 
| 228 | 8 |  |  |  |  | 14 | $seconds -= $whole_hours * 3600; | 
| 229 | 8 |  |  |  |  | 12 | my $whole_minutes = int( $seconds / 60 ); | 
| 230 | 8 |  |  |  |  | 11 | $seconds -= $whole_minutes * 60; | 
| 231 | 8 | 100 |  |  |  | 18 | $whole_minutes = "0$whole_minutes" if $whole_minutes < 10; | 
| 232 | 8 | 50 |  |  |  | 16 | $seconds = "0$seconds" if $seconds < 10; | 
| 233 | 8 |  |  |  |  | 9 | my $output; | 
| 234 | 8 | 100 |  |  |  | 19 | $output .=  "$whole_hours:" if $whole_hours; | 
| 235 | 8 | 100 | 66 |  |  | 28 | $output .=  "$whole_minutes:" if $whole_minutes > 0 or $whole_hours; | 
| 236 | 8 |  |  |  |  | 14 | $output .= $seconds; | 
| 237 | 8 |  |  |  |  | 19 | $output | 
| 238 |  |  |  |  |  |  | } | 
| 239 |  |  |  |  |  |  | sub decode_cutpoint { | 
| 240 | 8 |  |  | 8 | 0 | 4566 | my ($pos, $sources) = @_; | 
| 241 | 8 | 50 |  |  |  | 23 | return unless $pos; | 
| 242 |  |  |  |  |  |  | # 1+2+24:15 | 
| 243 |  |  |  |  |  |  | # 3-24:15 3rd file | 
| 244 | 8 |  |  |  |  | 37 | my ($nth, $time) = $pos =~ /(\d+)-([\d:]+)/; | 
| 245 | 8 |  |  |  |  | 12 | my $cutpoint; # this is a position in the final source file | 
| 246 |  |  |  |  |  |  | my $segments; # this is the count of the preceeding source files included at full length | 
| 247 | 8 | 100 |  |  |  | 17 | if ($nth){ | 
| 248 | 3 |  |  |  |  | 7 | $cutpoint = $time; | 
| 249 | 3 |  |  |  |  | 7 | $segments = $nth - 1; | 
| 250 |  |  |  |  |  |  | } | 
| 251 |  |  |  |  |  |  | else { | 
| 252 | 5 |  |  |  |  | 41 | my (@segments) = $pos =~ /(\d\+)?(\d\+)?(\d\+)?([^+]+)$/; | 
| 253 | 5 |  | 50 |  |  | 16 | $cutpoint = (pop @segments) // 0; | 
| 254 | 5 |  |  |  |  | 9 | $segments = 0; | 
| 255 | 5 | 50 |  |  |  | 11 | if (@segments){ | 
| 256 | 5 |  |  |  |  | 10 | @segments = map{ s/\+\s*//g; $_ } grep{$_} @segments; | 
|  | 5 |  |  |  |  | 18 |  | 
|  | 5 |  |  |  |  | 15 |  | 
|  | 15 |  |  |  |  | 41 |  | 
| 257 | 5 |  |  |  |  | 12 | $segments =  scalar @segments; | 
| 258 |  |  |  |  |  |  | } | 
| 259 |  |  |  |  |  |  | } | 
| 260 | 8 |  |  |  |  | 20 | my $total_length = seconds($cutpoint); | 
| 261 | 8 |  |  |  |  | 39 | $total_length += $length{$sources->[$_]} for 0 .. $segments - 1; | 
| 262 | 8 |  |  |  |  | 17 | hms($total_length) | 
| 263 |  |  |  |  |  |  | } | 
| 264 | 0 |  |  | 0 | 0 |  | sub join_path { join '/',@_ } | 
| 265 |  |  |  |  |  |  |  | 
| 266 | 0 | 0 |  | 0 | 0 |  | sub simulate { $opt->{n} or $opt->{m} } | 
| 267 | 0 | 0 |  | 0 | 0 |  | sub file_exists { $opt->{m} ? 0 : -e $_[0] } | 
| 268 |  |  |  |  |  |  |  | 
| 269 |  |  |  |  |  |  | sub video_length { | 
| 270 | 0 |  |  | 0 | 0 |  | my $videofile = shift; | 
| 271 | 0 |  |  |  |  |  | my $result = qx(ffmpeg -i "$videofile" 2>&1 | grep Duration | cut -d ' ' -f 4 | sed s/,//); | 
| 272 | 0 |  |  |  |  |  | chomp $result; | 
| 273 | 0 |  |  |  |  |  | $result | 
| 274 |  |  |  |  |  |  | } | 
| 275 |  |  |  |  |  |  | sub video_framerate { | 
| 276 | 0 |  |  | 0 | 0 |  | my $videofile = shift; | 
| 277 | 0 |  |  |  |  |  | my $result = qx(ffprobe "$videofile" 2>&1); | 
| 278 | 0 |  |  |  |  |  | my ($fps) = $result =~ /(\d+(.\d+)?) fps/; | 
| 279 |  |  |  |  |  |  | } | 
| 280 |  |  |  |  |  |  | =head1 NAME | 
| 281 |  |  |  |  |  |  |  | 
| 282 |  |  |  |  |  |  | B - concatenate, trim and compress video files | 
| 283 |  |  |  |  |  |  |  | 
| 284 |  |  |  |  |  |  | =head1 VERSION | 
| 285 |  |  |  |  |  |  |  | 
| 286 |  |  |  |  |  |  | Version 0.02 | 
| 287 |  |  |  |  |  |  |  | 
| 288 |  |  |  |  |  |  | =cut | 
| 289 |  |  |  |  |  |  |  | 
| 290 |  |  |  |  |  |  | our $VERSION = '0.02'; | 
| 291 |  |  |  |  |  |  |  | 
| 292 |  |  |  |  |  |  | =head1 SYNOPSIS | 
| 293 |  |  |  |  |  |  |  | 
| 294 |  |  |  |  |  |  | see 'man fftrim' or 'perldoc fftrim'. | 
| 295 |  |  |  |  |  |  |  | 
| 296 |  |  |  |  |  |  | =cut | 
| 297 |  |  |  |  |  |  | 1; # End of App::fftrim |