[
  {
    "path": ".gitattributes",
    "content": "###############################################################################\n# Set default behavior to automatically normalize line endings.\n###############################################################################\n* text=auto\n\n###############################################################################\n# Set default behavior for command prompt diff.\n#\n# This is need for earlier builds of msysgit that does not have it on by\n# default for csharp files.\n# Note: This is only used by command line\n###############################################################################\n#*.cs     diff=csharp\n\n###############################################################################\n# Set the merge driver for project and solution files\n#\n# Merging from the command prompt will add diff markers to the files if there\n# are conflicts (Merging from VS is not affected by the settings below, in VS\n# the diff markers are never inserted). Diff markers may cause the following \n# file extensions to fail to load in VS. An alternative would be to treat\n# these files as binary and thus will always conflict and require user\n# intervention with every merge. To do so, just uncomment the entries below\n###############################################################################\n#*.sln       merge=binary\n#*.csproj    merge=binary\n#*.vbproj    merge=binary\n#*.vcxproj   merge=binary\n#*.vcproj    merge=binary\n#*.dbproj    merge=binary\n#*.fsproj    merge=binary\n#*.lsproj    merge=binary\n#*.wixproj   merge=binary\n#*.modelproj merge=binary\n#*.sqlproj   merge=binary\n#*.wwaproj   merge=binary\n\n###############################################################################\n# behavior for image files\n#\n# image files are treated as binary by default.\n###############################################################################\n#*.jpg   binary\n#*.png   binary\n#*.gif   binary\n\n###############################################################################\n# diff behavior for common document formats\n# \n# Convert binary document formats to text before diffing them. This feature\n# is only available from the command line. Turn it on by uncommenting the \n# entries below.\n###############################################################################\n#*.doc   diff=astextplain\n#*.DOC   diff=astextplain\n#*.docx  diff=astextplain\n#*.DOCX  diff=astextplain\n#*.dot   diff=astextplain\n#*.DOT   diff=astextplain\n#*.pdf   diff=astextplain\n#*.PDF   diff=astextplain\n#*.rtf   diff=astextplain\n#*.RTF   diff=astextplain\n"
  },
  {
    "path": ".gitignore",
    "content": "# Binary storage\nbin/\nobj/\nlib/\n\n# Binary files\n*.db\n*.dll\n*.exe\n\n# NuGet packages\n*.nupkg\n**/packages/*\n!**/packages/build/\n#!**/packages/repositories.config\n\n# IDE clutter\n.vs/\n*.suo\n*.user\n*.userprefs\n\n# Profiling sessions\n*.vspx\n*.psess\n\n# Miscellaneous\n*.cache\n"
  },
  {
    "path": "LICENSE",
    "content": "BSD 2-Clause License\n\nCopyright (c) 2021, OMANSAK\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n* Redistributions of source code must retain the above copyright notice, this\n  list of conditions and the following disclaimer.\n\n* Redistributions in binary form must reproduce the above copyright notice,\n  this list of conditions and the following disclaimer in the documentation\n  and/or other materials provided with the distribution.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "README.md",
    "content": "# libvideo\n\n![icon](icons/icon_200.png)\n\n[![NuGet](https://img.shields.io/nuget/dt/VideoLibrary.svg)](https://www.nuget.org/packages/VideoLibrary)\n[![NuGet](https://img.shields.io/nuget/v/VideoLibrary.svg)](https://www.nuget.org/packages/VideoLibrary)\n[![license](https://img.shields.io/github/license/i3arnon/libvideo.svg)](LICENSE)\n[![Join the chat at https://discord.gg/SERVhPp](https://user-images.githubusercontent.com/7288322/34429152-141689f8-ecb9-11e7-8003-b5a10a5fcb29.png)](https://discord.gg/SERVhPp)\n\nlibvideo (aka VideoLibrary) is a modern .NET library for downloading YouTube videos. It is portable to most platforms and is very lightweight.\n\n## Documentation\n- [Documentation](docs/README.md)\n- [Example Application](samples/Valks/Valks/Program.cs)\n- [Fast Downloader with Chunks](/src/libvideo.debug/CustomYoutubeClient.cs)\n## Installation\n\nYou can grab a copy of the library [on NuGet](https://www.nuget.org/packages/VideoLibrary) by running:\n\n    Install-Package VideoLibrary\n\nAlternatively, you can try building the repo if you like your assemblies extra-fresh.\n\n## Supported Platforms\n| Platform / Application                  | Minimum Supported Version | Notes |\n|-----------------------------------------|---------------------------|-------|\n| **.NET / .NET Core**                    | .NET Core 2.0+ / .NET 5+  | Fully supports .NET Standard 2.0 libraries. |\n| **.NET Framework**                      | 4.6.1+                    | Supports .NET Standard 2.0 (does NOT support 2.1). |\n| **Mono**                                | 5.4+                      | Official support for .NET Standard 2.0. |\n| **Unity**                               | 2018.1+                   | Requires scripting runtime set to .NET 4.x Equivalent. |\n| **Xamarin.iOS**                         | 10.14+                    | Supports .NET Standard 2.0 libraries. |\n| **Xamarin.Android**                     | 8.0+                      | Supports .NET Standard 2.0 libraries. |\n| **Xamarin.Mac**                         | 3.8+                      | Supports .NET Standard 2.0 libraries. |\n| **Universal Windows Platform (UWP)**    | 10.0.16299+               | Supported starting from this Windows 10 version. |\n\n## Getting Started\n\nHere's a small sample to help you get familiar with libvideo:\n\n```csharp\nusing VideoLibrary;\n\nvoid SaveVideoToDisk(string link)\n{\n    var youTube = YouTube.Default; // starting point for YouTube actions\n    var video = youTube.GetVideo(link); // gets a Video object with info about the video\n    File.WriteAllBytes(@\"C:\\\" + video.FullName, video.GetBytes());\n}\n```\n\nOr, if you use Visual Basic:\n\n```vbnet\nImports VideoLibrary\n\nSub SaveVideoToDisk(ByVal link As String)\n     Dim video = YouTube.Default.GetVideo(link)\n     File.WriteAllBytes(\"C:\\\" & video.FullName, video.GetBytes())\nEnd Sub\n```\n\nIf you'd like to check out some more of our features, take a look at our [docs](docs/README.md). You can also refer to our [example application](samples/Valks/Valks/Program.cs) (named Valks, yes, I know, it's a silly name) if you're looking for a more comprehensive sample.\n\n## License\n\nlibvideo is licensed under the [BSD 2-clause license](LICENSE).\n"
  },
  {
    "path": "changelog.md",
    "content": "# Changelog\n\n## v3.3.0\n\n- Fix Youtube: 403 Forbidden errors ([#307](https://github.com/omansak/libvideo/issues/307))"
  },
  {
    "path": "docs/README.md",
    "content": "# Documentation\n\nHere you'll find a more in-depth explanation of our API.\n\nTo get information about a video:\n\n```csharp\nstring uri = \"https://www.youtube.com/watch?v=vPto6XpRq-U\";\nvar youTube = YouTube.Default;\nvar video = youTube.GetVideo(uri);\n\nstring title = video.Title;\nVideoInfo info = video.Info; // (Title,Author,LengthSeconds)\nstring fileExtension = video.FileExtension;\nstring fullName = video.FullName; // same thing as title + fileExtension\nint resolution = video.Resolution;\n\n// etc.\n```\n\nYou can download it like this:\n\n```csharp\nbyte[] bytes = video.GetBytes();\nvar stream = video.Stream();\n```\n\nAnd save it to a file:\n\n```csharp\nFile.WriteAllBytes(@\"C:\\\" + fullName, bytes);\n```\n\n---\n\n## Advanced\n\nYouTube exposes multiple videos for each URL- e.g. when you change the resolution of a video, you're actually watching a different video. libvideo supports downloading multiple of them:\n\n```csharp\nvar videos = youTube.GetAllVideos(uri);\n```\n\nSome Informations of Video\n```csharp\nvar videoInfos = Client.For(YouTube.Default).GetAllVideosAsync(uri).GetAwaiter().GetResult();\nvar resolutions = videoInfos.Where(j => j.AdaptiveKind == AdaptiveKind.Video).Select(j => j.Resolution);\nvar bitRates = videoInfos.Where(j => j.AdaptiveKind == AdaptiveKind.Audio).Select(j => j.AudioBitrate);\nvar unknownFormats = videoInfos.Where(j => j.AdaptiveKind == AdaptiveKind.None).Select(j => j.Resolution);\n```\n\nGet specific resolution, bitrate, format\n```csharp\nvar youTube = YouTube.Default; // starting point for YouTube actions\nvar videoInfos = youTube.GetAllVideosAsync(link).GetAwaiter().GetResult();\nvar maxResolution = videoInfos.First(i => i.Resolution == videoInfos.Max(j => j.Resolution));\nvar minBitrate = videoInfos.First(i => i.AudioBitrate == videoInfos.Min(j => j.AudioBitrate));\nvar audioFormat = videoInfos.First(i => i.AudioFormat == AudioFormat.Aac);\nvar videoFormat = videoInfos.First(i => i.Format == VideoFormat.Mp4);\nvar adaptive = videoInfos.First(i => i.IsAdaptive);\n```\n\nWe also have full support for async:\n\n```csharp\nvar video = await youTube.GetVideoAsync(uri);\nvar videos = await youTube.GetAllVideosAsync(uri);\nvar contents = await video.GetBytesAsync();\n```\n\nIn addition, you should be aware that for every time you visit YouTube a new `HttpClient` is created and disposed. To avoid this, use the `Client` class:\n\n```csharp\nusing (var cli = Client.For(new YouTube()))\n{\n    cli.GetVideo(uri);\n    cli.GetVideo(\"[some other video]\"); // HttpClient is reused here\n}\n```\n\nLikewise, if you'd like to reuse `HttpClients` when downloading a video, use `VideoClient`.\n\n```csharp\nusing (var cli = new VideoClient())\n{\n    cli.GetBytes(video);\n    await cli.StreamAsync(video);\n}\n```\n\n### Custom HTTP Configurations\n\nIf you need to custom-configure the `HttpClient` for some reason- maybe you need to increase the timeout length, or add credentials, or use [a different message handler](https://github.com/paulcbetts/ModernHttpClient)- fear not. Simply derive your class from `YouTube` and configure as necessary:\n\n```csharp\nclass MyYouTube : YouTube\n{\n    protected override HttpMessageHandler MakeHandler()\n    {\n        return new BlahBlahMessageHandler();\n    }\n    \n    protected override HttpClient MakeClient(HttpMessageHandler handler)\n    {\n        return new HttpClient(handler)\n        {\n            Timeout = TimeSpan.FromSeconds(12345);\n        };\n    }\n}\n```\n\nUse like so:\n\n```csharp\nvar youTube = new MyYouTube();\nyouTube.GetVideo(\"foo\");\n\n// --- OR ---\n\nusing (var cli = Client.For(new MyYouTube()))\n{\n    // ...\n}\n```\n\nNote that this does not change the HTTP behavior when downloading the video itself. To do that, inherit from `VideoClient`:\n\n```csharp\nclass MyVideoClient : VideoClient\n{\n    protected override HttpMessageHandler MakeHandler() { ... }\n    protected override HttpClient MakeClient(HttpMessageHandler handler) { ... }\n}\n```\n\nAnd to use it:\n\n```csharp\nusing (var cli = new MyVideoClient())\n{\n    byte[] contents = cli.GetBytes(video);\n}\n```\n\nSample Progress\n```csharp\nclass Program\n    {\n        static async Task Main(string[] args)\n        {\n\n            var youtube = YouTube.Default;\n            var video = youtube.GetVideo(\"https://www.youtube.com/watch?v=GNxEEyOMce4\");\n            var client = new HttpClient();\n            long? totalByte = 0;\n            using (Stream output = File.OpenWrite(\"C:\\\\Users\" + video.Title))\n            {\n                using (var request = new HttpRequestMessage(HttpMethod.Head, video.Uri))\n                {\n                    totalByte = client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead).Result.Content.Headers.ContentLength;\n                }\n                using (var input = await client.GetStreamAsync(video.Uri))\n                {\n                    byte[] buffer = new byte[16 * 1024];\n                    int read;\n                    int totalRead = 0;\n                    Console.WriteLine(\"Download Started\");\n                    while ((read = input.Read(buffer, 0, buffer.Length)) > 0)\n                    {\n                        output.Write(buffer, 0, read);\n                        totalRead += read;\n                        Console.Write($\"\\rDownloading {totalRead}/{totalByte} ...\");\n                    }\n                    Console.WriteLine(\"Download Complete\");\n                }\n            }\n            Console.ReadLine();\n        }\n    }\n```\n\n### Custom Downloader with Chunks (Fast)\n\n[Sample Code](https://github.com/omansak/libvideo/blob/master/src/libvideo.debug/CustomYoutubeClient.cs)\n\n---\n\nThat's it, enjoy! If you're looking for more features, feel free to raise an issue and we can discuss it with you.\n"
  },
  {
    "path": "samples/Valks/Valks/Program.cs",
    "content": "using VideoLibrary;\nusing System;\nusing System.IO;\n\nnamespace Valks\n{\n    class MainClass\n    {\n        public static void Main(string[] args)\n        {\n            Console.WriteLine(\"Welcome to Valks!\");\n            Console.WriteLine(\"Easily save your favorite videos from YouTube.\");\n\n            using (var service = Client.For(YouTube.Default))\n            {\n                while (true)\n                {\n                    Console.WriteLine();\n                    Console.Write(\"Enter your video's ID: \");\n\n                    string id = Console.ReadLine();\n\n                    Console.WriteLine(\"Awesome! Downloading...\");\n\n                    var video = service.GetVideo(\"https://youtube.com/watch?v=\" + id);\n\n                    Console.Write(\"Finished! Would you like to save the video to Downloads? [y/n] \");\n\n                    char opt = Console.ReadKey().KeyChar;\n\n                    Console.WriteLine();\n\n                    string folder;\n\n                    if (char.ToUpper(opt) == 'Y')\n                        folder = GetDefaultFolder();\n                    else\n                    {\n                        Console.Write(\"Please tell us where you'd like to save it: \");\n                        folder = Console.ReadLine();\n                    }\n\n                    string path = Path.Combine(folder, video.FullName);\n\n                    Console.WriteLine(\"Saving...\");\n\n                    File.WriteAllBytes(path, video.GetBytes());\n\n                    Console.WriteLine(\"Done.\");\n                }\n            }\n        }\n\n        static string GetDefaultFolder()\n        {\n            var home = Environment.GetFolderPath(\n                Environment.SpecialFolder.UserProfile);\n\n            return Path.Combine(home, \"Downloads\");\n        }\n    }\n}\n"
  },
  {
    "path": "samples/Valks/Valks/Properties/AssemblyInfo.cs",
    "content": "using System.Reflection;\nusing System.Runtime.CompilerServices;\n\n// Information about this assembly is defined by the following attributes. \n// Change them to the values specific to your project.\n[assembly: AssemblyTitle(\"Valks\")]\n[assembly: AssemblyDescription(\"\")]\n[assembly: AssemblyConfiguration(\"\")]\n[assembly: AssemblyCompany(\"\")]\n[assembly: AssemblyProduct(\"\")]\n[assembly: AssemblyCopyright(\"i3arnon\")]\n[assembly: AssemblyTrademark(\"\")]\n[assembly: AssemblyCulture(\"\")]\n// The assembly version has the format \"{Major}.{Minor}.{Build}.{Revision}\".\n// The form \"{Major}.{Minor}.*\" will automatically update the build and revision,\n// and \"{Major}.{Minor}.{Build}.*\" will update just the revision.\n[assembly: AssemblyVersion(\"1.0.*\")]\n// The following attributes are used to specify the signing key for the assembly, \n// if desired. See the Mono documentation for more information about signing.\n//[assembly: AssemblyDelaySign(false)]\n//[assembly: AssemblyKeyFile(\"\")]\n\n"
  },
  {
    "path": "samples/Valks/Valks/Valks.csproj",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project DefaultTargets=\"Build\" ToolsVersion=\"4.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <PropertyGroup>\n    <Configuration Condition=\" '$(Configuration)' == '' \">Debug</Configuration>\n    <Platform Condition=\" '$(Platform)' == '' \">x86</Platform>\n    <ProductVersion>10.0.0</ProductVersion>\n    <SchemaVersion>2.0</SchemaVersion>\n    <ProjectGuid>{A600E2F2-81C4-4C6A-B87B-342766AA773E}</ProjectGuid>\n    <OutputType>Exe</OutputType>\n    <RootNamespace>Valks</RootNamespace>\n    <AssemblyName>Valks</AssemblyName>\n    <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>\n  </PropertyGroup>\n  <PropertyGroup Condition=\" '$(Configuration)|$(Platform)' == 'Debug|x86' \">\n    <DebugSymbols>true</DebugSymbols>\n    <DebugType>full</DebugType>\n    <Optimize>false</Optimize>\n    <OutputPath>bin\\Debug</OutputPath>\n    <DefineConstants>DEBUG;</DefineConstants>\n    <ErrorReport>prompt</ErrorReport>\n    <WarningLevel>4</WarningLevel>\n    <Externalconsole>true</Externalconsole>\n    <PlatformTarget>x86</PlatformTarget>\n  </PropertyGroup>\n  <PropertyGroup Condition=\" '$(Configuration)|$(Platform)' == 'Release|x86' \">\n    <DebugType>full</DebugType>\n    <Optimize>true</Optimize>\n    <OutputPath>bin\\Release</OutputPath>\n    <ErrorReport>prompt</ErrorReport>\n    <WarningLevel>4</WarningLevel>\n    <Externalconsole>true</Externalconsole>\n    <PlatformTarget>x86</PlatformTarget>\n  </PropertyGroup>\n  <ItemGroup>\n    <Reference Include=\"libvideo, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL\">\n      <SpecificVersion>False</SpecificVersion>\n      <HintPath>..\\..\\..\\src\\libvideo\\bin\\Release\\netstandard1.1\\libvideo.dll</HintPath>\n    </Reference>\n    <Reference Include=\"System\" />\n  </ItemGroup>\n  <ItemGroup>\n    <Compile Include=\"Program.cs\" />\n    <Compile Include=\"Properties\\AssemblyInfo.cs\" />\n  </ItemGroup>\n  <Import Project=\"$(MSBuildBinPath)\\Microsoft.CSharp.targets\" />\n</Project>"
  },
  {
    "path": "samples/Valks/Valks.sln",
    "content": "﻿\nMicrosoft Visual Studio Solution File, Format Version 11.00\n# Visual Studio 2010\nProject(\"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\") = \"Valks\", \"Valks\\Valks.csproj\", \"{A600E2F2-81C4-4C6A-B87B-342766AA773E}\"\nEndProject\nGlobal\n\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n\t\tDebug|x86 = Debug|x86\n\t\tRelease|x86 = Release|x86\n\tEndGlobalSection\n\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n\t\t{A600E2F2-81C4-4C6A-B87B-342766AA773E}.Debug|x86.ActiveCfg = Debug|x86\n\t\t{A600E2F2-81C4-4C6A-B87B-342766AA773E}.Debug|x86.Build.0 = Debug|x86\n\t\t{A600E2F2-81C4-4C6A-B87B-342766AA773E}.Release|x86.ActiveCfg = Release|x86\n\t\t{A600E2F2-81C4-4C6A-B87B-342766AA773E}.Release|x86.Build.0 = Release|x86\n\tEndGlobalSection\n\tGlobalSection(MonoDevelopProperties) = preSolution\n\t\tStartupItem = Valks\\Valks.csproj\n\tEndGlobalSection\nEndGlobal\n"
  },
  {
    "path": "src/libvideo/AdaptiveKind.cs",
    "content": "﻿namespace VideoLibrary\n{\n    public enum AdaptiveKind\n    {\n        None,\n        Audio,\n        Video\n    }\n}\n"
  },
  {
    "path": "src/libvideo/AudioFormat.cs",
    "content": "﻿namespace VideoLibrary\n{\n    public enum AudioFormat\n    {\n        Aac = 0,\n        Vorbis = 1,\n        Opus = 2,\n        Unknown = 3\n    }\n}"
  },
  {
    "path": "src/libvideo/Client.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Net;\nusing System.Net.Http;\nusing System.Text;\nusing System.Threading.Tasks;\nusing VideoLibrary.Helpers;\n\nnamespace VideoLibrary\n{\n    public static class Client\n    {\n        public static Client<T> For<T>(ServiceBase<T> baseService) \n            where T : Video => new Client<T>(baseService);\n    }\n\n    public class Client<T> : IService<T>, IAsyncService<T>, IDisposable \n        where T : Video\n    {\n        private bool disposed = false;\n        private readonly ServiceBase<T> baseService;\n        private readonly HttpClient client;\n\n        private Task<string> SourceFactory(string address) =>\n            client.GetStringAsync(address);\n\n        internal Client(ServiceBase<T> baseService)\n        {\n            Require.NotNull(baseService, nameof(baseService));\n\n            this.baseService = baseService;\n            this.client = baseService.MakeClient();\n        }\n\n        #region IDisposable\n\n        ~Client()\n        {\n            Dispose(false);\n        }\n\n        public void Dispose()\n        {\n            Dispose(true);\n            GC.SuppressFinalize(this);\n        }\n\n        protected virtual void Dispose(bool disposing)\n        {\n            if (disposed)\n                return;\n            disposed = true;\n\n            if (disposing)\n            {\n                if (client != null)\n                    client.Dispose();\n            }\n        }\n\n        #endregion\n\n        public T GetVideo(string videoUri) =>\n            baseService.GetVideo(videoUri, SourceFactory);\n\n        public IEnumerable<T> GetAllVideos(string videoUri) =>\n            baseService.GetAllVideos(videoUri, SourceFactory);\n\n        public Task<T> GetVideoAsync(string videoUri) =>\n            baseService.GetVideoAsync(videoUri, SourceFactory);\n\n        public Task<IEnumerable<T>> GetAllVideosAsync(string videoUri) =>\n            baseService.GetAllVideosAsync(videoUri, SourceFactory);\n    }\n}\n"
  },
  {
    "path": "src/libvideo/DelegatingClient.cs",
    "content": "﻿using System;\nusing System.IO;\nusing System.Net;\nusing System.Net.Http;\nusing System.Threading.Tasks;\n\nnamespace VideoLibrary\n{\n    public class DelegatingClient : IDisposable\n    {\n        private bool disposed = false;\n        private readonly HttpClient client;\n\n        public DelegatingClient()\n        {\n            this.client = MakeClient();\n        }\n\n        #region IDisposable\n\n        ~DelegatingClient()\n        {\n            Dispose(false);\n        }\n\n        public void Dispose()\n        {\n            Dispose(true);\n            GC.SuppressFinalize(this);\n        }\n\n        protected virtual void Dispose(bool disposing)\n        {\n            if (disposed)\n                return;\n\n            disposed = true;\n\n            if (disposing)\n            {\n                if (client != null)\n                    client.Dispose();\n            }\n        }\n\n        #endregion\n\n        #region MakeClient/MakeHandler\n\n        private HttpClient MakeClient() =>\n            MakeClient(MakeHandler());\n\n        protected virtual HttpMessageHandler MakeHandler()\n        {\n            var handler = new HttpClientHandler();\n\n            if (handler.SupportsAutomaticDecompression)\n            {\n                handler.AutomaticDecompression =\n                    DecompressionMethods.GZip |\n                    DecompressionMethods.Deflate;\n            }\n\n            return handler;\n        }\n\n        protected virtual HttpClient MakeClient(HttpMessageHandler handler)\n        {\n            return new HttpClient(handler);\n        }\n\n        #endregion\n\n        #region Synchronous wrappers\n\n        public HttpResponseMessage Get(string uri) =>\n            GetAsync(uri).GetAwaiter().GetResult();\n\n        public byte[] GetByteArray(string uri) =>\n            GetByteArrayAsync(uri).GetAwaiter().GetResult();\n\n        public Stream GetStream(string uri) =>\n            GetStreamAsync(uri).GetAwaiter().GetResult();\n\n        public string GetString(string uri) =>\n            GetStringAsync(uri).GetAwaiter().GetResult();\n\n        #endregion\n\n        #region HttpClient wrappers\n\n        // TODO: Support other kinds of HTTP requests, \n        // such as PUT, POST, DELETE, etc.\n\n        public Task<HttpResponseMessage> GetAsync(string uri) =>\n            client.GetAsync(uri);\n\n        public Task<byte[]> GetByteArrayAsync(string uri) =>\n            client.GetByteArrayAsync(uri);\n\n        public Task<Stream> GetStreamAsync(string uri) =>\n            client.GetStreamAsync(uri);\n\n        public Task<string> GetStringAsync(string uri) =>\n            client.GetStringAsync(uri);\n\n        #endregion\n    }\n}\n"
  },
  {
    "path": "src/libvideo/Exceptions/BadQueryException.cs",
    "content": "﻿using System;\n\nnamespace VideoLibrary.Exceptions\n{\n    internal class BadQueryException : Exception\n    {\n        public BadQueryException()\n            : base()\n        { }\n\n        public BadQueryException(string message)\n            : base(message)\n        { }\n\n        public BadQueryException(string message, Exception innerException)\n            : base(message, innerException)\n        { }\n    }\n}\n"
  },
  {
    "path": "src/libvideo/Exceptions/UnavaibleVideoException.cs",
    "content": "﻿using System;\n\nnamespace VideoLibrary.Exceptions\n{\n    public class UnavailableStreamException : Exception\n    {\n        public UnavailableStreamException()\n            : base()\n        { }\n\n        public UnavailableStreamException(string message)\n            : base(message)\n        { }\n\n        public UnavailableStreamException(string message, Exception innerException)\n            : base(message, innerException)\n        { }\n    }\n}\n"
  },
  {
    "path": "src/libvideo/Helpers/EmptyArray.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\n\nnamespace VideoLibrary.Helpers\n{\n    internal static class EmptyArray<T>\n    {\n        public static readonly T[] Value = new T[0];\n    }\n}\n"
  },
  {
    "path": "src/libvideo/Helpers/Html.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Net;\nusing System.Text;\nusing System.Threading.Tasks;\n\nnamespace VideoLibrary.Helpers\n{\n    internal static class Html\n    {\n        // TODO: Refactor?\n        public static string GetNode(string name, string source) =>\n            WebUtility.HtmlDecode(\n                Text.StringBetween(\n                    '<' + name + '>', \"</\" + name + '>', source));\n\n        public static IEnumerable<string> GetUrisFromManifest(string source)\n        {\n            string opening = \"<BaseURL>\";\n            string closing = \"</BaseURL>\";\n            int start = source.IndexOf(opening);\n            if (start != -1)\n            {\n                string temp = source.Substring(start);\n                var uris = temp.Split(new string[] { opening }, StringSplitOptions.RemoveEmptyEntries)\n                    .Select(v => v.Substring(0, v.IndexOf(closing)));\n                return uris;\n            }\n            throw new NotSupportedException();\n        }\n    }\n}\n"
  },
  {
    "path": "src/libvideo/Helpers/Json.cs",
    "content": "using System;\nusing System.Text;\nusing System.Text.Json;\n\nnamespace VideoLibrary.Helpers\n{\n    internal static class Json\n    {\n        public static string GetKey(string key, string source)\n        {\n            if (GetKey(key, source, out string result))\n            {\n                return result;\n            }\n            return null;\n        }\n\n        public static bool TryGetKey(string key, string source, out string target)\n        {\n            return GetKey(key, source, out target);\n        }\n\n        public static JsonElement? GetNullableProperty(this JsonElement jsonElement, string propertyName)\n        {\n            if (jsonElement.TryGetProperty(propertyName, out JsonElement returnElement))\n            {\n                return returnElement;\n            }\n\n            return null;\n        }\n\n        public static string Extract(string source)\n        {\n            StringBuilder sb = new StringBuilder();\n            int depth = 0;\n            int backSlashesCounter = 0;\n            char lastChar = '\\u0000';\n            bool isString = false;\n            foreach (var ch in source)\n            {\n                sb.Append(ch);\n                \n                if (ch == '\\\\')\n                {\n                    // count backslashes\n                    backSlashesCounter++;\n                }\n                else if (ch == '\"')\n                {\n                    // if current char is quote check last char and count of backslashes to be sure it is not doubled backslashes\n                    if (lastChar != '\\\\' || backSlashesCounter%2 == 0)\n                    {\n                        isString = !isString;\n                    }\n                }\n                else\n                {\n                    // reset backslashes count if its any other char\n                    backSlashesCounter = 0;\n                }\n\n                if (!isString)\n                {\n                    if (ch == '{' && lastChar != '\\\\')\n                        depth++;\n                    else if (ch == '}' && lastChar != '\\\\')\n                        depth--;\n                }\n\n                if (depth == 0)\n                    break;\n                lastChar = ch;\n            }\n            return sb.ToString();\n        }\n\n        private static bool GetKey(string key, string source, out string target)\n        {\n            // Example scenario: \"key\" : \"value\"\n\n            string quotedKey = '\"' + key + '\"';\n            int index = 0;\n\n            while (true)\n            {\n                index = source.IndexOf(quotedKey, index, StringComparison.Ordinal); // '\"'\n                if (index == -1)\n                {\n                    target = string.Empty;\n                    return false;\n                }\n                index += quotedKey.Length; // ' '\n\n                int start = index;\n                start = source.SkipWhitespace(start); // ':'\n                if (source[start++] != ':') // ' '\n                    continue;\n                start = source.SkipWhitespace(start); // '\"'\n                if (source[start++] != '\"') // 'v'\n                    continue;\n                int end = start;\n                while ((source[end - 1] == '\\\\' && source[end] == '\"') || source[end] != '\"') // \"value\\\"\"\n                {\n                    end++;\n                }\n                target = source.Substring(start, end - start);\n                return true;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/libvideo/Helpers/KeyCollection.cs",
    "content": "﻿using System;\nusing System.Collections;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\n\nnamespace VideoLibrary.Helpers\n{\n    internal partial class Query : IDictionary<string, string>\n    {\n        public class KeyCollection : ICollection<string>, IReadOnlyCollection<string>\n        {\n            private readonly Query query;\n\n            public KeyCollection(Query query)\n            {\n                this.query = query;\n            }\n\n            public int Count => query.Count;\n\n            public bool IsReadOnly => true;\n\n            public void Add(string item)\n            {\n                throw new NotSupportedException();\n            }\n\n            public void Clear()\n            {\n                throw new NotSupportedException();\n            }\n\n            public bool Contains(string item)\n            {\n                for (int i = 0; i < query.Count; i++)\n                {\n                    var pair = query.Pairs[i];\n                    if (item == pair.Key)\n                        return true;\n                }\n                return false;\n            }\n\n            public void CopyTo(string[] array, int arrayIndex)\n            {\n                for (int i = 0; i < query.Count; i++)\n                    array[arrayIndex++] = query.Pairs[i].Key;\n            }\n\n            public IEnumerator<string> GetEnumerator()\n            {\n                for (int i = 0; i < query.Count; i++)\n                    yield return query.Pairs[i].Key;\n            }\n\n            public bool Remove(string item)\n            {\n                throw new NotSupportedException();\n            }\n\n            IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();\n        }\n    }\n}\n"
  },
  {
    "path": "src/libvideo/Helpers/Operations.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\n\nnamespace VideoLibrary.Helpers\n{\n    internal struct Operations\n    {\n        public Operations(string reverse, \n            string swap, string splice)\n        {\n            this.Reverse = reverse;\n            this.Swap = swap;\n            this.Splice = splice;\n        }\n\n        public string Reverse { get; }\n        public string Swap { get; }\n        public string Splice { get; }\n    }\n}\n"
  },
  {
    "path": "src/libvideo/Helpers/Query.cs",
    "content": "﻿using System;\nusing System.Collections;\nusing System.Collections.Generic;\nusing System.Text;\n\nnamespace VideoLibrary.Helpers\n{\n    internal partial class Query : IDictionary<string, string>, IReadOnlyDictionary<string, string>\n    {\n        private int count;\n        private readonly string baseUri;\n        private KeyValuePair<string, string>[] pairs;\n\n        public Query(string uri)\n        {\n            int divide = uri.IndexOf('?');\n\n            if (divide == -1)\n            {\n                int amp = uri.IndexOf('&');\n                if (amp == -1)\n                {\n                    // no query parameters\n                    this.baseUri = uri;\n                    return;\n                }\n\n                // no base URL\n                this.baseUri = null;\n            }\n            else\n            {\n                // normal URL\n                this.baseUri = uri.Substring(0, divide);\n                uri = uri.Substring(divide + 1);\n            }\n\n            string[] keyValues = uri.Split('&');\n\n            string[] keys = EmptyArray<string>.Value;\n            string[] values = EmptyArray<string>.Value;\n            pairs = new KeyValuePair<string, string>[keyValues.Length];\n\n            for (int i = 0; i < keyValues.Length; i++)\n            {\n                string pair = keyValues[i];\n                int equals = pair.IndexOf('=');\n                string key;\n                string value;\n\n                key = pair.Substring(0, equals);\n                value = equals < pair.Length ? pair.Substring(equals + 1) : string.Empty;\n\n                pairs[i] = new KeyValuePair<string, string>(key, value);\n            }\n\n            this.count = keyValues.Length;\n        }\n\n        public string this[string key]\n        {\n            get\n            {\n                for (int i = 0; i < count; i++)\n                {\n                    var pair = pairs[i];\n                    if (pair.Key == key)\n                        return pair.Value;\n                }\n\n                throw new KeyNotFoundException();\n            }\n\n            set\n            {\n                for (int i = 0; i < count; i++)\n                {\n                    var pair = pairs[i];\n                    if (pair.Key == key)\n                    {\n                        pairs[i] = new KeyValuePair<string, string>(key, value);\n                        return;\n                    }\n                }\n\n                throw new KeyNotFoundException();\n            }\n        }\n\n        public string BaseUri => baseUri;\n\n        public int Count => count;\n\n        public bool IsReadOnly => false;\n\n        public KeyCollection Keys => new KeyCollection(this);\n\n        ICollection<string> IDictionary<string, string>.Keys => Keys;\n\n        public KeyValuePair<string, string>[] Pairs => pairs;\n\n        public ValueCollection Values => new ValueCollection(this);\n\n        ICollection<string> IDictionary<string, string>.Values => Values;\n\n        IEnumerable<string> IReadOnlyDictionary<string, string>.Keys => Keys;\n\n        IEnumerable<string> IReadOnlyDictionary<string, string>.Values => Values;\n\n        void ICollection<KeyValuePair<string, string>>.Add(KeyValuePair<string, string> item)\n        {\n            Add(item.Key, item.Value);\n        }\n\n        public void Add(string key, string value)\n        {\n            EnsureCapacity(count + 1);\n            pairs[count++] = new KeyValuePair<string, string>(key, value);\n        }\n\n        public void Clear()\n        {\n            if (count == 0)\n                return;\n\n            Array.Clear(pairs, 0, count);\n            count = 0;\n        }\n\n        bool ICollection<KeyValuePair<string, string>>.Contains(KeyValuePair<string, string> item)\n        {\n            for (int i = 0; i < count; i++)\n            {\n                var pair = pairs[i];\n\n                if (item.Key == pair.Key &&\n                    item.Value == pair.Value)\n                    return true;\n            }\n            return false;\n        }\n\n        public bool ContainsKey(string key)\n        {\n            for (int i = 0; i < count; i++)\n            {\n                if (key == pairs[i].Key)\n                    return true;\n            }\n            return false;\n        }\n\n        void ICollection<KeyValuePair<string, string>>.CopyTo(KeyValuePair<string, string>[] array, int arrayIndex)\n        {\n            Array.Copy(pairs, 0, array, arrayIndex, count);\n        }\n\n        public IEnumerator<KeyValuePair<string, string>> GetEnumerator()\n        {\n            for (int i = 0; i < count; i++)\n                yield return pairs[i];\n        }\n\n        bool ICollection<KeyValuePair<string, string>>.Remove(KeyValuePair<string, string> item)\n        {\n            return Remove(item.Key);\n        }\n\n        public bool Remove(string key)\n        {\n            for (int i = 0; i < count; i++)\n            {\n                var pair = pairs[i];\n                if (pair.Key == key)\n                {\n                    // found it\n                    if (i != count--)\n                        Array.Copy(pairs, i + 1, pairs, i, count - i);\n                    pairs[count] = default(KeyValuePair<string, string>);\n                    return true;\n                }\n            }\n            return false;\n        }\n\n        public bool TryGetValue(string key, out string value)\n        {\n            for (int i = 0; i < count; i++)\n            {\n                var pair = pairs[i];\n                if (key == pair.Key)\n                {\n                    value = pair.Value;\n                    return true;\n                }\n            }\n\n            value = null;\n            return false;\n        }\n\n        IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();\n\n        public override string ToString()\n        {\n            if (count == 0)\n                return baseUri;\n\n            var builder = new StringBuilder();\n            if (baseUri != null)\n                builder.Append(baseUri).Append('?');\n\n            var pair = pairs[0]; // OK since we know count is at least 1\n            builder.Append(pair.Key)\n                .Append('=').Append(pair.Value);\n\n            for (int i = 1; i < count; i++)\n            {\n                pair = pairs[i];\n\n                builder.Append('&').Append(pair.Key)\n                    .Append('=').Append(pair.Value);\n            }\n\n            return builder.ToString();\n        }\n\n        private void EnsureCapacity(int capacity)\n        {\n            if (capacity > pairs.Length)\n            {\n                capacity = Math.Max(capacity, pairs.Length * 2);\n\n                Array.Resize(ref pairs, capacity);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/libvideo/Helpers/Require.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\n\nnamespace VideoLibrary.Helpers\n{\n    internal static class Require\n    {\n        public static void NotNull<T>(T obj, string name)\n            where T : class\n        {\n            if (obj == null)\n                throw new ArgumentNullException(name);\n        }\n    }\n}\n"
  },
  {
    "path": "src/libvideo/Helpers/Text.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\n\nnamespace VideoLibrary.Helpers\n{\n    internal static class Text\n    {\n        public static string StringBetween(string prefix, string suffix, string parent)\n        {\n            int start = parent.IndexOf(prefix) + prefix.Length;\n\n            if (start < prefix.Length)\n                return string.Empty;\n\n            int end = parent.IndexOf(suffix, start);\n\n            if (end == -1)\n                end = parent.Length;\n\n            return parent.Substring(start, end - start);\n        }\n\n        public static int SkipWhitespace(this string text, int start)\n        {\n            int result = start;\n            while (char.IsWhiteSpace(text[result]))\n                result++;\n            return result;\n        }\n    }\n}\n"
  },
  {
    "path": "src/libvideo/Helpers/UnscrambledQuery.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\n\nnamespace VideoLibrary.Helpers\n{\n    internal readonly struct UnscrambledQuery\n    {\n        public UnscrambledQuery(string uri, bool encrypted)\n        {\n            this.Uri = uri;\n            this.IsEncrypted = encrypted;\n        }\n\n        public string Uri { get; }\n        public bool IsEncrypted { get; }\n    }\n}\n"
  },
  {
    "path": "src/libvideo/Helpers/ValueCollection.cs",
    "content": "﻿using System;\nusing System.Collections;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\n\nnamespace VideoLibrary.Helpers\n{\n    internal partial class Query\n    {\n        public class ValueCollection : ICollection<string>, IReadOnlyCollection<string>\n        {\n            private readonly Query query;\n\n            public ValueCollection(Query query)\n            {\n                this.query = query;\n            }\n\n            public int Count => query.Count;\n\n            public bool IsReadOnly => true;\n\n            public void Add(string item)\n            {\n                throw new NotSupportedException();\n            }\n\n            public void Clear()\n            {\n                throw new NotSupportedException();\n            }\n\n            public bool Contains(string item)\n            {\n                for (int i = 0; i < query.Count; i++)\n                {\n                    var pair = query.Pairs[i];\n                    if (item == pair.Value)\n                        return true;\n                }\n                return false;\n            }\n\n            public void CopyTo(string[] array, int arrayIndex)\n            {\n                for (int i = 0; i < query.Count; i++)\n                    array[arrayIndex++] = query.Pairs[i].Value;\n            }\n\n            public IEnumerator<string> GetEnumerator()\n            {\n                for (int i = 0; i < query.Count; i++)\n                    yield return query.Pairs[i].Value;\n            }\n\n            public bool Remove(string item)\n            {\n                throw new NotSupportedException();\n            }\n\n            IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();\n        }\n    }\n}\n"
  },
  {
    "path": "src/libvideo/IAsyncService.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\n\nnamespace VideoLibrary\n{\n    internal interface IAsyncService<T> where T : Video\n    {\n        Task<T> GetVideoAsync(string uri);\n        Task<IEnumerable<T>> GetAllVideosAsync(string uri);\n    }\n}\n"
  },
  {
    "path": "src/libvideo/IService.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\n\nnamespace VideoLibrary\n{\n    internal interface IService<T> where T : Video\n    {\n        T GetVideo(string uri);\n        IEnumerable<T> GetAllVideos(string uri);\n    }\n}\n"
  },
  {
    "path": "src/libvideo/ServiceBase.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Net;\nusing System.Net.Http;\nusing System.Threading.Tasks;\n\n\nnamespace VideoLibrary\n{\n    public abstract class ServiceBase<T> : IService<T>, IAsyncService<T>\n        where T : Video\n    {\n        internal virtual T VideoSelector(IEnumerable<T> videos) =>\n            videos.First();\n\n        #region Synchronous wrappers\n        public T GetVideo(string videoUri) =>\n            GetVideoAsync(videoUri).GetAwaiter().GetResult();\n\n        internal T GetVideo(string videoUri,\n            Func<string, Task<string>> sourceFactory) =>\n            GetVideoAsync(videoUri, sourceFactory).GetAwaiter().GetResult();\n\n        public IEnumerable<T> GetAllVideos(string videoUri) =>\n            GetAllVideosAsync(videoUri).GetAwaiter().GetResult();\n\n        internal IEnumerable<T> GetAllVideos(string videoUri,\n            Func<string, Task<string>> sourceFactory) =>\n            GetAllVideosAsync(videoUri, sourceFactory).GetAwaiter().GetResult();\n        #endregion\n\n        public async Task<T> GetVideoAsync(string videoUri)\n        {\n            using (var wrapped = Client.For(this))\n            {\n                return await wrapped\n                    .GetVideoAsync(videoUri)\n                    .ConfigureAwait(false);\n            }\n        }\n\n        internal async Task<T> GetVideoAsync(\n            string videoUri, Func<string, Task<string>> sourceFactory) =>\n            VideoSelector(await GetAllVideosAsync(\n                videoUri, sourceFactory).ConfigureAwait(false));\n\n        public async Task<IEnumerable<T>> GetAllVideosAsync(string videoUri)\n        {\n            using (var wrapped = Client.For(this))\n            {\n                return await wrapped\n                    .GetAllVideosAsync(videoUri)\n                    .ConfigureAwait(false);\n            }\n        }\n\n        internal abstract Task<IEnumerable<T>> GetAllVideosAsync(\n            string videoUri, Func<string, Task<string>> sourceFactory);\n\n        internal HttpClient MakeClient() =>\n            MakeClient(MakeHandler());\n\n        protected virtual HttpMessageHandler MakeHandler()\n        {\n            // Cookie\n            var cookieContainer = new CookieContainer();\n            cookieContainer.Add(new Uri(YouTube.YoutubeUrl), new Cookie(\"CONSENT\", \"YES+cb\", \"/\", \".youtube.com\"));\n            // Handler\n            var handler = new HttpClientHandler\n            {\n                UseCookies = true,\n                CookieContainer = cookieContainer\n            };\n            if (handler.SupportsAutomaticDecompression)\n                handler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;\n            return handler;\n        }\n\n        protected virtual HttpClient MakeClient(HttpMessageHandler handler)\n        {\n            var httpClient = new HttpClient(handler);\n            httpClient.DefaultRequestHeaders.Add(\n                \"User-Agent\",\n                \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36 Edg/89.0.774.76\"\n            );\n            return new HttpClient(handler);\n        }\n    }\n}\n"
  },
  {
    "path": "src/libvideo/Video.cs",
    "content": "﻿using System;\nusing System.IO;\nusing System.Text;\nusing System.Threading.Tasks;\n\nnamespace VideoLibrary\n{\n    public class VideoInfo\n    {\n        public VideoInfo(string title, int second, string author)\n        {\n            this.Title = title;\n            this.LengthSeconds = second;\n            this.Author = author;\n        }\n        public string Title { get; }\n        public int LengthSeconds { get; }\n        public string Author { get; }\n    }\n    public abstract class Video\n    {\n        internal Video()\n        {\n        }\n        public abstract string Uri { get; }\n        public abstract string Title { get; }\n        public abstract VideoInfo Info { get; }\n        public abstract WebSites WebSite { get; }\n        public virtual VideoFormat Format => VideoFormat.Unknown;\n        // public virtual AudioFormat AudioFormat => AudioFormat.Unknown;\n\n        public virtual Task<string> GetUriAsync() =>\n            Task.FromResult(Uri);\n\n        public byte[] GetBytes() =>\n            GetBytesAsync().GetAwaiter().GetResult();\n\n        public async Task<byte[]> GetBytesAsync()\n        {\n            using (var client = new VideoClient())\n            {\n                return await client\n                    .GetBytesAsync(this)\n                    .ConfigureAwait(false);\n            }\n        }\n\n        public Stream Stream() => StreamAsync().GetAwaiter().GetResult();\n\n        public async Task<Stream> StreamAsync()\n        {\n            using (var client = new VideoClient())\n            {\n                return await client\n                    .StreamAsync(this)\n                    .ConfigureAwait(false);\n            }\n        }\n        public Stream Head() => HeadAsync().GetAwaiter().GetResult();\n        public async Task<Stream> HeadAsync()\n        {\n            using (var client = new VideoClient())\n            {\n                return await client\n                    .StreamAsync(this)\n                    .ConfigureAwait(false);\n            }\n        }\n        public virtual string FileExtension\n        {\n            get\n            {\n                switch (Format)\n                {\n                    case VideoFormat.Mp4: return \".mp4\";\n                    case VideoFormat.WebM: return \".webm\";\n                    case VideoFormat.Unknown: return string.Empty;\n                    default:\n                        throw new NotImplementedException($\"Format {Format} is unrecognized! Please file an issue at libvideo on GitHub.\");\n                }\n            }\n        }\n\n        public string FullName\n        {\n            get\n            {\n                var builder =\n                    new StringBuilder(Title)\n                    .Append(FileExtension);\n\n                foreach (char bad in Path.GetInvalidFileNameChars())\n                    builder.Replace(bad, '_');\n\n                return builder.ToString();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/libvideo/VideoClient.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.Linq;\nusing System.Net.Http;\nusing System.Text;\nusing System.Threading.Tasks;\n\nnamespace VideoLibrary\n{\n    public class VideoClient : IDisposable\n    {\n        private bool disposed = false;\n        private readonly HttpClient client;\n\n        public VideoClient()\n        {\n            this.client = MakeClient();\n        }\n\n        #region IDisposable\n\n        ~VideoClient()\n        {\n            Dispose(false);\n        }\n\n        public void Dispose()\n        {\n            Dispose(true);\n            GC.SuppressFinalize(this);\n        }\n\n        protected virtual void Dispose(bool disposing)\n        {\n            if (disposed)\n                return;\n\n            disposed = true;\n\n            if (disposing)\n            {\n                if (client != null)\n                    client.Dispose();\n            }\n        }\n\n        #endregion\n\n        #region MakeClient/MakeHandler\n\n        private HttpClient MakeClient() => MakeClient(MakeHandler());\n\n        protected virtual HttpMessageHandler MakeHandler() => new HttpClientHandler();\n\n        protected virtual HttpClient MakeClient(HttpMessageHandler handler)\n        {\n            return new HttpClient(handler)\n            {\n                Timeout = TimeSpan.FromMilliseconds(int.MaxValue) // Longest TimeSpan HttpClient will accept\n            };\n        }\n\n        #endregion\n\n        public byte[] GetBytes(Video video) => GetBytesAsync(video).GetAwaiter().GetResult();\n\n        public async Task<byte[]> GetBytesAsync(Video video)\n        {\n            string uri = await\n                video.GetUriAsync()\n                .ConfigureAwait(false);\n\n            return await client\n                .GetByteArrayAsync(uri)\n                .ConfigureAwait(false);\n        }\n\n        public Stream Stream(Video video) => StreamAsync(video).GetAwaiter().GetResult();\n\n        public async Task<Stream> StreamAsync(Video video)\n        {\n            string uri = await\n                video.GetUriAsync()\n                .ConfigureAwait(false);\n\n            return await client\n                .GetStreamAsync(uri)\n                .ConfigureAwait(false);\n        }\n\n        public async Task<long?> GetContentLengthAsync(string requestUri)\n        {\n            using (var response = await HeadAsync(requestUri))\n            {\n                return response.Content.Headers.ContentLength;\n            }\n        }\n        public async Task<HttpResponseMessage> HeadAsync(string requestUri)\n        {\n            using (var request = new HttpRequestMessage(HttpMethod.Head, requestUri))\n                return await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);\n        }\n    }\n}\n"
  },
  {
    "path": "src/libvideo/VideoFormat.cs",
    "content": "﻿namespace VideoLibrary\n{\n    public enum VideoFormat\n    {\n        Mp4,\n        WebM,\n        Unknown\n    }\n}\n"
  },
  {
    "path": "src/libvideo/VisitorDataTokenGenerator.cs",
    "content": "﻿using System;\nusing System.Linq;\nusing System.Net.Http;\nusing System.Net.Http.Headers;\nusing System.Text.Json;\nusing System.Threading.Tasks;\n\nnamespace VideoLibrary\n{\n    internal class VisitorDataTokenGenerator : IDisposable\n    {\n        private bool _disposed;\n        private static string _visitorData = string.Empty;\n\n\n        public static async Task<string> GetVisitorDataFromYouTube(HttpClient http)\n        {\n            // Return cached visitor data if available\n            if (!string.IsNullOrEmpty(_visitorData))\n                return _visitorData;\n\n            try\n            {\n                // Configure request headers\n                http.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(\"application/json\"));\n\n                const string url = \"https://www.youtube.com/sw.js_data\";\n                var response = await http.GetAsync(url);\n                response.EnsureSuccessStatusCode();\n\n                var jsonString = await response.Content.ReadAsStringAsync();\n\n                // Remove the \")]}'\" prefix if present\n                jsonString = jsonString.StartsWith(\")]}'\") ? jsonString.Substring(4) : jsonString;\n\n                var doc = JsonDocument.Parse(jsonString);\n                var value = doc.RootElement[0]\n                    .EnumerateArray()\n                    .ElementAt(2)\n                    .EnumerateArray()\n                    .ElementAt(0)\n                    .EnumerateArray()\n                    .ElementAt(0)\n                    .EnumerateArray()\n                    .ElementAt(13)\n                    .GetString();\n\n                if (value == null)\n                    throw new Exception(\"Failed to fetch visitor data\");\n\n                _visitorData = value;\n                return _visitorData;\n            }\n            catch (HttpRequestException ex)\n            {\n                throw new Exception(\"Failed to fetch data from YouTube\", ex);\n            }\n            catch (JsonException ex)\n            {\n                throw new Exception(\"Failed to parse JSON response\", ex);\n            }\n        }\n        public void Dispose()\n        {\n            if (!_disposed)\n            {\n                _disposed = true;\n            }\n        }\n\n        ~VisitorDataTokenGenerator()\n        {\n            Dispose();\n        }\n    }\n}\n"
  },
  {
    "path": "src/libvideo/WebSites.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\n\nnamespace VideoLibrary\n{\n    public enum WebSites\n    {\n        YouTube = 0\n    }\n}\n"
  },
  {
    "path": "src/libvideo/YouTube.cs",
    "content": "using System;\r\nusing System.Collections.Generic;\r\nusing System.Linq;\r\nusing System.Net;\r\nusing System.Net.Http;\r\nusing System.Text;\r\nusing System.Text.RegularExpressions;\r\nusing System.Text.Json;\r\nusing System.Text.Json.Nodes;\r\nusing System.Threading.Tasks;\r\nusing VideoLibrary.Exceptions;\r\nusing VideoLibrary.Helpers;\r\nusing System.IO;\r\n\r\n\r\nnamespace VideoLibrary\r\n{\r\n    public class YouTube : ServiceBase<YouTubeVideo>\r\n    {\r\n        private const string Playback = \"videoplayback\";\r\n        private static string _signatureKey;\r\n        public static YouTube Default { get; } = new YouTube();\r\n        public const string YoutubeUrl = \"https://youtube.com/\";\r\n\r\n        internal override async Task<IEnumerable<YouTubeVideo>> GetAllVideosAsync(string videoUri, Func<string, Task<string>> sourceFactory)\r\n        {\r\n            if (!TryNormalize(videoUri, out videoUri))\r\n                throw new ArgumentException(\"URL is not a valid YouTube URL!\");\r\n\r\n            // TODO Remove\r\n            string source = await sourceFactory(videoUri).ConfigureAwait(false);\r\n\r\n            // TODO Remove\r\n            string jsPlayer = ParseJsPlayer(source);\r\n\r\n            if (jsPlayer == null)\r\n            {\r\n                throw new UnavailableStreamException($\"JS Player is not found\");\r\n            }\r\n\r\n            var playerResponseJson = JsonDocument.Parse(Json.Extract(ParsePlayerJson(source))).RootElement;\r\n\r\n            // PlayerJson from IOS content\r\n            var data = await GetPlayerResponseIOSAsync(playerResponseJson.GetProperty(\"videoDetails\").GetNullableProperty(\"videoId\")?.GetString())\r\n                .ConfigureAwait(false);\r\n\r\n            if (data != null)\r\n            {\r\n                playerResponseJson = JsonDocument.Parse(data).RootElement;\r\n            }\r\n\r\n            return ParseVideos(source, jsPlayer, playerResponseJson);\r\n        }\r\n        public static string GetSignatureKey()\r\n        {\r\n            return string.IsNullOrWhiteSpace(_signatureKey) ? \"signature\" : _signatureKey;\r\n        }\r\n\r\n        private bool TryNormalize(string videoUri, out string normalized)\r\n        {\r\n            // If you fix something in here, please be sure to fix in \r\n            // DownloadUrlResolver.TryNormalizeYoutubeUrl as well.\r\n\r\n            normalized = null;\r\n\r\n            var builder = new StringBuilder(videoUri);\r\n\r\n            videoUri = builder.Replace(\"youtu.be/\", \"youtube.com/watch?v=\")\r\n                .Replace(\"youtube.com/embed/\", \"youtube.com/watch?v=\")\r\n                .Replace(\"/v/\", \"/watch?v=\")\r\n                .Replace(\"/watch#\", \"/watch?\")\r\n                .Replace(\"youtube.com/shorts/\", \"youtube.com/watch?v=\")\r\n                .ToString();\r\n\r\n            var query = new Query(videoUri);\r\n\r\n            string value;\r\n\r\n            if (!query.TryGetValue(\"v\", out value))\r\n                return false;\r\n\r\n            normalized = $\"{YoutubeUrl}watch?v=\" + value;\r\n            return true;\r\n        }\r\n\r\n        private IEnumerable<YouTubeVideo> ParseVideos(string source, string jsPlayer, JsonElement playerResponseJson)\r\n        {\r\n            IEnumerable<UnscrambledQuery> queries;\r\n\r\n            if (string.Equals(playerResponseJson.GetProperty(\"playabilityStatus\").GetNullableProperty(\"status\")?.GetString(), \"error\", StringComparison.OrdinalIgnoreCase))\r\n            {\r\n                throw new UnavailableStreamException($\"Video has unavailable stream.\");\r\n            }\r\n\r\n            var errorReason = playerResponseJson.GetProperty(\"playabilityStatus\").GetNullableProperty(\"reason\")?.GetString();\r\n            if (string.IsNullOrWhiteSpace(errorReason))\r\n            {\r\n                var isLiveStream = playerResponseJson.GetProperty(\"videoDetails\").GetNullableProperty(\"isLive\")?.GetBoolean() == true;\r\n                var title = playerResponseJson.GetProperty(\"videoDetails\").GetNullableProperty(\"title\")?.GetString();\r\n                var lengthSeconds = playerResponseJson.GetProperty(\"videoDetails\").GetNullableProperty(\"lengthSeconds\")?.GetString() ?? \"0\";\r\n                var author = playerResponseJson.GetProperty(\"videoDetails\").GetNullableProperty(\"author\")?.GetString();\r\n\r\n                var videoInfo = new VideoInfo(title, int.Parse(lengthSeconds), author);\r\n\r\n                if (isLiveStream)\r\n                {\r\n                    throw new UnavailableStreamException($\"This is live stream so unavailable stream.\");\r\n                }\r\n\r\n                string map = Json.GetKey(\"url_encoded_fmt_stream_map\", source);\r\n                if (!string.IsNullOrWhiteSpace(map))\r\n                {\r\n                    queries = map.Split(',').Select(Unscramble);\r\n                    foreach (var query in queries)\r\n                        yield return new YouTubeVideo(videoInfo, query, jsPlayer);\r\n                }\r\n                else // player_response\r\n                {\r\n                    List<JsonElement> streamObjects = new List<JsonElement>();\r\n\r\n                    // Extract Muxed streams\r\n                    var streamFormat = playerResponseJson.GetNullableProperty(\"streamingData\")?.GetNullableProperty(\"formats\");\r\n                    if (streamFormat != null)\r\n                    {\r\n                        streamObjects.AddRange(streamFormat?.EnumerateArray());\r\n                    }\r\n\r\n                    // Extract AdaptiveFormat streams\r\n                    var streamAdaptiveFormats = playerResponseJson.GetNullableProperty(\"streamingData\")?.GetNullableProperty(\"adaptiveFormats\");\r\n                    if (streamAdaptiveFormats != null)\r\n                    {\r\n                        streamObjects.AddRange(streamAdaptiveFormats?.EnumerateArray());\r\n                    }\r\n\r\n                    foreach (var item in streamObjects)\r\n                    {\r\n                        var urlValue = item.GetNullableProperty(\"url\")?.GetString();\r\n                        if (!string.IsNullOrEmpty(urlValue))\r\n                        {\r\n                            var query = new UnscrambledQuery(urlValue, false);\r\n                            yield return new YouTubeVideo(videoInfo, query, jsPlayer);\r\n                            continue;\r\n                        }\r\n\r\n                        var cipherValue = (item.GetNullableProperty(\"cipher\") ?? item.GetNullableProperty(\"signatureCipher\"))?.GetString();\r\n                        if (!string.IsNullOrEmpty(cipherValue))\r\n                        {\r\n                            yield return new YouTubeVideo(videoInfo, Unscramble(cipherValue), jsPlayer);\r\n                        }\r\n                    }\r\n                }\r\n\r\n                // adaptive_fmts\r\n                string adaptiveMap = Json.GetKey(\"adaptive_fmts\", source);\r\n                if (!string.IsNullOrWhiteSpace(adaptiveMap))\r\n                {\r\n                    queries = adaptiveMap.Split(',').Select(Unscramble);\r\n                    foreach (var query in queries)\r\n                        yield return new YouTubeVideo(videoInfo, query, jsPlayer);\r\n                }\r\n                else\r\n                {\r\n                    // dashmpd\r\n                    string dashmpdMap = Json.GetKey(\"dashmpd\", source);\r\n                    if (!string.IsNullOrWhiteSpace(adaptiveMap))\r\n                    {\r\n                        using (HttpClient hc = new HttpClient())\r\n                        {\r\n                            IEnumerable<string> uris = null;\r\n                            try\r\n                            {\r\n\r\n                                dashmpdMap = WebUtility.UrlDecode(dashmpdMap).Replace(@\"\\/\", \"/\");\r\n\r\n                                var manifest = hc\r\n                                    .GetStringAsync(dashmpdMap)\r\n                                    .GetAwaiter()\r\n                                    .GetResult()\r\n                                    .Replace(@\"\\/\", \"/\");\r\n\r\n                                uris = Html.GetUrisFromManifest(manifest);\r\n                            }\r\n                            catch (Exception e)\r\n                            {\r\n                                throw new UnavailableStreamException(e.Message);\r\n                            }\r\n\r\n                            if (uris != null)\r\n                            {\r\n                                foreach (var v in uris)\r\n                                {\r\n                                    yield return new YouTubeVideo(videoInfo, UnscrambleManifestUri(v), jsPlayer);\r\n                                }\r\n                            }\r\n                        }\r\n                    }\r\n                }\r\n            }\r\n            else\r\n            {\r\n                throw new UnavailableStreamException($\"Error caused by Youtube.({errorReason}))\");\r\n            }\r\n        }\r\n\r\n        private string ParsePlayerJson(string source)\r\n        {\r\n            string playerResponseMap = null, ytInitialPlayerPattern = @\"\\s*var\\s*ytInitialPlayerResponse\\s*=\\s*(\\{\\\"\"responseContext\\\"\".*\\});\", ytWindowInitialPlayerResponse = @\"\\[\\\"\"ytInitialPlayerResponse\\\"\"\\]\\s*=\\s*(\\{.*\\});\", ytPlayerPattern = @\"ytplayer\\.config\\s*=\\s*(\\{\\\"\".*\\\"\"\\}\\});\";\r\n            Match match;\r\n            if ((match = Regex.Match(source, ytPlayerPattern)).Success && Json.TryGetKey(\"player_response\", match.Groups[1].Value, out string json))\r\n            {\r\n                playerResponseMap = Regex.Unescape(json);\r\n            }\r\n            if (string.IsNullOrWhiteSpace(playerResponseMap) && (match = Regex.Match(source, ytInitialPlayerPattern)).Success)\r\n            {\r\n                playerResponseMap = match.Groups[1].Value;\r\n            }\r\n            if (string.IsNullOrWhiteSpace(playerResponseMap) && (match = Regex.Match(source, ytWindowInitialPlayerResponse)).Success)\r\n            {\r\n                playerResponseMap = match.Groups[1].Value;\r\n            }\r\n            if (string.IsNullOrWhiteSpace(playerResponseMap))\r\n            {\r\n                throw new UnavailableStreamException(\"Player json has no found.\");\r\n            }\r\n            return playerResponseMap.Replace(@\"\\u0026\", \"&\").Replace(\"\\r\\n\", string.Empty).Replace(\"\\n\", string.Empty).Replace(\"\\r\", string.Empty).Replace(\"\\\\&\", \"\\\\\\\\&\");\r\n        }\r\n\r\n        private string ParseJsPlayer(string source)\r\n        {\r\n            if (Json.TryGetKey(\"jsUrl\", source, out var jsPlayer) || Json.TryGetKey(\"PLAYER_JS_URL\", source, out jsPlayer))\r\n            {\r\n                jsPlayer = jsPlayer.Replace(@\"\\/\", \"/\");\r\n            }\r\n            else\r\n            {\r\n                // Alternative solution\r\n                Match match = Regex.Match(source, \"<script\\\\s*src=\\\"([-a-zA-Z0-9()@:%_\\\\+.~#?&//=]*)\\\".*name=\\\"player_ias/base\\\".*>\\\\s*</script>\");\r\n                if (match.Success)\r\n                {\r\n                    jsPlayer = match.Groups[1].Value.Replace(@\"\\/\", \"/\");\r\n                }\r\n                else\r\n                {\r\n                    return null;\r\n                }\r\n            }\r\n\r\n            if (jsPlayer.StartsWith(\"/yts\") || jsPlayer.StartsWith(\"/s\"))\r\n            {\r\n                return $\"https://www.youtube.com{jsPlayer}\";\r\n            }\r\n\r\n            // Fall back on old implementation (not sure it's needed)\r\n            if (!jsPlayer.StartsWith(\"http\"))\r\n            {\r\n                jsPlayer = $\"https:{jsPlayer}\";\r\n            }\r\n\r\n            return jsPlayer;\r\n        }\r\n\r\n        private UnscrambledQuery Unscramble(string queryString)\r\n        {\r\n            queryString = queryString.Replace(@\"\\u0026\", \"&\");\r\n            var query = new Query(queryString);\r\n            string uri = query[\"url\"];\r\n\r\n            query.TryGetValue(\"sp\", out _signatureKey);\r\n\r\n            bool encrypted = false;\r\n            string signature;\r\n\r\n            if (query.TryGetValue(\"s\", out signature))\r\n            {\r\n                encrypted = true;\r\n                uri += GetSignatureAndHost(GetSignatureKey(), signature, query);\r\n            }\r\n            else if (query.TryGetValue(\"sig\", out signature))\r\n                uri += GetSignatureAndHost(GetSignatureKey(), signature, query);\r\n\r\n            uri = WebUtility.UrlDecode(\r\n                WebUtility.UrlDecode(uri));\r\n\r\n            var uriQuery = new Query(uri);\r\n\r\n            if (!uriQuery.ContainsKey(\"ratebypass\"))\r\n                uri += \"&ratebypass=yes\";\r\n\r\n            return new UnscrambledQuery(uri, encrypted);\r\n        }\r\n\r\n        private string GetSignatureAndHost(string key, string signature, Query query)\r\n        {\r\n            string result = $\"&{key}={signature}\";\r\n\r\n            string host;\r\n\r\n            if (query.TryGetValue(\"fallback_host\", out host))\r\n                result += \"&fallback_host=\" + host;\r\n\r\n            return result;\r\n        }\r\n\r\n        private UnscrambledQuery UnscrambleManifestUri(string manifestUri)\r\n        {\r\n            int start = manifestUri.IndexOf(Playback) + Playback.Length;\r\n            string baseUri = manifestUri.Substring(0, start);\r\n            string parametersString = manifestUri.Substring(start, manifestUri.Length - start);\r\n            var parameters = parametersString.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries);\r\n\r\n            var builder = new StringBuilder(baseUri);\r\n            builder.Append(\"?\");\r\n            for (var i = 0; i < parameters.Length; i += 2)\r\n            {\r\n                builder.Append(parameters[i]);\r\n                builder.Append('=');\r\n                builder.Append(parameters[i + 1].Replace(\"%2F\", \"/\"));\r\n                if (i < parameters.Length - 2)\r\n                {\r\n                    builder.Append('&');\r\n                }\r\n            }\r\n\r\n            return new UnscrambledQuery(builder.ToString(), false);\r\n        }\r\n\r\n        private async Task<string> GetPlayerResponseIOSAsync(string id)\r\n        {\r\n            var httpClient = new HttpClient();\r\n            var request = new HttpRequestMessage(HttpMethod.Post, \"https://www.youtube.com/youtubei/v1/player\");\r\n\r\n            var content = new\r\n            {\r\n                videoId = id,\r\n                contentCheckOk = true,\r\n                context = new\r\n                {\r\n                    client = new\r\n                    {\r\n                        clientName = \"ANDROID_VR\",\r\n                        clientVersion = \"1.60.19\",\r\n                        deviceMake = \"Oculus\",\r\n                        deviceModel = \"Quest 3\",\r\n                        osName = \"Android\",\r\n                        osVersion = \"12L\",\r\n                        platform = \"MOBILE\",\r\n                        hl = \"en\",\r\n                        gl = \"US\",\r\n                        utcOffsetMinutes = 0,\r\n                        visitorData = await VisitorDataTokenGenerator.GetVisitorDataFromYouTube(httpClient),\r\n                    }\r\n                }\r\n            };\r\n\r\n            request.Content = new StringContent(JsonSerializer.Serialize(content));\r\n            request.Headers.Add(\"User-Agent\", \"com.google.android.apps.youtube.vr.oculus/1.60.19 (Linux; U; Android 12L; Quest 3 Build/SQ3A.220605.009.A1) gzip\");\r\n            var response = await httpClient.SendAsync(request);\r\n\r\n            if (response.IsSuccessStatusCode)\r\n            {\r\n\r\n                var responseContent = await response.Content.ReadAsStringAsync();\r\n                httpClient.Dispose();\r\n                request.Dispose();\r\n                request.Dispose();\r\n\r\n                return responseContent;\r\n            }\r\n\r\n            return null;\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/libvideo/YouTubeVideo.Decrypt.cs",
    "content": "using System;\nusing System.Collections.Generic;\nusing System.Globalization;\nusing System.Text;\nusing System.Text.RegularExpressions;\nusing System.Threading.Tasks;\nusing VideoLibrary.Helpers;\n\nnamespace VideoLibrary\n{\n    public partial class YouTubeVideo\n    {\n        private async Task<string> DecryptAsync(string uri, Func<DelegatingClient> makeClient)\n        {\n            var query = new Query(uri);\n\n            string signature;\n            if (!query.TryGetValue(YouTube.GetSignatureKey(), out signature))\n                return uri;\n\n            if (string.IsNullOrWhiteSpace(signature))\n                throw new Exception(\"Signature not found.\");\n\n            if (jsPlayer == null)\n            {\n                jsPlayer = await makeClient()\n                    .GetStringAsync(jsPlayerUrl)\n                    .ConfigureAwait(false);\n            }\n\n            query[YouTube.GetSignatureKey()] = DecryptSignature(jsPlayer, signature);\n            return query.ToString();\n        }\n\n        private string DecryptSignature(string js, string signature)\n        {\n            var functionNameRegex = new Regex(@\"\\w+(?:.|\\[)(\\\"\"?\\w+(?:\\\"\")?)\\]?\\(\");\n            var functionLines = GetDecryptionFunctionLines(js);\n            var decryptor = new Decryptor();\n            var decipherDefinitionName = Regex.Match(string.Join(\";\", functionLines), \"([\\\\$_\\\\w]+).\\\\w+\\\\(\\\\w+,\\\\d+\\\\);\").Groups[1].Value;\n            if (string.IsNullOrEmpty(decipherDefinitionName))\n            {\n                throw new Exception(\"Could not find signature decipher definition name. Please report this issue to us.\");\n            }\n\n            var decipherDefinitionBody = Regex.Match(js, $@\"var\\s+{Regex.Escape(decipherDefinitionName)}=\\{{(\\w+:function\\(\\w+(,\\w+)?\\)\\{{(.*?)\\}}),?\\}};\", RegexOptions.Singleline).Groups[0].Value;\n            if (string.IsNullOrEmpty(decipherDefinitionBody))\n            {\n                throw new Exception(\"Could not find signature decipher definition body. Please report this issue to us.\");\n            }\n            foreach (var functionLine in functionLines)\n            {\n                if (decryptor.IsComplete)\n                {\n                    break;\n                }\n\n                var match = functionNameRegex.Match(functionLine);\n                if (match.Success)\n                {\n                    decryptor.AddFunction(decipherDefinitionBody, match.Groups[1].Value);\n                }\n            }\n\n            foreach (var functionLine in functionLines)\n            {\n                var match = functionNameRegex.Match(functionLine);\n                if (match.Success)\n                {\n                    signature = decryptor.ExecuteFunction(signature, functionLine, match.Groups[1].Value);\n                }\n            }\n\n            return signature;\n        }\n\n        private string[] GetDecryptionFunctionLines(string js)\n        {\n            var decipherFuncName = Regex.Match(js, @\"(\\w+)=function\\(\\w+\\){(\\w+)=\\2\\.split\\(\\x22{2}\\);.*?return\\s+\\2\\.join\\(\\x22{2}\\)}\");\n            return decipherFuncName.Success ? decipherFuncName.Groups[0].Value.Split(';') : null;\n        }\n\n        private class Decryptor\n        {\n            private static readonly Regex ParametersRegex = new Regex(@\"\\(\\w+,(\\d+)\\)\");\n\n            private readonly Dictionary<string, FunctionType> _functionTypes = new Dictionary<string, FunctionType>();\n            private readonly StringBuilder _stringBuilder = new StringBuilder();\n\n            public bool IsComplete =>\n                _functionTypes.Count == Enum.GetValues(typeof(FunctionType)).Length;\n\n            public void AddFunction(string js, string function)\n            {\n                var escapedFunction = Regex.Escape(function);\n                FunctionType? type = null;\n                /* Pass  \"do\":function(a){} or xa:function(a,b){} */\n                if (Regex.IsMatch(js, $@\"(\\\"\")?{escapedFunction}(\\\"\")?:\\bfunction\\b\\([a],b\\).(\\breturn\\b)?.?\\w+\\.\"))\n                {\n                    type = FunctionType.Slice;\n                }\n                else if (Regex.IsMatch(js, $@\"(\\\"\")?{escapedFunction}(\\\"\")?:\\bfunction\\b\\(\\w+\\,\\w\\).\\bvar\\b.\\bc=a\\b\"))\n                {\n                    type = FunctionType.Swap;\n                }\n                if (Regex.IsMatch(js, $@\"(\\\"\")?{escapedFunction}(\\\"\")?:\\bfunction\\b\\(\\w+\\){{\\w+\\.reverse\"))\n                {\n                    type = FunctionType.Reverse;\n                }\n                if (type.HasValue)\n                {\n                    _functionTypes[function] = type.Value;\n                }\n            }\n\n            public string ExecuteFunction(string signature, string line, string function)\n            {\n                if (!_functionTypes.TryGetValue(function, out var type))\n                {\n                    return signature;\n                }\n\n                switch (type)\n                {\n                    case FunctionType.Reverse:\n                        return Reverse(signature);\n                    case FunctionType.Slice:\n                    case FunctionType.Swap:\n                        var index =\n                            int.Parse(\n                                ParametersRegex.Match(line).Groups[1].Value,\n                                NumberStyles.AllowThousands,\n                                NumberFormatInfo.InvariantInfo);\n                        return\n                            type == FunctionType.Slice\n                                ? Slice(signature, index)\n                                : Swap(signature, index);\n                    default:\n                        throw new ArgumentOutOfRangeException(nameof(type));\n                }\n            }\n\n            private string Reverse(string signature)\n            {\n                _stringBuilder.Clear();\n                for (var index = signature.Length - 1; index >= 0; index--)\n                {\n                    _stringBuilder.Append(signature[index]);\n                }\n\n                return _stringBuilder.ToString();\n            }\n\n            private string Slice(string signature, int index) => signature.Substring(index);\n\n            private string Swap(string signature, int index)\n            {\n                _stringBuilder.Clear();\n                _stringBuilder.Append(signature);\n                _stringBuilder[0] = _stringBuilder[index % _stringBuilder.Length];\n                _stringBuilder[index % _stringBuilder.Length] = signature[0];\n                return _stringBuilder.ToString();\n            }\n\n            private enum FunctionType\n            {\n                Reverse,\n                Slice,\n                Swap\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/libvideo/YouTubeVideo.Format.cs",
    "content": "﻿namespace VideoLibrary\n{\n    // TODO Add/Fix itags\n    public partial class YouTubeVideo\n    {\n        public int Fps\n        {\n            get\n            {\n                switch (FormatCode)\n                {\n                    case 571:\n                    case 402:\n                    case 401:\n                    case 400:\n                    case 399:\n                    case 398:\n                    case 337:\n                    case 336:\n                    case 335:\n                    case 334:\n                    case 333:\n                    case 332:\n                    case 331:\n                    case 330:\n                    case 272:\n                    case 315:\n                    case 308:\n                    case 303:\n                    case 302:\n                    case 305:\n                    case 304:\n                    case 299:\n                    case 298:\n                        return 60;\n                    case 18:\n                    case 22:\n                    case 37:\n                    case 43:\n                    case 59:\n                    case 397:\n                    case 396:\n                    case 395:\n                    case 394:\n                    case 313:\n                    case 271:\n                    case 248:\n                    case 247:\n                    case 244:\n                    case 243:\n                    case 242:\n                    case 278:\n                    case 138:\n                    case 266:\n                    case 264:\n                    case 137:\n                    case 136:\n                    case 135:\n                    case 134:\n                    case 133:\n                    case 160:\n                        return 30;\n                    default:\n                        return -1;\n                }\n            }\n        }\n\n        public bool IsAdaptive => this.AdaptiveKind != AdaptiveKind.None;\n\n        public AdaptiveKind AdaptiveKind\n        {\n            get\n            {\n                switch (FormatCode)\n                {\n                    case 18:\n                    case 22:\n                    case 37:\n                    case 43:\n                    case 59:\n                    case 133:\n                    case 134:\n                    case 135:\n                    case 136:\n                    case 137:\n                    case 138:\n                    case 160:\n                    case 242:\n                    case 243:\n                    case 244:\n                    case 247:\n                    case 248:\n                    case 264:\n                    case 266:\n                    case 271:\n                    case 272:\n                    case 298:\n                    case 299:\n                    case 302:\n                    case 303:\n                    case 304:\n                    case 305:\n                    case 308:\n                    case 313:\n                    case 315:\n                    case 330:\n                    case 331:\n                    case 332:\n                    case 333:\n                    case 334:\n                    case 335:\n                    case 336:\n                    case 337:\n                    case 394:\n                    case 395:\n                    case 396:\n                    case 397:\n                    case 398:\n                    case 399:\n                    case 400:\n                    case 401:\n                    case 402:\n                    case 571:\n                    case 278:\n                    case 694:\n                    case 695:\n                    case 696:\n                    case 697:\n                    case 698:\n                    case 699:\n                    case 700:\n                    case 701:\n                        return AdaptiveKind.Video;\n                    case 139:\n                    case 140:\n                    case 141:\n                    case 171:\n                    case 172:\n                    case 249:\n                    case 250:\n                    case 251:\n                    case 256:\n                    case 258:\n                    case 327:\n                    case 338:\n                        return AdaptiveKind.Audio;\n                    default:\n                        return AdaptiveKind.None;\n                }\n            }\n        }\n\n        public int AudioBitrate\n        {\n            get\n            {\n                switch (FormatCode)\n                {\n                    case 139:\n                    case 249:\n                    case 250:\n                    case 599:\n                    case 600:\n                        return 48;\n                    case 18:\n                        return 96;\n                    case 37:\n                    case 43:\n                    case 59:\n                    case 140:\n                    case 171:\n                    case 251:\n                        return 128;\n                    case 22:\n                    case 256:\n                        return 192;\n                    case 141:\n                    case 172:\n                    case 327:\n                    case 774:\n                        return 256;\n                    case 258:\n                    case 325: \n                    case 328: \n                    case 380:\n                        return 384;\n                    case 338:\n                        return 480;\n                    case 773:\n                        return 960;\n                    default:\n                        return -1;\n                }\n            }\n        }\n\n        public int Resolution\n        {\n            get\n            {\n                switch (FormatCode)\n                {\n                    case 394:\n                    case 330:\n                    case 278:\n                    case 160:\n                    case 694:\n                    case 597:\n                    case 598:\n                        return 144;\n                    case 395:\n                    case 331:\n                    case 242:\n                    case 133:\n                    case 695:\n                        return 240;\n                    case 18:\n                    case 43:\n                    case 396:\n                    case 332:\n                    case 243:\n                    case 134:\n                    case 696:\n                    case 167:\n                        return 360;\n                    case 59:\n                    case 397:\n                    case 333:\n                    case 244:\n                    case 135:\n                    case 697:\n                    case 168:\n                        return 480;\n                    case 22:\n                    case 398:\n                    case 334:\n                    case 302:\n                    case 247:\n                    case 298:\n                    case 136:\n                    case 698:\n                    case 169:\n                    case 612:\n                        return 720;\n                    case 37:\n                    case 399:\n                    case 335:\n                    case 303:\n                    case 248:\n                    case 299:\n                    case 137:\n                    case 699:\n                    case 170:\n                    case 216:\n                    case 616:\n                    case 721:\n                        return 1080;\n                    case 400:\n                    case 336:\n                    case 308:\n                    case 271:\n                    case 304:\n                    case 264:\n                    case 700:\n                        return 1440;\n                    case 401:\n                    case 337:\n                    case 315:\n                    case 313:\n                    case 305:\n                    case 266:\n                    case 701:\n                        return 2160;\n                    case 138:\n                    case 272:\n                    case 402:\n                    case 571:\n                    case 702:\n                        return 4320;\n                    default:\n                        return -1;\n                }\n            }\n        }\n\n        public override VideoFormat Format\n        {\n            get\n            {\n                switch (FormatCode)\n                {\n                    case 18:\n                    case 22:\n                    case 37:\n                    case 59:\n                    case 133:\n                    case 134:\n                    case 135:\n                    case 136:\n                    case 137:\n                    case 138:\n                    case 160:\n                    case 264:\n                    case 266:\n                    case 298:\n                    case 299:\n                    case 304:\n                    case 305:\n                    case 394:\n                    case 395:\n                    case 396:\n                    case 397:\n                    case 398:\n                    case 399:\n                    case 400:\n                    case 401:\n                    case 402:\n                    case 571:\n                    case 168:\n                    case 169:\n                    case 170:\n                    case 216:\n                    case 278:\n                    case 694:\n                    case 695:\n                    case 696:\n                    case 697:\n                    case 698:\n                    case 699:\n                    case 700:\n                    case 701:\n                    case 702:\n                    case 721:\n                        return VideoFormat.Mp4;\n                    case 43:\n                    case 242:\n                    case 243:\n                    case 244:\n                    case 247:\n                    case 248:\n                    case 271:\n                    case 272:\n                    case 302:\n                    case 303:\n                    case 308:\n                    case 313:\n                    case 598:\n                    case 597:\n                    case 612:\n                    case 616:\n                        return VideoFormat.WebM;\n                    default:\n                        return VideoFormat.Unknown;\n                }\n            }\n        }\n\n        public AudioFormat AudioFormat\n        {\n            get\n            {\n                switch (FormatCode)\n                {\n                    case 18:\n                    case 22:\n                    case 37:\n                    case 59:\n                    case 139:\n                    case 140:\n                    case 141:\n                    case 256:\n                    case 258:\n                    case 327:\n                    case 325: \n                    case 328: \n                    case 380: \n                    case 599:\n                        return AudioFormat.Aac;\n                    case 171:\n                    case 172:\n                        return AudioFormat.Vorbis;\n                    case 43:\n                    case 249:\n                    case 250:\n                    case 251:\n                    case 338:\n                    case 600:\n                    case 773:\n                    case 774:\n                        return AudioFormat.Opus;\n                    default:\n                        return AudioFormat.Unknown;\n                }\n            }\n        }\n    }\n}"
  },
  {
    "path": "src/libvideo/YouTubeVideo.cs",
    "content": "using System;\nusing System.Threading.Tasks;\nusing VideoLibrary.Helpers;\n\nnamespace VideoLibrary\n{\n    public partial class YouTubeVideo : Video\n    {\n        private readonly string jsPlayerUrl;\n        private string jsPlayer;\n        private string uri;\n        private readonly Query _uriQuery;\n        private bool _encrypted;\n        private bool _needNDescramble;\n        internal YouTubeVideo(VideoInfo info, UnscrambledQuery query, string jsPlayerUrl)\n        {\n            this.Info = info;\n            this.Title = info?.Title;\n            this.uri = query.Uri;\n            this._uriQuery = new Query(uri);\n            this.jsPlayerUrl = jsPlayerUrl;\n            this._encrypted = query.IsEncrypted;\n            this._needNDescramble = _uriQuery.ContainsKey(\"n\");\n            this.FormatCode = int.Parse(_uriQuery[\"itag\"]);\n        }\n\n        public override string Title { get; }\n\n        public override VideoInfo Info { get; }\n\n        public override WebSites WebSite => WebSites.YouTube;\n\n        public override string Uri => GetUriAsync().GetAwaiter().GetResult();\n\n        public string GetUri(Func<DelegatingClient> makeClient) => GetUriAsync(makeClient).GetAwaiter().GetResult();\n\n        public override Task<string> GetUriAsync() => GetUriAsync(() => new DelegatingClient());\n\n        public async Task<string> GetUriAsync(Func<DelegatingClient> makeClient)\n        {\n            if (_encrypted)\n            {\n                uri = await DecryptAsync(uri, makeClient).ConfigureAwait(false);\n                _encrypted = false;\n            }\n\n            if (_needNDescramble)\n            {\n                uri = await NDescrambleAsync(uri, makeClient).ConfigureAwait(false);\n                _needNDescramble = false;\n            }\n\n            return uri;\n        }\n\n        public int FormatCode { get; }\n\n        public long? ContentLength\n        {\n            get\n            {\n                if (_contentLength.HasValue)\n                    return _contentLength;\n                _contentLength = this.GetContentLength(_uriQuery).Result;\n                return _contentLength;\n            }\n        }\n\n        public bool IsEncrypted => _encrypted;\n\n        // Private's\n        private long? _contentLength { get; set; }\n        private async Task<long?> GetContentLength(Query query)\n        {\n            if (query.TryGetValue(\"clen\", out string clen))\n            {\n                return long.Parse(clen);\n            }\n            using (var client = new VideoClient())\n            {\n                return await client.GetContentLengthAsync(uri);\n            }\n        }\n\n    }\n}\n"
  },
  {
    "path": "src/libvideo/YoutubeVideo.Descramble.cs",
    "content": "﻿using System;\nusing System.Text.RegularExpressions;\nusing System.Threading.Tasks;\nusing VideoLibrary.Helpers;\n\nnamespace VideoLibrary\n{\n    public partial class YouTubeVideo\n    {\n        private async Task<string> NDescrambleAsync(string uri, Func<DelegatingClient> makeClient)\n        {\n            var query = new Query(uri);\n\n            if (!query.TryGetValue(\"n\", out var signature))\n                return uri;\n\n            if (string.IsNullOrWhiteSpace(signature))\n                throw new Exception(\"N Signature not found.\");\n\n            if (jsPlayer == null)\n            {\n                jsPlayer = await makeClient()\n                    .GetStringAsync(jsPlayerUrl)\n                    .ConfigureAwait(false);\n            }\n\n            query[\"n\"] = DescrambleNSignature(jsPlayer, signature);\n            return query.ToString();\n        }\n\n        private string DescrambleNSignature(string js, string signature)\n        {\n            return signature;\n            //var func = GetDescrambleFunctionBody(js);\n            //var asd = \"var \" + func.Item2.Replace(\"===\\\"undefined\\\"\", \"!=='undefined'\");\n            //var result = new Engine()\n            //    .Execute(asd)\n            //    .Invoke(func.Item1, signature); // -> 3\n\n            //File.WriteAllText(\"C:\\\\Users\\\\OMANSAK\\\\Desktop\\\\asd.txt\", js);\n            //// TODO Add Native Descramble for \"N\" Signature\n            //return result.ToString();\n        }\n\n        private Tuple<string, string> GetDescrambleFunctionBody(string js)\n        {\n            string functionName = null;\n            var functionLine = Regex.Match(js, @\"([\\w\\d]*)=function\\(\\w+?\\){var \\w+=\\w+.split\\(\\w+\\.slice\\(0,0\\)\\),Z=\\[\");\n\n            if (functionLine.Success && !functionLine.Groups[2].Success)\n            {\n                functionName = functionLine.Groups[1].Value;\n            }\n            else\n            {\n                var fname = Regex.Match(js, $@\"var {functionLine.Groups[1]}\\s*=\\s*(\\[.+?\\]);\");\n                if (fname.Success && fname.Groups[1].Success)\n                {\n                    functionName = fname.Groups[1].Value\n                        .Replace(\"[\", string.Empty)\n                        .Replace(\"]\", string.Empty)\n                        .Split(',')[int.Parse(functionLine.Groups[2].Value)];\n                }\n            }\n\n            if (!string.IsNullOrWhiteSpace(functionName))\n            {\n                var decipherDefinitionBody = Regex.Match(js, $@\"{Regex.Escape(functionName)}=function\\(\\w+(,\\w+)?\\)\\{{(?s:.*?)\\}};\", RegexOptions.Singleline);\n\n                if (decipherDefinitionBody.Success)\n                {\n                    return new Tuple<string, string>(functionName, decipherDefinitionBody.Groups[0].Value);\n                }\n            }\n\n            return new Tuple<string, string>(functionName, null);\n        }\n    }\n}"
  },
  {
    "path": "src/libvideo/libvideo.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\n\n\t<PropertyGroup>\n\t\t<Authors>Bar Arnon,OMANSAK</Authors>\n\t\t<Copyright>Copyright 2018 Bar Arnon | 2026 OMANSAK</Copyright>\n\t\t<Description>libvideo (aka VideoLibrary) is a modern .NET library for downloading YouTube videos. It is portable to most platforms and is very lightweight.\n\nFind us on GitHub at https://github.com/omansak/libvideo</Description>\n\t\t<GeneratePackageOnBuild>true</GeneratePackageOnBuild>\n\t\t<PackageIconUrl></PackageIconUrl>\n\t\t<PackageId>VideoLibrary</PackageId>\n\t\t<PackageLicenseUrl></PackageLicenseUrl>\n\t\t<PackageProjectUrl>https://github.com/omansak/libvideo</PackageProjectUrl>\n\t\t<PackageReleaseNotes>Fix 403 Forbidden errors</PackageReleaseNotes>\n\t\t<PackageTags>youtube youtubeexplode downloader libvideo lib libs video videos library libraries download extract get vimeo scrape scraping downloader extractor getter scraper youtubeextract videolibrary library winrt wp windows phone pcl portable class library .net framework core compat compatibility api apis layer layers emulator emulators emulation emulations xamarin mono monotouch xamarin.ios ios xamarin.android android youtubedownloader c# .net standart</PackageTags>\n\t\t<RepositoryType>git</RepositoryType>\n\t\t<RepositoryUrl>https://github.com/omansak/libvideo</RepositoryUrl>\n\t\t<RootNamespace>VideoLibrary</RootNamespace>\n\t\t<Version>3.3.1</Version>\n\t\t<TargetFramework>netstandard2.0</TargetFramework>\n\t\t<Title>libvideo</Title>\n\t\t<TreatWarningsAsErrors>true</TreatWarningsAsErrors>\n\t\t<AssemblyVersion>3.3.1.0</AssemblyVersion>\n\t\t<FileVersion>3.3.1.0</FileVersion>\n\t\t<PackageLicenseFile>LICENSE</PackageLicenseFile>\n\t\t<PackageIcon>icon_586.png</PackageIcon>\n\t\t<PackageReadmeFile>README.md</PackageReadmeFile>\n\t</PropertyGroup>\n\n\t<ItemGroup>\n\t\t<None Include=\"..\\..\\icons\\icon_586.png\">\n\t\t\t<Pack>True</Pack>\n\t\t\t<PackagePath></PackagePath>\n\t\t</None>\n\t\t<None Include=\"..\\..\\LICENSE\">\n\t\t\t<Pack>True</Pack>\n\t\t\t<PackagePath></PackagePath>\n\t\t</None>\n\t\t<None Include=\"..\\..\\README.md\">\n\t\t\t<Pack>True</Pack>\n\t\t\t<PackagePath></PackagePath>\n\t\t</None>\n\t</ItemGroup>\n\n\t<ItemGroup>\n\t  <PackageReference Include=\"System.Text.Json\" Version=\"10.0.1\" />\n\t</ItemGroup>\n\n</Project>"
  },
  {
    "path": "src/libvideo.compat/AdaptiveType.cs",
    "content": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\n\nnamespace YoutubeExtractor\n{\n    public enum AdaptiveType\n    {\n        None, Audio, Video\n    }\n}\n"
  },
  {
    "path": "src/libvideo.compat/AudioExtractionException.cs",
    "content": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\n\nnamespace YoutubeExtractor\n{\n    public class AudioExtractionException : Exception\n    {\n        public AudioExtractionException(string message)\n            : base(message)\n        { }\n    }\n}\n"
  },
  {
    "path": "src/libvideo.compat/AudioType.cs",
    "content": "﻿namespace YoutubeExtractor\n{\n    public enum AudioType\n    {\n        Aac, Vorbis, Opus, Unknown\n    }\n}\n"
  },
  {
    "path": "src/libvideo.compat/DownloadUrlResolver.cs",
    "content": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\nusing VideoLibrary;\nusing VideoLibrary.Helpers;\n\nnamespace YoutubeExtractor\n{\n    public static class DownloadUrlResolver\n    {\n        private static YouTube Service = new YouTube();\n\n        public static void DecryptDownloadUrl(VideoInfo info)\n        {\n            // Nothing to do here, URL is decrypted automatically \n            // upon calling YouTubeVideo.Uri.\n        }\n\n        public static IEnumerable<VideoInfo> GetDownloadUrls(string videoUrl, bool decryptSignature = true)\n        {\n            Require.NotNull(videoUrl, nameof(videoUrl));\n\n            // GetAllVideos normalizes the URL as of libvideo v0.4.1, \n            // don't call TryNormalizeYoutubeUrl here.\n\n            return Service.GetAllVideos(videoUrl).Select(v => new VideoInfo(v));\n        }\n\n        public async static Task<IEnumerable<VideoInfo>> GetDownloadUrlsAsync(\n            string videoUrl, bool decryptSignature = true)\n        {\n            var videos = await Service\n                .GetAllVideosAsync(videoUrl)\n                .ConfigureAwait(false);\n\n            return videos.Select(v => new VideoInfo(v));\n        }\n\n        public static bool TryNormalizeYoutubeUrl(string url, out string normalizedUrl)\n        {\n            // If you fix something in here, please be sure to fix in \n            // YouTubeService.TryNormalize as well.\n\n            normalizedUrl = null;\n\n            var builder = new StringBuilder(url);\n\n            url = builder.Replace(\"youtu.be/\", \"youtube.com/watch?v=\")\n                .Replace(\"youtube.com/embed/\", \"youtube.com/watch?v=\")\n                .Replace(\"/v/\", \"/watch?v=\")\n                .Replace(\"/watch#\", \"/watch?\")\n                .Replace(\"youtube.com/shorts/\", \"youtube.com/watch?v=\")\n                .ToString();\n\n            string value;\n\n            var query = new Query(url);\n\n            if (!query.TryGetValue(\"v\", out value))\n                return false;\n\n            normalizedUrl = \"https://youtube.com/watch?v=\" + value;\n            return true;\n        }\n    }\n}\n"
  },
  {
    "path": "src/libvideo.compat/VideoInfo.cs",
    "content": "using System;\nusing VideoLibrary;\n\nnamespace YoutubeExtractor\n{\n    public class VideoInfo\n    {\n        private readonly YouTubeVideo video;\n\n        internal VideoInfo(YouTubeVideo video)\n        {\n            this.video = video;\n        }\n\n        public AdaptiveType AdaptiveType\n        {\n            get\n            {\n                switch (video.AdaptiveKind)\n                {\n                    case AdaptiveKind.Audio:\n                        return AdaptiveType.Audio;\n                    case AdaptiveKind.Video:\n                        return AdaptiveType.Video;\n                    default:\n                        return AdaptiveType.None;\n                }\n            }\n        }\n\n        public int AudioBitrate\n        {\n            get\n            {\n                int result = video.AudioBitrate;\n                return result == -1 ? 0 : result;\n            }\n        }\n\n        public string AudioExtension\n        {\n            get\n            {\n                switch (video.AudioFormat)\n                {\n                    case AudioFormat.Aac: return \".aac\";\n                    case AudioFormat.Vorbis: return \".ogg\";\n                    case AudioFormat.Opus: return \".ogg\";\n                    case AudioFormat.Unknown: return String.Empty;\n                    default:\n                        throw new NotImplementedException($\"Audio format {video.AudioFormat} is unrecognized! Please file an issue at libvideo on GitHub.\");\n                }\n            }\n        }\n\n        public AudioType AudioType\n        {\n            get\n            {\n                switch (video.AudioFormat)\n                {\n                    case AudioFormat.Aac:\n                        return AudioType.Aac;\n                    case AudioFormat.Vorbis:\n                        return AudioType.Vorbis;\n                    case AudioFormat.Opus:\n                        return AudioType.Opus;\n                    default:\n                        return AudioType.Unknown;\n                }\n            }\n        }\n\n        public bool CanExtractAudio => false;\n\n        public string DownloadUrl => video.Uri;\n\n        public int FormatCode\n        {\n            get\n            {\n                int result = video.FormatCode;\n                return result == -1 ? 0 : result;\n            }\n        }\n\n        public int Fps\n        {\n            get\n            {\n                int fps = video.Fps;\n                return fps == -1 ? 0 : fps;\n            }\n        }\n\n        public bool RequiresDecryption => false;\n\n        public int Resolution\n        {\n            get\n            {\n                int result = video.Resolution;\n                return result == -1 ? 0 : result;\n            }\n        }\n\n        public string Title => video.Title;\n\n        public string VideoExtension => video.FileExtension;\n\n        public VideoType VideoType\n        {\n            get\n            {\n                switch (video.Format)\n                {\n                    case VideoFormat.Mp4:\n                        return VideoType.Mp4;\n                    case VideoFormat.WebM:\n                        return VideoType.WebM;\n                    default:\n                        return VideoType.Unknown;\n                }\n            }\n        }\n\n        public override string ToString()\n        {\n            return string.Format($\"Full Title\\t{Title + VideoExtension}\\nType\\t{VideoType}\\nResolution\\t{Resolution}p\\nFormat\\t{FormatCode}\\nFPS\\t{Fps}\\nAudioType\\t{AudioType}\\nBitrate\\t{AudioBitrate}\\n\");\n        }\n    }\n}\n"
  },
  {
    "path": "src/libvideo.compat/VideoNotAvailableException.cs",
    "content": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\n\nnamespace YoutubeExtractor\n{\n    public class VideoNotAvailableException : Exception\n    {\n        public VideoNotAvailableException()\n        { }\n\n        public VideoNotAvailableException(string message)\n            : base(message)\n        { }\n    }\n}\n"
  },
  {
    "path": "src/libvideo.compat/VideoType.cs",
    "content": "namespace YoutubeExtractor\n{\n    public enum VideoType\n    {\n        Mp4, WebM, Unknown\n    }\n}\n"
  },
  {
    "path": "src/libvideo.compat/YoutubeParseException.cs",
    "content": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\n\nnamespace YoutubeExtractor\n{\n    public class YoutubeParseException : Exception\n    {\n        public YoutubeParseException(string message, Exception innerException)\n            : base(message, innerException)\n        { }\n    }\n}\n"
  },
  {
    "path": "src/libvideo.compat/libvideo.compat.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\n\n\t<PropertyGroup>\n\t\t<Authors>Bar Arnon,OMANSAK</Authors>\n\t\t<Copyright>Copyright 2018 Bar Arnon | 2026 OMANSAK</Copyright>\n\t\t<Description>libvideo (aka VideoLibrary) is a modern .NET library for downloading YouTube videos. It is portable to most platforms and is very lightweight.\n\nFind us on GitHub at https://github.com/omansak/libvideo</Description>\n\t\t<GeneratePackageOnBuild>true</GeneratePackageOnBuild>\n\t\t<PackageIconUrl></PackageIconUrl>\n\t\t<PackageId>VideoLibrary.Compat</PackageId>\n\t\t<PackageLicenseUrl></PackageLicenseUrl>\n\t\t<PackageProjectUrl>https://github.com/omansak/libvideo</PackageProjectUrl>\n\t\t<PackageReleaseNotes>Fix 403 Forbidden errors</PackageReleaseNotes>\n\t\t<PackageTags>youtube youtubeexplode downloader libvideo lib libs video videos library libraries download extract get vimeo scrape scraping downloader extractor getter scraper youtubeextract videolibrary library winrt wp windows phone pcl portable class library .net framework core compat compatibility api apis layer layers emulator emulators emulation emulations xamarin mono monotouch xamarin.ios ios xamarin.android android youtubedownloader</PackageTags>\n\t\t<RepositoryType>git</RepositoryType>\n\t\t<RepositoryUrl>https://github.com/omansak/libvideo</RepositoryUrl>\n\t\t<RootNamespace>YoutubeExtractor</RootNamespace>\n\t\t<Version>3.3.1</Version>\n\t\t<TargetFramework>netstandard2.0</TargetFramework>\n\t\t<Title>libvideo.compat</Title>\n\t\t<TreatWarningsAsErrors>true</TreatWarningsAsErrors>\n\t\t<AssemblyVersion>3.3.1.0</AssemblyVersion>\n\t\t<FileVersion>3.3.1.0</FileVersion>\n\t\t<PackageIcon>icon_586.png</PackageIcon>\n\t\t<NeutralLanguage>en</NeutralLanguage>\n\t\t<PackageLicenseExpression></PackageLicenseExpression>\n\t\t<PackageLicenseFile>LICENSE</PackageLicenseFile>\n\t\t<PackageReadmeFile>README.md</PackageReadmeFile>\n\t</PropertyGroup>\n\n\t<ItemGroup>\n\t\t<ProjectReference Include=\"..\\libvideo\\libvideo.csproj\" />\n\t</ItemGroup>\n\n\t<ItemGroup>\n\t\t<Compile Include=\"..\\libvideo\\Exceptions\\BadQueryException.cs\">\n\t\t\t<Link>Exceptions\\BadQueryException.cs</Link>\n\t\t</Compile>\n\t\t<Compile Include=\"..\\libvideo\\Helpers\\EmptyArray.cs\">\n\t\t\t<Link>Helpers\\EmptyArray.cs</Link>\n\t\t</Compile>\n\t\t<Compile Include=\"..\\libvideo\\Helpers\\KeyCollection.cs\">\n\t\t\t<Link>Helpers\\KeyCollection.cs</Link>\n\t\t</Compile>\n\t\t<Compile Include=\"..\\libvideo\\Helpers\\Query.cs\">\n\t\t\t<Link>Helpers\\Query.cs</Link>\n\t\t</Compile>\n\t\t<Compile Include=\"..\\libvideo\\Helpers\\Require.cs\">\n\t\t\t<Link>Helpers\\Require.cs</Link>\n\t\t</Compile>\n\t\t<Compile Include=\"..\\libvideo\\Helpers\\ValueCollection.cs\">\n\t\t\t<Link>Helpers\\ValueCollection.cs</Link>\n\t\t</Compile>\n\t</ItemGroup>\n\n\t<ItemGroup>\n\t\t<None Include=\"..\\..\\icons\\icon_586.png\">\n\t\t\t<Pack>True</Pack>\n\t\t\t<PackagePath></PackagePath>\n\t\t</None>\n\t\t<None Include=\"..\\..\\LICENSE\">\n\t\t\t<Pack>True</Pack>\n\t\t\t<PackagePath></PackagePath>\n\t\t</None>\n\t\t<None Include=\"..\\..\\README.md\">\n\t\t\t<Pack>True</Pack>\n\t\t\t<PackagePath></PackagePath>\n\t\t</None>\n\t</ItemGroup>\n\n</Project>"
  },
  {
    "path": "src/libvideo.debug/CustomYoutubeClient.cs",
    "content": "﻿using System;\nusing System.IO;\nusing System.Linq;\nusing System.Net;\nusing System.Net.Http;\nusing System.Net.Http.Headers;\nusing System.Threading;\nusing System.Threading.Tasks;\n\nnamespace VideoLibrary.Debug\n{\n    class CustomHandler\n    {\n        public HttpMessageHandler GetHandler()\n        {\n            CookieContainer cookieContainer = new CookieContainer();\n            cookieContainer.Add(new Cookie(\"CONSENT\", \"YES+cb\", \"/\", \"youtube.com\"));\n            return new HttpClientHandler\n            {\n                UseCookies = true,\n                CookieContainer = cookieContainer\n            };\n\n        }\n    }\n    class CustomYouTube : YouTube\n    {\n        private long chunkSize = 10_485_760;\n        private long _fileSize = 0L;\n        private HttpClient _client = new HttpClient();\n        protected override HttpClient MakeClient(HttpMessageHandler handler)\n        {\n            return base.MakeClient(handler);\n        }\n        protected override HttpMessageHandler MakeHandler()\n        {\n            return new CustomHandler().GetHandler();\n        }\n        public async Task CreateDownloadAsync(Uri uri, string filePath, IProgress<Tuple<long, long>> progress)\n        {\n            var totalBytesCopied = 0L;\n            _fileSize = await GetContentLengthAsync(uri.AbsoluteUri) ?? 0;\n            if (_fileSize == 0)\n            {\n                throw new Exception(\"File has no any content !\");\n            }\n            using (Stream output = File.OpenWrite(filePath))\n            {\n                var segmentCount = (int)Math.Ceiling(1.0 * _fileSize / chunkSize);\n                for (var i = 0; i < segmentCount; i++)\n                {\n                    var from = i * chunkSize;\n                    var to = (i + 1) * chunkSize - 1;\n                    var request = new HttpRequestMessage(HttpMethod.Get, uri);\n                    request.Headers.Range = new RangeHeaderValue(from, to);\n                    using (request)\n                    {\n                        // Download Stream\n                        var response = await _client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);\n                        if (response.IsSuccessStatusCode)\n                            response.EnsureSuccessStatusCode();\n                        var stream = await response.Content.ReadAsStreamAsync();\n                        //File Steam\n                        var buffer = new byte[81920];\n                        int bytesCopied;\n                        do\n                        {\n                            bytesCopied = await stream.ReadAsync(buffer, 0, buffer.Length);\n                            output.Write(buffer, 0, bytesCopied);\n                            totalBytesCopied += bytesCopied;\n                            progress.Report(new Tuple<long, long>(totalBytesCopied, _fileSize));\n                        } while (bytesCopied > 0);\n                    }\n                }\n            }\n        }\n        private async Task<long?> GetContentLengthAsync(string requestUri, bool ensureSuccess = true)\n        {\n            using (var request = new HttpRequestMessage(HttpMethod.Head, requestUri))\n            {\n                var response = await _client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);\n                if (ensureSuccess)\n                    response.EnsureSuccessStatusCode();\n                return response.Content.Headers.ContentLength;\n            }\n        }\n    }\n    class Test\n    {\n        public void Run()\n        {\n            // Custom Youtube\n            var youtube = new CustomYouTube();\n            var videos = youtube.GetAllVideosAsync(\"https://www.youtube.com/watch?v=qK_NeRZOdq4\").GetAwaiter().GetResult();\n            var maxResolution = videos.First(i => i.Resolution == videos.Max(j => j.Resolution));\n            youtube\n                .CreateDownloadAsync(\n                new Uri(maxResolution.Uri),\n                Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), maxResolution.FullName),\n                new Progress<Tuple<long, long>>((Tuple<long, long> v) =>\n                 {\n                     var percent = (int)((v.Item1 * 100) / v.Item2);\n                     Console.Write(string.Format(\"Downloading.. ( % {0} ) {1} / {2} MB\\r\", percent, (v.Item1 / (double)(1024 * 1024)).ToString(\"N\"), (v.Item2 / (double)(1024 * 1024)).ToString(\"N\")));\n                 }))\n                .GetAwaiter().GetResult();\n        }\n    }\n}\n"
  },
  {
    "path": "src/libvideo.debug/Program.cs",
    "content": "﻿using System;\nusing System.Diagnostics;\n\nnamespace VideoLibrary.Debug\n{\n    class Program\n    {\n        static void Main()\n        {\n            string[] queries =\n            {\n                \"https://www.youtube.com/watch?v=jfobiCq0YUc&ab_channel=EminemMusic\",                   //1080\n                \"https://www.youtube.com/watch?v=LXb3EKWsInQ&ab_channel=Jacob%2BKatieSchwarz\",          //2060\n                \"https://www.youtube.com/watch?v=U2XK_TJZ3PI\",                                          //JSON Parse Error\n            };\n\n            TestVideoLib(queries);\n            Console.WriteLine(\"Done.\");\n            Console.ReadKey();\n        }\n\n        public static void TestVideoLib(string[] queries)\n        {\n            //new Test().Run();\n\n            using (var cli = Client.For(YouTube.Default))\n            {\n                Console.WriteLine(\"Downloading...\");\n                for (int i = 0; i < queries.Length; i++)\n                {\n                    string uri = queries[i];\n                    try\n                    {\n                        var videoInfos = cli.GetAllVideosAsync(uri).GetAwaiter().GetResult();\n                        Console.WriteLine($\"Link #{i + 1}\");\n                        foreach (YouTubeVideo v in videoInfos)\n                        {\n                            if (v.Resolution > 0 && v.AudioBitrate < 0)\n                            {\n                                Console.WriteLine(v.Uri);\n                                Console.WriteLine(string.Format($\"Full Title\\t{v.Title + v.FileExtension}\\nType\\t{v.AdaptiveKind}\\nResolution\\t{v.Resolution}p\\nFormat\\t{v.FormatCode}\\nFPS\\t{v.Fps}\\nBitrate\\t{v.AudioBitrate}\\n\"));\n                                Console.WriteLine(\"Success : \" + v.Head().CanRead);\n                                Console.WriteLine();\n                            }\n                        }\n                    }\n                    catch (Exception e)\n                    {\n                        System.Diagnostics.Debug.WriteLine(e);\n                        Debugger.Break();\n                    }\n                }\n            }\n        }\n    }\n}"
  },
  {
    "path": "src/libvideo.debug/libvideo.debug.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\n\n  <PropertyGroup>\n    <OutputType>Exe</OutputType>\n    <RootNamespace>VideoLibrary.Debug</RootNamespace>\n    <TargetFramework>net10.0</TargetFramework>\n    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>\n  </PropertyGroup>\n\n  <ItemGroup>\n    <ProjectReference Include=\"..\\libvideo\\libvideo.csproj\" />\n  </ItemGroup>\n\n</Project>"
  },
  {
    "path": "src/libvideo.sln",
    "content": "﻿\nMicrosoft Visual Studio Solution File, Format Version 12.00\n# Visual Studio 15\nVisualStudioVersion = 15.0.27703.2000\nMinimumVisualStudioVersion = 10.0.40219.1\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"libvideo\", \"libvideo\\libvideo.csproj\", \"{9C71A8F8-39B6-4A53-8960-AEABF72A7771}\"\nEndProject\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"libvideo.compat\", \"libvideo.compat\\libvideo.compat.csproj\", \"{B50E1120-908F-40E0-9E90-5CF87FBCBB71}\"\nEndProject\nProject(\"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\") = \"libvideo.debug\", \"libvideo.debug\\libvideo.debug.csproj\", \"{8EE6CE4C-B1C1-4CA1-9CBF-7C07C6BF993E}\"\nEndProject\nGlobal\n\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n\t\tDebug|Any CPU = Debug|Any CPU\n\t\tRelease|Any CPU = Release|Any CPU\n\tEndGlobalSection\n\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n\t\t{9C71A8F8-39B6-4A53-8960-AEABF72A7771}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{9C71A8F8-39B6-4A53-8960-AEABF72A7771}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{9C71A8F8-39B6-4A53-8960-AEABF72A7771}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{9C71A8F8-39B6-4A53-8960-AEABF72A7771}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{B50E1120-908F-40E0-9E90-5CF87FBCBB71}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{B50E1120-908F-40E0-9E90-5CF87FBCBB71}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{B50E1120-908F-40E0-9E90-5CF87FBCBB71}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{B50E1120-908F-40E0-9E90-5CF87FBCBB71}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{8EE6CE4C-B1C1-4CA1-9CBF-7C07C6BF993E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{8EE6CE4C-B1C1-4CA1-9CBF-7C07C6BF993E}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{8EE6CE4C-B1C1-4CA1-9CBF-7C07C6BF993E}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{8EE6CE4C-B1C1-4CA1-9CBF-7C07C6BF993E}.Release|Any CPU.Build.0 = Release|Any CPU\n\tEndGlobalSection\n\tGlobalSection(SolutionProperties) = preSolution\n\t\tHideSolutionNode = FALSE\n\tEndGlobalSection\n\tGlobalSection(ExtensibilityGlobals) = postSolution\n\t\tSolutionGuid = {F1D2D10E-73CA-4A16-A045-6F6115731093}\n\tEndGlobalSection\nEndGlobal\n"
  },
  {
    "path": "tests/Compat/Compat/App.config",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<configuration>\n    <startup> \n        <supportedRuntime version=\"v4.0\" sku=\".NETFramework,Version=v4.5.2\" />\n    </startup>\n  <runtime>\n    <assemblyBinding xmlns=\"urn:schemas-microsoft-com:asm.v1\">\n      <dependentAssembly>\n        <assemblyIdentity name=\"Newtonsoft.Json\" publicKeyToken=\"30ad4fe6b2a6aeed\" culture=\"neutral\" />\n        <bindingRedirect oldVersion=\"0.0.0.0-6.0.0.0\" newVersion=\"6.0.0.0\" />\n      </dependentAssembly>\n    </assemblyBinding>\n  </runtime>\n</configuration>"
  },
  {
    "path": "tests/Compat/Compat/Compat.csproj",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project ToolsVersion=\"14.0\" DefaultTargets=\"Build\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <Import Project=\"$(MSBuildExtensionsPath)\\$(MSBuildToolsVersion)\\Microsoft.Common.props\" Condition=\"Exists('$(MSBuildExtensionsPath)\\$(MSBuildToolsVersion)\\Microsoft.Common.props')\" />\n  <PropertyGroup>\n    <Configuration Condition=\" '$(Configuration)' == '' \">Debug</Configuration>\n    <Platform Condition=\" '$(Platform)' == '' \">AnyCPU</Platform>\n    <ProjectGuid>{4ABF577F-35D9-45C5-95E1-075AD73AE5E6}</ProjectGuid>\n    <OutputType>Exe</OutputType>\n    <AppDesignerFolder>Properties</AppDesignerFolder>\n    <RootNamespace>Compat</RootNamespace>\n    <AssemblyName>Compat</AssemblyName>\n    <TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>\n    <FileAlignment>512</FileAlignment>\n    <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>\n  </PropertyGroup>\n  <PropertyGroup Condition=\" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' \">\n    <PlatformTarget>AnyCPU</PlatformTarget>\n    <DebugSymbols>true</DebugSymbols>\n    <DebugType>full</DebugType>\n    <Optimize>false</Optimize>\n    <OutputPath>bin\\Debug\\</OutputPath>\n    <DefineConstants>DEBUG;TRACE</DefineConstants>\n    <ErrorReport>prompt</ErrorReport>\n    <WarningLevel>4</WarningLevel>\n  </PropertyGroup>\n  <PropertyGroup Condition=\" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' \">\n    <PlatformTarget>AnyCPU</PlatformTarget>\n    <DebugType>pdbonly</DebugType>\n    <Optimize>true</Optimize>\n    <OutputPath>bin\\Release\\</OutputPath>\n    <DefineConstants>TRACE</DefineConstants>\n    <ErrorReport>prompt</ErrorReport>\n    <WarningLevel>4</WarningLevel>\n  </PropertyGroup>\n  <ItemGroup>\n    <Reference Include=\"libvideo.compat\">\n      <HintPath>..\\..\\..\\src\\libvideo.compat\\bin\\$(Configuration)\\netstandard1.1\\libvideo.compat.dll</HintPath>\n    </Reference>\n    <Reference Include=\"System\" />\n    <Reference Include=\"System.Core\" />\n    <Reference Include=\"System.Xml.Linq\" />\n    <Reference Include=\"System.Data.DataSetExtensions\" />\n    <Reference Include=\"Microsoft.CSharp\" />\n    <Reference Include=\"System.Data\" />\n    <Reference Include=\"System.Net.Http\" />\n    <Reference Include=\"System.Xml\" />\n  </ItemGroup>\n  <ItemGroup>\n    <Compile Include=\"Program.cs\" />\n    <Compile Include=\"Properties\\AssemblyInfo.cs\" />\n  </ItemGroup>\n  <ItemGroup>\n    <None Include=\"App.config\" />\n  </ItemGroup>\n  <Import Project=\"$(MSBuildToolsPath)\\Microsoft.CSharp.targets\" />\n  <!-- To modify your build process, add your task inside one of the targets below and uncomment it. \n       Other similar extension points exist, see Microsoft.Common.targets.\n  <Target Name=\"BeforeBuild\">\n  </Target>\n  <Target Name=\"AfterBuild\">\n  </Target>\n  -->\n</Project>"
  },
  {
    "path": "tests/Compat/Compat/Program.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\nusing YoutubeExtractor;\n\nnamespace Compat\n{\n    class Program\n    {\n        static void Main(string[] args)\n        {\n            /*\n            Do nothing here.\n            We just want to make sure this code compiles.\n            */\n        }\n\n        static void CompileThis()\n        {\n            AdaptiveType a = AdaptiveType.Audio;\n            a = AdaptiveType.None;\n            a = AdaptiveType.Video;\n\n            AudioExtractionException b = new AudioExtractionException(default(string));\n\n            AudioType c = AudioType.Aac;\n            c = AudioType.Mp3;\n            c = AudioType.Unknown;\n            c = AudioType.Vorbis;\n\n            DownloadUrlResolver.DecryptDownloadUrl(default(VideoInfo));\n            IEnumerable<VideoInfo> d = DownloadUrlResolver.GetDownloadUrls(default(string));\n            d = DownloadUrlResolver.GetDownloadUrls(default(string), default(bool));\n            string e = default(string);\n            bool f = DownloadUrlResolver.TryNormalizeYoutubeUrl(e, out e);\n\n            VideoInfo g = default(VideoInfo);\n            AdaptiveType h = g.AdaptiveType;\n            int i = g.AudioBitrate;\n            string j = g.AudioExtension;\n            AudioType k = g.AudioType;\n            bool l = g.CanExtractAudio;\n            string m = g.DownloadUrl;\n            int n = g.FormatCode;\n            bool o = g.Is3D;\n            bool p = g.RequiresDecryption;\n            int q = g.Resolution;\n            string r = g.Title;\n            string s = g.VideoExtension;\n            VideoType t = g.VideoType;\n\n            VideoNotAvailableException u = new VideoNotAvailableException();\n            u = new VideoNotAvailableException(default(string));\n\n            VideoType v = VideoType.Flash;\n            v = VideoType.Mobile;\n            v = VideoType.Mp4;\n            v = VideoType.Unknown;\n            v = VideoType.WebM;\n\n            YoutubeParseException w = new YoutubeParseException(default(string), default(Exception));\n        }\n    }\n}\n"
  },
  {
    "path": "tests/Compat/Compat/Properties/AssemblyInfo.cs",
    "content": "﻿using System.Reflection;\nusing System.Runtime.CompilerServices;\nusing System.Runtime.InteropServices;\n\n// General Information about an assembly is controlled through the following \n// set of attributes. Change these attribute values to modify the information\n// associated with an assembly.\n[assembly: AssemblyTitle(\"Compat\")]\n[assembly: AssemblyDescription(\"\")]\n[assembly: AssemblyConfiguration(\"\")]\n[assembly: AssemblyCompany(\"\")]\n[assembly: AssemblyProduct(\"Compat\")]\n[assembly: AssemblyCopyright(\"Copyright ©  2015\")]\n[assembly: AssemblyTrademark(\"\")]\n[assembly: AssemblyCulture(\"\")]\n\n// Setting ComVisible to false makes the types in this assembly not visible \n// to COM components.  If you need to access a type in this assembly from \n// COM, set the ComVisible attribute to true on that type.\n[assembly: ComVisible(false)]\n\n// The following GUID is for the ID of the typelib if this project is exposed to COM\n[assembly: Guid(\"4abf577f-35d9-45c5-95e1-075ad73ae5e6\")]\n\n// Version information for an assembly consists of the following four values:\n//\n//      Major Version\n//      Minor Version \n//      Build Number\n//      Revision\n//\n// You can specify all the values or you can default the Build and Revision Numbers \n// by using the '*' as shown below:\n// [assembly: AssemblyVersion(\"1.0.*\")]\n[assembly: AssemblyVersion(\"1.0.0.0\")]\n[assembly: AssemblyFileVersion(\"1.0.0.0\")]\n"
  },
  {
    "path": "tests/Compat/Compat.sln",
    "content": "﻿\nMicrosoft Visual Studio Solution File, Format Version 12.00\n# Visual Studio 14\nVisualStudioVersion = 14.0.23107.0\nMinimumVisualStudioVersion = 10.0.40219.1\nProject(\"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\") = \"Compat\", \"Compat\\Compat.csproj\", \"{4ABF577F-35D9-45C5-95E1-075AD73AE5E6}\"\nEndProject\nGlobal\n\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n\t\tDebug|Any CPU = Debug|Any CPU\n\t\tRelease|Any CPU = Release|Any CPU\n\tEndGlobalSection\n\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n\t\t{4ABF577F-35D9-45C5-95E1-075AD73AE5E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{4ABF577F-35D9-45C5-95E1-075AD73AE5E6}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{4ABF577F-35D9-45C5-95E1-075AD73AE5E6}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{4ABF577F-35D9-45C5-95E1-075AD73AE5E6}.Release|Any CPU.Build.0 = Release|Any CPU\n\tEndGlobalSection\n\tGlobalSection(SolutionProperties) = preSolution\n\t\tHideSolutionNode = FALSE\n\tEndGlobalSection\nEndGlobal\n"
  },
  {
    "path": "tests/Core/Core/Core.csproj",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project ToolsVersion=\"14.0\" DefaultTargets=\"Build;Test\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <Import Project=\"..\\packages\\xunit.runner.msbuild.2.0.0\\build\\portable-net45+win+wpa81+wp80+monotouch+monoandroid+Xamarin.iOS\\xunit.runner.msbuild.props\" Condition=\"Exists('..\\packages\\xunit.runner.msbuild.2.0.0\\build\\portable-net45+win+wpa81+wp80+monotouch+monoandroid+Xamarin.iOS\\xunit.runner.msbuild.props')\" />\n  <Import Project=\"..\\packages\\xunit.core.2.0.0\\build\\portable-net45+win+wpa81+wp80+monotouch+monoandroid+Xamarin.iOS\\xunit.core.props\" Condition=\"Exists('..\\packages\\xunit.core.2.0.0\\build\\portable-net45+win+wpa81+wp80+monotouch+monoandroid+Xamarin.iOS\\xunit.core.props')\" />\n  <Import Project=\"$(MSBuildExtensionsPath)\\$(MSBuildToolsVersion)\\Microsoft.Common.props\" Condition=\"Exists('$(MSBuildExtensionsPath)\\$(MSBuildToolsVersion)\\Microsoft.Common.props')\" />\n  <PropertyGroup>\n    <Configuration Condition=\" '$(Configuration)' == '' \">Debug</Configuration>\n    <Platform Condition=\" '$(Platform)' == '' \">AnyCPU</Platform>\n    <ProjectGuid>{561A8EEA-46E2-4294-94D7-EDCE5FD971B7}</ProjectGuid>\n    <OutputType>Library</OutputType>\n    <AppDesignerFolder>Properties</AppDesignerFolder>\n    <RootNamespace>Core</RootNamespace>\n    <AssemblyName>Core</AssemblyName>\n    <TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>\n    <FileAlignment>512</FileAlignment>\n    <NuGetPackageImportStamp>\n    </NuGetPackageImportStamp>\n  </PropertyGroup>\n  <PropertyGroup Condition=\" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' \">\n    <DebugSymbols>true</DebugSymbols>\n    <DebugType>full</DebugType>\n    <Optimize>false</Optimize>\n    <OutputPath>bin\\Debug\\</OutputPath>\n    <DefineConstants>DEBUG;TRACE</DefineConstants>\n    <ErrorReport>prompt</ErrorReport>\n    <WarningLevel>4</WarningLevel>\n  </PropertyGroup>\n  <PropertyGroup Condition=\" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' \">\n    <DebugType>pdbonly</DebugType>\n    <Optimize>true</Optimize>\n    <OutputPath>bin\\Release\\</OutputPath>\n    <DefineConstants>TRACE</DefineConstants>\n    <ErrorReport>prompt</ErrorReport>\n    <WarningLevel>4</WarningLevel>\n  </PropertyGroup>\n  <ItemGroup>\n    <Reference Include=\"libvideo\">\n      <HintPath>..\\..\\..\\src\\libvideo\\bin\\$(Configuration)\\netstandard1.1\\libvideo.dll</HintPath>\n    </Reference>\n    <Reference Include=\"System\" />\n    <Reference Include=\"System.Core\" />\n    <Reference Include=\"System.Xml.Linq\" />\n    <Reference Include=\"System.Data.DataSetExtensions\" />\n    <Reference Include=\"Microsoft.CSharp\" />\n    <Reference Include=\"System.Data\" />\n    <Reference Include=\"System.Net.Http\" />\n    <Reference Include=\"System.Xml\" />\n    <Reference Include=\"xunit.abstractions, Version=2.0.0.0, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL\">\n      <HintPath>..\\packages\\xunit.abstractions.2.0.0\\lib\\net35\\xunit.abstractions.dll</HintPath>\n      <Private>True</Private>\n    </Reference>\n    <Reference Include=\"xunit.assert, Version=2.0.0.2929, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL\">\n      <HintPath>..\\packages\\xunit.assert.2.0.0\\lib\\portable-net45+win+wpa81+wp80+monotouch+monoandroid+Xamarin.iOS\\xunit.assert.dll</HintPath>\n      <Private>True</Private>\n    </Reference>\n    <Reference Include=\"xunit.core, Version=2.0.0.2929, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL\">\n      <HintPath>..\\packages\\xunit.extensibility.core.2.0.0\\lib\\portable-net45+win+wpa81+wp80+monotouch+monoandroid+Xamarin.iOS\\xunit.core.dll</HintPath>\n      <Private>True</Private>\n    </Reference>\n  </ItemGroup>\n  <ItemGroup>\n    <Compile Include=\"Properties\\AssemblyInfo.cs\" />\n    <Compile Include=\"UnitTests.cs\" />\n  </ItemGroup>\n  <ItemGroup>\n    <None Include=\"packages.config\" />\n  </ItemGroup>\n  <Import Project=\"$(MSBuildToolsPath)\\Microsoft.CSharp.targets\" />\n  <Target Name=\"EnsureNuGetPackageBuildImports\" BeforeTargets=\"PrepareForBuild\">\n    <PropertyGroup>\n      <ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them.  For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>\n    </PropertyGroup>\n    <Error Condition=\"!Exists('..\\packages\\xunit.core.2.0.0\\build\\portable-net45+win+wpa81+wp80+monotouch+monoandroid+Xamarin.iOS\\xunit.core.props')\" Text=\"$([System.String]::Format('$(ErrorText)', '..\\packages\\xunit.core.2.0.0\\build\\portable-net45+win+wpa81+wp80+monotouch+monoandroid+Xamarin.iOS\\xunit.core.props'))\" />\n    <Error Condition=\"!Exists('..\\packages\\xunit.runner.msbuild.2.0.0\\build\\portable-net45+win+wpa81+wp80+monotouch+monoandroid+Xamarin.iOS\\xunit.runner.msbuild.props')\" Text=\"$([System.String]::Format('$(ErrorText)', '..\\packages\\xunit.runner.msbuild.2.0.0\\build\\portable-net45+win+wpa81+wp80+monotouch+monoandroid+Xamarin.iOS\\xunit.runner.msbuild.props'))\" />\n  </Target>\n  <!-- To modify your build process, add your task inside one of the targets below and uncomment it. \n       Other similar extension points exist, see Microsoft.Common.targets.\n  <Target Name=\"BeforeBuild\">\n  </Target>\n  <Target Name=\"AfterBuild\">\n  </Target>\n  -->\n  <Target Name=\"Test\">\n    <xunit Assemblies=\"bin\\$(Configuration)\\Core.dll\"/>\n  </Target>\n</Project>"
  },
  {
    "path": "tests/Core/Core/Properties/AssemblyInfo.cs",
    "content": "﻿using System.Reflection;\nusing System.Runtime.CompilerServices;\nusing System.Runtime.InteropServices;\n\n// General Information about an assembly is controlled through the following \n// set of attributes. Change these attribute values to modify the information\n// associated with an assembly.\n[assembly: AssemblyTitle(\"Core\")]\n[assembly: AssemblyDescription(\"\")]\n[assembly: AssemblyConfiguration(\"\")]\n[assembly: AssemblyCompany(\"\")]\n[assembly: AssemblyProduct(\"Core\")]\n[assembly: AssemblyCopyright(\"Copyright ©  2015\")]\n[assembly: AssemblyTrademark(\"\")]\n[assembly: AssemblyCulture(\"\")]\n\n// Setting ComVisible to false makes the types in this assembly not visible \n// to COM components.  If you need to access a type in this assembly from \n// COM, set the ComVisible attribute to true on that type.\n[assembly: ComVisible(false)]\n\n// The following GUID is for the ID of the typelib if this project is exposed to COM\n[assembly: Guid(\"561a8eea-46e2-4294-94d7-edce5fd971b7\")]\n\n// Version information for an assembly consists of the following four values:\n//\n//      Major Version\n//      Minor Version \n//      Build Number\n//      Revision\n//\n// You can specify all the values or you can default the Build and Revision Numbers \n// by using the '*' as shown below:\n// [assembly: AssemblyVersion(\"1.0.*\")]\n[assembly: AssemblyVersion(\"1.0.0.0\")]\n[assembly: AssemblyFileVersion(\"1.0.0.0\")]\n"
  },
  {
    "path": "tests/Core/Core/UnitTests.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Diagnostics;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\nusing VideoLibrary;\nusing Xunit;\n\nnamespace Core\n{\n    public class UnitTests\n    {\n        private const string YouTubeInvalidUriOne = \"https://wikipedia.com\";\n        private const string YouTubeInvalidUriTwo = \"123ABC!@#\";\n\n        private const string YouTubeUri = \"https://www.youtube.com/watch?v=JjCaRS-CABk\";\n        private const string YouTubeDecryptSigUri = \"https://www.youtube.com/watch?v=09R8_2nJtjg\";\n        private const string YouTubeWithDataManifest = \"https://www.youtube.com/watch?v=EphGWZKtXvE\";\n        private const string YouTubeShortsUri = \"https://www.youtube.com/shorts/xuCO7-DLCaA\";\n\n        // private const string VimeoUri = \"https://vimeo.com/131417856\";\n\n        [Theory]\n        [InlineData(YouTubeUri)]\n        [InlineData(YouTubeDecryptSigUri)]\n        [InlineData(YouTubeWithDataManifest)]\n        [InlineData(YouTubeShortsUri)]\n        public void YouTube_GetAllVideos(string uri)\n        {\n            var videos = YouTube.Default.GetAllVideos(uri);\n\n            Assert.NotNull(videos);\n            Assert.DoesNotContain(null, videos);\n\n            foreach (var video in videos)\n            {\n                Assert.NotNull(video.Uri);\n                Assert.Equal(video.WebSite, WebSites.YouTube);\n            }\n        }\n\n        [Theory]\n        [InlineData(YouTubeInvalidUriOne)]\n        [InlineData(YouTubeInvalidUriTwo)]\n        public void YouTube_ThrowOnInvalidUri(string invalid)\n        {\n            Assert.Throws<ArgumentException>(() \n                => YouTube.Default.GetVideo(invalid));\n        }\n    }\n}\n"
  },
  {
    "path": "tests/Core/Core/packages.config",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<packages>\n  <package id=\"xunit\" version=\"2.0.0\" targetFramework=\"net452\" />\n  <package id=\"xunit.abstractions\" version=\"2.0.0\" targetFramework=\"net452\" />\n  <package id=\"xunit.assert\" version=\"2.0.0\" targetFramework=\"net452\" />\n  <package id=\"xunit.core\" version=\"2.0.0\" targetFramework=\"net452\" />\n  <package id=\"xunit.extensibility.core\" version=\"2.0.0\" targetFramework=\"net452\" />\n  <package id=\"xunit.runner.msbuild\" version=\"2.0.0\" targetFramework=\"net452\" />\n</packages>"
  },
  {
    "path": "tests/Core/Core.sln",
    "content": "﻿\nMicrosoft Visual Studio Solution File, Format Version 12.00\n# Visual Studio 14\nVisualStudioVersion = 14.0.23107.0\nMinimumVisualStudioVersion = 10.0.40219.1\nProject(\"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\") = \"Core\", \"Core\\Core.csproj\", \"{561A8EEA-46E2-4294-94D7-EDCE5FD971B7}\"\nEndProject\nGlobal\n\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n\t\tDebug|Any CPU = Debug|Any CPU\n\t\tRelease|Any CPU = Release|Any CPU\n\tEndGlobalSection\n\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n\t\t{561A8EEA-46E2-4294-94D7-EDCE5FD971B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{561A8EEA-46E2-4294-94D7-EDCE5FD971B7}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{561A8EEA-46E2-4294-94D7-EDCE5FD971B7}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{561A8EEA-46E2-4294-94D7-EDCE5FD971B7}.Release|Any CPU.Build.0 = Release|Any CPU\n\tEndGlobalSection\n\tGlobalSection(SolutionProperties) = preSolution\n\t\tHideSolutionNode = FALSE\n\tEndGlobalSection\nEndGlobal\n"
  },
  {
    "path": "tests/Speed.Test/Speed.Test/App.config",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<configuration>\n    <startup> \n        <supportedRuntime version=\"v4.0\" sku=\".NETFramework,Version=v4.5.2\" />\n    </startup>\n  <runtime>\n    <assemblyBinding xmlns=\"urn:schemas-microsoft-com:asm.v1\">\n      <dependentAssembly>\n        <assemblyIdentity name=\"Newtonsoft.Json\" publicKeyToken=\"30ad4fe6b2a6aeed\" culture=\"neutral\" />\n        <bindingRedirect oldVersion=\"0.0.0.0-6.0.0.0\" newVersion=\"6.0.0.0\" />\n      </dependentAssembly>\n    </assemblyBinding>\n  </runtime>\n</configuration>"
  },
  {
    "path": "tests/Speed.Test/Speed.Test/Program.cs",
    "content": "﻿using VideoLibrary;\nusing System;\nusing System.Collections.Generic;\nusing System.Diagnostics;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\nusing YoutubeExtractor;\n\nnamespace Speed.Test\n{\n    class Program\n    {\n        const string Uri = \"https://www.youtube.com/watch?v=NLddPakLF1I\";\n\n        static void Main(string[] args)\n        {\n            int times = args.Length == 0 ? 3 : int.Parse(args[0]);\n            int iterations = args.Length <= 1 ? 2 : int.Parse(args[1]);\n\n            Console.WriteLine(\"Starting...\");\n            Console.WriteLine($\"Times: {times}.\");\n            Console.WriteLine($\"Iterations: {iterations}.\");\n            Console.WriteLine(\"Getting results for YoutubeExtractor...\");\n\n            var elapsed = TimeSpan.Zero;\n\n            for (int i = 0; i < times; i++)\n            {\n                RunChecked(() =>\n                {\n                    var watch = Stopwatch.StartNew();\n\n                    for (int j = 0; j < iterations; j++)\n                        GC.KeepAlive(DownloadUrlResolver.GetDownloadUrls(Uri));\n\n                    watch.Stop();\n                    elapsed += watch.Elapsed;\n                });\n            }\n\n            Console.WriteLine($\"YoutubeExtractor: took {elapsed}.\");\n            Console.WriteLine(\"Getting results for libvideo...\");\n\n            elapsed = TimeSpan.Zero;\n\n            using (var service = Client.For(YouTube.Default))\n            {\n                for (int i = 0; i < times; i++)\n                {\n                    RunChecked(() =>\n                    {\n                        var watch = Stopwatch.StartNew();\n\n                        for (int j = 0; j < iterations; j++)\n                            GC.KeepAlive(service.GetAllVideos(Uri));\n\n                        watch.Stop();\n                        elapsed += watch.Elapsed;\n                    });\n                }\n            }\n\n            Console.WriteLine($\"libvideo: took {elapsed}.\");\n        }\n\n        static void RunChecked(Action action)\n        {\n            try\n            {\n                action();\n            }\n            catch (Exception e)\n            {\n                string[] lines =\n                {\n                    $\"A {e.GetType()} was thrown!\",\n                    \"Message:\",\n                    string.Empty,\n                    e.ToString(),\n                    string.Empty,\n                    \"Restarting...\"\n                };\n                \n                Console.WriteLine(string.Join(Environment.NewLine, lines));\n                \n                RunChecked(action);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "tests/Speed.Test/Speed.Test/Properties/AssemblyInfo.cs",
    "content": "﻿using System.Reflection;\nusing System.Runtime.CompilerServices;\nusing System.Runtime.InteropServices;\n\n// General Information about an assembly is controlled through the following \n// set of attributes. Change these attribute values to modify the information\n// associated with an assembly.\n[assembly: AssemblyTitle(\"Speed.Test\")]\n[assembly: AssemblyDescription(\"\")]\n[assembly: AssemblyConfiguration(\"\")]\n[assembly: AssemblyCompany(\"\")]\n[assembly: AssemblyProduct(\"Speed.Test\")]\n[assembly: AssemblyCopyright(\"Copyright ©  2015\")]\n[assembly: AssemblyTrademark(\"\")]\n[assembly: AssemblyCulture(\"\")]\n\n// Setting ComVisible to false makes the types in this assembly not visible \n// to COM components.  If you need to access a type in this assembly from \n// COM, set the ComVisible attribute to true on that type.\n[assembly: ComVisible(false)]\n\n// The following GUID is for the ID of the typelib if this project is exposed to COM\n[assembly: Guid(\"448bde69-df8a-4090-b0a0-81bfb9d84e28\")]\n\n// Version information for an assembly consists of the following four values:\n//\n//      Major Version\n//      Minor Version \n//      Build Number\n//      Revision\n//\n// You can specify all the values or you can default the Build and Revision Numbers \n// by using the '*' as shown below:\n// [assembly: AssemblyVersion(\"1.0.*\")]\n[assembly: AssemblyVersion(\"1.0.0.0\")]\n[assembly: AssemblyFileVersion(\"1.0.0.0\")]\n"
  },
  {
    "path": "tests/Speed.Test/Speed.Test/Speed.Test.csproj",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project ToolsVersion=\"14.0\" DefaultTargets=\"Build\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <Import Project=\"$(MSBuildExtensionsPath)\\$(MSBuildToolsVersion)\\Microsoft.Common.props\" Condition=\"Exists('$(MSBuildExtensionsPath)\\$(MSBuildToolsVersion)\\Microsoft.Common.props')\" />\n  <PropertyGroup>\n    <Configuration Condition=\" '$(Configuration)' == '' \">Debug</Configuration>\n    <Platform Condition=\" '$(Platform)' == '' \">AnyCPU</Platform>\n    <ProjectGuid>{448BDE69-DF8A-4090-B0A0-81BFB9D84E28}</ProjectGuid>\n    <OutputType>Exe</OutputType>\n    <AppDesignerFolder>Properties</AppDesignerFolder>\n    <RootNamespace>Speed.Test</RootNamespace>\n    <AssemblyName>Speed.Test</AssemblyName>\n    <TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>\n    <FileAlignment>512</FileAlignment>\n    <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>\n  </PropertyGroup>\n  <PropertyGroup Condition=\" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' \">\n    <PlatformTarget>AnyCPU</PlatformTarget>\n    <DebugSymbols>true</DebugSymbols>\n    <DebugType>full</DebugType>\n    <Optimize>false</Optimize>\n    <OutputPath>bin\\Debug\\</OutputPath>\n    <DefineConstants>DEBUG;TRACE</DefineConstants>\n    <ErrorReport>prompt</ErrorReport>\n    <WarningLevel>4</WarningLevel>\n  </PropertyGroup>\n  <PropertyGroup Condition=\" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' \">\n    <PlatformTarget>AnyCPU</PlatformTarget>\n    <DebugType>pdbonly</DebugType>\n    <Optimize>true</Optimize>\n    <OutputPath>bin\\Release\\</OutputPath>\n    <DefineConstants>TRACE</DefineConstants>\n    <ErrorReport>prompt</ErrorReport>\n    <WarningLevel>4</WarningLevel>\n  </PropertyGroup>\n  <ItemGroup>\n    <Reference Include=\"libvideo\">\n      <HintPath>..\\..\\..\\src\\libvideo\\bin\\$(Configuration)\\netstandard1.1\\libvideo.dll</HintPath>\n    </Reference>\n    <Reference Include=\"Newtonsoft.Json, Version=6.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL\">\n      <HintPath>..\\packages\\Newtonsoft.Json.6.0.6\\lib\\net45\\Newtonsoft.Json.dll</HintPath>\n    </Reference>\n    <Reference Include=\"System\" />\n    <Reference Include=\"System.Core\" />\n    <Reference Include=\"System.Xml.Linq\" />\n    <Reference Include=\"System.Data.DataSetExtensions\" />\n    <Reference Include=\"Microsoft.CSharp\" />\n    <Reference Include=\"System.Data\" />\n    <Reference Include=\"System.Net.Http\" />\n    <Reference Include=\"System.Xml\" />\n    <Reference Include=\"YoutubeExtractor, Version=0.10.11.0, Culture=neutral, processorArchitecture=MSIL\">\n      <HintPath>..\\packages\\YoutubeExtractor.0.10.11\\lib\\net35\\YoutubeExtractor.dll</HintPath>\n    </Reference>\n  </ItemGroup>\n  <ItemGroup>\n    <Compile Include=\"Program.cs\" />\n    <Compile Include=\"Properties\\AssemblyInfo.cs\" />\n  </ItemGroup>\n  <ItemGroup>\n    <None Include=\"App.config\" />\n    <None Include=\"packages.config\">\n      <SubType>Designer</SubType>\n    </None>\n  </ItemGroup>\n  <Import Project=\"$(MSBuildToolsPath)\\Microsoft.CSharp.targets\" />\n  <!-- To modify your build process, add your task inside one of the targets below and uncomment it. \n       Other similar extension points exist, see Microsoft.Common.targets.\n  <Target Name=\"BeforeBuild\">\n  </Target>\n  <Target Name=\"AfterBuild\">\n  </Target>\n  -->\n</Project>"
  },
  {
    "path": "tests/Speed.Test/Speed.Test/packages.config",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<packages>\n  <package id=\"Newtonsoft.Json\" version=\"13.0.1\" targetFramework=\"net452\" />\n  <package id=\"YoutubeExtractor\" version=\"0.10.11\" targetFramework=\"net452\" userInstalled=\"true\" />\n</packages>"
  },
  {
    "path": "tests/Speed.Test/Speed.Test.sln",
    "content": "﻿\nMicrosoft Visual Studio Solution File, Format Version 12.00\n# Visual Studio 14\nVisualStudioVersion = 14.0.23107.0\nMinimumVisualStudioVersion = 10.0.40219.1\nProject(\"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\") = \"Speed.Test\", \"Speed.Test\\Speed.Test.csproj\", \"{448BDE69-DF8A-4090-B0A0-81BFB9D84E28}\"\nEndProject\nGlobal\n\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n\t\tDebug|Any CPU = Debug|Any CPU\n\t\tRelease|Any CPU = Release|Any CPU\n\tEndGlobalSection\n\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n\t\t{448BDE69-DF8A-4090-B0A0-81BFB9D84E28}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{448BDE69-DF8A-4090-B0A0-81BFB9D84E28}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{448BDE69-DF8A-4090-B0A0-81BFB9D84E28}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{448BDE69-DF8A-4090-B0A0-81BFB9D84E28}.Release|Any CPU.Build.0 = Release|Any CPU\n\tEndGlobalSection\n\tGlobalSection(SolutionProperties) = preSolution\n\t\tHideSolutionNode = FALSE\n\tEndGlobalSection\nEndGlobal\n"
  }
]