Compare commits

...

2 Commits

Author SHA1 Message Date
Seungmin Kim
a98e47a474
Merge 045c865904 into 0ec9f3535b 2025-03-24 21:40:20 +09:00
Seungmin Kim
045c865904 Add ISRC track search for YouTube 2025-03-24 05:40:01 -07:00

View File

@ -240,25 +240,63 @@ class YoutubeSourcedTrack extends SourcedTrack {
required Track track, required Track track,
required Ref ref, required Ref ref,
}) async { }) async {
List<SiblingType> siblings = [];
final isrc = track.externalIds?.isrc.toString();
if (isrc != null && isrc.isNotEmpty) {
final isrcResults =
await ref.read(youtubeEngineProvider).searchVideos(isrc);
if (isrcResults.isNotEmpty) {
final rankedResults = rankResults(
isrcResults.map(YoutubeVideoInfo.fromVideo).toList(), track);
final matchingResults = <YoutubeVideoInfo>[];
for (final video in rankedResults) {
final titleWords = video.title
.replaceAll(RegExp(r'\((.*)\)'), '')
.toLowerCase()
.split(RegExp(r'\s+'))
.toList();
final nameLower = track.name!
.replaceAll(RegExp(r'\((.*)\)'), '')
.toLowerCase()
.split(RegExp(r'\s+'))
.toList();
final matchCount =
titleWords.where((word) => nameLower.contains(word)).length;
if (matchCount >= nameLower.length) {
matchingResults.add(video);
}
}
if (matchingResults.isNotEmpty) {
final matchingSiblings =
await Future.wait(matchingResults.map((matchingResult) async {
try {
return await toSiblingType(0, matchingResult, ref);
} on VideoUnplayableException catch (e, stack) {
// Ignore this error and continue with the search
AppLogger.reportError(e, stack);
return null;
}
}));
siblings.addAll(matchingSiblings.whereType<SiblingType>());
}
}
}
final links = await SongLinkService.links(track.id!); final links = await SongLinkService.links(track.id!);
final ytLink = links.firstWhereOrNull((link) => link.platform == "youtube"); final ytLink = links.firstWhereOrNull((link) => link.platform == "youtube");
if (ytLink?.url != null if (ytLink?.url != null) {
// allows to fetch siblings more results for already sourced track
&&
track is! SourcedTrack) {
try { try {
return [ siblings.add(await toSiblingType(
await toSiblingType( 0,
0, YoutubeVideoInfo.fromVideo(
YoutubeVideoInfo.fromVideo( await ref.read(youtubeEngineProvider).getVideo(
await ref.read(youtubeEngineProvider).getVideo( Uri.parse(ytLink!.url!).queryParameters["v"]!,
Uri.parse(ytLink!.url!).queryParameters["v"]!, ),
), ),
), ref,
ref, ));
)
];
} on VideoUnplayableException catch (e, stack) { } on VideoUnplayableException catch (e, stack) {
// Ignore this error and continue with the search // Ignore this error and continue with the search
AppLogger.reportError(e, stack); AppLogger.reportError(e, stack);
@ -271,20 +309,31 @@ class YoutubeSourcedTrack extends SourcedTrack {
await ref.read(youtubeEngineProvider).searchVideos(query); await ref.read(youtubeEngineProvider).searchVideos(query);
if (ServiceUtils.onlyContainsEnglish(query)) { if (ServiceUtils.onlyContainsEnglish(query)) {
return await Future.wait(searchResults siblings.addAll(await Future.wait(searchResults
.map(YoutubeVideoInfo.fromVideo) .map(YoutubeVideoInfo.fromVideo)
.mapIndexed((index, info) => toSiblingType(index, info, ref))); .mapIndexed((index, info) => toSiblingType(index, info, ref))));
} else {
final rankedSiblings = rankResults(
searchResults.map(YoutubeVideoInfo.fromVideo).toList(),
track,
);
siblings.addAll(await Future.wait(
rankedSiblings
.mapIndexed((index, info) => toSiblingType(index, info, ref)),
));
} }
return await (() async {
final rankedSiblings = rankResults( // Deduplicate siblings by info.id, keeping the first occurrence
searchResults.map(YoutubeVideoInfo.fromVideo).toList(), final seenIds = <String>{};
track, final uniqueSiblings = <SiblingType>[];
); for (final sibling in siblings) {
if (!seenIds.contains(sibling.info.id)) {
return await Future.wait( seenIds.add(sibling.info.id);
rankedSiblings uniqueSiblings.add(sibling);
.mapIndexed((index, info) => toSiblingType(index, info, ref)), }
); }
return uniqueSiblings;
})();
} }
@override @override