111 lines
3.8 KiB
Dart
111 lines
3.8 KiB
Dart
class Song {
|
||
final int? id;
|
||
final String titel;
|
||
final String kuenstler;
|
||
final String album;
|
||
final String? jahr;
|
||
final String? genre;
|
||
final String? track;
|
||
final int dauerSekunden;
|
||
final String dateiPfad;
|
||
final String? coverPfad;
|
||
final int groesseBytes;
|
||
final bool istHeruntergeladen;
|
||
final bool istKorrupt; // true wenn Datei defekt (Magic-Byte-Check, ffprobe, Playback-Fehler)
|
||
final String hinzugefuegtAm;
|
||
final String downloadQuelle; // "local", "youtube", "server"
|
||
final String? streamUrl; // Für Server-Streaming (Navidrome) – wird bewusst NICHT in der DB persistiert (Auth-Token-Schutz)
|
||
final String? ytUrl; // YouTube-Quell-URL – NUR Fallback (Datei korrupt + kein Server-Download möglich); wird nicht mehr lokal geschrieben
|
||
final String? cloudId; // Verknüpfung lokal ↔ Cloud (Server-Song-ID) für bidirektionalen Favoriten-Sync
|
||
int? zuletztPosition; // Sekunden, für Wiederaufnahme
|
||
Set<int>? tagIds; // Cache für Tag-Filterung (nicht in DB gespeichert)
|
||
|
||
Song({
|
||
this.id,
|
||
required this.titel,
|
||
required this.kuenstler,
|
||
this.album = '',
|
||
this.jahr,
|
||
this.genre,
|
||
this.track,
|
||
required this.dauerSekunden,
|
||
required this.dateiPfad,
|
||
this.coverPfad,
|
||
this.groesseBytes = 0,
|
||
this.istHeruntergeladen = false,
|
||
this.istKorrupt = false,
|
||
String? hinzugefuegtAm,
|
||
this.downloadQuelle = 'local',
|
||
this.streamUrl,
|
||
this.ytUrl,
|
||
this.cloudId,
|
||
this.zuletztPosition,
|
||
}) : hinzugefuegtAm = hinzugefuegtAm ?? DateTime.now().toIso8601String();
|
||
|
||
Map<String, dynamic> toMap() => {
|
||
'id': id,
|
||
'titel': titel,
|
||
'kuenstler': kuenstler,
|
||
'album': album,
|
||
'jahr': jahr,
|
||
'genre': genre,
|
||
'track': track,
|
||
'dauer_sekunden': dauerSekunden,
|
||
'datei_pfad': dateiPfad,
|
||
'cover_pfad': coverPfad,
|
||
'groesse_bytes': groesseBytes,
|
||
'ist_heruntergeladen': istHeruntergeladen ? 1 : 0,
|
||
'ist_korrupt': istKorrupt ? 1 : 0,
|
||
'hinzugefuegt_am': hinzugefuegtAm,
|
||
'download_quelle': downloadQuelle,
|
||
'stream_url': null, // Token-haltige Stream-URLs nie persistieren (Sicherheit)
|
||
'yt_url': ytUrl,
|
||
'cloud_id': cloudId,
|
||
'zuletzt_position': zuletztPosition,
|
||
};
|
||
|
||
factory Song.fromMap(Map<String, dynamic> m) => Song(
|
||
id: m['id'] as int?,
|
||
titel: m['titel'] as String,
|
||
kuenstler: m['kuenstler'] as String,
|
||
album: m['album'] as String? ?? '',
|
||
jahr: m['jahr'] as String?,
|
||
genre: m['genre'] as String?,
|
||
track: m['track'] as String?,
|
||
dauerSekunden: m['dauer_sekunden'] as int,
|
||
dateiPfad: m['datei_pfad'] as String,
|
||
coverPfad: m['cover_pfad'] as String?,
|
||
groesseBytes: m['groesse_bytes'] as int? ?? 0,
|
||
istHeruntergeladen: (m['ist_heruntergeladen'] as int?) == 1,
|
||
istKorrupt: (m['ist_korrupt'] as int?) == 1,
|
||
hinzugefuegtAm: m['hinzugefuegt_am'] as String?,
|
||
downloadQuelle: m['download_quelle'] as String? ?? 'local',
|
||
streamUrl: m['stream_url'] as String?,
|
||
ytUrl: m['yt_url'] as String?,
|
||
cloudId: m['cloud_id'] as String?,
|
||
zuletztPosition: m['zuletzt_position'] as int?,
|
||
);
|
||
|
||
String get dauerFormatiert {
|
||
final min = dauerSekunden ~/ 60;
|
||
final sek = dauerSekunden % 60;
|
||
return '$min:${sek.toString().padLeft(2, '0')}';
|
||
}
|
||
|
||
String get groesseFormatiert {
|
||
if (groesseBytes < 1048576) return '${(groesseBytes / 1024).toStringAsFixed(0)} KB';
|
||
return '${(groesseBytes / 1048576).toStringAsFixed(1)} MB';
|
||
}
|
||
|
||
/// Dateiformat aus Dateiendung ableiten
|
||
String get dateiFormat {
|
||
final lower = dateiPfad.toLowerCase();
|
||
if (lower.endsWith('.mp3')) return 'MP3';
|
||
if (lower.endsWith('.m4a')) return 'M4A (AAC)';
|
||
if (lower.endsWith('.flac')) return 'FLAC';
|
||
if (lower.endsWith('.wav')) return 'WAV';
|
||
if (lower.endsWith('.ogg')) return 'OGG';
|
||
return lower.split('.').last.toUpperCase();
|
||
}
|
||
}
|