De Youtube a Podcast

De Youtube a Podcast

Si no tienes tiempo para ver los vídeos de un canal de Youtube pero te interesa el audio de los mismos para escucharlo en tu reproductor de Podcast, gracias a yt-dlp puedes hacerlo de una forma sencilla.

Hace un tiempo escribí un post sobre el método que utilizo para pasar los vídeos de Twitch en formato audio a un servidor webdav alojado en mi Raspberry y que coge el contenido de una nube pública mediante Rclone al mismo tiempo que publica un feed para que mi reproductor de podcast sea capaz de coger de este servidor los audios. Ahora hago lo mismo pero a partir de un vídeo de Youtube.

Para descargar los vídeos de Twitch uso el servicio twitch-dl, para hacer lo propio con Youtube utilizo yt-dlp.

Comprobar los últimos vídeos del canal

mapfile -t videos < <( yt-dlp --dateafter now-5day --get-filename -o "%(id)s/%(duration)s" $CANAL_YT )

Con esta línea se crea una matriz “vídeos” en la que, gracias a yt-dlp se introducen la identificación y duración de los vídeos que se han publicado en un canal de Youtube en los últimos cinco días. Utilizo la duración del mismo para no descargar los vídeos de menos de diez minutos.

Descargar el audio de un vídeo

Conocida la identificación del vídeo que queremos descargar (id), para descargar el audio del mismo ejecutaremos los siguientes comandos.

url="https://www.youtube.com/watch?v=$id"
yt-dlp -o "%(id)s.%(ext)s" --extract-audio --audio-format mp3 $url

Añadir los tag al audio

Al descargar el audio de Youtube no se le asigna automáticamente la información id3v2 correspondiente por lo que hay que hacerlo manualmente para que posteriormente en el reproductor de Podcast tanto el “Artista” como el “Podcast” aparezca correctamente identificado. Para esto obtengo el título del vídeo de Youtube gracias a la función de yt-dlp “–get-title” e incrusto esta información en el “track” descargado a través del comando “id3v2”. Todo gracias a dos sencillas líneas de código.

titulo=$(yt-dlp --get-title "https://www.youtube.com/watch?v=$nombre")
id3v2 -t "$titulo" -a "$artista" -A "$album" $track

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

Todo el código del script lo pueden encontrar en mi repositorio de scripts GitHub

#!/bin/bash

###################################################################
#Script Name: youtube2podcast.sh
#Description: Creación de un podcast a partir de un canal de youtube
#Args: N/A
#Creation/Update: 20220605/20230511
#Author: www.sherblog.pro                                             
#Email: sherlockes@gmail.com                               
###################################################################

################################
####       Variables        ####
################################

PATH="/home/pi/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"

CANAL="jordillatzer"
CANAL_NOMBRE="Jordi Llatzer"
TITULO="Jordi Llatzer en Twitch"
SERVIDOR="http://192.168.10.202:5005"
CANAL_YT="https://www.youtube.com/@jordillatzer/videos"

twitch_dir=~/twitch
DESCARGADOS="$twitch_dir/$CANAL/descargados_yt.txt"

notificacion=~/SherloScripts/bash/telegram.sh
inicio=$( date +%s )

mensaje=$'Actualizar Youtube via <a href="https://raw.githubusercontent.com/sherlockes/SherloScripts/master/bash/youtube2podcast.sh">youtube2podcast.sh</a>\n'
mensaje+=$'- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n'


echo "######################################"
echo "## Youtube to Podcast by Sherlockes ##"
echo "######################################"

################################
####      Dependencias      ####
################################

dependencias(){
    # yt-dlp
    if command -v yt-dlp >/dev/null 2>&1 ; then
	echo "Versión de yt-dlp: $(yt-dlp --version)"
	sudo yt-dlp -U
    else
	echo "ATENCION: yt-dlp no está disponible"
	sudo wget https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp -O /usr/local/bin/yt-dlp
	sudo chmod a+rx /usr/local/bin/yt-dlp
    fi

    # id3v2
    if command -v id3v2 >/dev/null 2>&1 ; then
	echo "Versión de id3v2: $(id3v2 --version | head -n 1)"
    else
	echo "ATENCION: id3v2 no está disponible"
    fi

    # ffmpeg
    if command -v ffmpeg >/dev/null 2>&1 ; then
	echo "Versión de ffmpeg: $(ffmpeg -version | grep 'ffmpeg version' | sed 's/ffmpeg version \([-0-9.]*\).*/\1/')"
    else
	echo "ATENCION: ffmpeg no está disponible"
    fi
}


################################
####       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_yt(){
    # Valores de argumentos
    local canal=${1:?Falta el nombre del canal}
    local videos
    local id
    local duracion

    # Obtiene el json de los ultimos vídeos.
    mensaje+=$'Obteniendo últimos vídeos . . . . . . . . . . . . .'
    echo "- Buscando últimos vídeos de $CANAL_NOMBRE"

    mapfile -t videos < <( yt-dlp --flat-playlist --print "%(id)s/%(duration)s" --playlist-end 10 $CANAL_YT )

    comprobar $?


    for video in ${videos[@]}
    do
	id=$( echo "$video" |cut -d\/ -f1 )
	duracion=$( echo "$video" |cut -d\/ -f2 )
	duracion=${duracion%??}

	# Comprueba si el archivo es de más de 10'
	if (( $duracion > 600 )) && ! grep -q $id "$DESCARGADOS"; then
	    # Descargando el episodio
	    descargar_video_yt $id
	    comprobar $?
	else
	    echo "- El episodio $id es corto o ya descargado"
	fi
	
    done
    
}

descargar_video_yt(){
    local id=${1:?Falta el id del video}
    local url="https://www.youtube.com/watch?v=$id"
    
    echo "- Descargando el vídeo $id"
    mensaje+=$"Descargando vídeo $id . . . . . . "
    yt-dlp -o "%(id)s.%(ext)s" --extract-audio --audio-format mp3 $url

    if [ $? -eq 0 ]; then
	# Añadiendo el episodio descargado a la lista
	echo -e "- Añadiendo a la lista de episodios descargados"
	echo $id | cat - $DESCARGADOS > temp && mv temp $DESCARGADOS
    fi
}

tag_y_mover(){
    cd $twitch_dir

    echo -e "- Taggeando y moviendo los archivos"

    # Lista todos los mp3 de la carpeta
    for track in *.mp3 ; do
	local nombre="${track%.*}"
	local titulo=$(yt-dlp --get-title "https://www.youtube.com/watch?v=$nombre")

	# Poniendo título al audio
	id3v2 -t "$titulo" -a "$CANAL_NOMBRE" -A "Youtube2Podcast" $track

	# Añadir el item al listado del feed
	anadir_item $track "youtube" "$CANAL"

	# Mover a la carpeta mp3
	mv $track $CANAL/mp3
    done
}

#----------------------------------------------------------#
#                   Añadir un nuevo item                   #
#----------------------------------------------------------#
anadir_item(){
    echo "- Añadiendo elemento a la lista..."
    local file=${1:?Falta el nombre del archivo}
    local servicio=${2:?Falta el servicio de descarga}
    local canal=${3:?Falta el canal de descarga}
    
    local ID_EP="${track%.*}"
    
    local TIT_EP=$(ffprobe -loglevel error -show_entries format_tags=title -of default=noprint_wrappers=1:nokey=1 $file)
    local ART_EP=$(ffprobe -loglevel error -show_entries format_tags=artist -of default=noprint_wrappers=1:nokey=1 $file)

    local URL_VID
    local FEC_EP

    if [ "$servicio" = "youtube" ]; then
	# Personalización para Youtube
	URL_VID="https://www.youtube.com/watch?v=$ID_EP"
	FEC_EP=$(yt-dlp --print "%(upload_date)s" $URL_VID)
	FEC_EP=$(date -d $FEC_EP +"%Y-%m-%dT%H:%M:%S%:z")
	FEC_EP=$(date --date "$FEC_EP+14 hours" "+%a, %d %b %Y %T %Z")
    else
	# Personalización para Twitch
	URL_VID="https://www.twitch.tv/videos/$ID_EP"
    fi

    local LEN_EP=$(ffprobe -i $file -show_format -v quiet | sed -n 's/duration=//p')
    LEN_EP=$(echo ${LEN_EP%.*})

    # 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>$URL_EP</link>
      <description>$TIT_EP</description>
      <pubDate>$FEC_EP</pubDate>
      <author>$ART_EP</author>
      <content:encoded><![CDATA[<p>Episodio descargado de $servicio.</p>]]></content:encoded>
      <enclosure length="$LEN_EP" type="audio/mpeg" url="$SERVIDOR/twitch/$canal/mp3/$ID_EP.mp3"/>
    </item>
END_ITEM

    # 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
}


#----------------------------------------------------------#
#                    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}

    cd $twitch_dir

    # 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"

    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

    # 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}
    
    # Subiendo archivos a la nube via rclone
    echo "- Subiendo los mp3's al servidor remoto"
    rclone copy $canal Sherlockes78_UN3_en:twitch/$canal/ --create-empty-src-dirs

    # 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
    rclone delete Sherlockes78_UN3_en:twitch/$canal/mp3 --min-age 30d
}


################################
####    Script principal    ####
################################

cd $twitch_dir
echo "- Corriendo en $twitch_dir"

# Comprobar dependencias
mensaje+=$'Comprobando dependencias. . . . . . . . . . . '
dependencias
comprobar $?

# Buscar nuevos videos y convertirlos a mp3
buscar_ultimos_yt "$CANAL"

# Añadir info si hay nuevos vídeos y moverlos a la carpeta mp3
if ls ./*.mp3 1> /dev/null 2>&1; then
    mensaje+=$'Añadiendo info del nuevo vídeo . . . . . . . . '
    tag_y_mover
    comprobar $?
fi

# Actualizar el feed si hay nuevos vídeos
if ls ./$CANAL/mp3/*.mp3 1> /dev/null 2>&1; then
    mensaje+=$'Actualizando el Feed . . . . . . . . . . . . . . . . '
    actualizar_feed "$SERVIDOR" "$CANAL" "$TITULO"
    comprobar $?
fi

# Subir si hay nuevo contenido al servidor
if ls ./$CANAL/mp3/*.mp3 1> /dev/null 2>&1; then
    mensaje+=$"Subiendo los mp3's al sevidor webdav . . ."
    subir_contenido "$CANAL"
    comprobar $?
fi

# 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"

Enlaces de interés