Crear Podcast a partir de un canal de Twitch

No tengo tiempo para ver los canales de Twitch que me gustaría seguir en el momento de la emisión y tras unos días probando una aplicación Android para descargar los vídeos me he decidido a crear un podcast privado que se genere de forma automática a partir del contenido de un canal en Twitch.
Buscar nuevos vídeos publicados
Una vez más, gracias a Angel he descubierto twitch-dl, una aplicación en python con la que listar y descargar los vídeos de cualquier canal de Twitch. Para listarlos en formato “json” es tan sencillo como ejecutar el siguiente comando desde el directorio donde tengamos descargado “twitch-dl”.
python3 twitch-dl.pyz videos jordillatzer -j
Descargar los vídeos nuevos
Ya tenemos un precioso “json” del que extraer las “id” de los últimos vídeos publicados. En mi script he utilizado jq para extraer la id de los últimos vídeos y compruebo si ya lo he descargado anteriormente con las “id’s” que tengo guardadas en el archivo “descargados.txt”. Para descargar el audio del vídeo es tan sencillo como lo siguiente.
twitch-dl.pyz download -q audio_only 221837124
Convertir los vídeos descargados a mp3
Pero el archivo que se descarga es muy grande para ser un audio y está en formato “mkv” por lo que lo proceso con FFMPEG y elimino los silencios pasándolo al mismo tiempo de mkv a mp3.
ffmpeg -loglevel 24 -i "$file" -af silenceremove=1:0:-50dB "${file%.mkv}.mp3"
Subir el audio generado a un servidor WebDav
Ya he escrito en un post como tengo implementado un servidor webdav gracias a Rclone usando como almacenamiento mis nubes públicas. Uso el mismo Rclone en este script para subir el contenido descargado y recodificado en local.
rclone copy $canal remoto:twitch/$canal/ --create-empty-src-dirs
Generar el feed con todos los archivos
Como plantilla para la generación del feed he utilizado esta de Matthew Dickens eliminando de la misma todo lo relativo a itunes ya que va a ser de uso privado. Guardo por un lado el encabezado del xml y por otro los archivos ya incluidos junto con el pie, así me resulta fácil poner como primer “item” del feed un nuevo audio que descargue. Hago uso de ffprobe para extraer los metadatos del mp3 que incrustar en el feed y el comando awk
para extraer la información del nombre del archivo.
Tras poner el feed en el servidor webdav donde he colocado los archivos de audio ya sólo resta añadirlo y actualizarlo en la aplicación de Podcast.
Script completo
A continuación dejo el código completo de script, si quieres descargarlo o copiarlo te recomiendo que pases por mi Repositorio de scripts y lo descargues ya que Hugo no lleva bien la importación de texto externo de momento.
#!/bin/bash
###################################################################
#Script Name: twitch2podcast.sh
#Description: Generación de Podcast a partir de canal de Twitch
#Args: N/A
#Creation/Update: 20220317/20220901
#Author: www.sherblog.pro
#Email: sherlockes@gmail.com
###################################################################
CANAL="jordillatzer"
TITULO="Jordi Llatzer en Twitch"
SERVIDOR="http://192.168.10.202:5005"
FECHA=$(date)
twitch_dir=~/twitch
twdl=$twitch_dir/twitch-dl.pyz
notificacion=~/SherloScripts/bash/telegram.sh
inicio=$( date +%s )
mensaje=$'Actualizar Twitch desde <a href="https://raw.githubusercontent.com/sherlockes/SherloScripts/master/bash/twitch2podcast.sh">twitch2podcast.sh</a>\n'
mensaje+=$'- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n'
###############
## Funciones ##
###############
#----------------------------------------------------------#
# Comprobar la salida #
#----------------------------------------------------------#
comprobar(){
if [ $1 -eq 0 ]; then
mensaje+=$'OK'
else
mensaje+=$'ERROR'
fi
mensaje+=$'\n'
}
#----------------------------------------------------------#
# Buscar los últimos vídeos de un canal no descargados #
#----------------------------------------------------------#
buscar_ultimos () {
# Valores de argumentos
local canal=${1:?Falta el nombre del canal}
local titulo=${2:?Falta el título del canal}
# Obtiene el json de los ultimos vídeos.
mensaje+=$'Obteniendo últimos vídeos. . . . . . . . . . . . . '
echo "- Obteniendo últimos vídeos de $titulo"
json=$(python3 $twdl videos $canal -j)
comprobar $?
# Limitar a 15 videos la lista de descargados
mensaje+=$'Recortando listas de descargados. . . . . . . '
echo "- Recortando listas de descargados"
head -n15 $twitch_dir/$canal/descargados.txt > tmp
mv tmp $twitch_dir/$canal/descargados.txt
# Limitar a 15 videos la lista de items
head -n150 $twitch_dir/$canal/items.xml > tmp
mv tmp $twitch_dir/$canal/items.xml
comprobar $?
# Busca sobre los ultimos diez videos
for i in {0..9}
do
# Si el vídeo a comenzado hace menos de 3 horas pasa al siguiente
publicado=$(echo "$json" | jq ".videos[$i].publishedAt" | cut -c2- | rev | cut -c2- | rev)
publicado=$(date -d "$publicado+3 hours" +%s)
if [[ $(date +%s) < $publicado ]]
then
mensaje+=$"El vídeo $id está en emisión."
mensaje+=$'\n'
continue
fi
# obtiene la identificación y minutos de duración del último vídeo
id=$(echo "$json" | jq ".videos[$i].id" | cut -c2- | rev | cut -c2- | rev)
mins=$(expr $(echo "$json" | jq ".videos[$i].lengthSeconds") / 60)
# Comprobar si el archivo ya ha sido descargado
if grep -q $id $twitch_dir/$canal/descargados.txt
then
echo $i
if [ $i -eq 0 ]
then
echo "- No hay nuevos vídeos.";
mensaje+=$"No hay nuevos vídeos."
mensaje+=$'\n'
# No sigue comprobando si ya ha visto uno descargado
break
fi
echo "- El vídeo $id ya ha sido descargado.";
mensaje+=$"El vídeo $id ya ha sido descargado."
mensaje+=$'\n'
# No sigue comprobando si ya ha visto uno descargado
break
else
echo "- Descargando el audio del vídeo $id.";
mensaje+=$"Descargando el audio de $id . . ."
if (( $mins > 10 ))
then
# Descarga el audio en formato mkv
$twdl download -q audio_only $id;
comprobar $?
else
echo "- El archivo sólo tiene $mins minutos, no se descarga."
mensaje+=$"El archivo sólo tiene $mins minutos, no se descarga."
mensaje+=$'\n'
fi
# Añade el archivo al principio de la lista de descargados
#echo $id >> $twitch_dir/$canal/descargados.txt;
echo $id | cat - $twitch_dir/$canal/descargados.txt > temp && mv temp $twitch_dir/$canal/descargados.txt
fi
done
}
#----------------------------------------------------------#
# Pasa a mp3 los vídeos descargados en la carpeta #
#----------------------------------------------------------#
convertir_mp3 () {
# comprueba si hay algún video, si no hay sale de la función
if [ ! -e ./*.mkv ]; then return; fi
local canal=${1:?Falta el nombre del canal}
echo "- Buscando archivos para convertir en $canal"
mensaje+=$"Buscando archivos en $canal . . . ."
mensaje+=$'\n'
for file in ./*.mkv; do
local nombre=$(basename $file .mkv)
local id_ep=$(echo $nombre | awk -F'_' '{print $2}')
echo "- Episodio $id_ep, codificando audio y eliminando silencios"
mensaje+=$"Recodificando audio de $id_ep . . . . "
ffmpeg -loglevel 24 -i "$file" -af silenceremove=1:0:-50dB "${file%.mkv}.mp3"
comprobar $?
echo "- Episodio $id_ep, moviendo mp3"
mensaje+=$"Moviendo mp3 $id_ep . . . . . . . . . . . ."
mv $nombre.mp3 $canal/mp3/$id_ep.mp3
comprobar $?
echo "- Episodio $id_ep, eliminando el video"
mensaje+=$"Eliminando vídeo $id_ep . . . . . . . . . ."
rm $file
comprobar $?
done
}
#----------------------------------------------------------#
# Actualizar el feed #
#----------------------------------------------------------#
# (Coge la info de
actualizar_feed () {
# Valores de argumentos
local servidor=${1:?Falta el servidor del feed}
local canal=${2:?Falta el nombre del canal}
local titulo=${3:?Falta el título del canal}
# Comprueba si hay algún mp3 en la carpeta del canal, si no hay sale de la función
if [ ! -e ./$canal/mp3/*.mp3 ]; then return; fi
# Encabezado del feed
echo "- Insertando el encabezado del feed"
mensaje+=$"Actualizando el Feed"
mensaje+=$'\n'
cat > $canal/feed.xml <<END_HEADER
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
xmlns:atom="http://www.w3.org/2005/Atom"
xmlns:content="http://purl.org/rss/1.0/modules/content/"
xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd">
<channel>
<atom:link href="$servidor/twitch/$canal/feed.xml" rel="self" type="application/rss+xml"/>
<title>$TITULO</title>
<description>Un podcast creado por Sherlockes a partir del canal de $TITULO</description>
<copyright>Copyright 2022 Sherlockes</copyright>
<language>es</language>
<pubDate>$FECHA</pubDate>
<lastBuildDate>$FECHA</lastBuildDate>
<image>
<link>https://sherblog.pro</link>
<title>$TITULO</title>
<url>$servidor/twitch/$canal/artwork.jpg</url>
</image>
<link>https://www.sherblog.pro</link>
END_HEADER
# Buscando mp3's locales para extraer info
echo "- Buscando nuevos episodios descargados"
find $canal -type f -name "*.mp3" | while read -r file; do
NOM_EP=$(basename $file)
ART_EP=$(ffprobe -loglevel error -show_entries format_tags=artist -of default=noprint_wrappers=1:nokey=1 $file)
TIT_EP=$(ffprobe -loglevel error -show_entries format_tags=title -of default=noprint_wrappers=1:nokey=1 $file)
ID_EP=$(echo $NOM_EP | awk -F'_' '{print $2}')
ID_EP=$(basename $file .mp3)
# Obteniendo info a partir de la ID
json=$(python3 $twdl info $ID_EP -j)
FEC_EP=$(echo "$json" | jq ".publishedAt" | cut -c2- | rev | cut -c2- | rev)
FEC_EP=$(date -d $FEC_EP +"%Y-%m-%dT%H:%M:%S%:z")
FEC_EP=$(date --date "$FEC_EP-2 hours" "+%a, %d %b %Y %T %Z")
lengthSeconds=$(echo "$json" | jq ".lengthSeconds")
echo "- Añadiendo episodio $ID_EP a la lista de episodios"
mensaje+=$"Añadiendo episodio $ID_EP a la lista"
mensaje+=$'\n'
# crea el "item.xml" con info del episodio
cat >> $canal/item.xml <<END_ITEM
<item>
<guid isPermaLink="true">$servidor/$canal/$ID_EP</guid>
<title>$TIT_EP</title>
<link>https://www.twitch.tv/videos/$ID_EP</link>
<description>$TIT_EP</description>
<pubDate>$FEC_EP</pubDate>
<author>$ART_EP</author>
<content:encoded><![CDATA[<p>Episodio descargado de Twitch.</p>]]></content:encoded>
<enclosure length="$lengthSeconds" type="audio/mpeg" url="$servidor/twitch/$canal/mp3/$NOM_EP"/>
</item>
END_ITEM
done
# Añade los "items.xml" ya descargado a continuación del "item.xml"
cat $canal/items.xml >> $canal/item.xml
mv $canal/item.xml $canal/items.xml
# Añadir lista de episodios al feed
echo "- Añadiendo lista de episodios al feed"
cat $canal/items.xml >> $canal/feed.xml
# Añadiendo el pie del feed
echo "- Añadiendo el pie del feed"
cat >> $canal/feed.xml <<END
</channel>
</rss>
END
}
#----------------------------------------------------------#
# Subir contenido actualizado #
#----------------------------------------------------------#
subir_contenido () {
# Valores de argumentos
local canal=${1:?Falta el nombre del canal}
# Comprueba si hay algún mp3 en la carpeta del canal, si no hay sale de la función
if [ ! -e ./$canal/mp3/*.mp3 ]; then return; fi
# Subiendo archivos a la nube via rclone
echo "- Subiendo los mp3's al sevidor remoto"
mensaje+=$"Subiendo los mp3's al sevidor webdav . . ."
rclone copy $canal Sherlockes78_UN_en:twitch/$canal/ --create-empty-src-dirs
comprobar $?
# Eliminando audio y video local
echo "- Eliminando audios locales"
find . -type f -name "*.mp3" -delete
# Borrando los archivos de la nube anteriores a 30 días
mensaje+=$"Borrando contenido antiguo . . . . . . . . . . . ."
rclone delete Sherlockes78_UN_en:twitch/$canal/mp3 --min-age 30d
comprobar $?
}
########################
## Programa principal ##
########################
echo "#####################################"
echo "## Twitch to Podcast by Sherlockes ##"
echo "#####################################"
cd $twitch_dir
echo "- Corriendo en $twitch_dir"
# Comprobar la instalación de twitch-dl en el directorio
#. ~/SherloScripts/bash/twitch-dl.sh && check
# Buscar nuevos videos y convertirlos a mp3
buscar_ultimos "$CANAL" "$TITULO"
# Convertir a mp3 los vídeos descargados
convertir_mp3 "$CANAL"
# Actualizar el feed con los nuevos vídeos
actualizar_feed "$SERVIDOR" "$CANAL" "$TITULO"
# Subir el nuevo contenido al servidor
subir_contenido "$CANAL"
# Envia el mensaje de telegram con el resultado
fin=$( date +%s )
let duracion=$fin-$inicio
mensaje+=$'- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n'
mensaje+=$"Duración del Script: $duracion segundos"
$notificacion "$mensaje"