-
Notifications
You must be signed in to change notification settings - Fork 0
/
loudnessfix.raku
158 lines (140 loc) · 5.07 KB
/
loudnessfix.raku
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# Copyright Mike Swierczek, 2019.
# Licensed under the Lesser GNU Public License version 2.1 or later.
# Please see the included LICENSE file for details.
### NOTE: This code seems to 'work' in the sense that it runs and completes,
### but the resulting file does not seem to have more consistent sound volume
### ranges than the input file.
use JSON::Fast;
constant $ffmpeg = 'ffmpeg';
sub USAGE() {
print Q:c:to/EOH/;
Usage:
option 1:
raku loudnessfix.raku
-- interactively prompts you for inputs
option 2:
raku loudnessfix.raku --src=foo --dest=bar
-- reencodes movie foo to bar with loudness normalization
applied to the first audio stream in it. Video
streams and subtitles, if any, are unchanged.
The operation uses temporary files, so if you're confident
in the results it is safe to use the same input and output file name.
EOH
}
multi sub MAIN {
my Str $src = prompt "Please enter the name of the source film: ";
my Str $dest = prompt "Please enter the name of the destination film: ";
say "In the future, you could have invoked this with:";
say "raku loudnessfix.raku --src=$src --dest=$dest";
run-process($src, $dest);
}
multi sub MAIN(Str :$src! where $src.chars > 0,
Str :$dest! where $dest.chars > 0) {
run-process($src, $dest);
}
sub run-process(Str $src, Str $dest) {
say "Checking prerequisites.";
try {
my $ffmpegcheck = run 'which', $ffmpeg, :out;
for $ffmpegcheck.out.lines -> $line {
say "Found $ffmpeg at '$line'.";
}
$ffmpegcheck.out.close();
CATCH {
when X::Proc::Unsuccessful {
die "$ffmpeg is required for this script, please install it and try again.";
}
}
}
if (!$src.IO.f) {
die "File '$src' does not exist, are you running this from the right location? Did you make a typo?";
}
if ($dest.IO.f) {
die "File '$dest' already exists. If you're sure you want to replace it, delete it manually before running this program.";
}
wrap-loudness-norm-encode($src.IO, $dest);
say "Finished.";
}
# taken from https://blog.matatu.org/tailgrep
sub spinner() {
<\ - | - / ->[$++ % 6]
}
sub wrap-loudness-norm-encode(IO::Path $infile, Str $outfile) {
my $start-time = DateTime.now;
my $encoding-json = run-first-pass($infile);
run-second-pass($infile, $outfile, $encoding-json);
my $finish-time = DateTime.now;
say " Finished, took " ~ ($finish-time - $start-time) ~ " s.";
}
#
# target_il = -24.0
# target_lra = +11.0
# target_tp = -2.0
# samplerate = '48k'
constant $target_il = -24.0;
constant $target_lra = 11.0;
constant $target_tp = -2.0;
constant $samplerate = "48k";
sub run-first-pass(IO::Path $infile -->Str) {
my $encode-cmd = Proc::Async.new('ffmpeg', '-i', $infile, '-af',
"loudnorm=I=$target_il\:LRA=$target_lra\:tp=$target_tp\:print_format=json", '-f', 'null', '-');
my Str $result;
react {
whenever $encode-cmd.stdout -> $my-out {
# print spinner() ~ "\r";
say $my-out;
}
whenever $encode-cmd.stderr -> $my-err {
# print spinner() ~ "\r";
$result ~= $my-err;
say "Capturing: $my-err.";
}
whenever signal(SIGINT) {
say "Received SIGINT, stopping.";
exit;
}
whenever $encode-cmd.start {
say " Done pass 1.";
done # gracefully jump from the react block
}
}
# need the last twelve lines
my Str @lines = split("\n", $result);
my Str $last-twelve = @lines[(@lines.end - 12)[email protected]].join("\n");
say "Returning: $last-twelve ";
$last-twelve;
}
sub run-second-pass(IO::Path $infile, Str $outfile, Str $encoding-json) {
say "Trying to parse: $encoding-json .";
my $parsed-json = from-json($encoding-json);
say "Parsed to $parsed-json.";
my $input_i = $parsed-json<input_i>;
my $input_lra = $parsed-json<input_lra>;
my $input_tp = $parsed-json<input_tp>;
my $input_thresh = $parsed-json<input_thresh>;
my $offset = $parsed-json<target_offset>;
say "Have $input_i $input_lra $input_tp $input_thresh .";
my $encode-cmd = Proc::Async.new('ffmpeg', '-i', $infile, '-c:v', 'copy', '-c:s', 'copy',
'-af',
"loudnorm=print_format=summary\:I=$target_il\:LRA=$target_lra\:tp=$target_tp\:" ~
"measured_I=$input_i\:measured_LRA=$input_lra\:measured_tp=$input_tp\:measured_thresh=$input_thresh\:offset=$offset",
'-ar', $samplerate, $outfile);
react {
whenever $encode-cmd.stdout -> $my-out {
# print spinner() ~ "\r";
say $my-out;
}
whenever $encode-cmd.stderr -> $my-err {
# print spinner() ~ "\r";
say $my-err;
}
whenever signal(SIGINT) {
say "Received SIGINT, stopping.";
exit;
}
whenever $encode-cmd.start {
say " Done pass 2.";
done # gracefully jump from the react block
}
}
}