Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added method to fetch lyrics from AZLyrics or Genius as a fallback #1251

Open
wants to merge 11 commits into
base: dev
Choose a base branch
from
66 changes: 38 additions & 28 deletions lib/pages/lyrics/plain_lyrics.dart
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,12 @@ class PlainLyrics extends HookConsumerWidget {
final playlist = ref.watch(ProxyPlaylistNotifier.provider);
final lyricsQuery =
useQueries.lyrics.spotifySynced(ref, playlist.activeTrack);
final azLyricsQuery = useQueries.lyrics.azLyrics(playlist.activeTrack);
final mediaQuery = MediaQuery.of(context);
final textTheme = Theme.of(context).textTheme;

final textZoomLevel = useState<int>(defaultTextZoom);
bool useAZLyrics = false;

return Stack(
children: [
Expand Down Expand Up @@ -75,38 +77,46 @@ class PlainLyrics extends HookConsumerWidget {
if (lyricsQuery.isLoading || lyricsQuery.isRefreshing) {
return const ShimmerLyrics();
} else if (lyricsQuery.hasError) {
return Container(
alignment: Alignment.center,
padding: const EdgeInsets.all(16),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
context.l10n.no_lyrics_available,
style: textTheme.bodyLarge?.copyWith(
color: palette.bodyTextColor,
if (azLyricsQuery.isLoading ||
azLyricsQuery.isRefreshing) {
return const ShimmerLyrics();
} else if (azLyricsQuery.hasError) {
return Container(
alignment: Alignment.center,
padding: const EdgeInsets.all(16),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
context.l10n.no_lyrics_available,
style: textTheme.bodyLarge?.copyWith(
color: palette.bodyTextColor,
),
textAlign: TextAlign.center,
),
textAlign: TextAlign.center,
),
const Gap(26),
const Icon(SpotubeIcons.noLyrics, size: 60),
],
),
);
const Gap(26),
const Icon(SpotubeIcons.noLyrics, size: 60),
],
),
);
} else {
useAZLyrics = true;
}
}

final lyrics =
lyricsQuery.data?.lyrics.mapIndexed((i, e) {
final next =
lyricsQuery.data?.lyrics.elementAtOrNull(i + 1);
if (next != null &&
e.time - next.time >
const Duration(milliseconds: 700)) {
return "${e.text}\n";
}
final lyrics = !useAZLyrics
? lyricsQuery.data?.lyrics.mapIndexed((i, e) {
final next = lyricsQuery.data?.lyrics
.elementAtOrNull(i + 1);
if (next != null &&
e.time - next.time >
const Duration(milliseconds: 700)) {
return "${e.text}\n";
}

return e.text;
}).join("\n");
return e.text;
}).join("\n")
: azLyricsQuery.data;

return AnimatedDefaultTextStyle(
duration: const Duration(milliseconds: 200),
Expand Down
14 changes: 14 additions & 0 deletions lib/services/queries/lyrics.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,20 @@ import 'package:http/http.dart' as http;
class LyricsQueries {
const LyricsQueries();

Query<String, dynamic> azLyrics(Track? track) {
return useQuery<String, dynamic>("azlyrics-query/${track?.id}", () async {
if (track == null) {
throw "No Track Currently";
}
final lyrics = await ServiceUtils.getAZLyrics(
title: track.name!,
artists:
track.artists?.map((s) => s.name).whereNotNull().toList() ?? []);
return lyrics;
});
}


Query<String, dynamic> static(
Track? track,
String geniusAccessToken,
Expand Down
64 changes: 64 additions & 0 deletions lib/utils/service_utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,70 @@ abstract class ServiceUtils {
return results;
}

static Future<String?> getAZLyrics(
{required String title, required List<String> artists}) async {
const Map<String, String> headers = {
HttpHeaders.userAgentHeader:
"Mozilla/5.0 (Linux i656 ; en-US) AppleWebKit/601.49 (KHTML, like Gecko) Chrome/51.0.1145.334 Safari/600"
};

//Will throw error 400 when you request the script without the host header
const Map<String, String> headersForScript = {
HttpHeaders.userAgentHeader:
"Mozilla/5.0 (Linux i656 ; en-US) AppleWebKit/601.49 (KHTML, like Gecko) Chrome/51.0.1145.334 Safari/600",
HttpHeaders.hostHeader: "www.azlyrics.com",
Spectre-hidN marked this conversation as resolved.
Show resolved Hide resolved
};

final azLyricsGeoScript = await http.get(
Uri.parse("https://www.azlyrics.com/geo.js"),
headers: headersForScript);

RegExp scriptValueRegex = RegExp(r'\.setAttribute\("value", "(.*)"\);');
RegExp scriptNameRegex = RegExp(r'\.setAttribute\("name", "(.*)"\);');
final String? v =
scriptValueRegex.firstMatch(azLyricsGeoScript.body)?.group(1);
final String? x =
scriptNameRegex.firstMatch(azLyricsGeoScript.body)?.group(1);

debugPrint("getAZLyrics -> Additional URL params: $x=$v");

final suggestionUrl = Uri.parse(
"https://search.azlyrics.com/suggest.php?q=${title.replaceAll(RegExp(r"(\(.*\))"), "")} ${artists[0]}&${x.toString()}=${v.toString()}");

final searchResponse = await http.get(suggestionUrl, headers: headers);
if (searchResponse.statusCode != 200) {
throw "searchResponse = ${searchResponse.statusCode}";
}

final Map searchResult = jsonDecode(searchResponse.body);

String bestLyricsURL;

try {
bestLyricsURL = searchResult["songs"][0]["url"];
debugPrint("getAZLyrics -> bestLyricsURL: $bestLyricsURL");
} catch (e) {
throw "No best Lyrics URL";
}

final lyricsResponse =
await http.get(Uri.parse(bestLyricsURL), headers: headers);

if (lyricsResponse.statusCode != 200) {
throw "lyricsResponse = ${lyricsResponse.statusCode}";
}

var document = parser.parse(lyricsResponse.body);
var lyricsDiv = document.querySelectorAll(
"body > div.container.main-page > div.row > div.col-xs-12.col-lg-8.text-center > div");

if (lyricsDiv.isEmpty) throw "lyricsDiv is empty";

final String lyrics = lyricsDiv[4].text;

return lyrics.trim();
}

@Deprecated("In favor spotify lyrics api, this isn't needed anymore")
static Future<String?> getLyrics(
String title,
Expand Down