import 'dart:convert'; import 'package:collection/collection.dart'; import 'package:spotube/services/youtube_engine/youtube_engine.dart'; import 'package:spotube/utils/platform.dart'; import 'package:youtube_explode_dart/youtube_explode_dart.dart'; import 'package:yt_dlp_dart/yt_dlp_dart.dart'; // ignore: depend_on_referenced_packages import 'package:http_parser/http_parser.dart'; class YtDlpEngine implements YouTubeEngine { StreamManifest _parseFormats(List formats, videoId) { final audioOnlyStreams = formats .where((f) => f["resolution"] == "audio only") .sorted((a, b) => a["quality"] > b["quality"] ? 1 : -1) .map((f) { final filesize = f["filesize"] ?? f["filesize_approx"]; return AudioOnlyStreamInfo( VideoId(videoId), 0, Uri.parse(f["url"]), StreamContainer.parse( f["container"]?.replaceAll("_dash", "").replaceAll("m4a", "mp4") ?? (f["protocol"] == "m3u8_native" ? "m3u8" : "mp4"), ), filesize != null ? FileSize(filesize) : FileSize.unknown, Bitrate( (((f["abr"] ?? f["tbr"] ?? 0) * 1000) as num).toInt(), ), f["acodec"] ?? "aac", f["format_note"], [], MediaType.parse( "audio/${f["audio_ext"]}", ), null, ); }); return StreamManifest(audioOnlyStreams); } Video _parseInfo(Map info) { final publishDate = info["upload_date"] != null ? DateTime.fromMillisecondsSinceEpoch( int.parse(info["upload_date"]) * 1000, ) : DateTime.now(); return Video( VideoId(info["id"]), info["title"], info["channel"], ChannelId(info["channel_id"]), publishDate, info["upload_date"] as String? ?? DateTime.now().toString(), publishDate, info["description"] ?? "", Duration(seconds: (info["duration"] as num).toInt()), ThumbnailSet(info["id"]), info["tags"]?.cast() ?? [], Engagement( info["view_count"], info["like_count"], null, ), info["is_live"] ?? false, ); } static bool get isAvailableForPlatform => kIsDesktop; static Future isInstalled() async { return isAvailableForPlatform && await YtDlp.instance.checkAvailableInPath(); } @override Future getStreamManifest(String videoId) async { final formats = await YtDlp.instance.extractInfo( "https://www.youtube.com/watch?v=$videoId", formatSpecifiers: "%(formats)j", extraArgs: [ "--no-check-certificate", "--geo-bypass", "--quiet", "--ignore-errors" ], ) as List; return _parseFormats(formats, videoId); } @override Future