機能 #690
完了Navidrome からのプレイリストエクスポートと WALKMAN 用自動編集
100%
NO nop_thread さんが12ヶ月前に更新
- 前回確認日 を 2025/07/13 にセット
Release v0.56.0 · navidrome/navidrome
"M3U File Downloads & Unified Export Logic" が最近のバージョンで実装されたようなので、これを使えば済むかもしれない。
要確認。
NO nop_thread さんが12ヶ月前に更新
- ステータス を 新規 から 進行中 に変更
- 開始日 を 2025/07/14 にセット
- 前回確認日 を 2025/07/13 から 2025/07/14 に変更
Subsonic API を使うなら ND_SUBSONIC_DEFAULTREPORTREALPATH=true したうえで getPlaylists.view と getPlaylist.view から情報を持ってこられそう。
NO nop_thread さんが12ヶ月前に更新
native API についてはドキュメントがなさそう?
/api/playlist/${id}/tracks から持ってこられそうな雰囲気があるが、認証をどうすればいいか不明。
プレイリストの ID は Subsonic API の getPlaylists から持ってこられるやつだと思う (未確認)。
Documentation for the Navidrome API : r/navidrome を見た感じだと native API は意図して消極的公開になっているようなので、 Subsonic API を使うべきなのだろう。たぶん。
一応 /auth/login からトークンを取得して X-ND-Authorization に Bearer ${token} するといけそうだが、そこまでして得られるものがせいぜい「サーバの設定を変更しなくても動く」くらいで、むしろ失効期限の存在する bearer token など管理すべき状態が増えてしまうので、あまり嬉しくない。
NO nop_thread さんが12ヶ月前に更新
nop_thread さんは #note-2 で書きました:
Subsonic API を使うなら
ND_SUBSONIC_DEFAULTREPORTREALPATH=trueしたうえで
nop_thread さんは #note-3 で書きました:
そこまでして得られるものがせいぜい「サーバの設定を変更しなくても動く」くらいで、
どうもサーバでの設定は新規 Subsonic API クライアントへのデフォルト値でしかないようで、 web UI の右上のメニューから「Players」→(クライアントを選択)→「Report Real Path」を有効化、と設定することで実際のファイルパスを取得できるようになった。
NO nop_thread さんが12ヶ月前に更新 · 編集済み
たぶんいけた。
curl で --insecure しているのは単に LAN 用 root CA の証明書をシステムにインストールするのをサボっているせい。
気が向いたらそのうちやる。
あとは Navidrome 上で削除されたプレイリストをローカルからも消す処理を入れたい。
雑にやるなら rm -r ${PLAYLIST_DEST_DIR}/* などすればよさそう。
効率化とか差分更新はプレイリスト数が増えすぎて待てなくなってから考えればいいか。
NO nop_thread さんが12ヶ月前に更新 · 編集済み
NO nop_thread さんが約1ヶ月前に更新
- 関連している 機能 #108: 音楽の管理方式の改善 を追加
NO nop_thread さんが約1ヶ月前に更新
内容に変化がないときファイルを上書きしないようにした:
#!/bin/sh
set -eu
# last update: 2025-08-31
cd "$(dirname "$(readlink -f "$0")")/.."
USER=nop_thread
PASSWORD_SALT='REPLACE_WITH_RANDOM_PASSWORD_SALT'
# `echo -n "${PASSWORD}${SALT}" | md5sum - | cut -d' ' -f1`.
HASHED_PASSWORD='REPLACE_WITH_MD5_OF_PASSWORD_AND_SALT'
# Should NOT have a trailing slash.
BASE_URL='https://my.navidrome.host.example'
API_VERSION='1.16.1'
FORMAT=json
# Don't forget to update the config from the web UI.
# (Menu) > "Players" > $CLIENT_STRING > Enable "Report Real Path".
CLIENT_STRING='REPLACE_WITH_YOUR_CLIENT_NAME'
PLAYLIST_DEST_DIR="playlists/navidrome"
# This temp dir should not be inside `$PLAYLIST_DEST_DIR` since they will be
# sync'ed using rsync.
PLAYLIST_DEST_DIR_TEMP="playlists/navidrome.temp"
get() {
ENDPOINT="$1"
PARAMS="${2:+&$2}"
curl --silent --insecure "${BASE_URL}/rest/${ENDPOINT}?u=${USER}&s=${PASSWORD_SALT}&t=${HASHED_PASSWORD}&v=${API_VERSION}&f=${FORMAT}&c=${CLIENT_STRING}${PARAMS}"
}
get_playlists() {
get getPlaylists.view | jq --raw-output '."subsonic-response".playlists.playlist[] | [.id, .name] | @tsv'
}
rm -rf "$PLAYLIST_DEST_DIR_TEMP"
mkdir -p "$PLAYLIST_DEST_DIR_TEMP"
# Create `.ndignore` file (or use the existing one).
if [ -f "${PLAYLIST_DEST_DIR}/.ndignore" ] ; then
cp --reflink=always --preserve "${PLAYLIST_DEST_DIR}/.ndignore" "${PLAYLIST_DEST_DIR_TEMP}/.ndignore"
else
{
echo '# Files in this directory will be ignored by Navidrome.'
echo '# See <https://github.com/navidrome/navidrome/issues/297>.'
} >"${PLAYLIST_DEST_DIR_TEMP}/.ndignore"
fi
get_playlists | while read id name ; do
SAFE_NAME="$(echo "$name" | tr '/<>:"/\|?*' '[_*]')"
OLD_FILE="${PLAYLIST_DEST_DIR}/${SAFE_NAME}.m3u8"
DEST_FILE="${PLAYLIST_DEST_DIR_TEMP}/${SAFE_NAME}.m3u8"
DEST_FILE_TEMP="${PLAYLIST_DEST_DIR_TEMP}/${SAFE_NAME}.m3u8.tmp"
DEST_REL_PATH_TO_MUSIC_ROOT="../.."
# Generate the playlist content and save as a temporary file.
echo '#EXTM3U' >"${DEST_FILE_TEMP}"
echo "#PLAYLIST:${name} (from Navidrome)" >>"${DEST_FILE_TEMP}"
RAW_JSON="$(get getPlaylist.view "id=${id}")"
ATTRS="$(echo "$RAW_JSON" | jq --raw-output '."subsonic-response".playlist.comment | select(.!=null)' | sed -ne 's/^ATTR://p')"
if echo "$ATTRS" | grep -q '^\(sortable\|unordered\)$' ; then
#echo "Playlist $name is unordered."
ENTRIES_JSON="$(echo "$RAW_JSON" | jq --raw-output '."subsonic-response".playlist.entry // [] | sort_by(.path)')"
else
#echo "Playlist $name is ordered."
ENTRIES_JSON="$(echo "$RAW_JSON" | jq --raw-output '."subsonic-response".playlist.entry')"
fi
echo "$ENTRIES_JSON" | jq --raw-output '.[] | "#EXTINF:\(.duration|tostring),\(.displayArtist) - \(.title)\n\(.path)"' | sed -e '/^#/! s:/mnt/music/:'"${DEST_REL_PATH_TO_MUSIC_ROOT}/:" >>"${DEST_FILE_TEMP}"
# Overwrite the destination playlist only when they really differ.
if ! diff --brief "$OLD_FILE" "$DEST_FILE_TEMP" >/dev/null 2>&1 ; then
# Different or a new file. Overwrite by the new content.
mv "$DEST_FILE_TEMP" "$DEST_FILE"
else
# Unchanged. Use the original file as is.
cp --reflink=always --preserve "$OLD_FILE" "$DEST_FILE"
rm "$DEST_FILE_TEMP"
fi
done
# Note that timestamps should not be sync'ed if the content is identical.
rsync -r --delete-after --info=name "${PLAYLIST_DEST_DIR_TEMP}/" "${PLAYLIST_DEST_DIR}/"
rm -r "${PLAYLIST_DEST_DIR_TEMP}"
NO nop_thread さんが約1ヶ月前に更新
- 前回確認日 を 2025/07/14 から 2026/06/07 に変更