mirror of
https://github.com/markuryy/shark.git
synced 2025-12-15 04:41:01 +00:00
feat(dz): add playlist download, existence check, and improved queue handling
Add ability to download entire playlists as M3U8 files, with UI integration and per-track download actions. Implement track existence checking to avoid duplicate downloads, respecting the overwrite setting. Improve queue manager to sync downloaded tracks to the library incrementally. Refactor playlist parsing and metadata reading to use the Rust backend for better performance and accuracy. Update UI to reflect track existence and download status in playlist views. BREAKING CHANGE: Deezer playlist and track download logic now relies on Rust backend for metadata and new existence checking; some APIs and internal behaviors have changed.
This commit is contained in:
89
src/lib/library/m3u8.ts
Normal file
89
src/lib/library/m3u8.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
import { writeFile } from '@tauri-apps/plugin-fs';
|
||||
import { sanitizeFilename } from '$lib/services/deezer/paths';
|
||||
|
||||
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<string> {
|
||||
// Sanitize playlist name for filename
|
||||
const sanitizedName = sanitizeFilename(playlistName);
|
||||
const playlistPath = `${playlistsFolder}/${sanitizedName}.m3u8`;
|
||||
|
||||
// Build m3u8 content
|
||||
const lines: string[] = [
|
||||
'#EXTM3U',
|
||||
`#PLAYLIST:${playlistName}`,
|
||||
'#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('/')}`;
|
||||
}
|
||||
Reference in New Issue
Block a user