| 1 | #!/bin/bash |
| 2 | # ============================================================================= |
| 3 | # ingestEpisode.sh |
| 4 | # ============================================================================= |
| 5 | # Descripció: |
| 6 | # Script unificat per al processament d'episodis de podcast. |
| 7 | # S'executa automàticament des de FlexGet un cop finalitzada la descàrrega. |
| 8 | # |
| 9 | # Flux d'execució: |
| 10 | # 1. Processa l'arxiu d'àudio (metadades + portada via ffmpeg) |
| 11 | # 2. Regenera el feed RSS del podcast corresponent |
| 12 | # 3. Envia una notificació NTFY amb el resum de l'operació |
| 13 | # |
| 14 | # Ús: |
| 15 | # ./ingestEpisode.sh "YYYY-MM-DD" "Títol" "Autor" "/ruta/base/podcast" |
| 16 | # |
| 17 | # Paràmetres: |
| 18 | # $1 rss_pubdate Data de publicació de l'episodi (format: YYYY-MM-DD) |
| 19 | # $2 titol Títol de l'episodi (també s'usa com a nom d'arxiu) |
| 20 | # $3 autor Autor / nom del podcast |
| 21 | # $4 path Ruta absoluta al directori base del podcast |
| 22 | # (ex: /data/podcast/NomDelPodcast) |
| 23 | # |
| 24 | # Dependències: |
| 25 | # - ffmpeg Processament d'àudio i incrustació de metadades |
| 26 | # - curl Enviament de notificacions a NTFY |
| 27 | # - find / sort Generació del feed RSS |
| 28 | # |
| 29 | # Arxius esperats al directori base ($path): |
| 30 | # - YYYYMMDD.mp3 o YYYYMMDD.m4a Arxiu d'àudio descarregat per FlexGet |
| 31 | # - cover.jpg Portada del podcast (opcional) |
| 32 | # - .ntfyToken Token d'autenticació NTFY (a /config/) |
| 33 | # |
| 34 | # Sortida: |
| 35 | # - $path/$titol/$titol.mp3|m4a Arxiu processat amb metadades i portada |
| 36 | # - $path/$titol/cover.jpg Còpia de la portada (si existeix) |
| 37 | # - /data/podcast/$DIR_NAME.xml Feed RSS regenerat |
| 38 | # |
| 39 | # Configuració: |
| 40 | # Les constants de la secció "Config RSS & NTFY" s'han d'adaptar |
| 41 | # si canvia el servidor, el domini o l'estructura de directoris. |
| 42 | # |
| 43 | # ============================================================================= |
| 44 | |
| 45 | set -e |
| 46 | |
| 47 | rss_pubdate="$1" |
| 48 | titol="$2" |
| 49 | autor="$3" |
| 50 | path="$4" |
| 51 | |
| 52 | # --- Config RSS & NTFY ----------------------------------------------------------- |
| 53 | # BASE_DIR: directori arrel on resideixen tots els podcasts |
| 54 | # OUTPUT_DIR: on es desen els feeds XML generats (normalment el mateix que BASE_DIR) |
| 55 | # LINK_BASE: URL pública base des d'on s'accedeix als arxius |
| 56 | # NTFY_SERVER: URL interna del contenidor NTFY |
| 57 | # NTFY_TOPIC: tema NTFY on s'envien les notificacions |
| 58 | BASE_DIR="/data/podcast" |
| 59 | OUTPUT_DIR="/data/podcast" |
| 60 | LINK_BASE="https://podcast.tuServidor.org" |
| 61 | NTFY_SERVER="http://ntfy:80" |
| 62 | NTFY_TOPIC="podcast-ingest" |
| 63 | NTFY_TOKEN="$(cat /config/.ntfyToken | tr -d '\r\n')" |
| 64 | |
| 65 | [ -z "$NTFY_TOKEN" ] && { echo "Token NTFY vacio"; exit 1; } |
| 66 | |
| 67 | # --- Fechas ---------------------------------------------------------------------- |
| 68 | # pubdateYmd: format compacte per localitzar l'arxiu descarregat (ex: 20250728) |
| 69 | # pubdateY: any per a la metadada 'date' de l'arxiu d'àudio (ex: 2025) |
| 70 | pubdateYmd=$(date -d "$rss_pubdate" +"%Y%m%d") |
| 71 | pubdateY=$(date -d "$rss_pubdate" +"%Y") |
| 72 | |
| 73 | cover="$path/cover.jpg" |
| 74 | destFolder="$path/$titol" |
| 75 | |
| 76 | mkdir -p "$destFolder" |
| 77 | |
| 78 | # --- Procesar audio -------------------------------------------------------------- |
| 79 | # Incrusta metadades ID3 i portada a l'arxiu d'àudio mitjançant ffmpeg. |
| 80 | # Si existeix cover.jpg al directori base, s'afegeix com a attached_pic. |
| 81 | # L'arxiu original es elimina un cop processat correctament. |
| 82 | processFile () { |
| 83 | local input="$1" |
| 84 | local ext="$2" |
| 85 | local output="$destFolder/$titol.$ext" |
| 86 | |
| 87 | if [[ -f "$cover" ]]; then |
| 88 | ffmpeg -y -loglevel quiet -i "$input" \ |
| 89 | -i "$cover" \ |
| 90 | -map 0:a -map 1:v \ |
| 91 | -map_metadata -1 \ |
| 92 | -c copy \ |
| 93 | -metadata title="$titol" \ |
| 94 | -metadata artist="$autor" \ |
| 95 | -metadata album="$titol" \ |
| 96 | -metadata genre="Podcast" \ |
| 97 | -metadata track="1" \ |
| 98 | -metadata date="$pubdateY" \ |
| 99 | -disposition:v:0 attached_pic \ |
| 100 | "$output" |
| 101 | else |
| 102 | ffmpeg -y -loglevel quiet -i "$input" \ |
| 103 | -map 0:a \ |
| 104 | -c copy \ |
| 105 | -metadata title="$titol" \ |
| 106 | -metadata artist="$autor" \ |
| 107 | -metadata album="$titol" \ |
| 108 | -metadata genre="Podcast" \ |
| 109 | -metadata track="1" \ |
| 110 | -metadata date="$pubdateY" \ |
| 111 | "$output" |
| 112 | fi |
| 113 | |
| 114 | rm -f "$input" |
| 115 | } |
| 116 | |
| 117 | # Cerca l'arxiu descarregat pel nom de data. Si no existeix cap dels dos formats |
| 118 | # suportats (mp3, m4a), el script finalitza sense error ni notificació. |
| 119 | if [[ -f "$path/${pubdateYmd}.mp3" ]]; then |
| 120 | processFile "$path/${pubdateYmd}.mp3" "mp3" |
| 121 | elif [[ -f "$path/${pubdateYmd}.m4a" ]]; then |
| 122 | processFile "$path/${pubdateYmd}.m4a" "m4a" |
| 123 | else |
| 124 | exit 0 |
| 125 | fi |
| 126 | |
| 127 | # Copia la portada al directori de l'episodi per si el client RSS la necessita |
| 128 | [[ -f "$cover" ]] && cp "$cover" "$destFolder/" |
| 129 | |
| 130 | # --- Generar feed RSS del podcast descargado ------------------------------------ |
| 131 | # Regenera únicament el feed del podcast que acaba de rebre un episodi nou. |
| 132 | # Recorre tots els arxius mp3/m4a del directori i els ordena per data (més recent primer). |
| 133 | # La URL de cada arxiu s'escapa per reemplaçar espais per %20. |
| 134 | DIR_NAME=$(basename "$path") |
| 135 | OUTPUT_FILE="$OUTPUT_DIR/${DIR_NAME}.xml" |
| 136 | TITLE="Podcast: ${DIR_NAME}" |
| 137 | LINK="${LINK_BASE}/${DIR_NAME}.xml" |
| 138 | IMAGE_URL="${LINK_BASE}/${DIR_NAME}/cover.jpg" |
| 139 | NOW=$(date -R) |
| 140 | |
| 141 | { |
| 142 | echo "<?xml version='1.0' encoding='UTF-8'?>" |
| 143 | echo "<rss version='2.0' xmlns:itunes='http://www.itunes.com/dtds/podcast-1.0.dtd'>" |
| 144 | echo "<channel>" |
| 145 | echo " <title>$TITLE</title>" |
| 146 | echo " <link>$LINK</link>" |
| 147 | echo " <lastBuildDate>$NOW</lastBuildDate>" |
| 148 | echo " <description>Podcast de ${DIR_NAME} generat automaticament</description>" |
| 149 | echo " <image>" |
| 150 | echo " <url>$IMAGE_URL</url>" |
| 151 | echo " <title>$TITLE</title>" |
| 152 | echo " <link>$LINK</link>" |
| 153 | echo " </image>" |
| 154 | echo " <itunes:image href='$IMAGE_URL' />" |
| 155 | |
| 156 | find "$path" -type f \( -name "*.mp3" -o -name "*.m4a" \) | sort -r | while read -r audio; do |
| 157 | FILENAME=$(basename "$audio") |
| 158 | RELATIVE_PATH=${audio#$BASE_DIR/} |
| 159 | URL_ENCODED=$(echo "${LINK_BASE}/${RELATIVE_PATH}" | sed 's/ /%20/g; s|\([^:]\)//|\1/|g') |
| 160 | PUBDATE=$(date -R -r "$audio") |
| 161 | echo " <item>" |
| 162 | echo " <title>${FILENAME%.*}</title>" |
| 163 | echo " <itunes:author>vctrsnts</itunes:author>" |
| 164 | echo " <link>$URL_ENCODED</link>" |
| 165 | echo " <guid isPermaLink=\"false\">${FILENAME}</guid>" |
| 166 | echo " <enclosure url=\"$URL_ENCODED\" type=\"audio/mpeg\" />" |
| 167 | echo " <pubDate>$PUBDATE</pubDate>" |
| 168 | echo " <description>${FILENAME%.*}</description>" |
| 169 | echo " </item>" |
| 170 | done |
| 171 | |
| 172 | echo "</channel>" |
| 173 | echo "</rss>" |
| 174 | } > "$OUTPUT_FILE" |
| 175 | |
| 176 | chown root:root "$OUTPUT_FILE" |
| 177 | |
| 178 | # --- Notificación única ---------------------------------------------------------- |
| 179 | # Envia un unic missatge NTFY amb el resum complet de l'operacio: |
| 180 | # episodi processat + feed regenerat. |
| 181 | # S'usa text/plain per evitar problemes de format amb el body de curl. |
| 182 | #NTFY_BODY="<200b>Nou episodi: |
| 183 | #- ${titol} (${rss_pubdate}) |
| 184 | # |
| 185 | #Feed actualitzat: |
| 186 | #- ${DIR_NAME}.xml" |
| 187 | # |
| 188 | NTFY_BODY=" |
| 189 | Nou episodi: |
| 190 | - ${titol} (${rss_pubdate}) |
| 191 | |
| 192 | Feed actualitzat: |
| 193 | - ${DIR_NAME}.xml" |
| 194 | |
| 195 | curl -s \ |
| 196 | -o /dev/null \ |
| 197 | 2>/dev/null \ |
| 198 | -H "Authorization: Bearer ${NTFY_TOKEN}" \ |
| 199 | -H "Title: Podcast Sync" \ |
| 200 | -H "Tags: headphones" \ |
| 201 | -H "Priority: default" \ |
| 202 | -H "Content-Type: text/plain" \ |
| 203 | -d "${NTFY_BODY}" \ |
| 204 | "${NTFY_SERVER}/${NTFY_TOPIC}" |
vsantos / ingestEpisode
Last active 1 day ago
Script para la gestión de ficheros de mp3 y m4a
Revision 0acdd70e4cef31aec66c24539539d452cb007e7a