76 lines
3.9 KiB
Bash
Executable File
76 lines
3.9 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# diariz-yt — send a YouTube (or any yt-dlp-supported) link to Diariz for transcription + diarization.
|
|
#
|
|
# diariz-yt <url> [--title "Custom title"] [--keep]
|
|
#
|
|
# Diariz has no URL ingest of its own (files only), so this fetches the audio and uploads it as a file:
|
|
# 1. yt-dlp pulls the best audio-only stream, natively (no transcode): YouTube's Opus/WebM, else M4A. Both are
|
|
# formats Diariz accepts (its upload sniffs the bytes: wav/mp3/flac/ogg/webm, and m4a with UPLOAD_ALLOW_AAC).
|
|
# Anything else is converted to Ogg/Opus with ffmpeg.
|
|
# 2. ffprobe reads the duration (Diariz wants durationMs).
|
|
# 3. POST /api/recordings (multipart, source=Upload) with the vaulted personal API token `diariz/api-token`. The
|
|
# recording lands in Prime's personal room, and Diariz transcribes, diarizes and summarizes it on its own.
|
|
#
|
|
# Runs on nh3-dev: NH3's egress is residential, which YouTube does not bot-flag the way it flags colo IPs. Set
|
|
# DIARIZ_YT_PROXY (e.g. socks5h://100.64.0.1:1080, the nh3-scale residential proxy) to route yt-dlp through a proxy
|
|
# from anywhere else. Provenance is kept in the title: "<title> — <channel> [YouTube <id>]".
|
|
set -euo pipefail
|
|
|
|
BASE=${DIARIZ_URL:-https://diariz.nh3.phasefinal.com}
|
|
usage() { sed -n '2,4p' "$0" | sed 's/^# \{0,1\}//'; exit 2; }
|
|
url="" title="" keep=0
|
|
while [ $# -gt 0 ]; do
|
|
case $1 in
|
|
--title) title=${2:?--title needs a value}; shift 2 ;;
|
|
--keep) keep=1; shift ;;
|
|
-h|--help) usage ;;
|
|
-*) echo "unknown option: $1" >&2; usage ;;
|
|
*) [ -z "$url" ] || { echo "one URL at a time" >&2; exit 2; }; url=$1; shift ;;
|
|
esac
|
|
done
|
|
[ -n "$url" ] || usage
|
|
for t in yt-dlp ffprobe ffmpeg curl python3 secret; do command -v "$t" >/dev/null || { echo "missing tool: $t" >&2; exit 3; }; done
|
|
|
|
work=$(mktemp -d "${TMPDIR:-/tmp}/diariz-yt.XXXXXX")
|
|
[ "$keep" = 1 ] || trap 'rm -rf "$work"' EXIT
|
|
proxy=(); [ -n "${DIARIZ_YT_PROXY:-}" ] && proxy=(--proxy "$DIARIZ_YT_PROXY")
|
|
|
|
echo "fetching audio: $url" >&2
|
|
yt-dlp "${proxy[@]}" --no-playlist --no-progress -q \
|
|
-f 'bestaudio[ext=webm]/bestaudio[ext=m4a]/bestaudio' \
|
|
-o "$work/%(id)s.%(ext)s" --write-info-json "$url"
|
|
info=$(ls "$work"/*.info.json 2>/dev/null | head -1); [ -n "$info" ] || { echo "yt-dlp wrote no metadata" >&2; exit 4; }
|
|
audio=$(ls "$work"/* | grep -v '\.info\.json$' | head -1); [ -s "$audio" ] || { echo "yt-dlp produced no audio" >&2; exit 4; }
|
|
|
|
case ${audio##*.} in
|
|
webm|m4a|ogg|opus|mp3|wav|flac) ;;
|
|
*) echo "converting ${audio##*.} to Ogg/Opus" >&2
|
|
ffmpeg -hide_banner -loglevel error -i "$audio" -vn -c:a libopus -b:a 96k "$work/audio.ogg" && audio=$work/audio.ogg ;;
|
|
esac
|
|
# A bare .opus is Ogg inside; rename so the multipart filename matches what Diariz sniffs.
|
|
[ "${audio##*.}" = opus ] && { mv "$audio" "${audio%.opus}.ogg"; audio=${audio%.opus}.ogg; }
|
|
|
|
secs=$(ffprobe -v error -show_entries format=duration -of default=nw=1:nk=1 "$audio")
|
|
dur_ms=$(python3 -c "import sys; print(int(float(sys.argv[1]) * 1000))" "$secs")
|
|
if [ -z "$title" ]; then
|
|
title=$(python3 -c "
|
|
import json, sys
|
|
d = json.load(open(sys.argv[1]))
|
|
t = (d.get('title') or 'Untitled').strip(); ch = (d.get('channel') or d.get('uploader') or '').strip()
|
|
src = d.get('extractor_key') or 'web'; vid = d.get('id') or ''
|
|
print((t + (' — ' + ch if ch else '') + f' [{src} {vid}]')[:250])" "$info")
|
|
fi
|
|
bytes=$(stat -c %s "$audio")
|
|
echo "uploading \"$title\" ($(python3 -c "print(round($secs/60, 1))") min, $((bytes / 1024 / 1024)) MiB)" >&2
|
|
|
|
token=$(secret get diariz/api-token)
|
|
resp=$(curl -sS --fail-with-body -m 900 -X POST "$BASE/api/recordings" \
|
|
-H @<(printf 'Authorization: Bearer %s\n' "$token") \
|
|
-F "title=$title" -F "durationMs=$dur_ms" -F "source=Upload" \
|
|
-F "audio=@$audio;filename=$(basename "$audio")") || { echo "upload failed: $resp" >&2; exit 5; }
|
|
unset token
|
|
python3 -c "
|
|
import json, sys
|
|
r = json.loads(sys.argv[1]); print('queued recording', r.get('id'), 'status', r.get('status'))
|
|
print('open:', sys.argv[2])" "$resp" "$BASE"
|