// lib/metadata/metadata_service.dart import 'dart:io'; // ⭐ 添加这一行 import 'dart:typed_data'; // ⭐ 如果已经有更好 import 'package:flutter/foundation.dart'; import '../database/song_database.dart'; import 'metadata_model.dart'; import 'metadata_reader.dart'; import 'metadata_normalizer.dart'; import 'metadata_cache.dart'; import 'file_provider.dart'; import '../utils/artwork_helper.dart'; class MetadataService { static final MetadataService _instance = MetadataService._internal(); factory MetadataService() => _instance; MetadataService._internal(); final MetadataReader _reader = MetadataReader(); final MetadataNormalizer _normalizer = MetadataNormalizer(); final MetadataCache _memoryCache = MetadataCache(); final SongDatabase _db = SongDatabase(); String _generateSongKey(String url, int fileSize, int modifiedTime) { final raw = '$url|$fileSize|$modifiedTime'; return raw.hashCode.toString(); } FileProvider _getProvider(String url) { if (url.startsWith('http://') || url.startsWith('https://')) { return WebDAVFileProvider(); } return LocalFileProvider(); } Future getMetadata({ required String url, required String fileName, required String fileId, bool forceRefresh = false, }) async { // 1. 内存缓存 if (!forceRefresh) { final cached = await _memoryCache.get(fileId); if (cached != null && cached.isNotEmpty) { debugPrint('📦 [MetadataService] memory hit: ${cached.title}'); return cached; } } // 2. 获取文件 final provider = _getProvider(url); final file = await provider.getFile(url); if (file == null || !await file.exists()) { debugPrint('⚠️ [MetadataService] file not available, using fallback'); return _fallbackFromFileName(fileName); } final stat = await file.stat(); final songKey = _generateSongKey(url, stat.size, stat.modified.millisecondsSinceEpoch); // 3. SQLite 查询(高置信度直接使用) if (!forceRefresh) { final dbSong = await _db.getSong(songKey); if (dbSong != null) { final confidence = (dbSong['confidence'] as num?)?.toDouble() ?? 0.0; if (confidence >= 0.6) { debugPrint( '📦 [MetadataService] SQLite hit: ${dbSong['title']} - ${dbSong['artist']}'); // 读取封面图 final artworkPath = dbSong['artwork_path'] as String?; Uint8List? artwork; if (artworkPath != null && artworkPath.isNotEmpty) { try { final artFile = File(artworkPath); if (await artFile.exists()) { artwork = await artFile.readAsBytes(); } } catch (e) { // ignore } } final metadata = FinalMetadata( title: dbSong['title'] as String? ?? '', artist: dbSong['artist'] as String? ?? '', album: dbSong['album'] as String? ?? '', genre: dbSong['genre'] as String? ?? '', artwork: artwork, source: 'database', ); await _memoryCache.put(fileId, metadata); return metadata; } } } // 4. 读取原始 metadata final raw = await _reader.readRawMetadata(file); // 5. 标准化 final normalized = _normalizer.normalize( raw, fileName: fileName, filePath: url, ); // 6. 评估 final history = await _db.getSongsByArtist(normalized.artist, limit: 10); final candidate = _normalizer.evaluate(normalized, history); final finalMetadata = _normalizer.decide(candidate); // 7. 保存封面图到本地 String? artworkPath; if (finalMetadata.artwork != null && finalMetadata.artwork!.isNotEmpty) { artworkPath = await ArtworkHelper.saveArtwork(finalMetadata.artwork!, songKey); } // 8. 保存到 SQLite await _db.insertSong({ 'song_key': songKey, 'remote_path': url, 'file_size': stat.size, 'modified_time': stat.modified.millisecondsSinceEpoch, 'title': finalMetadata.title, 'artist': finalMetadata.artist, 'album': finalMetadata.album, 'genre': finalMetadata.genre, 'artwork_path': artworkPath, 'confidence': candidate.confidence, 'validation_count': 1, 'first_scan': DateTime.now().millisecondsSinceEpoch, 'last_scan': DateTime.now().millisecondsSinceEpoch, 'metadata_status': candidate.confidence >= 0.6 ? 'verified' : 'pending', }); // 9. 内存缓存 await _memoryCache.put(fileId, finalMetadata); debugPrint( '✅ [MetadataService] saved: ${finalMetadata.title} - ${finalMetadata.artist} (confidence: ${candidate.confidence})'); return finalMetadata; } FinalMetadata _fallbackFromFileName(String fileName) { final dotIndex = fileName.lastIndexOf('.'); final title = dotIndex > 0 ? fileName.substring(0, dotIndex) : fileName; return FinalMetadata( title: title, artist: '未知艺术家', source: 'filename', ); } }