vsantos

vsantos / ingestEpisode

Last active 1 day ago

Like 0

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

vsantos revised this gist 1 day ago · 0acdd70

1 file changed, 204 insertions

ingestEpisode.sh (file created)
@@ -0,0 +1,204 @@
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}"