Hello,
Les vacances étant propices aux réflexions, j'ai pondu un script shell (comptabile POSIX, donc pouvant tourner sur des shells de type Bourne et avec des outils disponibles de base sur les distributions GNU/Linux et les *BSD) permettant de récupérer la totalité des médias servis par un Jellyfin local (actuellement seules les catégories de film et de séries TV ; toute ma musithèque étant déjà en FLAC) en stockant les informations dans une base SQL.
Ceci afin de visualiser et identifier facilement les métadonnées dans le but d'harmoniser et/ou optimiser les fichiers d'après ces dernières (optimisation de la taille des fichiers, des débits, des formats, des définitions, etc.).
Jellyfin comporte une API RESTful afin de récupérer quantité de métadonnées stockées dans sa base SQLite, donc avec une fonction inhérente à MySQL/MariaDB (LOAD DATA Statement), l'extraction et l'import est extrêmement rapide : ~9 secondes pour ~5 200 fichiers
Pour automatiser tout ça, un cronjob tourne quotidiennement.
A la fin, une synthèse des outils utilisés par ce script.
Pour avoir une vue synthétique de la table
Les vacances étant propices aux réflexions, j'ai pondu un script shell (comptabile POSIX, donc pouvant tourner sur des shells de type Bourne et avec des outils disponibles de base sur les distributions GNU/Linux et les *BSD) permettant de récupérer la totalité des médias servis par un Jellyfin local (actuellement seules les catégories de film et de séries TV ; toute ma musithèque étant déjà en FLAC) en stockant les informations dans une base SQL.
Ceci afin de visualiser et identifier facilement les métadonnées dans le but d'harmoniser et/ou optimiser les fichiers d'après ces dernières (optimisation de la taille des fichiers, des débits, des formats, des définitions, etc.).
Jellyfin comporte une API RESTful afin de récupérer quantité de métadonnées stockées dans sa base SQLite, donc avec une fonction inhérente à MySQL/MariaDB (LOAD DATA Statement), l'extraction et l'import est extrêmement rapide : ~9 secondes pour ~5 200 fichiers
Pour automatiser tout ça, un cronjob tourne quotidiennement.
A la fin, une synthèse des outils utilisés par ce script.
Bash:
#!/bin/sh
# Before invoking this script,
#
# 1) From an admin account, create an API key on Jellyfin:
# Go to the dashboard of Jellyfin > API Keys
#
# 2) For ease and convenience when working with a local SQL server,
# configure MySQL to use --login-path:
# https://dev.mysql.com/doc/refman/8.4/en/mysql-config-editor.html
#
# NB. For MariaDB users, it is safer to use inplace of `mysql --login-path=local [...]`:
#
# mysql \
# -h localhost \
# -u SQL_USER \
# -p"SQL_USER_PASSWORD"
#
# ... or set up a ~/.my.cnf
#
# More information:
# https://mariadb.com/docs/server/security/user-account-management/authentication-from-mariadb-10-4
set -e
JELLYFIN="http://localhost:8096"
API_KEY="REDACTED"
MYSQL_DB="video_catalog"
SECURE_FILE_PRIV=$(mysql --login-path=local -Nse "SELECT @@secure_file_priv;")
TMPFILE="${SECURE_FILE_PRIV%/}/jellyfin_catalog.tsv"
trap 'rm -f "$TMPFILE"' EXIT INT TERM HUP
DATE=$(date "+%Y-%m-%d %H:%M:%S")
curl -fsS \
"$JELLYFIN/Items?Recursive=true&IncludeItemTypes=Movie,Episode&Fields=Path,MediaStreams,ParentIndexNumber,IndexNumber,SeriesName&api_key=$API_KEY" |
jq -r --arg DATE "$DATE" '
.Items[]
| (
[(.MediaStreams // [])[] | select(.Type=="Video")][0] // {}
) as $v
| (
[(.MediaStreams // [])[] | select(.Type=="Audio")][0] // {}
) as $a
| [
$DATE,
(.Id // ""),
(.Name // ""),
(.Path // ""),
(if .Type=="Movie" then "movie" else "episode" end),
(.SeriesName // ""),
(.ParentIndexNumber // 0),
(.IndexNumber // 0),
($v.Codec // ""),
($v.Width // 0),
($v.Height // 0),
($v.BitRate // 0),
($a.Codec // ""),
($a.Channels // 0),
($a.Language // "")
]
| join("|")
' |
while IFS="|" read \
scan_date \
jellyfin_id \
name \
path \
media_type \
series_name \
season \
episode \
codec_video \
width \
height \
bitrate_video \
audio_codec \
audio_channels \
audio_language
do
if stat -f "%z" / >/dev/null 2>&1; then
STAT_SIZE='stat -f %z'
else
STAT_SIZE='stat -c %s'
fi
if [ -f "$path" ]; then
size=$($STAT_SIZE "$path")
else
echo "Fichier absent : $path" >&2
size=0
fi
printf "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n" \
"$scan_date" \
"$jellyfin_id" \
"$name" \
"$path" \
"$media_type" \
"$series_name" \
"$season" \
"$episode" \
"$codec_video" \
"$width" \
"$height" \
"$bitrate_video" \
"$audio_codec" \
"$audio_channels" \
"$audio_language" \
"$size"
done > "$TMPFILE"
mysql --login-path=local "$MYSQL_DB" <<EOF
TRUNCATE TABLE media;
LOAD DATA INFILE '$TMPFILE'
INTO TABLE media
FIELDS TERMINATED BY '\t'
LINES TERMINATED BY '\n'
(
scan_date,
jellyfin_id,
name,
path,
media_type,
series_name,
season_number,
episode_number,
codec_video,
width,
height,
bitrate_video,
audio_codec,
audio_channels,
audio_language,
size_bytes
);
EOF
exit 0
Pour avoir une vue synthétique de la table
media dans la base video_catalog, on peut créer une vue SQL :
SQL:
CREATE VIEW media_readable AS
SELECT
name,
codec_video,
CONCAT(width,'x',height) AS resolution,
ROUND(bitrate_video/1000) AS bitrate_kbps,
ROUND(size_bytes/1073741824,2) AS size_gb,
path,
media_type
FROM media;
| Outils | Description | URL |
|---|---|---|
curl | Récupération des données exposées par l'API de Jellyfin | https://curl.se/ |
jq | Formattage des données pour importation SQL | https://jqlang.org/ |
mysql | Client pour importation de données dans un serveur (My)SQL | https://www.mysql.com/fr/ |
stat | Récupération de la taille précise des fichiers | https://pubs.opengroup.org/onlinepubs/009696799/functions/stat.html |
sh | Interpréteur | https://fr.wikipedia.org/wiki/Bourne_shell |