231 lines
5.5 KiB
TypeScript
231 lines
5.5 KiB
TypeScript
/**
|
|
* Service API pour interagir avec le cache de pistes audio
|
|
*/
|
|
|
|
export interface AudioCacheMetadata {
|
|
origin_url?: string;
|
|
title?: string;
|
|
artist?: string;
|
|
album?: string;
|
|
year?: number;
|
|
genre?: string;
|
|
track_number?: number;
|
|
track_total?: number;
|
|
disc_number?: number;
|
|
disc_total?: number;
|
|
duration_ms?: number;
|
|
duration_secs?: number;
|
|
sample_rate?: number;
|
|
bitrate?: number;
|
|
channels?: number;
|
|
conversion?: ConversionInfo;
|
|
[key: string]: unknown;
|
|
}
|
|
|
|
export interface AudioCacheEntry {
|
|
pk: string;
|
|
id: string | null;
|
|
hits: number;
|
|
last_used: string | null;
|
|
collection?: string | null;
|
|
metadata?: AudioCacheMetadata | null;
|
|
}
|
|
|
|
export interface ConversionInfo {
|
|
mode: string;
|
|
input_codec?: string;
|
|
details?: string;
|
|
}
|
|
|
|
export interface AddTrackRequest {
|
|
url: string;
|
|
collection?: string;
|
|
}
|
|
|
|
export interface AddTrackResponse {
|
|
pk: string;
|
|
url: string;
|
|
message: string;
|
|
}
|
|
|
|
export interface DownloadStatus {
|
|
pk: string;
|
|
in_progress: boolean;
|
|
finished: boolean;
|
|
current_size?: number;
|
|
transformed_size?: number;
|
|
expected_size?: number;
|
|
error?: string;
|
|
conversion?: ConversionInfo;
|
|
}
|
|
|
|
export interface ApiError {
|
|
error: string;
|
|
message: string;
|
|
}
|
|
|
|
export function getOriginUrl(entry: AudioCacheEntry): string | undefined {
|
|
const metadata = entry.metadata;
|
|
if (metadata && typeof metadata === "object") {
|
|
const origin = (metadata as { origin_url?: unknown }).origin_url;
|
|
if (typeof origin === "string" && origin.trim().length > 0) {
|
|
return origin;
|
|
}
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
export function getDurationMs(metadata?: AudioCacheMetadata | null): number | undefined {
|
|
if (!metadata) return undefined;
|
|
if (typeof metadata.duration_ms === "number" && !Number.isNaN(metadata.duration_ms)) {
|
|
return metadata.duration_ms;
|
|
}
|
|
if (typeof metadata.duration_secs === "number" && !Number.isNaN(metadata.duration_secs)) {
|
|
return metadata.duration_secs * 1000;
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
/**
|
|
* Liste toutes les pistes en cache
|
|
*/
|
|
export async function listTracks(): Promise<AudioCacheEntry[]> {
|
|
const response = await fetch("/api/audio");
|
|
if (!response.ok) {
|
|
const error: ApiError = await response.json();
|
|
throw new Error(error.message || "Failed to fetch tracks");
|
|
}
|
|
return response.json();
|
|
}
|
|
|
|
/**
|
|
* Récupère les informations d'une piste spécifique
|
|
*/
|
|
export async function getTrackInfo(pk: string): Promise<AudioCacheEntry> {
|
|
const response = await fetch(`/api/audio/${pk}`);
|
|
if (!response.ok) {
|
|
const error: ApiError = await response.json();
|
|
throw new Error(error.message || "Failed to fetch track info");
|
|
}
|
|
return response.json();
|
|
}
|
|
|
|
/**
|
|
* Récupère le statut de téléchargement d'une piste
|
|
*/
|
|
export async function getDownloadStatus(pk: string): Promise<DownloadStatus> {
|
|
const response = await fetch(`/api/audio/${pk}/status`);
|
|
if (!response.ok) {
|
|
const error: ApiError = await response.json();
|
|
throw new Error(error.message || "Failed to fetch download status");
|
|
}
|
|
return response.json();
|
|
}
|
|
|
|
/**
|
|
* Ajoute une nouvelle piste au cache depuis une URL
|
|
*/
|
|
export async function addTrack(url: string, collection?: string): Promise<AddTrackResponse> {
|
|
const body: AddTrackRequest = { url };
|
|
if (collection) {
|
|
body.collection = collection;
|
|
}
|
|
|
|
const response = await fetch("/api/audio", {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify(body),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const error: ApiError = await response.json();
|
|
throw new Error(error.message || "Failed to add track");
|
|
}
|
|
return response.json();
|
|
}
|
|
|
|
/**
|
|
* Supprime une piste du cache
|
|
*/
|
|
export async function deleteTrack(pk: string): Promise<void> {
|
|
const response = await fetch(`/api/audio/${pk}`, {
|
|
method: "DELETE",
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const error: ApiError = await response.json();
|
|
throw new Error(error.message || "Failed to delete track");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Purge complètement le cache
|
|
*/
|
|
export async function purgeCache(): Promise<void> {
|
|
const response = await fetch("/api/audio", {
|
|
method: "DELETE",
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const error: ApiError = await response.json();
|
|
throw new Error(error.message || "Failed to purge cache");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Consolide le cache (re-télécharge les pistes manquantes)
|
|
*/
|
|
export async function consolidateCache(): Promise<void> {
|
|
const response = await fetch("/api/audio/consolidate", {
|
|
method: "POST",
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const error: ApiError = await response.json();
|
|
throw new Error(error.message || "Failed to consolidate cache");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Génère l'URL pour streamer une piste
|
|
*/
|
|
export function getTrackUrl(pk: string): string {
|
|
return `/audio/flac/${pk}`;
|
|
}
|
|
|
|
/**
|
|
* Génère l'URL pour télécharger la piste originale
|
|
*/
|
|
export function getOriginalTrackUrl(pk: string): string {
|
|
return `/audio/flac/${pk}/orig`;
|
|
}
|
|
|
|
/**
|
|
* Formatte la durée en millisecondes au format MM:SS
|
|
*/
|
|
export function formatDuration(ms?: number): string {
|
|
if (!ms) return "Unknown";
|
|
const seconds = Math.floor(ms / 1000);
|
|
const minutes = Math.floor(seconds / 60);
|
|
const remainingSeconds = seconds % 60;
|
|
return `${minutes}:${remainingSeconds.toString().padStart(2, "0")}`;
|
|
}
|
|
|
|
/**
|
|
* Formatte le bitrate en kbps
|
|
*/
|
|
export function formatBitrate(bitrate?: number): string {
|
|
if (!bitrate) return "Unknown";
|
|
return `${Math.round(bitrate / 1000)} kbps`;
|
|
}
|
|
|
|
/**
|
|
* Formatte le sample rate en kHz
|
|
*/
|
|
export function formatSampleRate(sampleRate?: number): string {
|
|
if (!sampleRate) return "Unknown";
|
|
return `${(sampleRate / 1000).toFixed(1)} kHz`;
|
|
}
|