fix: m3u8 relative path generation/resolution

This commit is contained in:
Markury
2026-03-19 11:37:27 -04:00
parent e5d12c9041
commit cc92640908
5 changed files with 24 additions and 28 deletions

View File

@@ -57,35 +57,32 @@ export async function writeM3U8(
}
/**
* Convert absolute music file path to relative path from playlists folder
* Assumes music folder and playlists folder are siblings:
* Compute a relative path from the playlists folder to a music file.
* Music and playlists folders are expected to be siblings:
* /path/to/Music/Artist/Album/Track.flac
* /path/to/Playlists/playlist.m3u8
* Becomes: ../Music/Artist/Album/Track.flac
*
* @param absoluteMusicPath - Absolute path to music file
* @param musicFolderName - Name of music folder (default: 'Music')
* @param playlistsFolder - Absolute path to playlists folder
* @returns Relative path from playlists folder
*/
export function makeRelativePath(
absoluteMusicPath: string,
musicFolderName: string = 'Music'
playlistsFolder: string
): string {
// Split path into parts
const parts = absoluteMusicPath.split('/');
const fileParts = absoluteMusicPath.split('/').filter(Boolean);
const baseParts = playlistsFolder.replace(/\/$/, '').split('/').filter(Boolean);
// Find the music folder index
const musicIndex = parts.findIndex(part => part === musicFolderName);
if (musicIndex === -1) {
// Fallback: if music folder not found, use the path as-is
console.warn(`[M3U8] Could not find "${musicFolderName}" in path: ${absoluteMusicPath}`);
return absoluteMusicPath;
// Find common prefix length
let common = 0;
while (common < baseParts.length && common < fileParts.length && baseParts[common] === fileParts[common]) {
common++;
}
// Take everything from music folder onwards
const relativeParts = parts.slice(musicIndex);
// Go up from playlists folder to common ancestor, then down to the file
const ups = baseParts.length - common;
const remaining = fileParts.slice(common);
// Prepend ../ to go up from playlists folder
return `../${relativeParts.join('/')}`;
return [...Array(ups).fill('..'), ...remaining].join('/');
}

View File

@@ -277,23 +277,22 @@ export async function findPlaylistCoverFallback(
*/
export async function loadPlaylistTracks(
playlistPath: string,
playlistName: string,
baseFolder: string
playlistName: string
): Promise<PlaylistWithTracks> {
const parsedTracks = await parsePlaylist(playlistPath);
// Resolve relative paths against the playlist file's directory
const playlistDir = playlistPath.split('/').slice(0, -1).join('/');
// Load tracks with metadata in parallel
const tracks: Track[] = await Promise.all(
parsedTracks.map(async (parsedTrack) => {
const trackPath = parsedTrack.path;
// Handle relative paths - resolve relative to playlist location or music folder
// Resolve path: absolute paths used as-is, relative paths resolved from playlist dir
let fullPath = trackPath.startsWith('/') || trackPath.includes(':\\')
? trackPath // Absolute path
: `${baseFolder}/${trackPath}`; // Relative path
// Normalize path to remove .. and . segments for Tauri security
fullPath = normalizePath(fullPath);
? trackPath
: normalizePath(`${playlistDir}/${trackPath}`);
// Try to find the actual file (handles track number mismatches)
const actualPath = await findActualFilePath(fullPath);

View File

@@ -93,7 +93,7 @@ export async function downloadDeezerPlaylist(
const absolutePath = `${paths.filepath}/${paths.filename}`;
// Convert to relative path from playlists folder
const relativePath = makeRelativePath(absolutePath, 'Music');
const relativePath = makeRelativePath(absolutePath, playlistsFolder);
return {
duration: track.duration,

View File

@@ -221,7 +221,7 @@ export async function downloadSpotifyPlaylist(
const m3u8Tracks: M3U8Track[] = successfulTracks.map(({ deezerTrack }) => {
const paths = generateTrackPath(deezerTrack, musicFolder, appSettings.deezerFormat, false);
const absolutePath = `${paths.filepath}/${paths.filename}`;
const relativePath = makeRelativePath(absolutePath, 'Music');
const relativePath = makeRelativePath(absolutePath, playlistsFolder);
return {
duration: deezerTrack.duration,

View File

@@ -51,7 +51,7 @@
// Load tracks and cover art in parallel
const [tracksData, coverPath] = await Promise.all([
loadPlaylistTracks(playlist.path, playlist.name, $settings.musicFolder),
loadPlaylistTracks(playlist.path, playlist.name),
findPlaylistArt(playlist.path)
]);