spotube/lib/helpers/search_youtube.dart
Kingkor Roy Tirtho 799e13c376 Flutter app added
2022-01-03 10:27:25 +06:00

146 lines
4.4 KiB
Dart

import 'dart:convert';
import 'package:spotube/models/YoutubeTrack.dart';
import 'package:http/http.dart';
import 'package:spotube/models/YoutubeSearchResult.dart';
import 'package:spotify/spotify.dart';
import 'package:youtube_explode_dart/youtube_explode_dart.dart';
Future<List<YtSearchResult>> searchYoutube(String query,
{int limit = 20}) async {
try {
if (query.trim().isEmpty) throw Exception("query can't be blank");
Client client = Client();
Uri url = Uri(
scheme: "https",
host: "www.youtube.com",
path: "results",
queryParameters: {
"search_query": query,
},
);
Response page = await client.get(url);
return parseSearch(page.body, limit);
} catch (e) {
throw e;
}
}
List<YtSearchResult> parseSearch(String html, int limit) {
List<YtSearchResult> results = [];
List<dynamic> dataInfo = [];
bool scrapped = false;
try {
var initDoc = html.split("var ytInitialData = ");
String data = initDoc[1].split(";</script><script")[0];
html = data;
} catch (e) {
print("[Error extracting ytInitialData]: $e");
}
try {
var decodedHtml = jsonDecode(html)["contents"]
["twoColumnSearchResultsRenderer"]["primaryContents"]
["sectionListRenderer"]["contents"]
.first["itemSectionRenderer"]["contents"];
dataInfo = decodedHtml;
scrapped = true;
} catch (e) {
print("[Error accessing itemSectionRenderer.contents]: $e");
}
if (!scrapped) {
try {
dataInfo = jsonDecode(html
.split("{\"itemSectionRenderer\":")
.last
.split("},{\"continuationItemRenderer\":{")
.first)["contents"];
scrapped = true;
} catch (err) {
print(
"[Error in try again <accessing itemSectionRenderer.contents>]: $err");
}
}
// failure
if (!scrapped) {
return [];
}
for (var data in dataInfo) {
try {
YtSearchResult result;
data = data["videoRenderer"];
if (data == null) continue;
result = YtSearchResult(
id: data["videoId"],
title: data["title"]["runs"].first["text"],
duration: "unavailable",
thumbnail: data["thumbnail"]["thumbnails"].last["url"],
channel: YtChannel(
id: data["ownerText"]["runs"].first["navigationEndpoint"]
["browseEndpoint"]["browseId"],
name: data["ownerText"]["runs"].first["text"],
url: "https://www.youtube.com" +
data["ownerText"]["runs"].first["navigationEndpoint"]
["browseEndpoint"]["canonicalBaseUrl"],
),
uploadDate: "unavailable",
viewCount: "unavailable",
type: "video");
results.add(result);
} catch (e) {
print("[Error in construction of result]: $e");
}
}
return results;
}
Future<Track> findYtVariant(Track track) async {
YoutubeExplode youtube = YoutubeExplode();
double includePercentage(String src, List matches) {
int count = 0;
matches.forEach((match) => {
if (src.contains(match.toString())) {count++}
});
return (count / matches.length) * 100;
}
var artistsName = track.artists?.map((ar) => ar.name).toList() ?? [];
String queryString =
"${artistsName.first} - ${track.name}${artistsName.length > 1 ? " feat. ${artistsName.sublist(1).join(" ")}" : ""}";
SearchList videos = await youtube.search.getVideos(queryString);
List<YoutubeRelevantTrack> tracksWithRelevance =
await Future.wait(videos.map((video) async {
double matchPercentage = includePercentage(video.title, [
track.name,
...artistsName,
]);
Channel channel = await youtube.channels.get(video.channelId);
bool sameChannel = (artistsName.first != null
? channel.title.contains(artistsName.first!)
: false) ||
(artistsName.first?.contains(channel.title) ?? false);
return YoutubeRelevantTrack(
url: video.url,
matchPercentage: matchPercentage,
sameChannel: sameChannel,
id: video.id.value);
}));
tracksWithRelevance.sort((a, b) {
return a.matchPercentage.compareTo(b.matchPercentage);
});
List<YoutubeRelevantTrack> sameChannelTracks =
tracksWithRelevance.where((tr) => tr.sameChannel).toList();
track.uri = (sameChannelTracks.isNotEmpty
? sameChannelTracks.first.url
: tracksWithRelevance.isNotEmpty
? tracksWithRelevance.first.url
: videos.first.url);
return track;
}