123 lines
5.8 KiB
Bash
123 lines
5.8 KiB
Bash
|
|
#!/bin/sh
|
|||
|
|
# Замер расхождения звука и картинки в собранном ролике.
|
|||
|
|
#
|
|||
|
|
# Как меряется. Делается исходник с метками: ровно на каждой секунде в
|
|||
|
|
# кадре белая вспышка, а в звуке щелчок. Из него собирается монтаж, у
|
|||
|
|
# которого начала кусков заведомо не попадают на опорные кадры. В готовом
|
|||
|
|
# ролике ищутся времена вспышек и времена щелчков; если звук и картинка
|
|||
|
|
# сходятся, каждая вспышка совпадает со щелчком.
|
|||
|
|
#
|
|||
|
|
# Сравнение идёт с поправкой на сам способ измерения: щелчок в сжатом
|
|||
|
|
# звуке размазывается на десяток-другой миллисекунд, и эта поправка
|
|||
|
|
# берётся замером того же исходника.
|
|||
|
|
#
|
|||
|
|
# Запуск: tests/syncprobe.sh — весь прогон
|
|||
|
|
# tests/syncprobe.sh measure ФАЙЛ — только замер готового файла
|
|||
|
|
|
|||
|
|
set -e
|
|||
|
|
|
|||
|
|
HERE=$(cd "$(dirname "$0")" && pwd)
|
|||
|
|
ROOT=$(dirname "$HERE")
|
|||
|
|
WORK="$ROOT/test/sync"
|
|||
|
|
SRC="$WORK/метки.mp4"
|
|||
|
|
PROJ="$WORK/синхро.nlve"
|
|||
|
|
OUT="$WORK/синхро.mkv"
|
|||
|
|
|
|||
|
|
# Времена вспышек: кадры, у которых средняя яркость выше порога. Берётся
|
|||
|
|
# первый кадр каждой вспышки — она длится несколько кадров.
|
|||
|
|
flashes() {
|
|||
|
|
ffprobe -v error -f lavfi "movie=$1,signalstats" \
|
|||
|
|
-show_entries frame=pts_time:frame_tags=lavfi.signalstats.YAVG \
|
|||
|
|
-of csv=p=0 2>/dev/null |
|
|||
|
|
awk -F, '$2>150 { if ($1 - prev > 0.5 || NR == 1) print $1; prev = $1 }'
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Времена щелчков: по самим отсчётам, без оконного анализа. Оконный
|
|||
|
|
# уплывает (окно двигается кратно своему размеру), и на замере это
|
|||
|
|
# выглядит как накапливающийся рассинхрон, которого нет.
|
|||
|
|
clicks() {
|
|||
|
|
ffmpeg -v error -i "$1" -vn -ac 1 -ar 48000 -f s16le - |
|
|||
|
|
python3 "$HERE/clicktimes.py"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Сводит два списка времён и печатает расхождение по каждой метке.
|
|||
|
|
#
|
|||
|
|
# Пара ищется по близости, а не по порядку: при резе не на опорном кадре
|
|||
|
|
# в куске может оказаться лишняя вспышка без своего щелчка, и сравнение
|
|||
|
|
# «первый с первым» после такого куска поехало бы всё.
|
|||
|
|
compare() {
|
|||
|
|
awk '
|
|||
|
|
FNR == NR { v[++nv] = $1; next }
|
|||
|
|
{
|
|||
|
|
a = $1; best = 1e9;
|
|||
|
|
for (i = 1; i <= nv; i++) {
|
|||
|
|
d = a - v[i]; ad = (d < 0) ? -d : d;
|
|||
|
|
if (ad < best) { best = ad; bd = d }
|
|||
|
|
}
|
|||
|
|
{ n++ }
|
|||
|
|
{ # Щелчок, у которого вспышки рядом нет вообще, — это край куска:
|
|||
|
|
# метка попала в звук, а её кадр остался за резом. Такую пару
|
|||
|
|
# считать нельзя, иначе итог перевирает сам себя.
|
|||
|
|
if (best > 0.5) { skipped++; next }
|
|||
|
|
}
|
|||
|
|
printf " метка %2d: звук %8.3f ближайшая вспышка %+7.3f с\n", n, a, -bd;
|
|||
|
|
paired++;
|
|||
|
|
s += bd; if (best > worst) { worst = best; wi = n }
|
|||
|
|
}
|
|||
|
|
END {
|
|||
|
|
if (paired == 0) { print " пар не нашлось"; exit 1 }
|
|||
|
|
printf " ИТОГ: среднее расхождение %+.3f с, худшее %.3f с (метка %d)",
|
|||
|
|
s / paired, worst, wi
|
|||
|
|
if (skipped > 0) printf ", одиноких меток %d", skipped
|
|||
|
|
printf "\n"
|
|||
|
|
}' "$1" "$2"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
measure() {
|
|||
|
|
FILE="$1"
|
|||
|
|
flashes "$FILE" > "$WORK/вспышки.txt"
|
|||
|
|
clicks "$FILE" > "$WORK/щелчки.txt"
|
|||
|
|
echo " вспышек: $(wc -l < "$WORK/вспышки.txt"), щелчков: $(wc -l < "$WORK/щелчки.txt")"
|
|||
|
|
compare "$WORK/вспышки.txt" "$WORK/щелчки.txt"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if [ "$1" = "measure" ]; then
|
|||
|
|
mkdir -p "$WORK"
|
|||
|
|
measure "$2"
|
|||
|
|
exit 0
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
mkdir -p "$WORK"
|
|||
|
|
|
|||
|
|
if [ ! -f "$SRC" ]; then
|
|||
|
|
echo "=== Исходник с метками ==="
|
|||
|
|
# Опорные кадры через две секунды: тогда рез почти всегда приходится на
|
|||
|
|
# середину группы, как на настоящей съёмке с камеры.
|
|||
|
|
ffmpeg -y -v error \
|
|||
|
|
-f lavfi -i "color=c=black:s=640x360:r=60000/1001:d=60" \
|
|||
|
|
-f lavfi -i "aevalsrc=sin(1000*2*PI*t)*lt(mod(t\,1)\,0.05):s=48000:c=stereo:d=60" \
|
|||
|
|
-vf "drawbox=x=0:y=0:w=640:h=360:color=white:t=fill:enable='lt(mod(t\,1)\,0.02)'" \
|
|||
|
|
-c:v libx264 -preset veryfast -g 120 -keyint_min 120 -sc_threshold 0 \
|
|||
|
|
-pix_fmt yuv420p -c:a aac -b:a 128k "$SRC"
|
|||
|
|
echo "готово: $SRC"
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
echo "=== Поправка на способ измерения (сам исходник) ==="
|
|||
|
|
measure "$SRC"
|
|||
|
|
|
|||
|
|
echo "=== Монтаж с резами не на опорных кадрах ==="
|
|||
|
|
"$HERE/nlvetool" syncproj "$PROJ" "$SRC"
|
|||
|
|
|
|||
|
|
echo "=== Сборка ==="
|
|||
|
|
rm -f "$OUT"
|
|||
|
|
"$HERE/nlvetool" render "$PROJ" "$OUT" | tail -5
|
|||
|
|
|
|||
|
|
echo "=== Замер готового ролика ==="
|
|||
|
|
measure "$OUT"
|
|||
|
|
|
|||
|
|
echo "=== Длительности потоков готового ролика ==="
|
|||
|
|
ffprobe -v error -select_streams v:0 -count_packets \
|
|||
|
|
-show_entries stream=r_frame_rate,nb_read_packets -of csv=p=0 "$OUT" |
|
|||
|
|
awk -F, '{ split($1, r, "/"); printf " картинка: %s кадров = %.3f с\n", $2, $2 * r[2] / r[1] }'
|
|||
|
|
ffprobe -v error -show_entries format=duration -of csv=p=0 "$OUT" |
|
|||
|
|
awk -F, '{ printf " файл целиком: %.3f с\n", $2 }'
|