65 lines
3.6 KiB
Bash
Executable File
65 lines
3.6 KiB
Bash
Executable File
#!/bin/bash
|
||
# Jibri finalize-скрипт (CRM Tvorymo, модуль «Дзвінки»).
|
||
# Jibri викликає його після завершення запису з каталогом сесії ($1), де лежить відео *.mp4.
|
||
# 1. Лише звук: моно, 16 кГц, низький бітрейт (mp3, якщо є libmp3lame, інакше opus).
|
||
# 2. Відео одразу видаляється.
|
||
# 3. Звук ріжеться на частини (CALLS_CHUNK_SECONDS) для Whisper (ліміт API 25 МБ).
|
||
# 4. meta.json: тривалості, частини, статистика якості; маркер ready.
|
||
# Далі файли забирає модуль «Дзвінки» (спільний каталог записів) і видаляє після успішної обробки.
|
||
set -u
|
||
DIR="${1:?каталог запису}"
|
||
LOG="$DIR/finalize.log"
|
||
exec >>"$LOG" 2>&1
|
||
echo "$(date -Is) finalize $DIR"
|
||
|
||
BITRATE="${CALLS_AUDIO_BITRATE:-32k}"
|
||
CHUNK="${CALLS_CHUNK_SECONDS:-1200}"
|
||
fail() { echo "$(date -Is) ПОМИЛКА: $*"; echo "$*" > "$DIR/failed"; exit 0; }
|
||
|
||
VIDEO="$(ls -1 "$DIR"/*.mp4 2>/dev/null | head -1)"
|
||
[ -n "$VIDEO" ] || fail "немає відеофайлу"
|
||
|
||
if ffmpeg -hide_banner -encoders 2>/dev/null | grep -q libmp3lame; then
|
||
EXT=mp3; MIME=audio/mpeg; CODEC=(-c:a libmp3lame -b:a "$BITRATE")
|
||
else
|
||
EXT=ogg; MIME=audio/ogg; CODEC=(-c:a libopus -application voip -b:a "$BITRATE")
|
||
fi
|
||
AUDIO="$DIR/audio.$EXT"
|
||
|
||
ffmpeg -nostdin -hide_banner -loglevel error -y -i "$VIDEO" -vn -ac 1 -ar 16000 "${CODEC[@]}" "$AUDIO" \
|
||
|| fail "ffmpeg не зміг витягти звук"
|
||
VIDEO_BYTES=$(stat -c %s "$VIDEO" 2>/dev/null || echo 0)
|
||
rm -f "$VIDEO" "$DIR"/*.mp4
|
||
echo "$(date -Is) відео видалено ($VIDEO_BYTES байт), звук: $AUDIO"
|
||
|
||
mkdir -p "$DIR/parts"
|
||
ffmpeg -nostdin -hide_banner -loglevel error -y -i "$AUDIO" -f segment -segment_time "$CHUNK" -reset_timestamps 1 -c copy \
|
||
"$DIR/parts/part-%03d.$EXT" || fail "ffmpeg не зміг розрізати звук"
|
||
|
||
dur() { ffprobe -v error -show_entries format=duration -of default=nw=1:nk=1 "$1" 2>/dev/null || echo 0; }
|
||
|
||
PARTS='[]'
|
||
for p in "$DIR"/parts/part-*."$EXT"; do
|
||
[ -f "$p" ] || continue
|
||
PARTS=$(jq -c --arg f "parts/$(basename "$p")" --argjson d "$(dur "$p")" --argjson s "$(stat -c %s "$p")" \
|
||
'. + [{file:$f, duration:$d, bytes:$s}]' <<<"$PARTS")
|
||
done
|
||
|
||
# Якість: тиша (поріг -50 dB, від 2 с), гучність.
|
||
STATS=$(ffmpeg -nostdin -hide_banner -i "$AUDIO" -af silencedetect=n=-50dB:d=2,volumedetect -f null - 2>&1)
|
||
SIL_COUNT=$(grep -c 'silence_end' <<<"$STATS")
|
||
SIL_TOTAL=$(grep -o 'silence_duration: [0-9.]*' <<<"$STATS" | awk '{s+=$2} END {printf "%.1f", s+0}')
|
||
MEAN_DB=$(grep -o 'mean_volume: [-0-9.]*' <<<"$STATS" | awk '{print $2}')
|
||
MAX_DB=$(grep -o 'max_volume: [-0-9.]*' <<<"$STATS" | awk '{print $2}')
|
||
|
||
jq -n --arg audio "audio.$EXT" --arg mime "$MIME" --argjson duration "$(dur "$AUDIO")" \
|
||
--argjson bytes "$(stat -c %s "$AUDIO")" --argjson videoBytes "$VIDEO_BYTES" --argjson parts "$PARTS" \
|
||
--argjson silences "${SIL_COUNT:-0}" --argjson silenceSeconds "${SIL_TOTAL:-0}" \
|
||
--arg meanDb "${MEAN_DB:-}" --arg maxDb "${MAX_DB:-}" --arg finishedAt "$(date -Is)" \
|
||
'{audio:$audio, mime:$mime, duration:$duration, bytes:$bytes, videoBytes:$videoBytes, parts:$parts,
|
||
quality:{silences:$silences, silenceSeconds:$silenceSeconds, meanDb:$meanDb, maxDb:$maxDb}, finishedAt:$finishedAt}' \
|
||
> "$DIR/meta.json.tmp" && mv "$DIR/meta.json.tmp" "$DIR/meta.json" || fail "не вдалося записати meta.json"
|
||
chmod -R a+rwX "$DIR" 2>/dev/null
|
||
touch "$DIR/ready"
|
||
echo "$(date -Is) готово: $(cat "$DIR/meta.json")"
|