import 'dart:io'; import 'package:audio_metadata_reader/audio_metadata_reader.dart'; import 'package:drift/drift.dart'; import 'package:path/path.dart' as p; import 'package:path_provider/path_provider.dart'; import 'package:uuid/uuid.dart'; import 'database.dart'; const _audioExt = { '.mp3', '.m4a', '.aac', '.flac', '.wav', '.ogg', '.opus', '.wma', '.aiff', '.aif', '.alac', }; const _uuid = Uuid(); /// Scannt alle [folderPaths] rekursiv nach Audiodateien, liest Tags + Cover /// und schreibt sie in die DB. Bereits bekannte Dateien behalten ihre UUID /// (sync-stabil); verschwundene Dateien werden per Tombstone markiert. /// Gibt die Zahl der gefundenen Songs zurück. // ponytail: Tag-Parsing läuft synchron im UI-Isolate. Reicht für normale // Bibliotheken; bei >~mehreren Tausend Dateien in ein Isolate (compute) auslagern. Future scanFolders( MeloDb db, List folderPaths, { Directory? coverDir, void Function(int done, int total)? onProgress, }) async { final existing = {for (final s in await db.allSongs()) s.path: s}; final covers = coverDir ?? Directory(p.join((await getApplicationSupportDirectory()).path, 'covers')); await covers.create(recursive: true); final files = []; for (final folder in folderPaths) { final dir = Directory(folder); if (!dir.existsSync()) continue; await for (final entity in dir.list(recursive: true, followLinks: false)) { if (entity is File && _audioExt.contains(p.extension(entity.path).toLowerCase())) { files.add(entity); } } } final now = DateTime.now().millisecondsSinceEpoch; final companions = []; final livePaths = []; var done = 0; for (final file in files) { livePaths.add(file.path); final prev = existing[file.path]; final id = prev?.id ?? _uuid.v4(); AudioMetadata? meta; try { meta = readMetadata(file, getImage: true); } catch (_) { // Unlesbare/kaputte Tags: Datei trotzdem mit Dateinamen aufnehmen. } final rawTitle = meta?.title?.trim(); final title = (rawTitle != null && rawTitle.isNotEmpty) ? rawTitle : p.basenameWithoutExtension(file.path); var coverPath = prev?.coverPath; final pics = meta?.pictures ?? const []; if (pics.isNotEmpty && coverPath == null) { final f = File(p.join(covers.path, '$id.img')); await f.writeAsBytes(pics.first.bytes); coverPath = f.path; } companions.add(SongsCompanion.insert( id: id, path: file.path, title: title, artist: Value(meta?.artist), album: Value(meta?.album), durationMs: Value(meta?.duration?.inMilliseconds), coverPath: Value(coverPath), dateAddedMs: prev?.dateAddedMs ?? now, updatedAtMs: now, )); onProgress?.call(++done, files.length); } await db.upsertSongs(companions); await db.markMissing(livePaths, now); return companions.length; }