App-FfmpegUtils
view release on metacpan or search on metacpan
lib/App/FfmpegUtils.pm view on Meta::CPAN
well compressed, so they make a good input for this utility. The default setting
is roughly similar to how Google Photos encodes videos (max 1080p).
The default settings are:
-v:c libx264
-preset veryslow (to get the best compression rate, but with the slowest encoding time)
-crf 28 (0-51, subjectively sane is 18-28, 18 ~ visually lossless, 28 ~ visually acceptable)
when a downsizing is requested (using the `--downsize-to` option), this utility
first checks each input video if it is indeed larger than the requested final
size. If it is, then the `-vf scale` option is added. This utility also
calculates a valid size for ffmpeg, since using `-vf scale=-1:720` sometimes
results in failure due to odd number.
Audio streams are copied, not re-encoded.
Output filenames are:
ORIGINAL_NAME.crf28.mp4
or (if downsizing is done):
ORIGINAL_NAME.480p-crf28.mp4
MARKDOWN
args => {
%argspec0_files,
%argspecopt_ffmpeg_path,
crf => {
schema => ['int*', between=>[0,51]],
},
scale => {
schema => 'str*',
default => '1080^>',
description => <<'MARKDOWN',
Scale video to specified size. See <pm:Math::Image::CalcResized> or
<prog:calc-image-resized-size> for more details on scale specification. Some
examples include:
The default is `1080^>` which means to shrink to 1080p if video size is larger
than 1080p.
To disable scaling, set `--scale` to '' (empty string), or specify
`--dont-scale` on the CLI.
MARKDOWN
cmdline_aliases => {
dont_scale => {summary=>"Alias for --scale ''", is_flag=>1, code=>sub {$_[0]{scale} = ''}},
no_scale => {summary=>"Alias for --scale ''", is_flag=>1, code=>sub {$_[0]{scale} = ''}},
},
},
preset => {
schema => ['str*', in=>\@presets],
default => 'veryslow',
cmdline_aliases => {
(map {($_ => {is_flag=>1, summary=>"Shortcut for --preset=$_", code=>do { my $p = $_; sub { $_[0]{preset} = $p }}})} @presets),
},
},
frame_rate => {
summary => 'Set frame rate, in fps',
schema => 'ufloat*',
cmdline_aliases => {r=>{}},
},
audio_sample_rate => {
summary => 'Set audio sample rate, in Hz',
schema => 'uint*',
cmdline_aliases => {sample_rate=>{}},
},
overwrite => {
schema => 'bool*',
cmdline_aliases => {O=>{}},
},
},
features => {
dry_run => 1,
},
examples => [
{
summary => 'The default setting is to shrink to 1080p if video is larger than 1080p',
src => '[[prog]] *',
src_plang => 'bash',
test => 0,
'x.doc.show_result' => 0,
},
{
summary => 'Do not scale/shrink',
src => '[[prog]] --dont-scale *',
src_plang => 'bash',
test => 0,
'x.doc.show_result' => 0,
},
{
summary => 'Shrink to 480p if video is larger than 480p, but make the reencoding "visually lossless"',
src => "[[prog]] --scale '480^>' --crf 18 *",
src_plang => 'bash',
test => 0,
'x.doc.show_result' => 0,
},
],
};
sub reencode_video_with_libx264 {
require File::Which;
require IPC::System::Options;
require Media::Info;
my %args = @_;
my $ffmpeg_path = $args{ffmpeg_path} // File::Which::which("ffmpeg");
my $scale = $args{scale};
unless ($args{-dry_run}) {
return [400, "Cannot find ffmpeg in path"] unless defined $ffmpeg_path;
return [400, "ffmpeg path $ffmpeg_path is not executable"] unless -f $ffmpeg_path;
}
for my $file (@{$args{files}}) {
log_info "Processing file %s ...", $file;
unless (-f $file) {
log_error "No such file %s, skipped", $file;
next;
}
my $res = Media::Info::get_media_info(media => $file);
unless ($res->[0] == 200) {
log_error "Can't get media information fod %s: %s - %s, skipped",
$file, $res->[0], $res->[1];
next;
}
my $video_info = $res->[2];
my $crf = $args{crf} // 28;
my @ffmpeg_args = (
"-i", $file,
($args{overwrite} ? "-y":"-n"),
);
my $scale_suffix;
SCALE: {
last unless defined $scale && length $scale;
require Math::Image::CalcResized;
my $calcres = Math::Image::CalcResized::calc_image_resized_size(
size => "$video_info->{video_width}x$video_info->{video_height}",
resize => $scale,
);
return [400, "Can't scale using '$scale': $calcres->[0] - $calcres->[1]"]
unless $calcres->[0] == 200;
my ($scaled_width, $scaled_height) = $calcres->[2] =~ /(.+)x(.+)/
or return [500, "calc_image_resized_size() doesn't return new WxH ($calcres->[2])"];
last unless $scaled_width != $video_info->{video_width} ||
$scaled_height != $video_info->{video_height};
($scale_suffix = $calcres->[3]{'func.human_specific'}) =~ s/\W+/_/g;
push @ffmpeg_args, "-vf", sprintf(
"scale=%d:%d",
_nearest($scaled_width, 2), # make sure divisible by 2 (optimum is divisible by 16, then 8, then 4)
_nearest($scaled_height, 2),
);
} # SCALE
my $output_file = $file;
my $ext = $scale_suffix ? ".$scale_suffix-crf$crf.mp4" : ".crf$crf.mp4";
$output_file =~ s/(\.\w{3,4})?\z/($1 eq ".mp4" ? "" : $1) . $ext/e;
my $audio_is_copy = 1;
$audio_is_copy = 0 if defined $args{audio_sample_rate};
push @ffmpeg_args, (
"-c:v", "libx264",
"-crf", $crf,
"-preset", ($args{preset} // 'veryslow'),
(defined $args{frame_rate} ? ("-r", $args{frame_rate}) : ()),
"-c:a", ($audio_is_copy ? "copy" : "aac"),
(defined $args{audio_sample_rate} ? ("-ar", $args{audio_sample_rate}) : ()),
$output_file,
);
if ($args{-dry_run}) {
log_info "[DRY-RUN] Running $ffmpeg_path with args %s ...", \@ffmpeg_args;
next;
}
IPC::System::Options::system(
{log=>1},
$ffmpeg_path, @ffmpeg_args,
);
if ($?) {
my ($exit_code, $signal, $core_dump) = ($? < 0 ? $? : $? >> 8, $? & 127, $? & 128);
log_error "ffmpeg for $file failed: exit_code=$exit_code, signal=$signal, core_dump=$core_dump";
}
}
[200];
}
$SPEC{split_video_by_duration} = {
v => 1.1,
summary => 'Split video by duration into parts',
description => <<'MARKDOWN',
This utility uses **ffmpeg** (particularly the `-t` and `-ss`) option to split a
longer video into shorter videos. For example, if you have `long.mp4` with
duration of 1h12m and you run it through this utility with `--every 15min` then
you will have 5 new video files: `long.1of5.mp4` (15min), `long.2of5.mp4`
(15min), `long.3of5.mp4` (15min), `long.4of5.mp4` (15min), and `long.5of5.mp4`
(12min).
Compared to using `ffmpeg` directly, this wrapper offers convenience of
calculating the times (`-ss`) option for you, handling multiple files,
automatically choosing output filename, and tab completion.
MARKDOWN
args => {
%argspec0_files,
# XXX start => {},
every => {
schema => ['any*', of=>['duration*', 'percent_str*']],
},
parts => {
schema => 'posint*',
},
%argspecopt_copy,
# XXX merge_if_last_part_is_shorter_than => {},
# XXX output_filename_pattern
overwrite => {
schema => 'bool*',
cmdline_aliases => {O=>{}},
},
},
args_rels => {
req_one => [qw/every parts/],
},
lib/App/FfmpegUtils.pm view on Meta::CPAN
This utility runs I<ffmpeg> to re-encode your video files using the libx264
codec. It is a wrapper to simplify invocation of ffmpeg. It selects the
appropriate ffmpeg options for you, allows you to specify multiple files, and
picks appropriate output filenames. It also sports a C<--dry-run> option to let
you see ffmpeg options to be used without actually running ffmpeg.
This utility is usually used to reduce the file size (and optionally video
width/height) of videos so they are smaller, while minimizing quality loss.
Smartphone-produced videos are often high bitrate (e.g. >10-20Mbit) and not yet
well compressed, so they make a good input for this utility. The default setting
is roughly similar to how Google Photos encodes videos (max 1080p).
The default settings are:
-v:c libx264
-preset veryslow (to get the best compression rate, but with the slowest encoding time)
-crf 28 (0-51, subjectively sane is 18-28, 18 ~ visually lossless, 28 ~ visually acceptable)
when a downsizing is requested (using the C<--downsize-to> option), this utility
first checks each input video if it is indeed larger than the requested final
size. If it is, then the C<-vf scale> option is added. This utility also
calculates a valid size for ffmpeg, since using C<-vf scale=-1:720> sometimes
results in failure due to odd number.
Audio streams are copied, not re-encoded.
Output filenames are:
ORIGINAL_NAME.crf28.mp4
or (if downsizing is done):
ORIGINAL_NAME.480p-crf28.mp4
This function is not exported.
This function supports dry-run operation.
Arguments ('*' denotes required arguments):
=over 4
=item * B<audio_sample_rate> => I<uint>
Set audio sample rate, in Hz.
=item * B<crf> => I<int>
(No description)
=item * B<ffmpeg_path> => I<filename>
(No description)
=item * B<files>* => I<array[filename]>
(No description)
=item * B<frame_rate> => I<ufloat>
Set frame rate, in fps.
=item * B<overwrite> => I<bool>
(No description)
=item * B<preset> => I<str> (default: "veryslow")
(No description)
=item * B<scale> => I<str> (default: "1080^>")
Scale video to specified size. See L<Math::Image::CalcResized> or
L<calc-image-resized-size> for more details on scale specification. Some
examples include:
The default is C<< 1080^E<gt> >> which means to shrink to 1080p if video size is larger
than 1080p.
To disable scaling, set C<--scale> to '' (empty string), or specify
C<--dont-scale> on the CLI.
=back
Special arguments:
=over 4
=item * B<-dry_run> => I<bool>
Pass -dry_run=E<gt>1 to enable simulation mode.
=back
Returns an enveloped result (an array).
First element ($status_code) is an integer containing HTTP-like status code
(200 means OK, 4xx caller error, 5xx function error). Second element
($reason) is a string containing error message, or something like "OK" if status is
200. Third element ($payload) is the actual result, but usually not present when enveloped result is an error response ($status_code is not 2xx). Fourth
element (%result_meta) is called result metadata and is optional, a hash
that contains extra information, much like how HTTP response headers provide additional metadata.
Return value: (any)
=head2 split_video_by_duration
Usage:
split_video_by_duration(%args) -> [$status_code, $reason, $payload, \%result_meta]
Split video by duration into parts.
This utility uses B<ffmpeg> (particularly the C<-t> and C<-ss>) option to split a
longer video into shorter videos. For example, if you have C<long.mp4> with
duration of 1h12m and you run it through this utility with C<--every 15min> then
you will have 5 new video files: C<long.1of5.mp4> (15min), C<long.2of5.mp4>
(15min), C<long.3of5.mp4> (15min), C<long.4of5.mp4> (15min), and C<long.5of5.mp4>
( run in 0.899 second using v1.01-cache-2.11-cpan-e1769b4cff6 )