mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-13 07:55:18 +00:00
ranking based synced lyrics selection for better accuracy
This commit is contained in:
parent
8af0281b23
commit
dc9b09f496
@ -7,6 +7,7 @@ import 'package:spotube/helpers/timed-lyrics.dart';
|
||||
import 'package:spotube/hooks/useAutoScrollController.dart';
|
||||
import 'package:spotube/hooks/useBreakpoints.dart';
|
||||
import 'package:spotube/hooks/useSyncedLyrics.dart';
|
||||
import 'package:spotube/models/SpotubeTrack.dart';
|
||||
import 'package:spotube/provider/Playback.dart';
|
||||
import 'package:scroll_to_index/scroll_to_index.dart';
|
||||
|
||||
@ -19,15 +20,17 @@ class SyncedLyrics extends HookConsumerWidget {
|
||||
final breakpoint = useBreakpoints();
|
||||
final controller = useAutoScrollController();
|
||||
final timedLyrics = useMemoized(() {
|
||||
if (playback.currentTrack == null) return null;
|
||||
return getTimedLyrics(playback.currentTrack!);
|
||||
if (playback.currentTrack == null ||
|
||||
playback.currentTrack is! SpotubeTrack) return null;
|
||||
return getTimedLyrics(playback.currentTrack as SpotubeTrack);
|
||||
}, [playback.currentTrack]);
|
||||
final lyricsSnapshot = useFuture(timedLyrics);
|
||||
final lyricsMap = useMemoized(
|
||||
() =>
|
||||
lyricsSnapshot.data?.lyrics
|
||||
.map((lyric) => {lyric.time.inSeconds: lyric.text})
|
||||
.reduce((a, b) => {...a, ...b}) ??
|
||||
.reduce((accumulator, lyricSlice) =>
|
||||
{...accumulator, ...lyricSlice}) ??
|
||||
{},
|
||||
[lyricsSnapshot.data],
|
||||
);
|
||||
|
@ -6,6 +6,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:spotify/spotify.dart';
|
||||
import 'package:spotube/helpers/artist-to-string.dart';
|
||||
import 'package:spotube/helpers/getLyrics.dart';
|
||||
import 'package:spotube/models/SpotubeTrack.dart';
|
||||
import 'package:spotube/provider/Playback.dart';
|
||||
import 'package:spotube/provider/UserPreferences.dart';
|
||||
import 'package:youtube_explode_dart/youtube_explode_dart.dart';
|
||||
@ -42,8 +43,8 @@ class DownloadTrackButton extends HookConsumerWidget {
|
||||
return;
|
||||
}
|
||||
}
|
||||
StreamManifest manifest =
|
||||
await yt.videos.streamsClient.getManifest(track?.href);
|
||||
StreamManifest manifest = await yt.videos.streamsClient
|
||||
.getManifest((track as SpotubeTrack).ytTrack.url);
|
||||
|
||||
String downloadFolder = path.join(
|
||||
Platform.isAndroid
|
||||
@ -177,10 +178,7 @@ class DownloadTrackButton extends HookConsumerWidget {
|
||||
}
|
||||
return IconButton(
|
||||
icon: const Icon(Icons.download_rounded),
|
||||
onPressed: track != null &&
|
||||
!(track!.href ?? "").startsWith("https://api.spotify.com")
|
||||
? _downloadTrack
|
||||
: null,
|
||||
onPressed: track != null && track is SpotubeTrack ? _downloadTrack : null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -2,12 +2,13 @@ import 'dart:io';
|
||||
import 'package:spotify/spotify.dart';
|
||||
import 'package:spotube/helpers/getLyrics.dart';
|
||||
import 'package:spotube/models/Logger.dart';
|
||||
import 'package:spotube/models/SpotubeTrack.dart';
|
||||
import 'package:youtube_explode_dart/youtube_explode_dart.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:spotube/extensions/list-sort-multiple.dart';
|
||||
|
||||
final logger = getLogger("toYoutubeTrack");
|
||||
Future<Track> toYoutubeTrack(
|
||||
final logger = getLogger("toSpotubeTrack");
|
||||
Future<SpotubeTrack> toSpotubeTrack(
|
||||
YoutubeExplode youtube, Track track, String format) async {
|
||||
final artistsName =
|
||||
track.artists?.map((ar) => ar.name).toList().whereNotNull().toList() ??
|
||||
@ -62,18 +63,22 @@ Future<Track> toYoutubeTrack(
|
||||
|
||||
final trackManifest = await youtube.videos.streams.getManifest(ytVideo.id);
|
||||
|
||||
logger.v(
|
||||
"[YouTube Matched Track] ${ytVideo.title} | ${ytVideo.author} - ${ytVideo.url}",
|
||||
);
|
||||
|
||||
return SpotubeTrack.fromTrack(
|
||||
track: track,
|
||||
ytTrack: ytVideo,
|
||||
// Since Mac OS's & IOS's CodeAudio doesn't support WebMedia
|
||||
// ('audio/webm', 'video/webm' & 'image/webp') thus using 'audio/mpeg'
|
||||
// codec/mimetype for those Platforms
|
||||
track.uri = (Platform.isMacOS || Platform.isIOS
|
||||
ytUri: (Platform.isMacOS || Platform.isIOS
|
||||
? trackManifest.audioOnly
|
||||
.where((info) => info.codec.mimeType == "audio/mp4")
|
||||
.withHighestBitrate()
|
||||
: trackManifest.audioOnly.withHighestBitrate())
|
||||
.url
|
||||
.toString();
|
||||
track.href = ytVideo.url;
|
||||
logger.v(
|
||||
"[YouTube Matched Track] ${ytVideo.title} | ${ytVideo.author} - ${track.href}");
|
||||
return track;
|
||||
.toString(),
|
||||
);
|
||||
}
|
||||
|
@ -1,7 +1,9 @@
|
||||
import 'package:html/dom.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:html/parser.dart';
|
||||
import 'package:spotify/spotify.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:spotube/helpers/getLyrics.dart';
|
||||
import 'package:spotube/models/SpotubeTrack.dart';
|
||||
|
||||
class SubtitleSimple {
|
||||
Uri uri;
|
||||
@ -28,7 +30,7 @@ class LyricSlice {
|
||||
|
||||
const baseUri = "https://www.rentanadviser.com/subtitles";
|
||||
|
||||
Future<SubtitleSimple?> getTimedLyrics(Track track) async {
|
||||
Future<SubtitleSimple?> getTimedLyrics(SpotubeTrack track) async {
|
||||
final artistNames =
|
||||
track.artists?.map((artist) => artist.name!).toList() ?? [];
|
||||
final query = getTitle(
|
||||
@ -41,10 +43,27 @@ Future<SubtitleSimple?> getTimedLyrics(Track track) async {
|
||||
|
||||
final res = await http.get(searchUri);
|
||||
final document = parse(res.body);
|
||||
final topResult =
|
||||
document.querySelector("#tablecontainer table tbody tr td a");
|
||||
final results =
|
||||
document.querySelectorAll("#tablecontainer table tbody tr td a");
|
||||
|
||||
if (topResult == null) return null;
|
||||
final topResult = results
|
||||
.map((result) {
|
||||
final title = result.text.trim().toLowerCase();
|
||||
int points = 0;
|
||||
final hasAllArtists = track.artists
|
||||
?.map((artist) => artist.name!)
|
||||
.every((artist) => title.contains(artist.toLowerCase())) ??
|
||||
false;
|
||||
final hasTrackName = title.contains(track.name!.toLowerCase());
|
||||
final exactYtMatch = title == track.ytTrack.title.toLowerCase();
|
||||
if (exactYtMatch) points = 8;
|
||||
for (final criteria in [hasTrackName, hasAllArtists]) {
|
||||
if (criteria) points++;
|
||||
}
|
||||
return {"result": result, "points": points};
|
||||
})
|
||||
.sorted((a, b) => (b["points"] as int).compareTo(a["points"] as int))
|
||||
.first["result"] as Element;
|
||||
|
||||
final subtitleUri =
|
||||
Uri.parse("$baseUri/${topResult.attributes["href"]}&type=lrc");
|
||||
|
32
lib/models/SpotubeTrack.dart
Normal file
32
lib/models/SpotubeTrack.dart
Normal file
@ -0,0 +1,32 @@
|
||||
import 'package:spotify/spotify.dart';
|
||||
import 'package:youtube_explode_dart/youtube_explode_dart.dart';
|
||||
|
||||
class SpotubeTrack extends Track {
|
||||
Video ytTrack;
|
||||
String ytUri;
|
||||
|
||||
SpotubeTrack.fromTrack({
|
||||
required Track track,
|
||||
required this.ytTrack,
|
||||
required this.ytUri,
|
||||
}) {
|
||||
album = track.album;
|
||||
artists = track.artists;
|
||||
availableMarkets = track.availableMarkets;
|
||||
discNumber = track.discNumber;
|
||||
durationMs = track.durationMs;
|
||||
explicit = track.explicit;
|
||||
externalIds = track.externalIds;
|
||||
externalUrls = track.externalUrls;
|
||||
href = track.href;
|
||||
id = track.id;
|
||||
isPlayable = track.isPlayable;
|
||||
linkedFrom = track.linkedFrom;
|
||||
name = track.name;
|
||||
popularity = track.popularity;
|
||||
previewUrl = track.previewUrl;
|
||||
trackNumber = track.trackNumber;
|
||||
type = track.type;
|
||||
uri = track.uri;
|
||||
}
|
||||
}
|
@ -274,21 +274,21 @@ class Playback extends ChangeNotifier {
|
||||
});
|
||||
}
|
||||
final preferences = ref.read(userPreferencesProvider);
|
||||
final ytTrack = await toYoutubeTrack(
|
||||
final spotubeTrack = await toSpotubeTrack(
|
||||
youtube,
|
||||
track,
|
||||
preferences.ytSearchFormat,
|
||||
);
|
||||
if (setTrackUriById(track.id!, ytTrack.uri!)) {
|
||||
if (setTrackUriById(track.id!, spotubeTrack.ytUri)) {
|
||||
_currentAudioSource =
|
||||
AudioSource.uri(Uri.parse(ytTrack.uri!), tag: tag);
|
||||
AudioSource.uri(Uri.parse(spotubeTrack.ytUri), tag: tag);
|
||||
await player
|
||||
.setAudioSource(
|
||||
_currentAudioSource!,
|
||||
preload: true,
|
||||
)
|
||||
.then((value) {
|
||||
_currentTrack = track;
|
||||
_currentTrack = spotubeTrack;
|
||||
notifyListeners();
|
||||
});
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user