mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-13 07:55:18 +00:00
Add ISRC track search for YouTube
This commit is contained in:
parent
0ec9f3535b
commit
2c5cd8d505
@ -94,6 +94,7 @@ abstract class FakeData {
|
||||
..trackNumber = 1
|
||||
..type = "type"
|
||||
..uri = "uri"
|
||||
..externalIds = externalIds
|
||||
..isPlayable = true
|
||||
..explicit = false
|
||||
..linkedFrom = trackLink;
|
||||
|
@ -4,7 +4,9 @@ import 'dart:typed_data';
|
||||
import 'package:metadata_god/metadata_god.dart';
|
||||
import 'package:path/path.dart';
|
||||
import 'package:spotify/spotify.dart';
|
||||
import 'package:spotube/provider/spotify/spotify.dart';
|
||||
import 'package:spotube/services/audio_player/audio_player.dart';
|
||||
import 'package:spotube/services/logger/logger.dart';
|
||||
|
||||
extension TrackExtensions on Track {
|
||||
Track fromFile(
|
||||
@ -68,7 +70,14 @@ extension TrackExtensions on Track {
|
||||
}
|
||||
|
||||
extension TrackSimpleExtensions on TrackSimple {
|
||||
Track asTrack(AlbumSimple album) {
|
||||
Future<Track> asTrack(AlbumSimple album, ref) async {
|
||||
try {
|
||||
final spotify = ref.read(spotifyProvider);
|
||||
return await spotify.invoke((api) => api.tracks.get(id!));
|
||||
} catch (e, stack) {
|
||||
// Ignore errors and create the track locally
|
||||
AppLogger.reportError(e, stack);
|
||||
|
||||
Track track = Track();
|
||||
track.name = name;
|
||||
track.album = album;
|
||||
@ -90,6 +99,7 @@ extension TrackSimpleExtensions on TrackSimple {
|
||||
return track;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension TracksToMediaExtension on Iterable<Track> {
|
||||
List<SpotubeMedia> asMediaList() {
|
||||
|
@ -54,7 +54,8 @@ class AlbumCard extends HookConsumerWidget {
|
||||
|
||||
Future<List<Track>> fetchAllTrack() async {
|
||||
if (album.tracks != null && album.tracks!.isNotEmpty) {
|
||||
return album.tracks!.map((track) => track.asTrack(album)).toList();
|
||||
return await Future.wait(
|
||||
album.tracks!.map((track) => track.asTrack(album, ref)));
|
||||
}
|
||||
await ref.read(albumTracksProvider(album).future);
|
||||
return ref.read(albumTracksProvider(album).notifier).fetchAll();
|
||||
|
@ -33,7 +33,7 @@ class AlbumTracksNotifier extends AutoDisposeFamilyPaginatedAsyncNotifier<Track,
|
||||
final tracks = await spotify.invoke(
|
||||
(api) => api.albums.tracks(arg.id!).getPage(limit, offset),
|
||||
);
|
||||
final items = tracks.items?.map((e) => e.asTrack(arg)).toList() ?? [];
|
||||
final List<Track> items = await Future.wait(tracks.items?.map((e) => e.asTrack(arg, ref)) ?? []);
|
||||
|
||||
return (
|
||||
items: items,
|
||||
|
@ -236,29 +236,66 @@ class YoutubeSourcedTrack extends SourcedTrack {
|
||||
.toList();
|
||||
}
|
||||
|
||||
static Future<List<YoutubeVideoInfo>> fetchFromIsrc({
|
||||
required Track track,
|
||||
required Provider provider,
|
||||
required Ref ref,
|
||||
}) async {
|
||||
final isrcResults = <YoutubeVideoInfo>[];
|
||||
final isrc = track.externalIds?.isrc;
|
||||
if (isrc != null && isrc.isNotEmpty) {
|
||||
final searchedVideos = await ref
|
||||
.read(provider)
|
||||
.searchVideos(isrc.toString());
|
||||
if (searchedVideos.isNotEmpty) {
|
||||
isrcResults.addAll(searchedVideos
|
||||
.map<YoutubeVideoInfo>(YoutubeVideoInfo.fromVideo)
|
||||
.map((YoutubeVideoInfo videoInfo) {
|
||||
final ytWords =
|
||||
videoInfo.title
|
||||
.toLowerCase()
|
||||
.replaceAll(RegExp(r'[^a-zA-Z0-9\s]+'), '')
|
||||
.split(RegExp(r'\s+'))
|
||||
.where((item) => item.isNotEmpty);
|
||||
final spWords =
|
||||
track.name!
|
||||
.toLowerCase()
|
||||
.replaceAll(RegExp(r'\((.*)\)'), '')
|
||||
.replaceAll(RegExp(r'[^a-zA-Z0-9\s]+'), '')
|
||||
.split(RegExp(r'\s+'))
|
||||
.where((item) => item.isNotEmpty);
|
||||
// Word match to filter out unrelated results
|
||||
final matchCount =
|
||||
ytWords.where((word) => spWords.contains(word)).length;
|
||||
if (matchCount > spWords.length ~/ 2) {
|
||||
return videoInfo;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
).whereType<YoutubeVideoInfo>().toList());
|
||||
}
|
||||
}
|
||||
return isrcResults;
|
||||
}
|
||||
|
||||
static Future<List<SiblingType>> fetchSiblings({
|
||||
required Track track,
|
||||
required Ref ref,
|
||||
}) async {
|
||||
final videoResults = <YoutubeVideoInfo>[];
|
||||
|
||||
videoResults.addAll(await fetchFromIsrc(track: track, provider: youtubeEngineProvider, ref: ref));
|
||||
|
||||
final links = await SongLinkService.links(track.id!);
|
||||
final ytLink = links.firstWhereOrNull((link) => link.platform == "youtube");
|
||||
|
||||
if (ytLink?.url != null
|
||||
// allows to fetch siblings more results for already sourced track
|
||||
&&
|
||||
track is! SourcedTrack) {
|
||||
if (ytLink?.url != null) {
|
||||
try {
|
||||
return [
|
||||
await toSiblingType(
|
||||
0,
|
||||
videoResults.add(
|
||||
YoutubeVideoInfo.fromVideo(
|
||||
await ref.read(youtubeEngineProvider).getVideo(
|
||||
Uri.parse(ytLink!.url!).queryParameters["v"]!,
|
||||
),
|
||||
),
|
||||
ref,
|
||||
)
|
||||
];
|
||||
await ref.read(youtubeEngineProvider)
|
||||
.getVideo(Uri.parse(ytLink!.url!).queryParameters["v"]!)
|
||||
));
|
||||
} on VideoUnplayableException catch (e, stack) {
|
||||
// Ignore this error and continue with the search
|
||||
AppLogger.reportError(e, stack);
|
||||
@ -271,20 +308,28 @@ class YoutubeSourcedTrack extends SourcedTrack {
|
||||
await ref.read(youtubeEngineProvider).searchVideos(query);
|
||||
|
||||
if (ServiceUtils.onlyContainsEnglish(query)) {
|
||||
return await Future.wait(searchResults
|
||||
.map(YoutubeVideoInfo.fromVideo)
|
||||
.mapIndexed((index, info) => toSiblingType(index, info, ref)));
|
||||
}
|
||||
|
||||
final rankedSiblings = rankResults(
|
||||
videoResults.addAll(
|
||||
searchResults.map(YoutubeVideoInfo.fromVideo).toList()
|
||||
);
|
||||
} else {
|
||||
videoResults.addAll(rankResults(
|
||||
searchResults.map(YoutubeVideoInfo.fromVideo).toList(),
|
||||
track,
|
||||
);
|
||||
));
|
||||
}
|
||||
|
||||
final seenIds = <String>{};
|
||||
int index = 0;
|
||||
return await Future.wait(
|
||||
rankedSiblings
|
||||
.mapIndexed((index, info) => toSiblingType(index, info, ref)),
|
||||
);
|
||||
videoResults.map((videoResult) async {
|
||||
// Deduplicate results
|
||||
if (!seenIds.contains(videoResult.id)) {
|
||||
seenIds.add(videoResult.id);
|
||||
return await toSiblingType(index++, videoResult, ref);
|
||||
}
|
||||
return null;
|
||||
}),
|
||||
).then((s) => s.whereType<SiblingType>().toList());
|
||||
}
|
||||
|
||||
@override
|
||||
|
Loading…
Reference in New Issue
Block a user