Files
QingTing-Player/lib/metadata/metadata_service.dart
T
lxh2875931338 f2b4d1f46d 通知中心封面图显示
部分界面的信息更新
缓存系统初步引入
即将进入播放列表更新
2026-08-25 23:09:17 +08:00

171 lines
5.5 KiB
Dart

// 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();
}
/// 公开给 AudioService 使用(用于清除缓存时获取文件)
FileProvider getProviderForUrl(String url) {
return _getProvider(url);
}
/// 清除内存缓存
Future<void> clearCache(String fileId) async {
await _memoryCache.remove(fileId);
debugPrint('🗑️ [MetadataService] memory cache cleared: $fileId');
}
Future<FinalMetadata> 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);
debugPrint(
'📢 [MetadataService] finalMetadata.artwork is ${finalMetadata.artwork != null ? 'not null (${finalMetadata.artwork!.length} bytes)' : 'null'}');
// 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',
);
}
}