Example #1
0
        public R <PlayResource> GetResourceById(AudioResource resource)
        {
            var result = ValidateUri(resource.ResourceId);

            if (!result)
            {
                return(result.Error);
            }
            else
            {
                var           resData = result.Value;
                AudioResource finalResource;
                if (resource.ResourceTitle != null)
                {
                    finalResource = resource;
                }
                else if (!string.IsNullOrWhiteSpace(resData.Title))
                {
                    finalResource = resource.WithName(resData.Title);
                }
                else
                {
                    finalResource = resource.WithName(resource.ResourceId);
                }
                return(new PlayResource(resData.FullUri, finalResource));
            }
        }
Example #2
0
        private R <PlayResource> YoutubeDlWrapped(AudioResource resource)
        {
            string title = null;
            string url   = null;

            Log.Write(Log.Level.Debug, "YT Ruined!");

            var result = YoutubeDlHelper.FindAndRunYoutubeDl(resource.ResourceId);

            if (!result.Ok)
            {
                return(result.Message);
            }

            var response = result.Value;

            title = response.Item1;
            var urlOptions = response.Item2;

            if (urlOptions.Count == 1)
            {
                url = urlOptions[0];
            }
            else if (urlOptions.Count >= 1)
            {
                Uri[] uriList   = urlOptions.Select(s => new Uri(s)).ToArray();
                Uri   bestMatch = uriList
                                  .FirstOrDefault(u => HttpUtility.ParseQueryString(u.Query)
                                                  .GetValues("mime")
                                                  .Any(x => x.StartsWith("audio", StringComparison.OrdinalIgnoreCase)));
                url = (bestMatch ?? uriList[0]).OriginalString;
            }

            if (string.IsNullOrEmpty(title) || string.IsNullOrEmpty(url))
            {
                return("No youtube-dl response");
            }

            Log.Write(Log.Level.Debug, "YT Saved!");
            return(new PlayResource(url, resource.WithName(title)));
        }
Example #3
0
        private static R <PlayResource> YoutubeDlWrapped(AudioResource resource)
        {
            Log.Debug("Falling back to youtube-dl!");

            var result = YoutubeDlHelper.FindAndRunYoutubeDl(resource.ResourceId);

            if (!result.Ok)
            {
                return(result.Error);
            }

            var response   = result.Value;
            var title      = response.title;
            var urlOptions = response.links;

            string url = null;

            if (urlOptions.Count == 1)
            {
                url = urlOptions[0];
            }
            else if (urlOptions.Count >= 1)
            {
                Uri[] uriList   = urlOptions.Select(s => new Uri(s)).ToArray();
                Uri   bestMatch = uriList
                                  .FirstOrDefault(u => HttpUtility.ParseQueryString(u.Query)
                                                  .GetValues("mime")?
                                                  .Any(x => x.StartsWith("audio", StringComparison.OrdinalIgnoreCase)) ?? false);
                url = (bestMatch ?? uriList[0]).OriginalString;
            }

            if (string.IsNullOrEmpty(title) || string.IsNullOrEmpty(url))
            {
                return("No youtube-dl response");
            }

            Log.Debug("youtube-dl succeeded!");
            return(new PlayResource(url, resource.WithName(title)));
        }
Example #4
0
        public R <PlayResource> GetResourceById(AudioResource resource)
        {
            if (!WebWrapper.DownloadString(out string resulthtml, new Uri($"http://www.youtube.com/get_video_info?video_id={resource.ResourceId}&el=info")))
            {
                return(RResultCode.NoConnection.ToString());
            }

            var videoTypes = new List <VideoData>();
            NameValueCollection dataParse = HttpUtility.ParseQueryString(resulthtml);

            string videoDataUnsplit = dataParse["url_encoded_fmt_stream_map"];

            if (videoDataUnsplit != null)
            {
                string[] videoData = videoDataUnsplit.Split(',');

                foreach (string vdat in videoData)
                {
                    NameValueCollection videoparse = HttpUtility.ParseQueryString(vdat);

                    string vLink = videoparse["url"];
                    if (vLink == null)
                    {
                        continue;
                    }

                    string vType = videoparse["type"];
                    if (vType == null)
                    {
                        continue;
                    }

                    string vQuality = videoparse["quality"];
                    if (vQuality == null)
                    {
                        continue;
                    }

                    var vt = new VideoData()
                    {
                        Link              = vLink,
                        Codec             = GetCodec(vType),
                        Qualitydesciption = vQuality
                    };
                    videoTypes.Add(vt);
                }
            }

            videoDataUnsplit = dataParse["adaptive_fmts"];
            if (videoDataUnsplit != null)
            {
                string[] videoData = videoDataUnsplit.Split(',');

                foreach (string vdat in videoData)
                {
                    NameValueCollection videoparse = HttpUtility.ParseQueryString(vdat);

                    string vType = videoparse["type"];
                    if (vType == null)
                    {
                        continue;
                    }

                    bool audioOnly = false;
                    if (vType.StartsWith("video/", StringComparison.Ordinal))
                    {
                        continue;
                    }
                    else if (vType.StartsWith("audio/", StringComparison.Ordinal))
                    {
                        audioOnly = true;
                    }

                    string vLink = videoparse["url"];
                    if (vLink == null)
                    {
                        continue;
                    }

                    var vt = new VideoData()
                    {
                        Codec             = GetCodec(vType),
                        Qualitydesciption = vType,
                        Link = vLink
                    };
                    if (audioOnly)
                    {
                        vt.AudioOnly = true;
                    }
                    else
                    {
                        vt.VideoOnly = true;
                    }
                    videoTypes.Add(vt);
                }
            }

            // Validation Process

            if (videoTypes.Count <= 0)
            {
                return(RResultCode.YtNoVideosExtracted.ToString());
            }

            int codec = SelectStream(videoTypes);

            if (codec < 0)
            {
                return("No playable codec found");
            }

            var result = ValidateMedia(videoTypes[codec]);

            if (!result)
            {
                if (string.IsNullOrWhiteSpace(data.YoutubedlPath))
                {
                    return(result.Message);
                }

                return(YoutubeDlWrapped(resource));
            }

            return(new PlayResource(videoTypes[codec].Link, resource.ResourceTitle != null ? resource : resource.WithName(dataParse["title"] ?? $"<YT - no title : {resource.ResourceTitle}>")));
        }
Example #5
0
        public R <PlayResource> GetResourceById(AudioResource resource)
        {
            var channel = resource.ResourceId;

            // request api token
            string jsonResponse;

            if (!WebWrapper.DownloadString(out jsonResponse, new Uri($"http://api.twitch.tv/api/channels/{channel}/access_token"), new Tuple <string, string>("Client-ID", twitchClientId)))
            {
                return(RResultCode.NoConnection.ToString());
            }

            var jsonDict = (Dictionary <string, object>)Util.Serializer.DeserializeObject(jsonResponse);

            // request m3u8 file
            var token = Uri.EscapeUriString(jsonDict["token"].ToString());
            var sig   = jsonDict["sig"];
            // guaranteed to be random, chosen by fair dice roll.
            var    random = 4;
            string m3u8;

            if (!WebWrapper.DownloadString(out m3u8, new Uri($"http://usher.twitch.tv/api/channel/hls/{channel}.m3u8?player=twitchweb&&token={token}&sig={sig}&allow_audio_only=true&allow_source=true&type=any&p={random}")))
            {
                return(RResultCode.NoConnection.ToString());
            }

            // parse m3u8 file
            var dataList = new List <StreamData>();

            using (var reader = new System.IO.StringReader(m3u8))
            {
                var header = reader.ReadLine();
                if (string.IsNullOrEmpty(header) || header != "#EXTM3U")
                {
                    return(RResultCode.TwitchMalformedM3u8File.ToString());
                }

                while (true)
                {
                    var blockInfo = reader.ReadLine();
                    if (string.IsNullOrEmpty(blockInfo))
                    {
                        break;
                    }

                    var match = M3U8ExtMatch.Match(blockInfo);
                    if (!match.Success)
                    {
                        continue;
                    }

                    switch (match.Groups[1].Value)
                    {
                    case "EXT-X-TWITCH-INFO": break;                             // Ignore twitch info line

                    case "EXT-X-MEDIA":
                        string streamInfo = reader.ReadLine();
                        Match  infoMatch;
                        if (string.IsNullOrEmpty(streamInfo) ||
                            !(infoMatch = M3U8ExtMatch.Match(streamInfo)).Success ||
                            infoMatch.Groups[1].Value != "EXT-X-STREAM-INF")
                        {
                            return(RResultCode.TwitchMalformedM3u8File.ToString());
                        }

                        var streamData = new StreamData();
                        // #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=128000,CODECS="mp4a.40.2",VIDEO="audio_only"
                        for (int i = 0; i < infoMatch.Groups[3].Captures.Count; i++)
                        {
                            string key   = infoMatch.Groups[4].Captures[i].Value.ToUpper(CultureInfo.InvariantCulture);
                            string value = infoMatch.Groups[5].Captures[i].Value;

                            switch (key)
                            {
                            case "BANDWIDTH": streamData.Bandwidth = int.Parse(value, CultureInfo.InvariantCulture); break;

                            case "CODECS": streamData.Codec = TextUtil.StripQuotes(value); break;

                            case "VIDEO":
                                StreamQuality quality;
                                if (Enum.TryParse(TextUtil.StripQuotes(value), out quality))
                                {
                                    streamData.QualityType = quality;
                                }
                                else
                                {
                                    streamData.QualityType = StreamQuality.unknown;
                                }
                                break;
                            }
                        }

                        streamData.Url = reader.ReadLine();
                        dataList.Add(streamData);
                        break;

                    default: break;
                    }
                }
            }

            // Validation Process

            if (dataList.Count <= 0)
            {
                return(RResultCode.TwitchNoStreamsExtracted.ToString());
            }

            int codec = SelectStream(dataList);

            if (codec < 0)
            {
                return("The stream has no audio_only version.");
            }

            return(new PlayResource(dataList[codec].Url, resource.ResourceTitle != null ? resource : resource.WithName($"Twitch channel: {channel}")));
        }
Example #6
0
        public R <PlayResource, LocalStr> GetResourceById(AudioResource resource)
        {
            var channel = resource.ResourceId;

            // request api token
            if (!WebWrapper.DownloadString(out string jsonResponse, new Uri($"https://api.twitch.tv/api/channels/{channel}/access_token"), ("Client-ID", TwitchClientId)))
            {
                return(new LocalStr(strings.error_net_no_connection));
            }

            var jObj = JObject.Parse(jsonResponse);

            // request m3u8 file
            var tokenResult = jObj.TryCast <string>("token");
            var sigResult   = jObj.TryCast <string>("sig");

            if (!tokenResult.Ok || !sigResult.Ok)
            {
                return(new LocalStr(strings.error_media_internal_invalid + " (tokenResult|sigResult)"));
            }
            var token = Uri.EscapeUriString(tokenResult.Value);
            var sig   = sigResult.Value;
            // guaranteed to be random, chosen by fair dice roll.
            const int random = 4;

            if (!WebWrapper.DownloadString(out string m3u8, new Uri($"http://usher.twitch.tv/api/channel/hls/{channel}.m3u8?player=twitchweb&&token={token}&sig={sig}&allow_audio_only=true&allow_source=true&type=any&p={random}")))
            {
                return(new LocalStr(strings.error_net_no_connection));
            }

            // parse m3u8 file
            var dataList = new List <StreamData>();

            using (var reader = new System.IO.StringReader(m3u8))
            {
                var header = reader.ReadLine();
                if (string.IsNullOrEmpty(header) || header != "#EXTM3U")
                {
                    return(new LocalStr(strings.error_media_internal_missing + " (m3uHeader)"));
                }

                while (true)
                {
                    var blockInfo = reader.ReadLine();
                    if (string.IsNullOrEmpty(blockInfo))
                    {
                        break;
                    }

                    var match = M3U8ExtMatch.Match(blockInfo);
                    if (!match.Success)
                    {
                        continue;
                    }

                    switch (match.Groups[1].Value)
                    {
                    case "EXT-X-TWITCH-INFO": break;                     // Ignore twitch info line

                    case "EXT-X-MEDIA":
                        string streamInfo = reader.ReadLine();
                        Match  infoMatch;
                        if (string.IsNullOrEmpty(streamInfo) ||
                            !(infoMatch = M3U8ExtMatch.Match(streamInfo)).Success ||
                            infoMatch.Groups[1].Value != "EXT-X-STREAM-INF")
                        {
                            return(new LocalStr(strings.error_media_internal_missing + " (m3uStream)"));
                        }

                        var streamData = new StreamData();
                        // #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=128000,CODECS="mp4a.40.2",VIDEO="audio_only"
                        for (int i = 0; i < infoMatch.Groups[3].Captures.Count; i++)
                        {
                            string key   = infoMatch.Groups[4].Captures[i].Value.ToUpper(CultureInfo.InvariantCulture);
                            string value = infoMatch.Groups[5].Captures[i].Value;

                            switch (key)
                            {
                            case "BANDWIDTH": streamData.Bandwidth = int.Parse(value, CultureInfo.InvariantCulture); break;

                            case "CODECS": streamData.Codec = TextUtil.StripQuotes(value); break;

                            case "VIDEO":
                                streamData.QualityType = Enum.TryParse(TextUtil.StripQuotes(value), out StreamQuality quality)
                                                                 ? quality
                                                                 : StreamQuality.unknown; break;
                            }
                        }

                        streamData.Url = reader.ReadLine();
                        dataList.Add(streamData);
                        break;
                    }
                }
            }

            // Validation Process

            if (dataList.Count <= 0)
            {
                return(new LocalStr(strings.error_media_no_stream_extracted));
            }

            int codec = SelectStream(dataList);

            if (codec < 0)
            {
                return(new LocalStr(strings.error_media_no_stream_extracted));
            }

            return(new PlayResource(dataList[codec].Url, resource.ResourceTitle != null ? resource : resource.WithName($"Twitch channel: {channel}")));
        }
Example #7
0
        private R <PlayResource, LocalStr> ResolveResourceInternal(AudioResource resource)
        {
            if (!WebWrapper.DownloadString(out string resulthtml, new Uri($"https://www.youtube.com/get_video_info?video_id={resource.ResourceId}")))
            {
                return(new LocalStr(strings.error_net_no_connection));
            }

            var videoTypes = new List <VideoData>();
            var dataParse  = ParseQueryString(resulthtml);

            if (dataParse.TryGetValue("url_encoded_fmt_stream_map", out var videoDataUnsplit))
            {
                string[] videoData = videoDataUnsplit[0].Split(',');

                foreach (string vdat in videoData)
                {
                    var videoparse = ParseQueryString(vdat);

                    if (!videoparse.TryGetValue("url", out var vLink))
                    {
                        continue;
                    }

                    if (!videoparse.TryGetValue("type", out var vType))
                    {
                        continue;
                    }

                    if (!videoparse.TryGetValue("quality", out var vQuality))
                    {
                        continue;
                    }

                    var vt = new VideoData()
                    {
                        Link              = vLink[0],
                        Codec             = GetCodec(vType[0]),
                        Qualitydesciption = vQuality[0]
                    };
                    videoTypes.Add(vt);
                }
            }

            if (dataParse.TryGetValue("adaptive_fmts", out videoDataUnsplit))
            {
                string[] videoData = videoDataUnsplit[0].Split(',');

                foreach (string vdat in videoData)
                {
                    var videoparse = ParseQueryString(vdat);

                    if (!videoparse.TryGetValue("type", out var vTypeArr))
                    {
                        continue;
                    }
                    var vType = vTypeArr[0];

                    bool audioOnly = false;
                    if (vType.StartsWith("video/", StringComparison.Ordinal))
                    {
                        continue;
                    }
                    else if (vType.StartsWith("audio/", StringComparison.Ordinal))
                    {
                        audioOnly = true;
                    }

                    if (!videoparse.TryGetValue("url", out var vLink))
                    {
                        continue;
                    }

                    var vt = new VideoData()
                    {
                        Codec             = GetCodec(vType),
                        Qualitydesciption = vType,
                        Link = vLink[0]
                    };
                    if (audioOnly)
                    {
                        vt.AudioOnly = true;
                    }
                    else
                    {
                        vt.VideoOnly = true;
                    }
                    videoTypes.Add(vt);
                }
            }

            // Validation Process

            if (videoTypes.Count <= 0)
            {
                return(new LocalStr(strings.error_media_no_stream_extracted));
            }

            int codec = SelectStream(videoTypes);

            if (codec < 0)
            {
                return(new LocalStr(strings.error_media_no_stream_extracted));
            }

            var result = ValidateMedia(videoTypes[codec]);

            if (!result.Ok)
            {
                return(result.Error);
            }

            var title = dataParse.TryGetValue("title", out var titleArr)
                                ? titleArr[0]
                                : $"<YT - no title : {resource.ResourceTitle}>";

            return(new PlayResource(videoTypes[codec].Link, resource.ResourceTitle != null ? resource : resource.WithName(title)));
        }