import { writeFile } from '@tauri-apps/plugin-fs'; import { sanitizeFilename } from '$lib/services/deezer/paths'; import { encodeEmojis } from '$lib/utils/emoji'; export interface M3U8Track { duration: number; // in seconds artist: string; title: string; path: string; // relative path from playlist file (e.g., ../Music/Artist/Album/01 - Track.flac) } /** * Write an M3U8 playlist file * Format: Extended M3U format with EXTINF metadata * * @param playlistName - Name of the playlist (will be sanitized) * @param tracks - Array of tracks to include * @param playlistsFolder - Absolute path to playlists folder * @returns Absolute path to created m3u8 file */ export async function writeM3U8( playlistName: string, tracks: M3U8Track[], playlistsFolder: string ): Promise { // Encode emojis and sanitize playlist name for filename const encodedName = encodeEmojis(playlistName); const sanitizedName = sanitizeFilename(encodedName); const playlistPath = `${playlistsFolder}/${sanitizedName}.m3u8`; // Build m3u8 content const lines: string[] = [ '#EXTM3U', `#PLAYLIST:${encodedName}`, '#EXTENC:UTF-8', '' ]; for (const track of tracks) { // EXTINF format: #EXTINF:duration,artist - title const durationSeconds = Math.round(track.duration); const extinf = `#EXTINF:${durationSeconds},${track.artist} - ${track.title}`; lines.push(extinf); lines.push(track.path); } // Add trailing newline lines.push(''); const content = lines.join('\n'); const encoder = new TextEncoder(); const data = encoder.encode(content); await writeFile(playlistPath, data); return playlistPath; } /** * Convert absolute music file path to relative path from playlists folder * Assumes music folder and playlists folder are 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') * @returns Relative path from playlists folder */ export function makeRelativePath( absoluteMusicPath: string, musicFolderName: string = 'Music' ): string { // Split path into parts const parts = absoluteMusicPath.split('/'); // 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; } // Take everything from music folder onwards const relativeParts = parts.slice(musicIndex); // Prepend ../ to go up from playlists folder return `../${relativeParts.join('/')}`; }