Compare commits
3 Commits
master
...
setup-arek
Author | SHA1 | Date | |
---|---|---|---|
5e15153eef | |||
58dd1d5beb | |||
068ab054bb |
2
.gitignore
vendored
|
@ -2,3 +2,5 @@
|
||||||
*.mp4
|
*.mp4
|
||||||
out/*
|
out/*
|
||||||
!out/keepme
|
!out/keepme
|
||||||
|
*.wav
|
||||||
|
.DS_Store
|
1
add-glitch.sh
Executable file
|
@ -0,0 +1 @@
|
||||||
|
ffmpeg -hwaccel videotoolbox -i glitch.mp4 -i video-with-new-audio.mp4 -i glitch.mp4 -filter_complex "[0:v:0][0:a:0][1:v:0][1:a:0][2:v:0][2:a:0]concat=n=3:v=1:a=1[vv][aa]" -map "[vv]" -map "[aa]" -c:v libx264 -r 50 -c:a aac -strict experimental -b:a 320k output.mp4
|
9
demux.sh
|
@ -1,9 +0,0 @@
|
||||||
echo "generating the demuxers..."
|
|
||||||
|
|
||||||
# generuje ścieżki do złożenia przez ffmpega:
|
|
||||||
ts-node generate-demuxer.ts >out/demuxer.txt
|
|
||||||
|
|
||||||
# używa demuxer.txt żeby skleić końcowe video z dźwiękiem:
|
|
||||||
echo generowanie całości
|
|
||||||
ffmpeg -y -f concat -safe 0 -i out/demuxer.txt -r $framerate -video_track_timescale $timescale -tune stillimage -pix_fmt yuv420p out/video.mp4
|
|
||||||
# ^ daję safe 0 aby przyjmowało bezwzględne ścieżki
|
|
0
find-loudness.ts
Normal file → Executable file
1
fix-audio.sh
Executable file
|
@ -0,0 +1 @@
|
||||||
|
ffmpeg -hwaccel videotoolbox -i out/video.mp4 -i icdw-11-final.wav -c:v copy -c:a aac -b:a 320k -strict experimental video-with-new-audio.mp4
|
116
generate-demuxer.ts
Normal file → Executable file
|
@ -2,9 +2,7 @@ import findLoudness, { SwapPoint } from "./find-loudness";
|
||||||
|
|
||||||
const graph_density = 8000;
|
const graph_density = 8000;
|
||||||
|
|
||||||
const threshold_at_point = parseInt(
|
const threshold_at_point = 2;
|
||||||
process.env.demuxer_volume_threshold || "1"
|
|
||||||
);
|
|
||||||
|
|
||||||
const inertia_s = 0.3;
|
const inertia_s = 0.3;
|
||||||
const inertia_samples = inertia_s * graph_density;
|
const inertia_samples = inertia_s * graph_density;
|
||||||
|
@ -16,65 +14,75 @@ const minutes = (units: number) => Math.floor(s(units) / 60);
|
||||||
const hours = (units: number) => Math.floor(units / graph_density / 60 / 60);
|
const hours = (units: number) => Math.floor(units / graph_density / 60 / 60);
|
||||||
|
|
||||||
const formatTime = (units: number) =>
|
const formatTime = (units: number) =>
|
||||||
`${hours(units)}:${minutes(units)}:${Math.floor(s(units) % 60)}`;
|
`${hours(units)}:${minutes(units)}:${Math.floor(s(units) % 60)}`;
|
||||||
|
|
||||||
type Mode = { left: boolean; right: boolean };
|
type Mode = { left: boolean; right: boolean };
|
||||||
|
|
||||||
async function run() {
|
async function run() {
|
||||||
const [left_breaks, right_breaks] = await Promise.all([
|
const [left_breaks, right_breaks] = await Promise.all([
|
||||||
findLoudness("/tmp/leftraw", threshold_at_point, inertia_samples, "left"),
|
findLoudness(
|
||||||
findLoudness("/tmp/rightraw", threshold_at_point, inertia_samples, "right"),
|
"/tmp/leftraw",
|
||||||
]);
|
threshold_at_point,
|
||||||
|
inertia_samples,
|
||||||
|
"left"
|
||||||
|
),
|
||||||
|
findLoudness(
|
||||||
|
"/tmp/rightraw",
|
||||||
|
threshold_at_point,
|
||||||
|
inertia_samples,
|
||||||
|
"right"
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
|
||||||
const merged = [...left_breaks, ...right_breaks].sort((a, b) =>
|
const merged = [...left_breaks, ...right_breaks].sort((a, b) =>
|
||||||
a.position_start < b.position_start
|
a.position_start < b.position_start
|
||||||
? -1
|
? -1
|
||||||
: a.position_start > b.position_start
|
: a.position_start > b.position_start
|
||||||
? 1
|
? 1
|
||||||
: 0
|
: 0
|
||||||
);
|
);
|
||||||
|
|
||||||
// console.log("left breaks:", left_breaks);
|
// console.log("left breaks:", left_breaks);
|
||||||
// console.log(`right_breaks`, right_breaks);
|
// console.log(`right_breaks`, right_breaks);
|
||||||
// console.log(`merged`, merged);
|
// console.log(`merged`, merged);
|
||||||
|
|
||||||
function new_mode(m: Mode, s: SwapPoint): Mode {
|
function new_mode(m: Mode, s: SwapPoint): Mode {
|
||||||
return { ...m, [s.label]: s.loud };
|
return { ...m, [s.label]: s.loud };
|
||||||
}
|
}
|
||||||
|
|
||||||
function mode_to_string(mode: Mode) {
|
function mode_to_string(mode: Mode) {
|
||||||
if (mode.left && mode.right) {
|
if (mode.left && mode.right) {
|
||||||
return "both";
|
return "both";
|
||||||
}
|
}
|
||||||
for (const side of ["left", "right"]) {
|
for (const side of ["left", "right"]) {
|
||||||
if (mode[side as keyof Mode]) {
|
if (mode[side as keyof Mode]) {
|
||||||
return side;
|
return side;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return "none";
|
return "none";
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("file", `${process.cwd()}/pics/none.png`);
|
console.log("file", `${process.cwd()}/pics/none.png`);
|
||||||
let last_point = 0;
|
let last_point = 0;
|
||||||
let mode: Mode = { left: false, right: false };
|
let mode: Mode = { left: false, right: false };
|
||||||
let last_file;
|
let last_file;
|
||||||
let total = 0;
|
let total = 0;
|
||||||
for (let i = 2; i < merged.length; i++) {
|
for (let i = 2; i < merged.length; i++) {
|
||||||
const point = merged[i];
|
const point = merged[i];
|
||||||
mode = new_mode(mode, point);
|
mode = new_mode(mode, point);
|
||||||
const file = `${process.cwd()}/pics/${mode_to_string(mode)}.png`;
|
const file = `${process.cwd()}/pics/${mode_to_string(mode)}.png`;
|
||||||
const duration = (point.position_start - last_point) / graph_density;
|
const duration = (point.position_start - last_point) / graph_density;
|
||||||
console.log(
|
console.log(
|
||||||
"duration",
|
"duration",
|
||||||
(point.position_start - last_point) / graph_density
|
(point.position_start - last_point) / graph_density
|
||||||
);
|
);
|
||||||
console.log("file", file);
|
console.log("file", file);
|
||||||
last_point = point.position_start;
|
last_point = point.position_start;
|
||||||
last_file = file;
|
last_file = file;
|
||||||
total += duration * graph_density;
|
total += duration * graph_density;
|
||||||
}
|
}
|
||||||
console.log("duration", merged[merged.length - 1].duration / graph_density);
|
console.log("duration", merged[merged.length - 1].duration / graph_density);
|
||||||
console.log("file", last_file);
|
console.log("file", last_file);
|
||||||
console.error(total, formatTime(total));
|
console.error(total, formatTime(total));
|
||||||
}
|
}
|
||||||
run();
|
run();
|
||||||
|
|
49
generate.sh
|
@ -6,18 +6,43 @@
|
||||||
# W katalogu z tym skryptem musisz mieć katalog "pics", w którym są pliki "left.png", "right.png", "none.png" i "both.png"
|
# W katalogu z tym skryptem musisz mieć katalog "pics", w którym są pliki "left.png", "right.png", "none.png" i "both.png"
|
||||||
#
|
#
|
||||||
|
|
||||||
export input=~/Downloads/icdw5/icdw5-stereo.wav # tutaj dajemy ścieżkę do pliku mp3 z Arkiem w jednym kanale i Kubą w drugim
|
framerate=60
|
||||||
export input_mono=~/Downloads/icdw5/icdw5-stereo.wav # tutaj dajemy ścieżkę do pliku mp3 z Arkiem w jednym kanale i Kubą w drugim
|
timescale=25000
|
||||||
export intro=~/projects/midline/podcast-visualizer/out/intro.mp4 # glitch
|
|
||||||
export outro=~/projects/midline/podcast-visualizer/out/intro.mp4 # to samo na końcu, co na początku
|
aresample=8000 # to bez zmian
|
||||||
export final_output=~/Downloads/icdw5-viz.mp4
|
|
||||||
export framerate=25
|
# echo dzielimy mp3 na dwa osobne wav
|
||||||
export timescale=25000
|
# ffmpeg -i $input -map_channel 0.0.0 /tmp/left.wav -map_channel 0.0.1 /tmp/right.wav
|
||||||
export demuxer_volume_threshold=15 #od 0 do 128
|
|
||||||
|
|
||||||
|
ffmpeg -i ./left.wav -ac 1 -filter:a aresample=$aresample -map 0:a -c:a pcm_u8 -f data - > /tmp/leftraw &
|
||||||
|
ffmpeg -i ./right.wav -ac 1 -filter:a aresample=$aresample -map 0:a -c:a pcm_u8 -f data - > /tmp/rightraw &
|
||||||
|
|
||||||
|
# czekamy aż obydwa wątki się zakończą
|
||||||
|
wait;
|
||||||
|
|
||||||
|
echo "generating the demuxers...";
|
||||||
|
|
||||||
|
# generuje ścieżki do złożenia przez ffmpega:
|
||||||
|
|
||||||
|
ts-node generate-demuxer.ts > out/demuxer.txt
|
||||||
|
|
||||||
aresample=8000 # to bez zmian
|
|
||||||
mkdir -p out
|
mkdir -p out
|
||||||
|
|
||||||
source ./split.sh
|
# używa demuxer.txt żeby skleić końcowe video z dźwiękiem:
|
||||||
source ./demux.sh
|
echo generowanie całości
|
||||||
source ./merge.sh
|
# ffmpeg -y -f concat -safe 0 -i out/demuxer.txt -r $framerate -video_track_timescale $timescale -tune stillimage -fps_mode vfr -pix_fmt yuv420p out/video.mp4
|
||||||
|
|
||||||
|
# linux
|
||||||
|
# ffmpeg -y -f concat -safe 0 -hwaccel vaapi -hwaccel_output_format vaapi -vaapi_device /dev/dri/renderD128 -i out/demuxer.txt -r $framerate -video_track_timescale $timescale -tune stillimage -pix_fmt yuv420p out/video.mp4
|
||||||
|
|
||||||
|
# macos mod
|
||||||
|
ffmpeg -y -f concat -safe 0 -hwaccel videotoolbox -i out/demuxer.txt -r $framerate -video_track_timescale $timescale -tune stillimage -pix_fmt yuv420p out/video.mp4
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ^ daję safe 0 aby przyjmowało bezwzględne ścieżki
|
||||||
|
|
||||||
|
|
||||||
|
# echo łączenie video z dźwiękiem:
|
||||||
|
# ffmpeg -i out/video.mp4 -i $input -ac 1 -video_track_timescale $timescale -tune stillimage out/video-and-audio.mp4
|
||||||
|
|
44
merge.sh
|
@ -1,44 +0,0 @@
|
||||||
processed_intro_path=$PWD/out/intro-processed.mp4
|
|
||||||
processed_outro_path=$PWD/out/outro-processed.mp4
|
|
||||||
|
|
||||||
echo łączenie video z dźwiękiem:
|
|
||||||
ffmpeg -y -vaapi_device /dev/dri/renderD128 \
|
|
||||||
-i out/video.mp4 -i $input_mono \
|
|
||||||
-video_track_timescale $timescale \
|
|
||||||
-tune stillimage \
|
|
||||||
-c:a aac \
|
|
||||||
-ac 1 \
|
|
||||||
-strict experimental \
|
|
||||||
-vf 'format=nv12,hwupload' \
|
|
||||||
-c:v h264_vaapi \
|
|
||||||
out/video-and-audio.mp4
|
|
||||||
|
|
||||||
echo reencoding intro to enable fast concat...
|
|
||||||
|
|
||||||
function reencode_for_demux_compatibility() {
|
|
||||||
ffmpeg -y -i "$1" \
|
|
||||||
-pix_fmt yuv420p \
|
|
||||||
-r $framerate \
|
|
||||||
-video_track_timescale $timescale \
|
|
||||||
-ac 1 \
|
|
||||||
-b:a 320k \
|
|
||||||
"$2"
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
reencode_for_demux_compatibility "$intro" "$processed_intro_path"
|
|
||||||
|
|
||||||
if [ "$intro" = "$outro" ]; then
|
|
||||||
processed_outro_path="$processed_intro_path"
|
|
||||||
else
|
|
||||||
reencode_for_demux_compatibility "$outro" "$processed_outro_path"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "" >out/demuxer-branding.txt
|
|
||||||
echo "file $processed_intro_path" >>out/demuxer-branding.txt
|
|
||||||
echo "file video-and-audio.mp4" >>out/demuxer-branding.txt
|
|
||||||
echo "file $processed_outro_path" >>out/demuxer-branding.txt
|
|
||||||
|
|
||||||
cat out/demuxer-branding.txt
|
|
||||||
|
|
||||||
ffmpeg -y -f concat -safe 0 -i out/demuxer-branding.txt -c:v copy -b:a 320k "$final_output"
|
|
0
package-lock.json
generated
Normal file → Executable file
0
package.json
Normal file → Executable file
BIN
pics/both.png
Normal file → Executable file
Before Width: | Height: | Size: 329 KiB After Width: | Height: | Size: 322 KiB |
BIN
pics/left.png
Normal file → Executable file
Before Width: | Height: | Size: 311 KiB After Width: | Height: | Size: 313 KiB |
BIN
pics/none.png
Normal file → Executable file
Before Width: | Height: | Size: 287 KiB After Width: | Height: | Size: 301 KiB |
BIN
pics/right.png
Normal file → Executable file
Before Width: | Height: | Size: 306 KiB After Width: | Height: | Size: 313 KiB |
9
split.sh
|
@ -1,9 +0,0 @@
|
||||||
echo dzielimy mp3 na dwa osobne wav
|
|
||||||
ffmpeg -y -i $input -map_channel 0.0.0 /tmp/left.wav -map_channel 0.0.1 /tmp/right.wav
|
|
||||||
|
|
||||||
echo na dwóch wątkach generujemy surowe pliki
|
|
||||||
ffmpeg -y -i /tmp/left.wav -ac 1 -filter:a aresample=$aresample -map 0:a -c:a pcm_u8 -f data - >/tmp/leftraw &
|
|
||||||
ffmpeg -y -i /tmp/right.wav -ac 1 -filter:a aresample=$aresample -map 0:a -c:a pcm_u8 -f data - >/tmp/rightraw &
|
|
||||||
|
|
||||||
# czekamy aż obydwa wątki się zakończą
|
|
||||||
wait
|
|