vsantos

vsantos / ingestEpisode

Last active 1 day ago

Like 0

Script para la gestión de ficheros de mp3 y m4a

Revision 0acdd70e4cef31aec66c24539539d452cb007e7a

ingestEpisode.sh Raw
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
45set -e
46
47rss_pubdate="$1"
48titol="$2"
49autor="$3"
50path="$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
58BASE_DIR="/data/podcast"
59OUTPUT_DIR="/data/podcast"
60LINK_BASE="https://podcast.tuServidor.org"
61NTFY_SERVER="http://ntfy:80"
62NTFY_TOPIC="podcast-ingest"
63NTFY_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)
70pubdateYmd=$(date -d "$rss_pubdate" +"%Y%m%d")
71pubdateY=$(date -d "$rss_pubdate" +"%Y")
72
73cover="$path/cover.jpg"
74destFolder="$path/$titol"
75
76mkdir -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.
82processFile () {
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ó.
119if [[ -f "$path/${pubdateYmd}.mp3" ]]; then
120 processFile "$path/${pubdateYmd}.mp3" "mp3"
121elif [[ -f "$path/${pubdateYmd}.m4a" ]]; then
122 processFile "$path/${pubdateYmd}.m4a" "m4a"
123else
124 exit 0
125fi
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.
134DIR_NAME=$(basename "$path")
135OUTPUT_FILE="$OUTPUT_DIR/${DIR_NAME}.xml"
136TITLE="Podcast: ${DIR_NAME}"
137LINK="${LINK_BASE}/${DIR_NAME}.xml"
138IMAGE_URL="${LINK_BASE}/${DIR_NAME}/cover.jpg"
139NOW=$(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
176chown 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#
188NTFY_BODY="​
189Nou episodi:
190- ${titol} (${rss_pubdate})
191
192Feed actualitzat:
193- ${DIR_NAME}.xml"
194
195curl -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}"