// lib/metadata/metadata_normalizer.dart import 'metadata_model.dart'; class MetadataNormalizer { /// 标准化原始数据(清洗空格、推断、fallback) NormalizedMetadata normalize(RawMetadata raw, {String fileName = '', String filePath = ''}) { // 1. 修剪空格 final title = raw.title.trim(); final artist = raw.artist.trim(); final album = raw.album.trim(); final albumArtist = raw.albumArtist.trim(); // 2. 如果 title 为空,尝试从文件名推断 final finalTitle = title.isNotEmpty ? title : _inferTitleFromFileName(fileName); // 3. 如果 artist 为空,尝试从路径推断或使用默认值 final finalArtist = artist.isNotEmpty ? artist : _inferArtistFromPath(filePath); return NormalizedMetadata( title: finalTitle, artist: finalArtist, album: album, albumArtist: albumArtist, composer: raw.composer.trim(), genre: raw.genre.trim(), year: raw.year, trackNumber: raw.trackNumber, discNumber: raw.discNumber, artwork: raw.artwork, ); } String _inferTitleFromFileName(String fileName) { // 去掉扩展名 final dotIndex = fileName.lastIndexOf('.'); if (dotIndex > 0) { return fileName.substring(0, dotIndex).trim(); } return fileName.trim(); } String _inferArtistFromPath(String filePath) { // 简单推断:尝试从路径中的文件夹名获取 final parts = filePath.split(RegExp(r'[/\\]')); if (parts.length >= 2) { // 倒数第二个文件夹可能是艺术家 final candidate = parts[parts.length - 2].trim(); if (candidate.isNotEmpty && !candidate.contains(' ')) { return candidate; } } return '未知艺术家'; } /// 评估候选元数据(第一阶段:直接接受,不纠错) CandidateMetadata evaluate( NormalizedMetadata normalized, List> history) { // 第一阶段:置信度固定为 0.8(文件名+标签混合) return CandidateMetadata( metadata: normalized, confidence: 0.8, evidence: {'normalized': 0.8}, ); } /// 决策是否修正(第一阶段:直接接受) FinalMetadata decide(CandidateMetadata candidate, {double threshold = 0.6}) { return FinalMetadata( title: candidate.metadata.title, artist: candidate.metadata.artist, album: candidate.metadata.album, albumArtist: candidate.metadata.albumArtist, composer: candidate.metadata.composer, genre: candidate.metadata.genre, year: candidate.metadata.year, trackNumber: candidate.metadata.trackNumber, discNumber: candidate.metadata.discNumber, artwork: candidate.metadata.artwork, source: 'normalized', ); } }