Inleiding
Afgelopen week vroeg iemand van de fanfare, waar ik al jarenlang muziek maak, om hulp. Hij had op internet een historische tv-opname gevonden van onze fanfare en wilde die graag downloaden en bewaren in het verenigingsarchief.
Mijn eerste ingeving was om yt-dlp te gebruiken; dit programma doet bij YouTube, Facebook, Vimeo en vele andere sites goede diensten voor het downloaden van audio en video's. Toen ik de URL echter invoerde, beklaagde het tool zich met de melding:
[generic] Extracting URL: https://...
[generic] 2101608120106259431: Downloading webpage
WARNING: [generic] Falling back on generic information extractor
[generic] 2101608120106259431: Extracting information
ERROR: Unsupported URL https://...
Onderzoek
Aldus ging ik op zoek naar een handmatig alternatief. In mijn browser (Mozilla
Firefox) werd de video zonder problemen afgespeeld, dus ergens in het
netwerkverkeer zou toch ook dat .mp4-bestand of videostream te vinden moete
zijn? Misschien met behulp van een M3U-playlist?
Ik drukte op Afspelen, na een paar seconden op Stop en filterde vervolgens
in het Netwerk-tab van de ontwikkeltools op m3u8. Er verschenen een aantal
resultaten, waarvan de eerste een HTTP 302 status had, dus die kon ik zonder meer
overslaan. Het tweede resultaat was op zich wel interessant, maar er ging bij
mij nog geen lichtje branden.
Het derde resultaat, genaamd SEG.m3u8 bevatte een complete lijst met alle
segmenten:
#EXTM3U
#EXT-X-VERSION:4
#EXT-X-TARGETDURATION:4
#EXT-X-MEDIA-SEQUENCE:1
#EXT-X-PLAYLIST-TYPE:VOD
#EXTINF:3,
SEG_00001.ts
#EXTINF:3,
SEG_00002.ts
...
#EXTINF:3,
SEG_00385.ts
#EXTINF:3,
SEG_00386.ts
#EXT-X-ENDLIST
Na deze playlist volgden tijdens het afspelen twee (oplopend genummerde) reeksen
van .ts- en .aac-requests. Dit herkende ik als Transport Stream en
Advanced Audio Codec. Dit waren de (stukjes) van de uiteindelijke video en
audio. Maar hoe kon ik die afzonderlijk downloaden en daarna samenvoegen?
cURL en ffmpeg
De ontwikkeltools van Firefox hebben een handige functie aan boord om een
specifieke request als geheel te kopiëren, zodat je het – bijvoorbeeld via
cURL – kunt uitvoeren of downloaden. Voor één enkele SEG_xxxxx.ts-request
werkte dat netjes, maar voor 386 stuks zou dat een behoorlijke hoeveelheid
handdwerk betekenen…
Hier kwam Bash, mijn favoriete shell om de hoek kijken. Met behulp van seq
en een for-loop kwam ik tot de volgende constructie. Let op de dubbele
aanhalingstekens " rondom de variabele ${i}; hierdoor wordt de actuele
waarde van de variabele ingevoegd op die plek(ken) in het cURL-commando. Met
enkele aanhalingstekens (apostrophes, ') werkt dat niet.
# Loop through sequence, zero-pad to 5 digits
for i in $( seq -f '%05g' 1 386 ); do
# Request .ts video segment
curl "https://sk-video.cdn.beeldengeluid.nl/.../SEG_${i}.ts" \
-H 'User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:152.0) Gecko/20100101 Firefox/152.0' \
-H 'Accept: */*' \
-H 'Accept-Language: en,nl;q=0.9,de;q=0.8' \
-H 'Accept-Encoding: gzip, deflate, br, zstd' \
-H 'Referer: https://schatkamer.beeldengeluid.nl/' \
-H 'Origin: https://schatkamer.beeldengeluid.nl' \
-H 'Connection: keep-alive' \
-H 'Cookie: ...' \
-H 'Sec-Fetch-Dest: empty' \
-H 'Sec-Fetch-Mode: cors' \
-H 'Sec-Fetch-Site: same-site' \
-H 'Priority: u=4' \
--output "SEG_${i}.ts";
# Request .aac audio segment
curl "https://sk-video.cdn.beeldengeluid.nl/.../SEG_${i}.aac" \
-H 'User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:152.0) Gecko/20100101 Firefox/152.0' \
-H 'Accept: */*' \
-H 'Accept-Language: en,nl;q=0.9,de;q=0.8' \
-H 'Accept-Encoding: gzip, deflate, br, zstd' \
-H 'Referer: https://schatkamer.beeldengeluid.nl/' \
-H 'Origin: https://schatkamer.beeldengeluid.nl' \
-H 'Connection: keep-alive' \
-H 'Cookie: ...' \
-H 'Sec-Fetch-Dest: empty' \
-H 'Sec-Fetch-Mode: cors' \
-H 'Sec-Fetch-Site: same-site' \
-H 'Priority: u=4' \
-H 'TE: trailers' \
--output "SEG_${i}.aac";
# Combine video and audio into single mp4-file
ffmpeg -i "SEG_${i}.ts" -i "SEG_${i}.aac" -c:v copy -c:a copy "${i}.mp4";
done
Samenvoegen
Nu had ik een verzameling van 386 korte .mp4-bestanden. Met behulp van ffmpeg
kon ik die samenvoegen door gebruik te maken van de concat-filter en een
tijdelijk bestand met de verschillende segmenten. Met dank aan ffmpeg.run
voor de betreffende instructie.
# List all mp4-segments, prepare list for concatenation by ffmpeg
ls -1 *.mp4 | sed -e "s/^\(.*\)/file '\1'/p" > list.txt;
# Concatenate segments into single video file
ffmpeg -f concat -safe 0 -i list.txt -c copy result.mp4;
Epiloog
Het ontbreekt mij aan Python-ervaring om een degelijke bijdrage te kunnen
leveren aan ytl-dlp, zodat de site van Beeld & Geluid in de toekomst wel
automatisch kan worden ondersteund. Misschien zijn er andere ontwikkelaars die
in de broncode van THEOplayer kunnen duiken en de onsteuning willen
toevoegen. Wie weet!