Full Code of i3arnon/libvideo for AI

master 7dae9908d4d1 cached
69 files
137.3 KB
32.0k tokens
213 symbols
1 requests
Download .txt
Repository: i3arnon/libvideo
Branch: master
Commit: 7dae9908d4d1
Files: 69
Total size: 137.3 KB

Directory structure:
gitextract_xcx1mnd_/

├── .gitattributes
├── .gitignore
├── LICENSE
├── README.md
├── changelog.md
├── docs/
│   └── README.md
├── samples/
│   └── Valks/
│       ├── Valks/
│       │   ├── Program.cs
│       │   ├── Properties/
│       │   │   └── AssemblyInfo.cs
│       │   └── Valks.csproj
│       └── Valks.sln
├── src/
│   ├── libvideo/
│   │   ├── AdaptiveKind.cs
│   │   ├── AudioFormat.cs
│   │   ├── Client.cs
│   │   ├── DelegatingClient.cs
│   │   ├── Exceptions/
│   │   │   ├── BadQueryException.cs
│   │   │   └── UnavaibleVideoException.cs
│   │   ├── Helpers/
│   │   │   ├── EmptyArray.cs
│   │   │   ├── Html.cs
│   │   │   ├── Json.cs
│   │   │   ├── KeyCollection.cs
│   │   │   ├── Operations.cs
│   │   │   ├── Query.cs
│   │   │   ├── Require.cs
│   │   │   ├── Text.cs
│   │   │   ├── UnscrambledQuery.cs
│   │   │   └── ValueCollection.cs
│   │   ├── IAsyncService.cs
│   │   ├── IService.cs
│   │   ├── ServiceBase.cs
│   │   ├── Video.cs
│   │   ├── VideoClient.cs
│   │   ├── VideoFormat.cs
│   │   ├── VisitorDataTokenGenerator.cs
│   │   ├── WebSites.cs
│   │   ├── YouTube.cs
│   │   ├── YouTubeVideo.Decrypt.cs
│   │   ├── YouTubeVideo.Format.cs
│   │   ├── YouTubeVideo.cs
│   │   ├── YoutubeVideo.Descramble.cs
│   │   └── libvideo.csproj
│   ├── libvideo.compat/
│   │   ├── AdaptiveType.cs
│   │   ├── AudioExtractionException.cs
│   │   ├── AudioType.cs
│   │   ├── DownloadUrlResolver.cs
│   │   ├── VideoInfo.cs
│   │   ├── VideoNotAvailableException.cs
│   │   ├── VideoType.cs
│   │   ├── YoutubeParseException.cs
│   │   └── libvideo.compat.csproj
│   ├── libvideo.debug/
│   │   ├── CustomYoutubeClient.cs
│   │   ├── Program.cs
│   │   └── libvideo.debug.csproj
│   └── libvideo.sln
└── tests/
    ├── Compat/
    │   ├── Compat/
    │   │   ├── App.config
    │   │   ├── Compat.csproj
    │   │   ├── Program.cs
    │   │   └── Properties/
    │   │       └── AssemblyInfo.cs
    │   └── Compat.sln
    ├── Core/
    │   ├── Core/
    │   │   ├── Core.csproj
    │   │   ├── Properties/
    │   │   │   └── AssemblyInfo.cs
    │   │   ├── UnitTests.cs
    │   │   └── packages.config
    │   └── Core.sln
    └── Speed.Test/
        ├── Speed.Test/
        │   ├── App.config
        │   ├── Program.cs
        │   ├── Properties/
        │   │   └── AssemblyInfo.cs
        │   ├── Speed.Test.csproj
        │   └── packages.config
        └── Speed.Test.sln

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitattributes
================================================
###############################################################################
# Set default behavior to automatically normalize line endings.
###############################################################################
* text=auto

###############################################################################
# Set default behavior for command prompt diff.
#
# This is need for earlier builds of msysgit that does not have it on by
# default for csharp files.
# Note: This is only used by command line
###############################################################################
#*.cs     diff=csharp

###############################################################################
# Set the merge driver for project and solution files
#
# Merging from the command prompt will add diff markers to the files if there
# are conflicts (Merging from VS is not affected by the settings below, in VS
# the diff markers are never inserted). Diff markers may cause the following 
# file extensions to fail to load in VS. An alternative would be to treat
# these files as binary and thus will always conflict and require user
# intervention with every merge. To do so, just uncomment the entries below
###############################################################################
#*.sln       merge=binary
#*.csproj    merge=binary
#*.vbproj    merge=binary
#*.vcxproj   merge=binary
#*.vcproj    merge=binary
#*.dbproj    merge=binary
#*.fsproj    merge=binary
#*.lsproj    merge=binary
#*.wixproj   merge=binary
#*.modelproj merge=binary
#*.sqlproj   merge=binary
#*.wwaproj   merge=binary

###############################################################################
# behavior for image files
#
# image files are treated as binary by default.
###############################################################################
#*.jpg   binary
#*.png   binary
#*.gif   binary

###############################################################################
# diff behavior for common document formats
# 
# Convert binary document formats to text before diffing them. This feature
# is only available from the command line. Turn it on by uncommenting the 
# entries below.
###############################################################################
#*.doc   diff=astextplain
#*.DOC   diff=astextplain
#*.docx  diff=astextplain
#*.DOCX  diff=astextplain
#*.dot   diff=astextplain
#*.DOT   diff=astextplain
#*.pdf   diff=astextplain
#*.PDF   diff=astextplain
#*.rtf   diff=astextplain
#*.RTF   diff=astextplain


================================================
FILE: .gitignore
================================================
# Binary storage
bin/
obj/
lib/

# Binary files
*.db
*.dll
*.exe

# NuGet packages
*.nupkg
**/packages/*
!**/packages/build/
#!**/packages/repositories.config

# IDE clutter
.vs/
*.suo
*.user
*.userprefs

# Profiling sessions
*.vspx
*.psess

# Miscellaneous
*.cache


================================================
FILE: LICENSE
================================================
BSD 2-Clause License

Copyright (c) 2021, OMANSAK
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this
  list of conditions and the following disclaimer.

* Redistributions in binary form must reproduce the above copyright notice,
  this list of conditions and the following disclaimer in the documentation
  and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


================================================
FILE: README.md
================================================
# libvideo

![icon](icons/icon_200.png)

[![NuGet](https://img.shields.io/nuget/dt/VideoLibrary.svg)](https://www.nuget.org/packages/VideoLibrary)
[![NuGet](https://img.shields.io/nuget/v/VideoLibrary.svg)](https://www.nuget.org/packages/VideoLibrary)
[![license](https://img.shields.io/github/license/i3arnon/libvideo.svg)](LICENSE)
[![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)

libvideo (aka VideoLibrary) is a modern .NET library for downloading YouTube videos. It is portable to most platforms and is very lightweight.

## Documentation
- [Documentation](docs/README.md)
- [Example Application](samples/Valks/Valks/Program.cs)
- [Fast Downloader with Chunks](/src/libvideo.debug/CustomYoutubeClient.cs)
## Installation

You can grab a copy of the library [on NuGet](https://www.nuget.org/packages/VideoLibrary) by running:

    Install-Package VideoLibrary

Alternatively, you can try building the repo if you like your assemblies extra-fresh.

## Supported Platforms
| Platform / Application                  | Minimum Supported Version | Notes |
|-----------------------------------------|---------------------------|-------|
| **.NET / .NET Core**                    | .NET Core 2.0+ / .NET 5+  | Fully supports .NET Standard 2.0 libraries. |
| **.NET Framework**                      | 4.6.1+                    | Supports .NET Standard 2.0 (does NOT support 2.1). |
| **Mono**                                | 5.4+                      | Official support for .NET Standard 2.0. |
| **Unity**                               | 2018.1+                   | Requires scripting runtime set to .NET 4.x Equivalent. |
| **Xamarin.iOS**                         | 10.14+                    | Supports .NET Standard 2.0 libraries. |
| **Xamarin.Android**                     | 8.0+                      | Supports .NET Standard 2.0 libraries. |
| **Xamarin.Mac**                         | 3.8+                      | Supports .NET Standard 2.0 libraries. |
| **Universal Windows Platform (UWP)**    | 10.0.16299+               | Supported starting from this Windows 10 version. |

## Getting Started

Here's a small sample to help you get familiar with libvideo:

```csharp
using VideoLibrary;

void SaveVideoToDisk(string link)
{
    var youTube = YouTube.Default; // starting point for YouTube actions
    var video = youTube.GetVideo(link); // gets a Video object with info about the video
    File.WriteAllBytes(@"C:\" + video.FullName, video.GetBytes());
}
```

Or, if you use Visual Basic:

```vbnet
Imports VideoLibrary

Sub SaveVideoToDisk(ByVal link As String)
     Dim video = YouTube.Default.GetVideo(link)
     File.WriteAllBytes("C:\" & video.FullName, video.GetBytes())
End Sub
```

If 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.

## License

libvideo is licensed under the [BSD 2-clause license](LICENSE).


================================================
FILE: changelog.md
================================================
# Changelog

## v3.3.0

- Fix Youtube: 403 Forbidden errors ([#307](https://github.com/omansak/libvideo/issues/307))

================================================
FILE: docs/README.md
================================================
# Documentation

Here you'll find a more in-depth explanation of our API.

To get information about a video:

```csharp
string uri = "https://www.youtube.com/watch?v=vPto6XpRq-U";
var youTube = YouTube.Default;
var video = youTube.GetVideo(uri);

string title = video.Title;
VideoInfo info = video.Info; // (Title,Author,LengthSeconds)
string fileExtension = video.FileExtension;
string fullName = video.FullName; // same thing as title + fileExtension
int resolution = video.Resolution;

// etc.
```

You can download it like this:

```csharp
byte[] bytes = video.GetBytes();
var stream = video.Stream();
```

And save it to a file:

```csharp
File.WriteAllBytes(@"C:\" + fullName, bytes);
```

---

## Advanced

YouTube 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:

```csharp
var videos = youTube.GetAllVideos(uri);
```

Some Informations of Video
```csharp
var videoInfos = Client.For(YouTube.Default).GetAllVideosAsync(uri).GetAwaiter().GetResult();
var resolutions = videoInfos.Where(j => j.AdaptiveKind == AdaptiveKind.Video).Select(j => j.Resolution);
var bitRates = videoInfos.Where(j => j.AdaptiveKind == AdaptiveKind.Audio).Select(j => j.AudioBitrate);
var unknownFormats = videoInfos.Where(j => j.AdaptiveKind == AdaptiveKind.None).Select(j => j.Resolution);
```

Get specific resolution, bitrate, format
```csharp
var youTube = YouTube.Default; // starting point for YouTube actions
var videoInfos = youTube.GetAllVideosAsync(link).GetAwaiter().GetResult();
var maxResolution = videoInfos.First(i => i.Resolution == videoInfos.Max(j => j.Resolution));
var minBitrate = videoInfos.First(i => i.AudioBitrate == videoInfos.Min(j => j.AudioBitrate));
var audioFormat = videoInfos.First(i => i.AudioFormat == AudioFormat.Aac);
var videoFormat = videoInfos.First(i => i.Format == VideoFormat.Mp4);
var adaptive = videoInfos.First(i => i.IsAdaptive);
```

We also have full support for async:

```csharp
var video = await youTube.GetVideoAsync(uri);
var videos = await youTube.GetAllVideosAsync(uri);
var contents = await video.GetBytesAsync();
```

In 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:

```csharp
using (var cli = Client.For(new YouTube()))
{
    cli.GetVideo(uri);
    cli.GetVideo("[some other video]"); // HttpClient is reused here
}
```

Likewise, if you'd like to reuse `HttpClients` when downloading a video, use `VideoClient`.

```csharp
using (var cli = new VideoClient())
{
    cli.GetBytes(video);
    await cli.StreamAsync(video);
}
```

### Custom HTTP Configurations

If 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:

```csharp
class MyYouTube : YouTube
{
    protected override HttpMessageHandler MakeHandler()
    {
        return new BlahBlahMessageHandler();
    }
    
    protected override HttpClient MakeClient(HttpMessageHandler handler)
    {
        return new HttpClient(handler)
        {
            Timeout = TimeSpan.FromSeconds(12345);
        };
    }
}
```

Use like so:

```csharp
var youTube = new MyYouTube();
youTube.GetVideo("foo");

// --- OR ---

using (var cli = Client.For(new MyYouTube()))
{
    // ...
}
```

Note that this does not change the HTTP behavior when downloading the video itself. To do that, inherit from `VideoClient`:

```csharp
class MyVideoClient : VideoClient
{
    protected override HttpMessageHandler MakeHandler() { ... }
    protected override HttpClient MakeClient(HttpMessageHandler handler) { ... }
}
```

And to use it:

```csharp
using (var cli = new MyVideoClient())
{
    byte[] contents = cli.GetBytes(video);
}
```

Sample Progress
```csharp
class Program
    {
        static async Task Main(string[] args)
        {

            var youtube = YouTube.Default;
            var video = youtube.GetVideo("https://www.youtube.com/watch?v=GNxEEyOMce4");
            var client = new HttpClient();
            long? totalByte = 0;
            using (Stream output = File.OpenWrite("C:\\Users" + video.Title))
            {
                using (var request = new HttpRequestMessage(HttpMethod.Head, video.Uri))
                {
                    totalByte = client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead).Result.Content.Headers.ContentLength;
                }
                using (var input = await client.GetStreamAsync(video.Uri))
                {
                    byte[] buffer = new byte[16 * 1024];
                    int read;
                    int totalRead = 0;
                    Console.WriteLine("Download Started");
                    while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
                    {
                        output.Write(buffer, 0, read);
                        totalRead += read;
                        Console.Write($"\rDownloading {totalRead}/{totalByte} ...");
                    }
                    Console.WriteLine("Download Complete");
                }
            }
            Console.ReadLine();
        }
    }
```

### Custom Downloader with Chunks (Fast)

[Sample Code](https://github.com/omansak/libvideo/blob/master/src/libvideo.debug/CustomYoutubeClient.cs)

---

That's it, enjoy! If you're looking for more features, feel free to raise an issue and we can discuss it with you.


================================================
FILE: samples/Valks/Valks/Program.cs
================================================
using VideoLibrary;
using System;
using System.IO;

namespace Valks
{
    class MainClass
    {
        public static void Main(string[] args)
        {
            Console.WriteLine("Welcome to Valks!");
            Console.WriteLine("Easily save your favorite videos from YouTube.");

            using (var service = Client.For(YouTube.Default))
            {
                while (true)
                {
                    Console.WriteLine();
                    Console.Write("Enter your video's ID: ");

                    string id = Console.ReadLine();

                    Console.WriteLine("Awesome! Downloading...");

                    var video = service.GetVideo("https://youtube.com/watch?v=" + id);

                    Console.Write("Finished! Would you like to save the video to Downloads? [y/n] ");

                    char opt = Console.ReadKey().KeyChar;

                    Console.WriteLine();

                    string folder;

                    if (char.ToUpper(opt) == 'Y')
                        folder = GetDefaultFolder();
                    else
                    {
                        Console.Write("Please tell us where you'd like to save it: ");
                        folder = Console.ReadLine();
                    }

                    string path = Path.Combine(folder, video.FullName);

                    Console.WriteLine("Saving...");

                    File.WriteAllBytes(path, video.GetBytes());

                    Console.WriteLine("Done.");
                }
            }
        }

        static string GetDefaultFolder()
        {
            var home = Environment.GetFolderPath(
                Environment.SpecialFolder.UserProfile);

            return Path.Combine(home, "Downloads");
        }
    }
}


================================================
FILE: samples/Valks/Valks/Properties/AssemblyInfo.cs
================================================
using System.Reflection;
using System.Runtime.CompilerServices;

// Information about this assembly is defined by the following attributes. 
// Change them to the values specific to your project.
[assembly: AssemblyTitle("Valks")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("")]
[assembly: AssemblyCopyright("i3arnon")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
// The form "{Major}.{Minor}.*" will automatically update the build and revision,
// and "{Major}.{Minor}.{Build}.*" will update just the revision.
[assembly: AssemblyVersion("1.0.*")]
// The following attributes are used to specify the signing key for the assembly, 
// if desired. See the Mono documentation for more information about signing.
//[assembly: AssemblyDelaySign(false)]
//[assembly: AssemblyKeyFile("")]



================================================
FILE: samples/Valks/Valks/Valks.csproj
================================================
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
    <Platform Condition=" '$(Platform)' == '' ">x86</Platform>
    <ProductVersion>10.0.0</ProductVersion>
    <SchemaVersion>2.0</SchemaVersion>
    <ProjectGuid>{A600E2F2-81C4-4C6A-B87B-342766AA773E}</ProjectGuid>
    <OutputType>Exe</OutputType>
    <RootNamespace>Valks</RootNamespace>
    <AssemblyName>Valks</AssemblyName>
    <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
    <DebugSymbols>true</DebugSymbols>
    <DebugType>full</DebugType>
    <Optimize>false</Optimize>
    <OutputPath>bin\Debug</OutputPath>
    <DefineConstants>DEBUG;</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
    <Externalconsole>true</Externalconsole>
    <PlatformTarget>x86</PlatformTarget>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
    <DebugType>full</DebugType>
    <Optimize>true</Optimize>
    <OutputPath>bin\Release</OutputPath>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
    <Externalconsole>true</Externalconsole>
    <PlatformTarget>x86</PlatformTarget>
  </PropertyGroup>
  <ItemGroup>
    <Reference Include="libvideo, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
      <SpecificVersion>False</SpecificVersion>
      <HintPath>..\..\..\src\libvideo\bin\Release\netstandard1.1\libvideo.dll</HintPath>
    </Reference>
    <Reference Include="System" />
  </ItemGroup>
  <ItemGroup>
    <Compile Include="Program.cs" />
    <Compile Include="Properties\AssemblyInfo.cs" />
  </ItemGroup>
  <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
</Project>

================================================
FILE: samples/Valks/Valks.sln
================================================

Microsoft Visual Studio Solution File, Format Version 11.00
# Visual Studio 2010
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Valks", "Valks\Valks.csproj", "{A600E2F2-81C4-4C6A-B87B-342766AA773E}"
EndProject
Global
	GlobalSection(SolutionConfigurationPlatforms) = preSolution
		Debug|x86 = Debug|x86
		Release|x86 = Release|x86
	EndGlobalSection
	GlobalSection(ProjectConfigurationPlatforms) = postSolution
		{A600E2F2-81C4-4C6A-B87B-342766AA773E}.Debug|x86.ActiveCfg = Debug|x86
		{A600E2F2-81C4-4C6A-B87B-342766AA773E}.Debug|x86.Build.0 = Debug|x86
		{A600E2F2-81C4-4C6A-B87B-342766AA773E}.Release|x86.ActiveCfg = Release|x86
		{A600E2F2-81C4-4C6A-B87B-342766AA773E}.Release|x86.Build.0 = Release|x86
	EndGlobalSection
	GlobalSection(MonoDevelopProperties) = preSolution
		StartupItem = Valks\Valks.csproj
	EndGlobalSection
EndGlobal


================================================
FILE: src/libvideo/AdaptiveKind.cs
================================================
namespace VideoLibrary
{
    public enum AdaptiveKind
    {
        None,
        Audio,
        Video
    }
}


================================================
FILE: src/libvideo/AudioFormat.cs
================================================
namespace VideoLibrary
{
    public enum AudioFormat
    {
        Aac = 0,
        Vorbis = 1,
        Opus = 2,
        Unknown = 3
    }
}

================================================
FILE: src/libvideo/Client.cs
================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using VideoLibrary.Helpers;

namespace VideoLibrary
{
    public static class Client
    {
        public static Client<T> For<T>(ServiceBase<T> baseService) 
            where T : Video => new Client<T>(baseService);
    }

    public class Client<T> : IService<T>, IAsyncService<T>, IDisposable 
        where T : Video
    {
        private bool disposed = false;
        private readonly ServiceBase<T> baseService;
        private readonly HttpClient client;

        private Task<string> SourceFactory(string address) =>
            client.GetStringAsync(address);

        internal Client(ServiceBase<T> baseService)
        {
            Require.NotNull(baseService, nameof(baseService));

            this.baseService = baseService;
            this.client = baseService.MakeClient();
        }

        #region IDisposable

        ~Client()
        {
            Dispose(false);
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposed)
                return;
            disposed = true;

            if (disposing)
            {
                if (client != null)
                    client.Dispose();
            }
        }

        #endregion

        public T GetVideo(string videoUri) =>
            baseService.GetVideo(videoUri, SourceFactory);

        public IEnumerable<T> GetAllVideos(string videoUri) =>
            baseService.GetAllVideos(videoUri, SourceFactory);

        public Task<T> GetVideoAsync(string videoUri) =>
            baseService.GetVideoAsync(videoUri, SourceFactory);

        public Task<IEnumerable<T>> GetAllVideosAsync(string videoUri) =>
            baseService.GetAllVideosAsync(videoUri, SourceFactory);
    }
}


================================================
FILE: src/libvideo/DelegatingClient.cs
================================================
using System;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;

namespace VideoLibrary
{
    public class DelegatingClient : IDisposable
    {
        private bool disposed = false;
        private readonly HttpClient client;

        public DelegatingClient()
        {
            this.client = MakeClient();
        }

        #region IDisposable

        ~DelegatingClient()
        {
            Dispose(false);
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposed)
                return;

            disposed = true;

            if (disposing)
            {
                if (client != null)
                    client.Dispose();
            }
        }

        #endregion

        #region MakeClient/MakeHandler

        private HttpClient MakeClient() =>
            MakeClient(MakeHandler());

        protected virtual HttpMessageHandler MakeHandler()
        {
            var handler = new HttpClientHandler();

            if (handler.SupportsAutomaticDecompression)
            {
                handler.AutomaticDecompression =
                    DecompressionMethods.GZip |
                    DecompressionMethods.Deflate;
            }

            return handler;
        }

        protected virtual HttpClient MakeClient(HttpMessageHandler handler)
        {
            return new HttpClient(handler);
        }

        #endregion

        #region Synchronous wrappers

        public HttpResponseMessage Get(string uri) =>
            GetAsync(uri).GetAwaiter().GetResult();

        public byte[] GetByteArray(string uri) =>
            GetByteArrayAsync(uri).GetAwaiter().GetResult();

        public Stream GetStream(string uri) =>
            GetStreamAsync(uri).GetAwaiter().GetResult();

        public string GetString(string uri) =>
            GetStringAsync(uri).GetAwaiter().GetResult();

        #endregion

        #region HttpClient wrappers

        // TODO: Support other kinds of HTTP requests, 
        // such as PUT, POST, DELETE, etc.

        public Task<HttpResponseMessage> GetAsync(string uri) =>
            client.GetAsync(uri);

        public Task<byte[]> GetByteArrayAsync(string uri) =>
            client.GetByteArrayAsync(uri);

        public Task<Stream> GetStreamAsync(string uri) =>
            client.GetStreamAsync(uri);

        public Task<string> GetStringAsync(string uri) =>
            client.GetStringAsync(uri);

        #endregion
    }
}


================================================
FILE: src/libvideo/Exceptions/BadQueryException.cs
================================================
using System;

namespace VideoLibrary.Exceptions
{
    internal class BadQueryException : Exception
    {
        public BadQueryException()
            : base()
        { }

        public BadQueryException(string message)
            : base(message)
        { }

        public BadQueryException(string message, Exception innerException)
            : base(message, innerException)
        { }
    }
}


================================================
FILE: src/libvideo/Exceptions/UnavaibleVideoException.cs
================================================
using System;

namespace VideoLibrary.Exceptions
{
    public class UnavailableStreamException : Exception
    {
        public UnavailableStreamException()
            : base()
        { }

        public UnavailableStreamException(string message)
            : base(message)
        { }

        public UnavailableStreamException(string message, Exception innerException)
            : base(message, innerException)
        { }
    }
}


================================================
FILE: src/libvideo/Helpers/EmptyArray.cs
================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace VideoLibrary.Helpers
{
    internal static class EmptyArray<T>
    {
        public static readonly T[] Value = new T[0];
    }
}


================================================
FILE: src/libvideo/Helpers/Html.cs
================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;

namespace VideoLibrary.Helpers
{
    internal static class Html
    {
        // TODO: Refactor?
        public static string GetNode(string name, string source) =>
            WebUtility.HtmlDecode(
                Text.StringBetween(
                    '<' + name + '>', "</" + name + '>', source));

        public static IEnumerable<string> GetUrisFromManifest(string source)
        {
            string opening = "<BaseURL>";
            string closing = "</BaseURL>";
            int start = source.IndexOf(opening);
            if (start != -1)
            {
                string temp = source.Substring(start);
                var uris = temp.Split(new string[] { opening }, StringSplitOptions.RemoveEmptyEntries)
                    .Select(v => v.Substring(0, v.IndexOf(closing)));
                return uris;
            }
            throw new NotSupportedException();
        }
    }
}


================================================
FILE: src/libvideo/Helpers/Json.cs
================================================
using System;
using System.Text;
using System.Text.Json;

namespace VideoLibrary.Helpers
{
    internal static class Json
    {
        public static string GetKey(string key, string source)
        {
            if (GetKey(key, source, out string result))
            {
                return result;
            }
            return null;
        }

        public static bool TryGetKey(string key, string source, out string target)
        {
            return GetKey(key, source, out target);
        }

        public static JsonElement? GetNullableProperty(this JsonElement jsonElement, string propertyName)
        {
            if (jsonElement.TryGetProperty(propertyName, out JsonElement returnElement))
            {
                return returnElement;
            }

            return null;
        }

        public static string Extract(string source)
        {
            StringBuilder sb = new StringBuilder();
            int depth = 0;
            int backSlashesCounter = 0;
            char lastChar = '\u0000';
            bool isString = false;
            foreach (var ch in source)
            {
                sb.Append(ch);
                
                if (ch == '\\')
                {
                    // count backslashes
                    backSlashesCounter++;
                }
                else if (ch == '"')
                {
                    // if current char is quote check last char and count of backslashes to be sure it is not doubled backslashes
                    if (lastChar != '\\' || backSlashesCounter%2 == 0)
                    {
                        isString = !isString;
                    }
                }
                else
                {
                    // reset backslashes count if its any other char
                    backSlashesCounter = 0;
                }

                if (!isString)
                {
                    if (ch == '{' && lastChar != '\\')
                        depth++;
                    else if (ch == '}' && lastChar != '\\')
                        depth--;
                }

                if (depth == 0)
                    break;
                lastChar = ch;
            }
            return sb.ToString();
        }

        private static bool GetKey(string key, string source, out string target)
        {
            // Example scenario: "key" : "value"

            string quotedKey = '"' + key + '"';
            int index = 0;

            while (true)
            {
                index = source.IndexOf(quotedKey, index, StringComparison.Ordinal); // '"'
                if (index == -1)
                {
                    target = string.Empty;
                    return false;
                }
                index += quotedKey.Length; // ' '

                int start = index;
                start = source.SkipWhitespace(start); // ':'
                if (source[start++] != ':') // ' '
                    continue;
                start = source.SkipWhitespace(start); // '"'
                if (source[start++] != '"') // 'v'
                    continue;
                int end = start;
                while ((source[end - 1] == '\\' && source[end] == '"') || source[end] != '"') // "value\""
                {
                    end++;
                }
                target = source.Substring(start, end - start);
                return true;
            }
        }
    }
}


================================================
FILE: src/libvideo/Helpers/KeyCollection.cs
================================================
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace VideoLibrary.Helpers
{
    internal partial class Query : IDictionary<string, string>
    {
        public class KeyCollection : ICollection<string>, IReadOnlyCollection<string>
        {
            private readonly Query query;

            public KeyCollection(Query query)
            {
                this.query = query;
            }

            public int Count => query.Count;

            public bool IsReadOnly => true;

            public void Add(string item)
            {
                throw new NotSupportedException();
            }

            public void Clear()
            {
                throw new NotSupportedException();
            }

            public bool Contains(string item)
            {
                for (int i = 0; i < query.Count; i++)
                {
                    var pair = query.Pairs[i];
                    if (item == pair.Key)
                        return true;
                }
                return false;
            }

            public void CopyTo(string[] array, int arrayIndex)
            {
                for (int i = 0; i < query.Count; i++)
                    array[arrayIndex++] = query.Pairs[i].Key;
            }

            public IEnumerator<string> GetEnumerator()
            {
                for (int i = 0; i < query.Count; i++)
                    yield return query.Pairs[i].Key;
            }

            public bool Remove(string item)
            {
                throw new NotSupportedException();
            }

            IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
        }
    }
}


================================================
FILE: src/libvideo/Helpers/Operations.cs
================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace VideoLibrary.Helpers
{
    internal struct Operations
    {
        public Operations(string reverse, 
            string swap, string splice)
        {
            this.Reverse = reverse;
            this.Swap = swap;
            this.Splice = splice;
        }

        public string Reverse { get; }
        public string Swap { get; }
        public string Splice { get; }
    }
}


================================================
FILE: src/libvideo/Helpers/Query.cs
================================================
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;

namespace VideoLibrary.Helpers
{
    internal partial class Query : IDictionary<string, string>, IReadOnlyDictionary<string, string>
    {
        private int count;
        private readonly string baseUri;
        private KeyValuePair<string, string>[] pairs;

        public Query(string uri)
        {
            int divide = uri.IndexOf('?');

            if (divide == -1)
            {
                int amp = uri.IndexOf('&');
                if (amp == -1)
                {
                    // no query parameters
                    this.baseUri = uri;
                    return;
                }

                // no base URL
                this.baseUri = null;
            }
            else
            {
                // normal URL
                this.baseUri = uri.Substring(0, divide);
                uri = uri.Substring(divide + 1);
            }

            string[] keyValues = uri.Split('&');

            string[] keys = EmptyArray<string>.Value;
            string[] values = EmptyArray<string>.Value;
            pairs = new KeyValuePair<string, string>[keyValues.Length];

            for (int i = 0; i < keyValues.Length; i++)
            {
                string pair = keyValues[i];
                int equals = pair.IndexOf('=');
                string key;
                string value;

                key = pair.Substring(0, equals);
                value = equals < pair.Length ? pair.Substring(equals + 1) : string.Empty;

                pairs[i] = new KeyValuePair<string, string>(key, value);
            }

            this.count = keyValues.Length;
        }

        public string this[string key]
        {
            get
            {
                for (int i = 0; i < count; i++)
                {
                    var pair = pairs[i];
                    if (pair.Key == key)
                        return pair.Value;
                }

                throw new KeyNotFoundException();
            }

            set
            {
                for (int i = 0; i < count; i++)
                {
                    var pair = pairs[i];
                    if (pair.Key == key)
                    {
                        pairs[i] = new KeyValuePair<string, string>(key, value);
                        return;
                    }
                }

                throw new KeyNotFoundException();
            }
        }

        public string BaseUri => baseUri;

        public int Count => count;

        public bool IsReadOnly => false;

        public KeyCollection Keys => new KeyCollection(this);

        ICollection<string> IDictionary<string, string>.Keys => Keys;

        public KeyValuePair<string, string>[] Pairs => pairs;

        public ValueCollection Values => new ValueCollection(this);

        ICollection<string> IDictionary<string, string>.Values => Values;

        IEnumerable<string> IReadOnlyDictionary<string, string>.Keys => Keys;

        IEnumerable<string> IReadOnlyDictionary<string, string>.Values => Values;

        void ICollection<KeyValuePair<string, string>>.Add(KeyValuePair<string, string> item)
        {
            Add(item.Key, item.Value);
        }

        public void Add(string key, string value)
        {
            EnsureCapacity(count + 1);
            pairs[count++] = new KeyValuePair<string, string>(key, value);
        }

        public void Clear()
        {
            if (count == 0)
                return;

            Array.Clear(pairs, 0, count);
            count = 0;
        }

        bool ICollection<KeyValuePair<string, string>>.Contains(KeyValuePair<string, string> item)
        {
            for (int i = 0; i < count; i++)
            {
                var pair = pairs[i];

                if (item.Key == pair.Key &&
                    item.Value == pair.Value)
                    return true;
            }
            return false;
        }

        public bool ContainsKey(string key)
        {
            for (int i = 0; i < count; i++)
            {
                if (key == pairs[i].Key)
                    return true;
            }
            return false;
        }

        void ICollection<KeyValuePair<string, string>>.CopyTo(KeyValuePair<string, string>[] array, int arrayIndex)
        {
            Array.Copy(pairs, 0, array, arrayIndex, count);
        }

        public IEnumerator<KeyValuePair<string, string>> GetEnumerator()
        {
            for (int i = 0; i < count; i++)
                yield return pairs[i];
        }

        bool ICollection<KeyValuePair<string, string>>.Remove(KeyValuePair<string, string> item)
        {
            return Remove(item.Key);
        }

        public bool Remove(string key)
        {
            for (int i = 0; i < count; i++)
            {
                var pair = pairs[i];
                if (pair.Key == key)
                {
                    // found it
                    if (i != count--)
                        Array.Copy(pairs, i + 1, pairs, i, count - i);
                    pairs[count] = default(KeyValuePair<string, string>);
                    return true;
                }
            }
            return false;
        }

        public bool TryGetValue(string key, out string value)
        {
            for (int i = 0; i < count; i++)
            {
                var pair = pairs[i];
                if (key == pair.Key)
                {
                    value = pair.Value;
                    return true;
                }
            }

            value = null;
            return false;
        }

        IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

        public override string ToString()
        {
            if (count == 0)
                return baseUri;

            var builder = new StringBuilder();
            if (baseUri != null)
                builder.Append(baseUri).Append('?');

            var pair = pairs[0]; // OK since we know count is at least 1
            builder.Append(pair.Key)
                .Append('=').Append(pair.Value);

            for (int i = 1; i < count; i++)
            {
                pair = pairs[i];

                builder.Append('&').Append(pair.Key)
                    .Append('=').Append(pair.Value);
            }

            return builder.ToString();
        }

        private void EnsureCapacity(int capacity)
        {
            if (capacity > pairs.Length)
            {
                capacity = Math.Max(capacity, pairs.Length * 2);

                Array.Resize(ref pairs, capacity);
            }
        }
    }
}


================================================
FILE: src/libvideo/Helpers/Require.cs
================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace VideoLibrary.Helpers
{
    internal static class Require
    {
        public static void NotNull<T>(T obj, string name)
            where T : class
        {
            if (obj == null)
                throw new ArgumentNullException(name);
        }
    }
}


================================================
FILE: src/libvideo/Helpers/Text.cs
================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace VideoLibrary.Helpers
{
    internal static class Text
    {
        public static string StringBetween(string prefix, string suffix, string parent)
        {
            int start = parent.IndexOf(prefix) + prefix.Length;

            if (start < prefix.Length)
                return string.Empty;

            int end = parent.IndexOf(suffix, start);

            if (end == -1)
                end = parent.Length;

            return parent.Substring(start, end - start);
        }

        public static int SkipWhitespace(this string text, int start)
        {
            int result = start;
            while (char.IsWhiteSpace(text[result]))
                result++;
            return result;
        }
    }
}


================================================
FILE: src/libvideo/Helpers/UnscrambledQuery.cs
================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace VideoLibrary.Helpers
{
    internal readonly struct UnscrambledQuery
    {
        public UnscrambledQuery(string uri, bool encrypted)
        {
            this.Uri = uri;
            this.IsEncrypted = encrypted;
        }

        public string Uri { get; }
        public bool IsEncrypted { get; }
    }
}


================================================
FILE: src/libvideo/Helpers/ValueCollection.cs
================================================
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace VideoLibrary.Helpers
{
    internal partial class Query
    {
        public class ValueCollection : ICollection<string>, IReadOnlyCollection<string>
        {
            private readonly Query query;

            public ValueCollection(Query query)
            {
                this.query = query;
            }

            public int Count => query.Count;

            public bool IsReadOnly => true;

            public void Add(string item)
            {
                throw new NotSupportedException();
            }

            public void Clear()
            {
                throw new NotSupportedException();
            }

            public bool Contains(string item)
            {
                for (int i = 0; i < query.Count; i++)
                {
                    var pair = query.Pairs[i];
                    if (item == pair.Value)
                        return true;
                }
                return false;
            }

            public void CopyTo(string[] array, int arrayIndex)
            {
                for (int i = 0; i < query.Count; i++)
                    array[arrayIndex++] = query.Pairs[i].Value;
            }

            public IEnumerator<string> GetEnumerator()
            {
                for (int i = 0; i < query.Count; i++)
                    yield return query.Pairs[i].Value;
            }

            public bool Remove(string item)
            {
                throw new NotSupportedException();
            }

            IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
        }
    }
}


================================================
FILE: src/libvideo/IAsyncService.cs
================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace VideoLibrary
{
    internal interface IAsyncService<T> where T : Video
    {
        Task<T> GetVideoAsync(string uri);
        Task<IEnumerable<T>> GetAllVideosAsync(string uri);
    }
}


================================================
FILE: src/libvideo/IService.cs
================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace VideoLibrary
{
    internal interface IService<T> where T : Video
    {
        T GetVideo(string uri);
        IEnumerable<T> GetAllVideos(string uri);
    }
}


================================================
FILE: src/libvideo/ServiceBase.cs
================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;


namespace VideoLibrary
{
    public abstract class ServiceBase<T> : IService<T>, IAsyncService<T>
        where T : Video
    {
        internal virtual T VideoSelector(IEnumerable<T> videos) =>
            videos.First();

        #region Synchronous wrappers
        public T GetVideo(string videoUri) =>
            GetVideoAsync(videoUri).GetAwaiter().GetResult();

        internal T GetVideo(string videoUri,
            Func<string, Task<string>> sourceFactory) =>
            GetVideoAsync(videoUri, sourceFactory).GetAwaiter().GetResult();

        public IEnumerable<T> GetAllVideos(string videoUri) =>
            GetAllVideosAsync(videoUri).GetAwaiter().GetResult();

        internal IEnumerable<T> GetAllVideos(string videoUri,
            Func<string, Task<string>> sourceFactory) =>
            GetAllVideosAsync(videoUri, sourceFactory).GetAwaiter().GetResult();
        #endregion

        public async Task<T> GetVideoAsync(string videoUri)
        {
            using (var wrapped = Client.For(this))
            {
                return await wrapped
                    .GetVideoAsync(videoUri)
                    .ConfigureAwait(false);
            }
        }

        internal async Task<T> GetVideoAsync(
            string videoUri, Func<string, Task<string>> sourceFactory) =>
            VideoSelector(await GetAllVideosAsync(
                videoUri, sourceFactory).ConfigureAwait(false));

        public async Task<IEnumerable<T>> GetAllVideosAsync(string videoUri)
        {
            using (var wrapped = Client.For(this))
            {
                return await wrapped
                    .GetAllVideosAsync(videoUri)
                    .ConfigureAwait(false);
            }
        }

        internal abstract Task<IEnumerable<T>> GetAllVideosAsync(
            string videoUri, Func<string, Task<string>> sourceFactory);

        internal HttpClient MakeClient() =>
            MakeClient(MakeHandler());

        protected virtual HttpMessageHandler MakeHandler()
        {
            // Cookie
            var cookieContainer = new CookieContainer();
            cookieContainer.Add(new Uri(YouTube.YoutubeUrl), new Cookie("CONSENT", "YES+cb", "/", ".youtube.com"));
            // Handler
            var handler = new HttpClientHandler
            {
                UseCookies = true,
                CookieContainer = cookieContainer
            };
            if (handler.SupportsAutomaticDecompression)
                handler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
            return handler;
        }

        protected virtual HttpClient MakeClient(HttpMessageHandler handler)
        {
            var httpClient = new HttpClient(handler);
            httpClient.DefaultRequestHeaders.Add(
                "User-Agent",
                "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"
            );
            return new HttpClient(handler);
        }
    }
}


================================================
FILE: src/libvideo/Video.cs
================================================
using System;
using System.IO;
using System.Text;
using System.Threading.Tasks;

namespace VideoLibrary
{
    public class VideoInfo
    {
        public VideoInfo(string title, int second, string author)
        {
            this.Title = title;
            this.LengthSeconds = second;
            this.Author = author;
        }
        public string Title { get; }
        public int LengthSeconds { get; }
        public string Author { get; }
    }
    public abstract class Video
    {
        internal Video()
        {
        }
        public abstract string Uri { get; }
        public abstract string Title { get; }
        public abstract VideoInfo Info { get; }
        public abstract WebSites WebSite { get; }
        public virtual VideoFormat Format => VideoFormat.Unknown;
        // public virtual AudioFormat AudioFormat => AudioFormat.Unknown;

        public virtual Task<string> GetUriAsync() =>
            Task.FromResult(Uri);

        public byte[] GetBytes() =>
            GetBytesAsync().GetAwaiter().GetResult();

        public async Task<byte[]> GetBytesAsync()
        {
            using (var client = new VideoClient())
            {
                return await client
                    .GetBytesAsync(this)
                    .ConfigureAwait(false);
            }
        }

        public Stream Stream() => StreamAsync().GetAwaiter().GetResult();

        public async Task<Stream> StreamAsync()
        {
            using (var client = new VideoClient())
            {
                return await client
                    .StreamAsync(this)
                    .ConfigureAwait(false);
            }
        }
        public Stream Head() => HeadAsync().GetAwaiter().GetResult();
        public async Task<Stream> HeadAsync()
        {
            using (var client = new VideoClient())
            {
                return await client
                    .StreamAsync(this)
                    .ConfigureAwait(false);
            }
        }
        public virtual string FileExtension
        {
            get
            {
                switch (Format)
                {
                    case VideoFormat.Mp4: return ".mp4";
                    case VideoFormat.WebM: return ".webm";
                    case VideoFormat.Unknown: return string.Empty;
                    default:
                        throw new NotImplementedException($"Format {Format} is unrecognized! Please file an issue at libvideo on GitHub.");
                }
            }
        }

        public string FullName
        {
            get
            {
                var builder =
                    new StringBuilder(Title)
                    .Append(FileExtension);

                foreach (char bad in Path.GetInvalidFileNameChars())
                    builder.Replace(bad, '_');

                return builder.ToString();
            }
        }
    }
}


================================================
FILE: src/libvideo/VideoClient.cs
================================================
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;

namespace VideoLibrary
{
    public class VideoClient : IDisposable
    {
        private bool disposed = false;
        private readonly HttpClient client;

        public VideoClient()
        {
            this.client = MakeClient();
        }

        #region IDisposable

        ~VideoClient()
        {
            Dispose(false);
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposed)
                return;

            disposed = true;

            if (disposing)
            {
                if (client != null)
                    client.Dispose();
            }
        }

        #endregion

        #region MakeClient/MakeHandler

        private HttpClient MakeClient() => MakeClient(MakeHandler());

        protected virtual HttpMessageHandler MakeHandler() => new HttpClientHandler();

        protected virtual HttpClient MakeClient(HttpMessageHandler handler)
        {
            return new HttpClient(handler)
            {
                Timeout = TimeSpan.FromMilliseconds(int.MaxValue) // Longest TimeSpan HttpClient will accept
            };
        }

        #endregion

        public byte[] GetBytes(Video video) => GetBytesAsync(video).GetAwaiter().GetResult();

        public async Task<byte[]> GetBytesAsync(Video video)
        {
            string uri = await
                video.GetUriAsync()
                .ConfigureAwait(false);

            return await client
                .GetByteArrayAsync(uri)
                .ConfigureAwait(false);
        }

        public Stream Stream(Video video) => StreamAsync(video).GetAwaiter().GetResult();

        public async Task<Stream> StreamAsync(Video video)
        {
            string uri = await
                video.GetUriAsync()
                .ConfigureAwait(false);

            return await client
                .GetStreamAsync(uri)
                .ConfigureAwait(false);
        }

        public async Task<long?> GetContentLengthAsync(string requestUri)
        {
            using (var response = await HeadAsync(requestUri))
            {
                return response.Content.Headers.ContentLength;
            }
        }
        public async Task<HttpResponseMessage> HeadAsync(string requestUri)
        {
            using (var request = new HttpRequestMessage(HttpMethod.Head, requestUri))
                return await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
        }
    }
}


================================================
FILE: src/libvideo/VideoFormat.cs
================================================
namespace VideoLibrary
{
    public enum VideoFormat
    {
        Mp4,
        WebM,
        Unknown
    }
}


================================================
FILE: src/libvideo/VisitorDataTokenGenerator.cs
================================================
using System;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text.Json;
using System.Threading.Tasks;

namespace VideoLibrary
{
    internal class VisitorDataTokenGenerator : IDisposable
    {
        private bool _disposed;
        private static string _visitorData = string.Empty;


        public static async Task<string> GetVisitorDataFromYouTube(HttpClient http)
        {
            // Return cached visitor data if available
            if (!string.IsNullOrEmpty(_visitorData))
                return _visitorData;

            try
            {
                // Configure request headers
                http.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

                const string url = "https://www.youtube.com/sw.js_data";
                var response = await http.GetAsync(url);
                response.EnsureSuccessStatusCode();

                var jsonString = await response.Content.ReadAsStringAsync();

                // Remove the ")]}'" prefix if present
                jsonString = jsonString.StartsWith(")]}'") ? jsonString.Substring(4) : jsonString;

                var doc = JsonDocument.Parse(jsonString);
                var value = doc.RootElement[0]
                    .EnumerateArray()
                    .ElementAt(2)
                    .EnumerateArray()
                    .ElementAt(0)
                    .EnumerateArray()
                    .ElementAt(0)
                    .EnumerateArray()
                    .ElementAt(13)
                    .GetString();

                if (value == null)
                    throw new Exception("Failed to fetch visitor data");

                _visitorData = value;
                return _visitorData;
            }
            catch (HttpRequestException ex)
            {
                throw new Exception("Failed to fetch data from YouTube", ex);
            }
            catch (JsonException ex)
            {
                throw new Exception("Failed to parse JSON response", ex);
            }
        }
        public void Dispose()
        {
            if (!_disposed)
            {
                _disposed = true;
            }
        }

        ~VisitorDataTokenGenerator()
        {
            Dispose();
        }
    }
}


================================================
FILE: src/libvideo/WebSites.cs
================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace VideoLibrary
{
    public enum WebSites
    {
        YouTube = 0
    }
}


================================================
FILE: src/libvideo/YouTube.cs
================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Text.RegularExpressions;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Threading.Tasks;
using VideoLibrary.Exceptions;
using VideoLibrary.Helpers;
using System.IO;


namespace VideoLibrary
{
    public class YouTube : ServiceBase<YouTubeVideo>
    {
        private const string Playback = "videoplayback";
        private static string _signatureKey;
        public static YouTube Default { get; } = new YouTube();
        public const string YoutubeUrl = "https://youtube.com/";

        internal override async Task<IEnumerable<YouTubeVideo>> GetAllVideosAsync(string videoUri, Func<string, Task<string>> sourceFactory)
        {
            if (!TryNormalize(videoUri, out videoUri))
                throw new ArgumentException("URL is not a valid YouTube URL!");

            // TODO Remove
            string source = await sourceFactory(videoUri).ConfigureAwait(false);

            // TODO Remove
            string jsPlayer = ParseJsPlayer(source);

            if (jsPlayer == null)
            {
                throw new UnavailableStreamException($"JS Player is not found");
            }

            var playerResponseJson = JsonDocument.Parse(Json.Extract(ParsePlayerJson(source))).RootElement;

            // PlayerJson from IOS content
            var data = await GetPlayerResponseIOSAsync(playerResponseJson.GetProperty("videoDetails").GetNullableProperty("videoId")?.GetString())
                .ConfigureAwait(false);

            if (data != null)
            {
                playerResponseJson = JsonDocument.Parse(data).RootElement;
            }

            return ParseVideos(source, jsPlayer, playerResponseJson);
        }
        public static string GetSignatureKey()
        {
            return string.IsNullOrWhiteSpace(_signatureKey) ? "signature" : _signatureKey;
        }

        private bool TryNormalize(string videoUri, out string normalized)
        {
            // If you fix something in here, please be sure to fix in 
            // DownloadUrlResolver.TryNormalizeYoutubeUrl as well.

            normalized = null;

            var builder = new StringBuilder(videoUri);

            videoUri = builder.Replace("youtu.be/", "youtube.com/watch?v=")
                .Replace("youtube.com/embed/", "youtube.com/watch?v=")
                .Replace("/v/", "/watch?v=")
                .Replace("/watch#", "/watch?")
                .Replace("youtube.com/shorts/", "youtube.com/watch?v=")
                .ToString();

            var query = new Query(videoUri);

            string value;

            if (!query.TryGetValue("v", out value))
                return false;

            normalized = $"{YoutubeUrl}watch?v=" + value;
            return true;
        }

        private IEnumerable<YouTubeVideo> ParseVideos(string source, string jsPlayer, JsonElement playerResponseJson)
        {
            IEnumerable<UnscrambledQuery> queries;

            if (string.Equals(playerResponseJson.GetProperty("playabilityStatus").GetNullableProperty("status")?.GetString(), "error", StringComparison.OrdinalIgnoreCase))
            {
                throw new UnavailableStreamException($"Video has unavailable stream.");
            }

            var errorReason = playerResponseJson.GetProperty("playabilityStatus").GetNullableProperty("reason")?.GetString();
            if (string.IsNullOrWhiteSpace(errorReason))
            {
                var isLiveStream = playerResponseJson.GetProperty("videoDetails").GetNullableProperty("isLive")?.GetBoolean() == true;
                var title = playerResponseJson.GetProperty("videoDetails").GetNullableProperty("title")?.GetString();
                var lengthSeconds = playerResponseJson.GetProperty("videoDetails").GetNullableProperty("lengthSeconds")?.GetString() ?? "0";
                var author = playerResponseJson.GetProperty("videoDetails").GetNullableProperty("author")?.GetString();

                var videoInfo = new VideoInfo(title, int.Parse(lengthSeconds), author);

                if (isLiveStream)
                {
                    throw new UnavailableStreamException($"This is live stream so unavailable stream.");
                }

                string map = Json.GetKey("url_encoded_fmt_stream_map", source);
                if (!string.IsNullOrWhiteSpace(map))
                {
                    queries = map.Split(',').Select(Unscramble);
                    foreach (var query in queries)
                        yield return new YouTubeVideo(videoInfo, query, jsPlayer);
                }
                else // player_response
                {
                    List<JsonElement> streamObjects = new List<JsonElement>();

                    // Extract Muxed streams
                    var streamFormat = playerResponseJson.GetNullableProperty("streamingData")?.GetNullableProperty("formats");
                    if (streamFormat != null)
                    {
                        streamObjects.AddRange(streamFormat?.EnumerateArray());
                    }

                    // Extract AdaptiveFormat streams
                    var streamAdaptiveFormats = playerResponseJson.GetNullableProperty("streamingData")?.GetNullableProperty("adaptiveFormats");
                    if (streamAdaptiveFormats != null)
                    {
                        streamObjects.AddRange(streamAdaptiveFormats?.EnumerateArray());
                    }

                    foreach (var item in streamObjects)
                    {
                        var urlValue = item.GetNullableProperty("url")?.GetString();
                        if (!string.IsNullOrEmpty(urlValue))
                        {
                            var query = new UnscrambledQuery(urlValue, false);
                            yield return new YouTubeVideo(videoInfo, query, jsPlayer);
                            continue;
                        }

                        var cipherValue = (item.GetNullableProperty("cipher") ?? item.GetNullableProperty("signatureCipher"))?.GetString();
                        if (!string.IsNullOrEmpty(cipherValue))
                        {
                            yield return new YouTubeVideo(videoInfo, Unscramble(cipherValue), jsPlayer);
                        }
                    }
                }

                // adaptive_fmts
                string adaptiveMap = Json.GetKey("adaptive_fmts", source);
                if (!string.IsNullOrWhiteSpace(adaptiveMap))
                {
                    queries = adaptiveMap.Split(',').Select(Unscramble);
                    foreach (var query in queries)
                        yield return new YouTubeVideo(videoInfo, query, jsPlayer);
                }
                else
                {
                    // dashmpd
                    string dashmpdMap = Json.GetKey("dashmpd", source);
                    if (!string.IsNullOrWhiteSpace(adaptiveMap))
                    {
                        using (HttpClient hc = new HttpClient())
                        {
                            IEnumerable<string> uris = null;
                            try
                            {

                                dashmpdMap = WebUtility.UrlDecode(dashmpdMap).Replace(@"\/", "/");

                                var manifest = hc
                                    .GetStringAsync(dashmpdMap)
                                    .GetAwaiter()
                                    .GetResult()
                                    .Replace(@"\/", "/");

                                uris = Html.GetUrisFromManifest(manifest);
                            }
                            catch (Exception e)
                            {
                                throw new UnavailableStreamException(e.Message);
                            }

                            if (uris != null)
                            {
                                foreach (var v in uris)
                                {
                                    yield return new YouTubeVideo(videoInfo, UnscrambleManifestUri(v), jsPlayer);
                                }
                            }
                        }
                    }
                }
            }
            else
            {
                throw new UnavailableStreamException($"Error caused by Youtube.({errorReason}))");
            }
        }

        private string ParsePlayerJson(string source)
        {
            string playerResponseMap = null, ytInitialPlayerPattern = @"\s*var\s*ytInitialPlayerResponse\s*=\s*(\{\""responseContext\"".*\});", ytWindowInitialPlayerResponse = @"\[\""ytInitialPlayerResponse\""\]\s*=\s*(\{.*\});", ytPlayerPattern = @"ytplayer\.config\s*=\s*(\{\"".*\""\}\});";
            Match match;
            if ((match = Regex.Match(source, ytPlayerPattern)).Success && Json.TryGetKey("player_response", match.Groups[1].Value, out string json))
            {
                playerResponseMap = Regex.Unescape(json);
            }
            if (string.IsNullOrWhiteSpace(playerResponseMap) && (match = Regex.Match(source, ytInitialPlayerPattern)).Success)
            {
                playerResponseMap = match.Groups[1].Value;
            }
            if (string.IsNullOrWhiteSpace(playerResponseMap) && (match = Regex.Match(source, ytWindowInitialPlayerResponse)).Success)
            {
                playerResponseMap = match.Groups[1].Value;
            }
            if (string.IsNullOrWhiteSpace(playerResponseMap))
            {
                throw new UnavailableStreamException("Player json has no found.");
            }
            return playerResponseMap.Replace(@"\u0026", "&").Replace("\r\n", string.Empty).Replace("\n", string.Empty).Replace("\r", string.Empty).Replace("\\&", "\\\\&");
        }

        private string ParseJsPlayer(string source)
        {
            if (Json.TryGetKey("jsUrl", source, out var jsPlayer) || Json.TryGetKey("PLAYER_JS_URL", source, out jsPlayer))
            {
                jsPlayer = jsPlayer.Replace(@"\/", "/");
            }
            else
            {
                // Alternative solution
                Match match = Regex.Match(source, "<script\\s*src=\"([-a-zA-Z0-9()@:%_\\+.~#?&//=]*)\".*name=\"player_ias/base\".*>\\s*</script>");
                if (match.Success)
                {
                    jsPlayer = match.Groups[1].Value.Replace(@"\/", "/");
                }
                else
                {
                    return null;
                }
            }

            if (jsPlayer.StartsWith("/yts") || jsPlayer.StartsWith("/s"))
            {
                return $"https://www.youtube.com{jsPlayer}";
            }

            // Fall back on old implementation (not sure it's needed)
            if (!jsPlayer.StartsWith("http"))
            {
                jsPlayer = $"https:{jsPlayer}";
            }

            return jsPlayer;
        }

        private UnscrambledQuery Unscramble(string queryString)
        {
            queryString = queryString.Replace(@"\u0026", "&");
            var query = new Query(queryString);
            string uri = query["url"];

            query.TryGetValue("sp", out _signatureKey);

            bool encrypted = false;
            string signature;

            if (query.TryGetValue("s", out signature))
            {
                encrypted = true;
                uri += GetSignatureAndHost(GetSignatureKey(), signature, query);
            }
            else if (query.TryGetValue("sig", out signature))
                uri += GetSignatureAndHost(GetSignatureKey(), signature, query);

            uri = WebUtility.UrlDecode(
                WebUtility.UrlDecode(uri));

            var uriQuery = new Query(uri);

            if (!uriQuery.ContainsKey("ratebypass"))
                uri += "&ratebypass=yes";

            return new UnscrambledQuery(uri, encrypted);
        }

        private string GetSignatureAndHost(string key, string signature, Query query)
        {
            string result = $"&{key}={signature}";

            string host;

            if (query.TryGetValue("fallback_host", out host))
                result += "&fallback_host=" + host;

            return result;
        }

        private UnscrambledQuery UnscrambleManifestUri(string manifestUri)
        {
            int start = manifestUri.IndexOf(Playback) + Playback.Length;
            string baseUri = manifestUri.Substring(0, start);
            string parametersString = manifestUri.Substring(start, manifestUri.Length - start);
            var parameters = parametersString.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries);

            var builder = new StringBuilder(baseUri);
            builder.Append("?");
            for (var i = 0; i < parameters.Length; i += 2)
            {
                builder.Append(parameters[i]);
                builder.Append('=');
                builder.Append(parameters[i + 1].Replace("%2F", "/"));
                if (i < parameters.Length - 2)
                {
                    builder.Append('&');
                }
            }

            return new UnscrambledQuery(builder.ToString(), false);
        }

        private async Task<string> GetPlayerResponseIOSAsync(string id)
        {
            var httpClient = new HttpClient();
            var request = new HttpRequestMessage(HttpMethod.Post, "https://www.youtube.com/youtubei/v1/player");

            var content = new
            {
                videoId = id,
                contentCheckOk = true,
                context = new
                {
                    client = new
                    {
                        clientName = "ANDROID_VR",
                        clientVersion = "1.60.19",
                        deviceMake = "Oculus",
                        deviceModel = "Quest 3",
                        osName = "Android",
                        osVersion = "12L",
                        platform = "MOBILE",
                        hl = "en",
                        gl = "US",
                        utcOffsetMinutes = 0,
                        visitorData = await VisitorDataTokenGenerator.GetVisitorDataFromYouTube(httpClient),
                    }
                }
            };

            request.Content = new StringContent(JsonSerializer.Serialize(content));
            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");
            var response = await httpClient.SendAsync(request);

            if (response.IsSuccessStatusCode)
            {

                var responseContent = await response.Content.ReadAsStringAsync();
                httpClient.Dispose();
                request.Dispose();
                request.Dispose();

                return responseContent;
            }

            return null;
        }
    }
}


================================================
FILE: src/libvideo/YouTubeVideo.Decrypt.cs
================================================
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using VideoLibrary.Helpers;

namespace VideoLibrary
{
    public partial class YouTubeVideo
    {
        private async Task<string> DecryptAsync(string uri, Func<DelegatingClient> makeClient)
        {
            var query = new Query(uri);

            string signature;
            if (!query.TryGetValue(YouTube.GetSignatureKey(), out signature))
                return uri;

            if (string.IsNullOrWhiteSpace(signature))
                throw new Exception("Signature not found.");

            if (jsPlayer == null)
            {
                jsPlayer = await makeClient()
                    .GetStringAsync(jsPlayerUrl)
                    .ConfigureAwait(false);
            }

            query[YouTube.GetSignatureKey()] = DecryptSignature(jsPlayer, signature);
            return query.ToString();
        }

        private string DecryptSignature(string js, string signature)
        {
            var functionNameRegex = new Regex(@"\w+(?:.|\[)(\""?\w+(?:\"")?)\]?\(");
            var functionLines = GetDecryptionFunctionLines(js);
            var decryptor = new Decryptor();
            var decipherDefinitionName = Regex.Match(string.Join(";", functionLines), "([\\$_\\w]+).\\w+\\(\\w+,\\d+\\);").Groups[1].Value;
            if (string.IsNullOrEmpty(decipherDefinitionName))
            {
                throw new Exception("Could not find signature decipher definition name. Please report this issue to us.");
            }

            var decipherDefinitionBody = Regex.Match(js, $@"var\s+{Regex.Escape(decipherDefinitionName)}=\{{(\w+:function\(\w+(,\w+)?\)\{{(.*?)\}}),?\}};", RegexOptions.Singleline).Groups[0].Value;
            if (string.IsNullOrEmpty(decipherDefinitionBody))
            {
                throw new Exception("Could not find signature decipher definition body. Please report this issue to us.");
            }
            foreach (var functionLine in functionLines)
            {
                if (decryptor.IsComplete)
                {
                    break;
                }

                var match = functionNameRegex.Match(functionLine);
                if (match.Success)
                {
                    decryptor.AddFunction(decipherDefinitionBody, match.Groups[1].Value);
                }
            }

            foreach (var functionLine in functionLines)
            {
                var match = functionNameRegex.Match(functionLine);
                if (match.Success)
                {
                    signature = decryptor.ExecuteFunction(signature, functionLine, match.Groups[1].Value);
                }
            }

            return signature;
        }

        private string[] GetDecryptionFunctionLines(string js)
        {
            var decipherFuncName = Regex.Match(js, @"(\w+)=function\(\w+\){(\w+)=\2\.split\(\x22{2}\);.*?return\s+\2\.join\(\x22{2}\)}");
            return decipherFuncName.Success ? decipherFuncName.Groups[0].Value.Split(';') : null;
        }

        private class Decryptor
        {
            private static readonly Regex ParametersRegex = new Regex(@"\(\w+,(\d+)\)");

            private readonly Dictionary<string, FunctionType> _functionTypes = new Dictionary<string, FunctionType>();
            private readonly StringBuilder _stringBuilder = new StringBuilder();

            public bool IsComplete =>
                _functionTypes.Count == Enum.GetValues(typeof(FunctionType)).Length;

            public void AddFunction(string js, string function)
            {
                var escapedFunction = Regex.Escape(function);
                FunctionType? type = null;
                /* Pass  "do":function(a){} or xa:function(a,b){} */
                if (Regex.IsMatch(js, $@"(\"")?{escapedFunction}(\"")?:\bfunction\b\([a],b\).(\breturn\b)?.?\w+\."))
                {
                    type = FunctionType.Slice;
                }
                else if (Regex.IsMatch(js, $@"(\"")?{escapedFunction}(\"")?:\bfunction\b\(\w+\,\w\).\bvar\b.\bc=a\b"))
                {
                    type = FunctionType.Swap;
                }
                if (Regex.IsMatch(js, $@"(\"")?{escapedFunction}(\"")?:\bfunction\b\(\w+\){{\w+\.reverse"))
                {
                    type = FunctionType.Reverse;
                }
                if (type.HasValue)
                {
                    _functionTypes[function] = type.Value;
                }
            }

            public string ExecuteFunction(string signature, string line, string function)
            {
                if (!_functionTypes.TryGetValue(function, out var type))
                {
                    return signature;
                }

                switch (type)
                {
                    case FunctionType.Reverse:
                        return Reverse(signature);
                    case FunctionType.Slice:
                    case FunctionType.Swap:
                        var index =
                            int.Parse(
                                ParametersRegex.Match(line).Groups[1].Value,
                                NumberStyles.AllowThousands,
                                NumberFormatInfo.InvariantInfo);
                        return
                            type == FunctionType.Slice
                                ? Slice(signature, index)
                                : Swap(signature, index);
                    default:
                        throw new ArgumentOutOfRangeException(nameof(type));
                }
            }

            private string Reverse(string signature)
            {
                _stringBuilder.Clear();
                for (var index = signature.Length - 1; index >= 0; index--)
                {
                    _stringBuilder.Append(signature[index]);
                }

                return _stringBuilder.ToString();
            }

            private string Slice(string signature, int index) => signature.Substring(index);

            private string Swap(string signature, int index)
            {
                _stringBuilder.Clear();
                _stringBuilder.Append(signature);
                _stringBuilder[0] = _stringBuilder[index % _stringBuilder.Length];
                _stringBuilder[index % _stringBuilder.Length] = signature[0];
                return _stringBuilder.ToString();
            }

            private enum FunctionType
            {
                Reverse,
                Slice,
                Swap
            }
        }
    }
}


================================================
FILE: src/libvideo/YouTubeVideo.Format.cs
================================================
namespace VideoLibrary
{
    // TODO Add/Fix itags
    public partial class YouTubeVideo
    {
        public int Fps
        {
            get
            {
                switch (FormatCode)
                {
                    case 571:
                    case 402:
                    case 401:
                    case 400:
                    case 399:
                    case 398:
                    case 337:
                    case 336:
                    case 335:
                    case 334:
                    case 333:
                    case 332:
                    case 331:
                    case 330:
                    case 272:
                    case 315:
                    case 308:
                    case 303:
                    case 302:
                    case 305:
                    case 304:
                    case 299:
                    case 298:
                        return 60;
                    case 18:
                    case 22:
                    case 37:
                    case 43:
                    case 59:
                    case 397:
                    case 396:
                    case 395:
                    case 394:
                    case 313:
                    case 271:
                    case 248:
                    case 247:
                    case 244:
                    case 243:
                    case 242:
                    case 278:
                    case 138:
                    case 266:
                    case 264:
                    case 137:
                    case 136:
                    case 135:
                    case 134:
                    case 133:
                    case 160:
                        return 30;
                    default:
                        return -1;
                }
            }
        }

        public bool IsAdaptive => this.AdaptiveKind != AdaptiveKind.None;

        public AdaptiveKind AdaptiveKind
        {
            get
            {
                switch (FormatCode)
                {
                    case 18:
                    case 22:
                    case 37:
                    case 43:
                    case 59:
                    case 133:
                    case 134:
                    case 135:
                    case 136:
                    case 137:
                    case 138:
                    case 160:
                    case 242:
                    case 243:
                    case 244:
                    case 247:
                    case 248:
                    case 264:
                    case 266:
                    case 271:
                    case 272:
                    case 298:
                    case 299:
                    case 302:
                    case 303:
                    case 304:
                    case 305:
                    case 308:
                    case 313:
                    case 315:
                    case 330:
                    case 331:
                    case 332:
                    case 333:
                    case 334:
                    case 335:
                    case 336:
                    case 337:
                    case 394:
                    case 395:
                    case 396:
                    case 397:
                    case 398:
                    case 399:
                    case 400:
                    case 401:
                    case 402:
                    case 571:
                    case 278:
                    case 694:
                    case 695:
                    case 696:
                    case 697:
                    case 698:
                    case 699:
                    case 700:
                    case 701:
                        return AdaptiveKind.Video;
                    case 139:
                    case 140:
                    case 141:
                    case 171:
                    case 172:
                    case 249:
                    case 250:
                    case 251:
                    case 256:
                    case 258:
                    case 327:
                    case 338:
                        return AdaptiveKind.Audio;
                    default:
                        return AdaptiveKind.None;
                }
            }
        }

        public int AudioBitrate
        {
            get
            {
                switch (FormatCode)
                {
                    case 139:
                    case 249:
                    case 250:
                    case 599:
                    case 600:
                        return 48;
                    case 18:
                        return 96;
                    case 37:
                    case 43:
                    case 59:
                    case 140:
                    case 171:
                    case 251:
                        return 128;
                    case 22:
                    case 256:
                        return 192;
                    case 141:
                    case 172:
                    case 327:
                    case 774:
                        return 256;
                    case 258:
                    case 325: 
                    case 328: 
                    case 380:
                        return 384;
                    case 338:
                        return 480;
                    case 773:
                        return 960;
                    default:
                        return -1;
                }
            }
        }

        public int Resolution
        {
            get
            {
                switch (FormatCode)
                {
                    case 394:
                    case 330:
                    case 278:
                    case 160:
                    case 694:
                    case 597:
                    case 598:
                        return 144;
                    case 395:
                    case 331:
                    case 242:
                    case 133:
                    case 695:
                        return 240;
                    case 18:
                    case 43:
                    case 396:
                    case 332:
                    case 243:
                    case 134:
                    case 696:
                    case 167:
                        return 360;
                    case 59:
                    case 397:
                    case 333:
                    case 244:
                    case 135:
                    case 697:
                    case 168:
                        return 480;
                    case 22:
                    case 398:
                    case 334:
                    case 302:
                    case 247:
                    case 298:
                    case 136:
                    case 698:
                    case 169:
                    case 612:
                        return 720;
                    case 37:
                    case 399:
                    case 335:
                    case 303:
                    case 248:
                    case 299:
                    case 137:
                    case 699:
                    case 170:
                    case 216:
                    case 616:
                    case 721:
                        return 1080;
                    case 400:
                    case 336:
                    case 308:
                    case 271:
                    case 304:
                    case 264:
                    case 700:
                        return 1440;
                    case 401:
                    case 337:
                    case 315:
                    case 313:
                    case 305:
                    case 266:
                    case 701:
                        return 2160;
                    case 138:
                    case 272:
                    case 402:
                    case 571:
                    case 702:
                        return 4320;
                    default:
                        return -1;
                }
            }
        }

        public override VideoFormat Format
        {
            get
            {
                switch (FormatCode)
                {
                    case 18:
                    case 22:
                    case 37:
                    case 59:
                    case 133:
                    case 134:
                    case 135:
                    case 136:
                    case 137:
                    case 138:
                    case 160:
                    case 264:
                    case 266:
                    case 298:
                    case 299:
                    case 304:
                    case 305:
                    case 394:
                    case 395:
                    case 396:
                    case 397:
                    case 398:
                    case 399:
                    case 400:
                    case 401:
                    case 402:
                    case 571:
                    case 168:
                    case 169:
                    case 170:
                    case 216:
                    case 278:
                    case 694:
                    case 695:
                    case 696:
                    case 697:
                    case 698:
                    case 699:
                    case 700:
                    case 701:
                    case 702:
                    case 721:
                        return VideoFormat.Mp4;
                    case 43:
                    case 242:
                    case 243:
                    case 244:
                    case 247:
                    case 248:
                    case 271:
                    case 272:
                    case 302:
                    case 303:
                    case 308:
                    case 313:
                    case 598:
                    case 597:
                    case 612:
                    case 616:
                        return VideoFormat.WebM;
                    default:
                        return VideoFormat.Unknown;
                }
            }
        }

        public AudioFormat AudioFormat
        {
            get
            {
                switch (FormatCode)
                {
                    case 18:
                    case 22:
                    case 37:
                    case 59:
                    case 139:
                    case 140:
                    case 141:
                    case 256:
                    case 258:
                    case 327:
                    case 325: 
                    case 328: 
                    case 380: 
                    case 599:
                        return AudioFormat.Aac;
                    case 171:
                    case 172:
                        return AudioFormat.Vorbis;
                    case 43:
                    case 249:
                    case 250:
                    case 251:
                    case 338:
                    case 600:
                    case 773:
                    case 774:
                        return AudioFormat.Opus;
                    default:
                        return AudioFormat.Unknown;
                }
            }
        }
    }
}

================================================
FILE: src/libvideo/YouTubeVideo.cs
================================================
using System;
using System.Threading.Tasks;
using VideoLibrary.Helpers;

namespace VideoLibrary
{
    public partial class YouTubeVideo : Video
    {
        private readonly string jsPlayerUrl;
        private string jsPlayer;
        private string uri;
        private readonly Query _uriQuery;
        private bool _encrypted;
        private bool _needNDescramble;
        internal YouTubeVideo(VideoInfo info, UnscrambledQuery query, string jsPlayerUrl)
        {
            this.Info = info;
            this.Title = info?.Title;
            this.uri = query.Uri;
            this._uriQuery = new Query(uri);
            this.jsPlayerUrl = jsPlayerUrl;
            this._encrypted = query.IsEncrypted;
            this._needNDescramble = _uriQuery.ContainsKey("n");
            this.FormatCode = int.Parse(_uriQuery["itag"]);
        }

        public override string Title { get; }

        public override VideoInfo Info { get; }

        public override WebSites WebSite => WebSites.YouTube;

        public override string Uri => GetUriAsync().GetAwaiter().GetResult();

        public string GetUri(Func<DelegatingClient> makeClient) => GetUriAsync(makeClient).GetAwaiter().GetResult();

        public override Task<string> GetUriAsync() => GetUriAsync(() => new DelegatingClient());

        public async Task<string> GetUriAsync(Func<DelegatingClient> makeClient)
        {
            if (_encrypted)
            {
                uri = await DecryptAsync(uri, makeClient).ConfigureAwait(false);
                _encrypted = false;
            }

            if (_needNDescramble)
            {
                uri = await NDescrambleAsync(uri, makeClient).ConfigureAwait(false);
                _needNDescramble = false;
            }

            return uri;
        }

        public int FormatCode { get; }

        public long? ContentLength
        {
            get
            {
                if (_contentLength.HasValue)
                    return _contentLength;
                _contentLength = this.GetContentLength(_uriQuery).Result;
                return _contentLength;
            }
        }

        public bool IsEncrypted => _encrypted;

        // Private's
        private long? _contentLength { get; set; }
        private async Task<long?> GetContentLength(Query query)
        {
            if (query.TryGetValue("clen", out string clen))
            {
                return long.Parse(clen);
            }
            using (var client = new VideoClient())
            {
                return await client.GetContentLengthAsync(uri);
            }
        }

    }
}


================================================
FILE: src/libvideo/YoutubeVideo.Descramble.cs
================================================
using System;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using VideoLibrary.Helpers;

namespace VideoLibrary
{
    public partial class YouTubeVideo
    {
        private async Task<string> NDescrambleAsync(string uri, Func<DelegatingClient> makeClient)
        {
            var query = new Query(uri);

            if (!query.TryGetValue("n", out var signature))
                return uri;

            if (string.IsNullOrWhiteSpace(signature))
                throw new Exception("N Signature not found.");

            if (jsPlayer == null)
            {
                jsPlayer = await makeClient()
                    .GetStringAsync(jsPlayerUrl)
                    .ConfigureAwait(false);
            }

            query["n"] = DescrambleNSignature(jsPlayer, signature);
            return query.ToString();
        }

        private string DescrambleNSignature(string js, string signature)
        {
            return signature;
            //var func = GetDescrambleFunctionBody(js);
            //var asd = "var " + func.Item2.Replace("===\"undefined\"", "!=='undefined'");
            //var result = new Engine()
            //    .Execute(asd)
            //    .Invoke(func.Item1, signature); // -> 3

            //File.WriteAllText("C:\\Users\\OMANSAK\\Desktop\\asd.txt", js);
            //// TODO Add Native Descramble for "N" Signature
            //return result.ToString();
        }

        private Tuple<string, string> GetDescrambleFunctionBody(string js)
        {
            string functionName = null;
            var functionLine = Regex.Match(js, @"([\w\d]*)=function\(\w+?\){var \w+=\w+.split\(\w+\.slice\(0,0\)\),Z=\[");

            if (functionLine.Success && !functionLine.Groups[2].Success)
            {
                functionName = functionLine.Groups[1].Value;
            }
            else
            {
                var fname = Regex.Match(js, $@"var {functionLine.Groups[1]}\s*=\s*(\[.+?\]);");
                if (fname.Success && fname.Groups[1].Success)
                {
                    functionName = fname.Groups[1].Value
                        .Replace("[", string.Empty)
                        .Replace("]", string.Empty)
                        .Split(',')[int.Parse(functionLine.Groups[2].Value)];
                }
            }

            if (!string.IsNullOrWhiteSpace(functionName))
            {
                var decipherDefinitionBody = Regex.Match(js, $@"{Regex.Escape(functionName)}=function\(\w+(,\w+)?\)\{{(?s:.*?)\}};", RegexOptions.Singleline);

                if (decipherDefinitionBody.Success)
                {
                    return new Tuple<string, string>(functionName, decipherDefinitionBody.Groups[0].Value);
                }
            }

            return new Tuple<string, string>(functionName, null);
        }
    }
}

================================================
FILE: src/libvideo/libvideo.csproj
================================================
<Project Sdk="Microsoft.NET.Sdk">

	<PropertyGroup>
		<Authors>Bar Arnon,OMANSAK</Authors>
		<Copyright>Copyright 2018 Bar Arnon | 2026 OMANSAK</Copyright>
		<Description>libvideo (aka VideoLibrary) is a modern .NET library for downloading YouTube videos. It is portable to most platforms and is very lightweight.

Find us on GitHub at https://github.com/omansak/libvideo</Description>
		<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
		<PackageIconUrl></PackageIconUrl>
		<PackageId>VideoLibrary</PackageId>
		<PackageLicenseUrl></PackageLicenseUrl>
		<PackageProjectUrl>https://github.com/omansak/libvideo</PackageProjectUrl>
		<PackageReleaseNotes>Fix 403 Forbidden errors</PackageReleaseNotes>
		<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>
		<RepositoryType>git</RepositoryType>
		<RepositoryUrl>https://github.com/omansak/libvideo</RepositoryUrl>
		<RootNamespace>VideoLibrary</RootNamespace>
		<Version>3.3.1</Version>
		<TargetFramework>netstandard2.0</TargetFramework>
		<Title>libvideo</Title>
		<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
		<AssemblyVersion>3.3.1.0</AssemblyVersion>
		<FileVersion>3.3.1.0</FileVersion>
		<PackageLicenseFile>LICENSE</PackageLicenseFile>
		<PackageIcon>icon_586.png</PackageIcon>
		<PackageReadmeFile>README.md</PackageReadmeFile>
	</PropertyGroup>

	<ItemGroup>
		<None Include="..\..\icons\icon_586.png">
			<Pack>True</Pack>
			<PackagePath></PackagePath>
		</None>
		<None Include="..\..\LICENSE">
			<Pack>True</Pack>
			<PackagePath></PackagePath>
		</None>
		<None Include="..\..\README.md">
			<Pack>True</Pack>
			<PackagePath></PackagePath>
		</None>
	</ItemGroup>

	<ItemGroup>
	  <PackageReference Include="System.Text.Json" Version="10.0.1" />
	</ItemGroup>

</Project>

================================================
FILE: src/libvideo.compat/AdaptiveType.cs
================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace YoutubeExtractor
{
    public enum AdaptiveType
    {
        None, Audio, Video
    }
}


================================================
FILE: src/libvideo.compat/AudioExtractionException.cs
================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace YoutubeExtractor
{
    public class AudioExtractionException : Exception
    {
        public AudioExtractionException(string message)
            : base(message)
        { }
    }
}


================================================
FILE: src/libvideo.compat/AudioType.cs
================================================
namespace YoutubeExtractor
{
    public enum AudioType
    {
        Aac, Vorbis, Opus, Unknown
    }
}


================================================
FILE: src/libvideo.compat/DownloadUrlResolver.cs
================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using VideoLibrary;
using VideoLibrary.Helpers;

namespace YoutubeExtractor
{
    public static class DownloadUrlResolver
    {
        private static YouTube Service = new YouTube();

        public static void DecryptDownloadUrl(VideoInfo info)
        {
            // Nothing to do here, URL is decrypted automatically 
            // upon calling YouTubeVideo.Uri.
        }

        public static IEnumerable<VideoInfo> GetDownloadUrls(string videoUrl, bool decryptSignature = true)
        {
            Require.NotNull(videoUrl, nameof(videoUrl));

            // GetAllVideos normalizes the URL as of libvideo v0.4.1, 
            // don't call TryNormalizeYoutubeUrl here.

            return Service.GetAllVideos(videoUrl).Select(v => new VideoInfo(v));
        }

        public async static Task<IEnumerable<VideoInfo>> GetDownloadUrlsAsync(
            string videoUrl, bool decryptSignature = true)
        {
            var videos = await Service
                .GetAllVideosAsync(videoUrl)
                .ConfigureAwait(false);

            return videos.Select(v => new VideoInfo(v));
        }

        public static bool TryNormalizeYoutubeUrl(string url, out string normalizedUrl)
        {
            // If you fix something in here, please be sure to fix in 
            // YouTubeService.TryNormalize as well.

            normalizedUrl = null;

            var builder = new StringBuilder(url);

            url = builder.Replace("youtu.be/", "youtube.com/watch?v=")
                .Replace("youtube.com/embed/", "youtube.com/watch?v=")
                .Replace("/v/", "/watch?v=")
                .Replace("/watch#", "/watch?")
                .Replace("youtube.com/shorts/", "youtube.com/watch?v=")
                .ToString();

            string value;

            var query = new Query(url);

            if (!query.TryGetValue("v", out value))
                return false;

            normalizedUrl = "https://youtube.com/watch?v=" + value;
            return true;
        }
    }
}


================================================
FILE: src/libvideo.compat/VideoInfo.cs
================================================
using System;
using VideoLibrary;

namespace YoutubeExtractor
{
    public class VideoInfo
    {
        private readonly YouTubeVideo video;

        internal VideoInfo(YouTubeVideo video)
        {
            this.video = video;
        }

        public AdaptiveType AdaptiveType
        {
            get
            {
                switch (video.AdaptiveKind)
                {
                    case AdaptiveKind.Audio:
                        return AdaptiveType.Audio;
                    case AdaptiveKind.Video:
                        return AdaptiveType.Video;
                    default:
                        return AdaptiveType.None;
                }
            }
        }

        public int AudioBitrate
        {
            get
            {
                int result = video.AudioBitrate;
                return result == -1 ? 0 : result;
            }
        }

        public string AudioExtension
        {
            get
            {
                switch (video.AudioFormat)
                {
                    case AudioFormat.Aac: return ".aac";
                    case AudioFormat.Vorbis: return ".ogg";
                    case AudioFormat.Opus: return ".ogg";
                    case AudioFormat.Unknown: return String.Empty;
                    default:
                        throw new NotImplementedException($"Audio format {video.AudioFormat} is unrecognized! Please file an issue at libvideo on GitHub.");
                }
            }
        }

        public AudioType AudioType
        {
            get
            {
                switch (video.AudioFormat)
                {
                    case AudioFormat.Aac:
                        return AudioType.Aac;
                    case AudioFormat.Vorbis:
                        return AudioType.Vorbis;
                    case AudioFormat.Opus:
                        return AudioType.Opus;
                    default:
                        return AudioType.Unknown;
                }
            }
        }

        public bool CanExtractAudio => false;

        public string DownloadUrl => video.Uri;

        public int FormatCode
        {
            get
            {
                int result = video.FormatCode;
                return result == -1 ? 0 : result;
            }
        }

        public int Fps
        {
            get
            {
                int fps = video.Fps;
                return fps == -1 ? 0 : fps;
            }
        }

        public bool RequiresDecryption => false;

        public int Resolution
        {
            get
            {
                int result = video.Resolution;
                return result == -1 ? 0 : result;
            }
        }

        public string Title => video.Title;

        public string VideoExtension => video.FileExtension;

        public VideoType VideoType
        {
            get
            {
                switch (video.Format)
                {
                    case VideoFormat.Mp4:
                        return VideoType.Mp4;
                    case VideoFormat.WebM:
                        return VideoType.WebM;
                    default:
                        return VideoType.Unknown;
                }
            }
        }

        public override string ToString()
        {
            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");
        }
    }
}


================================================
FILE: src/libvideo.compat/VideoNotAvailableException.cs
================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace YoutubeExtractor
{
    public class VideoNotAvailableException : Exception
    {
        public VideoNotAvailableException()
        { }

        public VideoNotAvailableException(string message)
            : base(message)
        { }
    }
}


================================================
FILE: src/libvideo.compat/VideoType.cs
================================================
namespace YoutubeExtractor
{
    public enum VideoType
    {
        Mp4, WebM, Unknown
    }
}


================================================
FILE: src/libvideo.compat/YoutubeParseException.cs
================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace YoutubeExtractor
{
    public class YoutubeParseException : Exception
    {
        public YoutubeParseException(string message, Exception innerException)
            : base(message, innerException)
        { }
    }
}


================================================
FILE: src/libvideo.compat/libvideo.compat.csproj
================================================
<Project Sdk="Microsoft.NET.Sdk">

	<PropertyGroup>
		<Authors>Bar Arnon,OMANSAK</Authors>
		<Copyright>Copyright 2018 Bar Arnon | 2026 OMANSAK</Copyright>
		<Description>libvideo (aka VideoLibrary) is a modern .NET library for downloading YouTube videos. It is portable to most platforms and is very lightweight.

Find us on GitHub at https://github.com/omansak/libvideo</Description>
		<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
		<PackageIconUrl></PackageIconUrl>
		<PackageId>VideoLibrary.Compat</PackageId>
		<PackageLicenseUrl></PackageLicenseUrl>
		<PackageProjectUrl>https://github.com/omansak/libvideo</PackageProjectUrl>
		<PackageReleaseNotes>Fix 403 Forbidden errors</PackageReleaseNotes>
		<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>
		<RepositoryType>git</RepositoryType>
		<RepositoryUrl>https://github.com/omansak/libvideo</RepositoryUrl>
		<RootNamespace>YoutubeExtractor</RootNamespace>
		<Version>3.3.1</Version>
		<TargetFramework>netstandard2.0</TargetFramework>
		<Title>libvideo.compat</Title>
		<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
		<AssemblyVersion>3.3.1.0</AssemblyVersion>
		<FileVersion>3.3.1.0</FileVersion>
		<PackageIcon>icon_586.png</PackageIcon>
		<NeutralLanguage>en</NeutralLanguage>
		<PackageLicenseExpression></PackageLicenseExpression>
		<PackageLicenseFile>LICENSE</PackageLicenseFile>
		<PackageReadmeFile>README.md</PackageReadmeFile>
	</PropertyGroup>

	<ItemGroup>
		<ProjectReference Include="..\libvideo\libvideo.csproj" />
	</ItemGroup>

	<ItemGroup>
		<Compile Include="..\libvideo\Exceptions\BadQueryException.cs">
			<Link>Exceptions\BadQueryException.cs</Link>
		</Compile>
		<Compile Include="..\libvideo\Helpers\EmptyArray.cs">
			<Link>Helpers\EmptyArray.cs</Link>
		</Compile>
		<Compile Include="..\libvideo\Helpers\KeyCollection.cs">
			<Link>Helpers\KeyCollection.cs</Link>
		</Compile>
		<Compile Include="..\libvideo\Helpers\Query.cs">
			<Link>Helpers\Query.cs</Link>
		</Compile>
		<Compile Include="..\libvideo\Helpers\Require.cs">
			<Link>Helpers\Require.cs</Link>
		</Compile>
		<Compile Include="..\libvideo\Helpers\ValueCollection.cs">
			<Link>Helpers\ValueCollection.cs</Link>
		</Compile>
	</ItemGroup>

	<ItemGroup>
		<None Include="..\..\icons\icon_586.png">
			<Pack>True</Pack>
			<PackagePath></PackagePath>
		</None>
		<None Include="..\..\LICENSE">
			<Pack>True</Pack>
			<PackagePath></PackagePath>
		</None>
		<None Include="..\..\README.md">
			<Pack>True</Pack>
			<PackagePath></PackagePath>
		</None>
	</ItemGroup>

</Project>

================================================
FILE: src/libvideo.debug/CustomYoutubeClient.cs
================================================
using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;

namespace VideoLibrary.Debug
{
    class CustomHandler
    {
        public HttpMessageHandler GetHandler()
        {
            CookieContainer cookieContainer = new CookieContainer();
            cookieContainer.Add(new Cookie("CONSENT", "YES+cb", "/", "youtube.com"));
            return new HttpClientHandler
            {
                UseCookies = true,
                CookieContainer = cookieContainer
            };

        }
    }
    class CustomYouTube : YouTube
    {
        private long chunkSize = 10_485_760;
        private long _fileSize = 0L;
        private HttpClient _client = new HttpClient();
        protected override HttpClient MakeClient(HttpMessageHandler handler)
        {
            return base.MakeClient(handler);
        }
        protected override HttpMessageHandler MakeHandler()
        {
            return new CustomHandler().GetHandler();
        }
        public async Task CreateDownloadAsync(Uri uri, string filePath, IProgress<Tuple<long, long>> progress)
        {
            var totalBytesCopied = 0L;
            _fileSize = await GetContentLengthAsync(uri.AbsoluteUri) ?? 0;
            if (_fileSize == 0)
            {
                throw new Exception("File has no any content !");
            }
            using (Stream output = File.OpenWrite(filePath))
            {
                var segmentCount = (int)Math.Ceiling(1.0 * _fileSize / chunkSize);
                for (var i = 0; i < segmentCount; i++)
                {
                    var from = i * chunkSize;
                    var to = (i + 1) * chunkSize - 1;
                    var request = new HttpRequestMessage(HttpMethod.Get, uri);
                    request.Headers.Range = new RangeHeaderValue(from, to);
                    using (request)
                    {
                        // Download Stream
                        var response = await _client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
                        if (response.IsSuccessStatusCode)
                            response.EnsureSuccessStatusCode();
                        var stream = await response.Content.ReadAsStreamAsync();
                        //File Steam
                        var buffer = new byte[81920];
                        int bytesCopied;
                        do
                        {
                            bytesCopied = await stream.ReadAsync(buffer, 0, buffer.Length);
                            output.Write(buffer, 0, bytesCopied);
                            totalBytesCopied += bytesCopied;
                            progress.Report(new Tuple<long, long>(totalBytesCopied, _fileSize));
                        } while (bytesCopied > 0);
                    }
                }
            }
        }
        private async Task<long?> GetContentLengthAsync(string requestUri, bool ensureSuccess = true)
        {
            using (var request = new HttpRequestMessage(HttpMethod.Head, requestUri))
            {
                var response = await _client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
                if (ensureSuccess)
                    response.EnsureSuccessStatusCode();
                return response.Content.Headers.ContentLength;
            }
        }
    }
    class Test
    {
        public void Run()
        {
            // Custom Youtube
            var youtube = new CustomYouTube();
            var videos = youtube.GetAllVideosAsync("https://www.youtube.com/watch?v=qK_NeRZOdq4").GetAwaiter().GetResult();
            var maxResolution = videos.First(i => i.Resolution == videos.Max(j => j.Resolution));
            youtube
                .CreateDownloadAsync(
                new Uri(maxResolution.Uri),
                Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), maxResolution.FullName),
                new Progress<Tuple<long, long>>((Tuple<long, long> v) =>
                 {
                     var percent = (int)((v.Item1 * 100) / v.Item2);
                     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")));
                 }))
                .GetAwaiter().GetResult();
        }
    }
}


================================================
FILE: src/libvideo.debug/Program.cs
================================================
using System;
using System.Diagnostics;

namespace VideoLibrary.Debug
{
    class Program
    {
        static void Main()
        {
            string[] queries =
            {
                "https://www.youtube.com/watch?v=jfobiCq0YUc&ab_channel=EminemMusic",                   //1080
                "https://www.youtube.com/watch?v=LXb3EKWsInQ&ab_channel=Jacob%2BKatieSchwarz",          //2060
                "https://www.youtube.com/watch?v=U2XK_TJZ3PI",                                          //JSON Parse Error
            };

            TestVideoLib(queries);
            Console.WriteLine("Done.");
            Console.ReadKey();
        }

        public static void TestVideoLib(string[] queries)
        {
            //new Test().Run();

            using (var cli = Client.For(YouTube.Default))
            {
                Console.WriteLine("Downloading...");
                for (int i = 0; i < queries.Length; i++)
                {
                    string uri = queries[i];
                    try
                    {
                        var videoInfos = cli.GetAllVideosAsync(uri).GetAwaiter().GetResult();
                        Console.WriteLine($"Link #{i + 1}");
                        foreach (YouTubeVideo v in videoInfos)
                        {
                            if (v.Resolution > 0 && v.AudioBitrate < 0)
                            {
                                Console.WriteLine(v.Uri);
                                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"));
                                Console.WriteLine("Success : " + v.Head().CanRead);
                                Console.WriteLine();
                            }
                        }
                    }
                    catch (Exception e)
                    {
                        System.Diagnostics.Debug.WriteLine(e);
                        Debugger.Break();
                    }
                }
            }
        }
    }
}

================================================
FILE: src/libvideo.debug/libvideo.debug.csproj
================================================
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <RootNamespace>VideoLibrary.Debug</RootNamespace>
    <TargetFramework>net10.0</TargetFramework>
    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
  </PropertyGroup>

  <ItemGroup>
    <ProjectReference Include="..\libvideo\libvideo.csproj" />
  </ItemGroup>

</Project>

================================================
FILE: src/libvideo.sln
================================================

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.27703.2000
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "libvideo", "libvideo\libvideo.csproj", "{9C71A8F8-39B6-4A53-8960-AEABF72A7771}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "libvideo.compat", "libvideo.compat\libvideo.compat.csproj", "{B50E1120-908F-40E0-9E90-5CF87FBCBB71}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "libvideo.debug", "libvideo.debug\libvideo.debug.csproj", "{8EE6CE4C-B1C1-4CA1-9CBF-7C07C6BF993E}"
EndProject
Global
	GlobalSection(SolutionConfigurationPlatforms) = preSolution
		Debug|Any CPU = Debug|Any CPU
		Release|Any CPU = Release|Any CPU
	EndGlobalSection
	GlobalSection(ProjectConfigurationPlatforms) = postSolution
		{9C71A8F8-39B6-4A53-8960-AEABF72A7771}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{9C71A8F8-39B6-4A53-8960-AEABF72A7771}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{9C71A8F8-39B6-4A53-8960-AEABF72A7771}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{9C71A8F8-39B6-4A53-8960-AEABF72A7771}.Release|Any CPU.Build.0 = Release|Any CPU
		{B50E1120-908F-40E0-9E90-5CF87FBCBB71}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{B50E1120-908F-40E0-9E90-5CF87FBCBB71}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{B50E1120-908F-40E0-9E90-5CF87FBCBB71}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{B50E1120-908F-40E0-9E90-5CF87FBCBB71}.Release|Any CPU.Build.0 = Release|Any CPU
		{8EE6CE4C-B1C1-4CA1-9CBF-7C07C6BF993E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{8EE6CE4C-B1C1-4CA1-9CBF-7C07C6BF993E}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{8EE6CE4C-B1C1-4CA1-9CBF-7C07C6BF993E}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{8EE6CE4C-B1C1-4CA1-9CBF-7C07C6BF993E}.Release|Any CPU.Build.0 = Release|Any CPU
	EndGlobalSection
	GlobalSection(SolutionProperties) = preSolution
		HideSolutionNode = FALSE
	EndGlobalSection
	GlobalSection(ExtensibilityGlobals) = postSolution
		SolutionGuid = {F1D2D10E-73CA-4A16-A045-6F6115731093}
	EndGlobalSection
EndGlobal


================================================
FILE: tests/Compat/Compat/App.config
================================================
<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
    </startup>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>
</configuration>

================================================
FILE: tests/Compat/Compat/Compat.csproj
================================================
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
  <PropertyGroup>
    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
    <ProjectGuid>{4ABF577F-35D9-45C5-95E1-075AD73AE5E6}</ProjectGuid>
    <OutputType>Exe</OutputType>
    <AppDesignerFolder>Properties</AppDesignerFolder>
    <RootNamespace>Compat</RootNamespace>
    <AssemblyName>Compat</AssemblyName>
    <TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
    <FileAlignment>512</FileAlignment>
    <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
    <PlatformTarget>AnyCPU</PlatformTarget>
    <DebugSymbols>true</DebugSymbols>
    <DebugType>full</DebugType>
    <Optimize>false</Optimize>
    <OutputPath>bin\Debug\</OutputPath>
    <DefineConstants>DEBUG;TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
    <PlatformTarget>AnyCPU</PlatformTarget>
    <DebugType>pdbonly</DebugType>
    <Optimize>true</Optimize>
    <OutputPath>bin\Release\</OutputPath>
    <DefineConstants>TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
  </PropertyGroup>
  <ItemGroup>
    <Reference Include="libvideo.compat">
      <HintPath>..\..\..\src\libvideo.compat\bin\$(Configuration)\netstandard1.1\libvideo.compat.dll</HintPath>
    </Reference>
    <Reference Include="System" />
    <Reference Include="System.Core" />
    <Reference Include="System.Xml.Linq" />
    <Reference Include="System.Data.DataSetExtensions" />
    <Reference Include="Microsoft.CSharp" />
    <Reference Include="System.Data" />
    <Reference Include="System.Net.Http" />
    <Reference Include="System.Xml" />
  </ItemGroup>
  <ItemGroup>
    <Compile Include="Program.cs" />
    <Compile Include="Properties\AssemblyInfo.cs" />
  </ItemGroup>
  <ItemGroup>
    <None Include="App.config" />
  </ItemGroup>
  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
  <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
       Other similar extension points exist, see Microsoft.Common.targets.
  <Target Name="BeforeBuild">
  </Target>
  <Target Name="AfterBuild">
  </Target>
  -->
</Project>

================================================
FILE: tests/Compat/Compat/Program.cs
================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using YoutubeExtractor;

namespace Compat
{
    class Program
    {
        static void Main(string[] args)
        {
            /*
            Do nothing here.
            We just want to make sure this code compiles.
            */
        }

        static void CompileThis()
        {
            AdaptiveType a = AdaptiveType.Audio;
            a = AdaptiveType.None;
            a = AdaptiveType.Video;

            AudioExtractionException b = new AudioExtractionException(default(string));

            AudioType c = AudioType.Aac;
            c = AudioType.Mp3;
            c = AudioType.Unknown;
            c = AudioType.Vorbis;

            DownloadUrlResolver.DecryptDownloadUrl(default(VideoInfo));
            IEnumerable<VideoInfo> d = DownloadUrlResolver.GetDownloadUrls(default(string));
            d = DownloadUrlResolver.GetDownloadUrls(default(string), default(bool));
            string e = default(string);
            bool f = DownloadUrlResolver.TryNormalizeYoutubeUrl(e, out e);

            VideoInfo g = default(VideoInfo);
            AdaptiveType h = g.AdaptiveType;
            int i = g.AudioBitrate;
            string j = g.AudioExtension;
            AudioType k = g.AudioType;
            bool l = g.CanExtractAudio;
            string m = g.DownloadUrl;
            int n = g.FormatCode;
            bool o = g.Is3D;
            bool p = g.RequiresDecryption;
            int q = g.Resolution;
            string r = g.Title;
            string s = g.VideoExtension;
            VideoType t = g.VideoType;

            VideoNotAvailableException u = new VideoNotAvailableException();
            u = new VideoNotAvailableException(default(string));

            VideoType v = VideoType.Flash;
            v = VideoType.Mobile;
            v = VideoType.Mp4;
            v = VideoType.Unknown;
            v = VideoType.WebM;

            YoutubeParseException w = new YoutubeParseException(default(string), default(Exception));
        }
    }
}


================================================
FILE: tests/Compat/Compat/Properties/AssemblyInfo.cs
================================================
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

// General Information about an assembly is controlled through the following 
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Compat")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Compat")]
[assembly: AssemblyCopyright("Copyright ©  2015")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

// Setting ComVisible to false makes the types in this assembly not visible 
// to COM components.  If you need to access a type in this assembly from 
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]

// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("4abf577f-35d9-45c5-95e1-075ad73ae5e6")]

// Version information for an assembly consists of the following four values:
//
//      Major Version
//      Minor Version 
//      Build Number
//      Revision
//
// You can specify all the values or you can default the Build and Revision Numbers 
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]


================================================
FILE: tests/Compat/Compat.sln
================================================

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.23107.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Compat", "Compat\Compat.csproj", "{4ABF577F-35D9-45C5-95E1-075AD73AE5E6}"
EndProject
Global
	GlobalSection(SolutionConfigurationPlatforms) = preSolution
		Debug|Any CPU = Debug|Any CPU
		Release|Any CPU = Release|Any CPU
	EndGlobalSection
	GlobalSection(ProjectConfigurationPlatforms) = postSolution
		{4ABF577F-35D9-45C5-95E1-075AD73AE5E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{4ABF577F-35D9-45C5-95E1-075AD73AE5E6}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{4ABF577F-35D9-45C5-95E1-075AD73AE5E6}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{4ABF577F-35D9-45C5-95E1-075AD73AE5E6}.Release|Any CPU.Build.0 = Release|Any CPU
	EndGlobalSection
	GlobalSection(SolutionProperties) = preSolution
		HideSolutionNode = FALSE
	EndGlobalSection
EndGlobal


================================================
FILE: tests/Core/Core/Core.csproj
================================================
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build;Test" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <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')" />
  <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')" />
  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
  <PropertyGroup>
    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
    <ProjectGuid>{561A8EEA-46E2-4294-94D7-EDCE5FD971B7}</ProjectGuid>
    <OutputType>Library</OutputType>
    <AppDesignerFolder>Properties</AppDesignerFolder>
    <RootNamespace>Core</RootNamespace>
    <AssemblyName>Core</AssemblyName>
    <TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
    <FileAlignment>512</FileAlignment>
    <NuGetPackageImportStamp>
    </NuGetPackageImportStamp>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
    <DebugSymbols>true</DebugSymbols>
    <DebugType>full</DebugType>
    <Optimize>false</Optimize>
    <OutputPath>bin\Debug\</OutputPath>
    <DefineConstants>DEBUG;TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
    <DebugType>pdbonly</DebugType>
    <Optimize>true</Optimize>
    <OutputPath>bin\Release\</OutputPath>
    <DefineConstants>TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
  </PropertyGroup>
  <ItemGroup>
    <Reference Include="libvideo">
      <HintPath>..\..\..\src\libvideo\bin\$(Configuration)\netstandard1.1\libvideo.dll</HintPath>
    </Reference>
    <Reference Include="System" />
    <Reference Include="System.Core" />
    <Reference Include="System.Xml.Linq" />
    <Reference Include="System.Data.DataSetExtensions" />
    <Reference Include="Microsoft.CSharp" />
    <Reference Include="System.Data" />
    <Reference Include="System.Net.Http" />
    <Reference Include="System.Xml" />
    <Reference Include="xunit.abstractions, Version=2.0.0.0, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
      <HintPath>..\packages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll</HintPath>
      <Private>True</Private>
    </Reference>
    <Reference Include="xunit.assert, Version=2.0.0.2929, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
      <HintPath>..\packages\xunit.assert.2.0.0\lib\portable-net45+win+wpa81+wp80+monotouch+monoandroid+Xamarin.iOS\xunit.assert.dll</HintPath>
      <Private>True</Private>
    </Reference>
    <Reference Include="xunit.core, Version=2.0.0.2929, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
      <HintPath>..\packages\xunit.extensibility.core.2.0.0\lib\portable-net45+win+wpa81+wp80+monotouch+monoandroid+Xamarin.iOS\xunit.core.dll</HintPath>
      <Private>True</Private>
    </Reference>
  </ItemGroup>
  <ItemGroup>
    <Compile Include="Properties\AssemblyInfo.cs" />
    <Compile Include="UnitTests.cs" />
  </ItemGroup>
  <ItemGroup>
    <None Include="packages.config" />
  </ItemGroup>
  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
  <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
    <PropertyGroup>
      <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>
    </PropertyGroup>
    <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'))" />
    <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'))" />
  </Target>
  <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
       Other similar extension points exist, see Microsoft.Common.targets.
  <Target Name="BeforeBuild">
  </Target>
  <Target Name="AfterBuild">
  </Target>
  -->
  <Target Name="Test">
    <xunit Assemblies="bin\$(Configuration)\Core.dll"/>
  </Target>
</Project>

================================================
FILE: tests/Core/Core/Properties/AssemblyInfo.cs
================================================
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

// General Information about an assembly is controlled through the following 
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Core")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Core")]
[assembly: AssemblyCopyright("Copyright ©  2015")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

// Setting ComVisible to false makes the types in this assembly not visible 
// to COM components.  If you need to access a type in this assembly from 
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]

// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("561a8eea-46e2-4294-94d7-edce5fd971b7")]

// Version information for an assembly consists of the following four values:
//
//      Major Version
//      Minor Version 
//      Build Number
//      Revision
//
// You can specify all the values or you can default the Build and Revision Numbers 
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]


================================================
FILE: tests/Core/Core/UnitTests.cs
================================================
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using VideoLibrary;
using Xunit;

namespace Core
{
    public class UnitTests
    {
        private const string YouTubeInvalidUriOne = "https://wikipedia.com";
        private const string YouTubeInvalidUriTwo = "123ABC!@#";

        private const string YouTubeUri = "https://www.youtube.com/watch?v=JjCaRS-CABk";
        private const string YouTubeDecryptSigUri = "https://www.youtube.com/watch?v=09R8_2nJtjg";
        private const string YouTubeWithDataManifest = "https://www.youtube.com/watch?v=EphGWZKtXvE";
        private const string YouTubeShortsUri = "https://www.youtube.com/shorts/xuCO7-DLCaA";

        // private const string VimeoUri = "https://vimeo.com/131417856";

        [Theory]
        [InlineData(YouTubeUri)]
        [InlineData(YouTubeDecryptSigUri)]
        [InlineData(YouTubeWithDataManifest)]
        [InlineData(YouTubeShortsUri)]
        public void YouTube_GetAllVideos(string uri)
        {
            var videos = YouTube.Default.GetAllVideos(uri);

            Assert.NotNull(videos);
            Assert.DoesNotContain(null, videos);

            foreach (var video in videos)
            {
                Assert.NotNull(video.Uri);
                Assert.Equal(video.WebSite, WebSites.YouTube);
            }
        }

        [Theory]
        [InlineData(YouTubeInvalidUriOne)]
        [InlineData(YouTubeInvalidUriTwo)]
        public void YouTube_ThrowOnInvalidUri(string invalid)
        {
            Assert.Throws<ArgumentException>(() 
                => YouTube.Default.GetVideo(invalid));
        }
    }
}


================================================
FILE: tests/Core/Core/packages.config
================================================
<?xml version="1.0" encoding="utf-8"?>
<packages>
  <package id="xunit" version="2.0.0" targetFramework="net452" />
  <package id="xunit.abstractions" version="2.0.0" targetFramework="net452" />
  <package id="xunit.assert" version="2.0.0" targetFramework="net452" />
  <package id="xunit.core" version="2.0.0" targetFramework="net452" />
  <package id="xunit.extensibility.core" version="2.0.0" targetFramework="net452" />
  <package id="xunit.runner.msbuild" version="2.0.0" targetFramework="net452" />
</packages>

================================================
FILE: tests/Core/Core.sln
================================================

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.23107.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core", "Core\Core.csproj", "{561A8EEA-46E2-4294-94D7-EDCE5FD971B7}"
EndProject
Global
	GlobalSection(SolutionConfigurationPlatforms) = preSolution
		Debug|Any CPU = Debug|Any CPU
		Release|Any CPU = Release|Any CPU
	EndGlobalSection
	GlobalSection(ProjectConfigurationPlatforms) = postSolution
		{561A8EEA-46E2-4294-94D7-EDCE5FD971B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{561A8EEA-46E2-4294-94D7-EDCE5FD971B7}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{561A8EEA-46E2-4294-94D7-EDCE5FD971B7}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{561A8EEA-46E2-4294-94D7-EDCE5FD971B7}.Release|Any CPU.Build.0 = Release|Any CPU
	EndGlobalSection
	GlobalSection(SolutionProperties) = preSolution
		HideSolutionNode = FALSE
	EndGlobalSection
EndGlobal


================================================
FILE: tests/Speed.Test/Speed.Test/App.config
================================================
<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
    </startup>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>
</configuration>

================================================
FILE: tests/Speed.Test/Speed.Test/Program.cs
================================================
using VideoLibrary;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using YoutubeExtractor;

namespace Speed.Test
{
    class Program
    {
        const string Uri = "https://www.youtube.com/watch?v=NLddPakLF1I";

        static void Main(string[] args)
        {
            int times = args.Length == 0 ? 3 : int.Parse(args[0]);
            int iterations = args.Length <= 1 ? 2 : int.Parse(args[1]);

            Console.WriteLine("Starting...");
            Console.WriteLine($"Times: {times}.");
            Console.WriteLine($"Iterations: {iterations}.");
            Console.WriteLine("Getting results for YoutubeExtractor...");

            var elapsed = TimeSpan.Zero;

            for (int i = 0; i < times; i++)
            {
                RunChecked(() =>
                {
                    var watch = Stopwatch.StartNew();

                    for (int j = 0; j < iterations; j++)
                        GC.KeepAlive(DownloadUrlResolver.GetDownloadUrls(Uri));

                    watch.Stop();
                    elapsed += watch.Elapsed;
                });
            }

            Console.WriteLine($"YoutubeExtractor: took {elapsed}.");
            Console.WriteLine("Getting results for libvideo...");

            elapsed = TimeSpan.Zero;

            using (var service = Client.For(YouTube.Default))
            {
                for (int i = 0; i < times; i++)
                {
                    RunChecked(() =>
                    {
                        var watch = Stopwatch.StartNew();

                        for (int j = 0; j < iterations; j++)
                            GC.KeepAlive(service.GetAllVideos(Uri));

                        watch.Stop();
                        elapsed += watch.Elapsed;
                    });
                }
            }

            Console.WriteLine($"libvideo: took {elapsed}.");
        }

        static void RunChecked(Action action)
        {
            try
            {
                action();
            }
            catch (Exception e)
            {
                string[] lines =
                {
                    $"A {e.GetType()} was thrown!",
                    "Message:",
                    string.Empty,
                    e.ToString(),
                    string.Empty,
                    "Restarting..."
                };
                
                Console.WriteLine(string.Join(Environment.NewLine, lines));
                
                RunChecked(action);
            }
        }
    }
}


================================================
FILE: tests/Speed.Test/Speed.Test/Properties/AssemblyInfo.cs
================================================
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

// General Information about an assembly is controlled through the following 
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Speed.Test")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Speed.Test")]
[assembly: AssemblyCopyright("Copyright ©  2015")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

// Setting ComVisible to false makes the types in this assembly not visible 
// to COM components.  If you need to access a type in this assembly from 
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]

// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("448bde69-df8a-4090-b0a0-81bfb9d84e28")]

// Version information for an assembly consists of the following four values:
//
//      Major Version
//      Minor Version 
//      Build Number
//      Revision
//
// You can specify all the values or you can default the Build and Revision Numbers 
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]


================================================
FILE: tests/Speed.Test/Speed.Test/Speed.Test.csproj
================================================
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
  <PropertyGroup>
    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
    <ProjectGuid>{448BDE69-DF8A-4090-B0A0-81BFB9D84E28}</ProjectGuid>
    <OutputType>Exe</OutputType>
    <AppDesignerFolder>Properties</AppDesignerFolder>
    <RootNamespace>Speed.Test</RootNamespace>
    <AssemblyName>Speed.Test</AssemblyName>
    <TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
    <FileAlignment>512</FileAlignment>
    <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
    <PlatformTarget>AnyCPU</PlatformTarget>
    <DebugSymbols>true</DebugSymbols>
    <DebugType>full</DebugType>
    <Optimize>false</Optimize>
    <OutputPath>bin\Debug\</OutputPath>
    <DefineConstants>DEBUG;TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
    <PlatformTarget>AnyCPU</PlatformTarget>
    <DebugType>pdbonly</DebugType>
    <Optimize>true</Optimize>
    <OutputPath>bin\Release\</OutputPath>
    <DefineConstants>TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
  </PropertyGroup>
  <ItemGroup>
    <Reference Include="libvideo">
      <HintPath>..\..\..\src\libvideo\bin\$(Configuration)\netstandard1.1\libvideo.dll</HintPath>
    </Reference>
    <Reference Include="Newtonsoft.Json, Version=6.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
      <HintPath>..\packages\Newtonsoft.Json.6.0.6\lib\net45\Newtonsoft.Json.dll</HintPath>
    </Reference>
    <Reference Include="System" />
    <Reference Include="System.Core" />
    <Reference Include="System.Xml.Linq" />
    <Reference Include="System.Data.DataSetExtensions" />
    <Reference Include="Microsoft.CSharp" />
    <Reference Include="System.Data" />
    <Reference Include="System.Net.Http" />
    <Reference Include="System.Xml" />
    <Reference Include="YoutubeExtractor, Version=0.10.11.0, Culture=neutral, processorArchitecture=MSIL">
      <HintPath>..\packages\YoutubeExtractor.0.10.11\lib\net35\YoutubeExtractor.dll</HintPath>
    </Reference>
  </ItemGroup>
  <ItemGroup>
    <Compile Include="Program.cs" />
    <Compile Include="Properties\AssemblyInfo.cs" />
  </ItemGroup>
  <ItemGroup>
    <None Include="App.config" />
    <None Include="packages.config">
      <SubType>Designer</SubType>
    </None>
  </ItemGroup>
  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
  <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
       Other similar extension points exist, see Microsoft.Common.targets.
  <Target Name="BeforeBuild">
  </Target>
  <Target Name="AfterBuild">
  </Target>
  -->
</Project>

================================================
FILE: tests/Speed.Test/Speed.Test/packages.config
================================================
<?xml version="1.0" encoding="utf-8"?>
<packages>
  <package id="Newtonsoft.Json" version="13.0.1" targetFramework="net452" />
  <package id="YoutubeExtractor" version="0.10.11" targetFramework="net452" userInstalled="true" />
</packages>

================================================
FILE: tests/Speed.Test/Speed.Test.sln
================================================

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.23107.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Speed.Test", "Speed.Test\Speed.Test.csproj", "{448BDE69-DF8A-4090-B0A0-81BFB9D84E28}"
EndProject
Global
	GlobalSection(SolutionConfigurationPlatforms) = preSolution
		Debug|Any CPU = Debug|Any CPU
		Release|Any CPU = Release|Any CPU
	EndGlobalSection
	GlobalSection(ProjectConfigurationPlatforms) = postSolution
		{448BDE69-DF8A-4090-B0A0-81BFB9D84E28}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{448BDE69-DF8A-4090-B0A0-81BFB9D84E28}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{448BDE69-DF8A-4090-B0A0-81BFB9D84E28}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{448BDE69-DF8A-4090-B0A0-81BFB9D84E28}.Release|Any CPU.Build.0 = Release|Any CPU
	EndGlobalSection
	GlobalSection(SolutionProperties) = preSolution
		HideSolutionNode = FALSE
	EndGlobalSection
EndGlobal
Download .txt
gitextract_xcx1mnd_/

├── .gitattributes
├── .gitignore
├── LICENSE
├── README.md
├── changelog.md
├── docs/
│   └── README.md
├── samples/
│   └── Valks/
│       ├── Valks/
│       │   ├── Program.cs
│       │   ├── Properties/
│       │   │   └── AssemblyInfo.cs
│       │   └── Valks.csproj
│       └── Valks.sln
├── src/
│   ├── libvideo/
│   │   ├── AdaptiveKind.cs
│   │   ├── AudioFormat.cs
│   │   ├── Client.cs
│   │   ├── DelegatingClient.cs
│   │   ├── Exceptions/
│   │   │   ├── BadQueryException.cs
│   │   │   └── UnavaibleVideoException.cs
│   │   ├── Helpers/
│   │   │   ├── EmptyArray.cs
│   │   │   ├── Html.cs
│   │   │   ├── Json.cs
│   │   │   ├── KeyCollection.cs
│   │   │   ├── Operations.cs
│   │   │   ├── Query.cs
│   │   │   ├── Require.cs
│   │   │   ├── Text.cs
│   │   │   ├── UnscrambledQuery.cs
│   │   │   └── ValueCollection.cs
│   │   ├── IAsyncService.cs
│   │   ├── IService.cs
│   │   ├── ServiceBase.cs
│   │   ├── Video.cs
│   │   ├── VideoClient.cs
│   │   ├── VideoFormat.cs
│   │   ├── VisitorDataTokenGenerator.cs
│   │   ├── WebSites.cs
│   │   ├── YouTube.cs
│   │   ├── YouTubeVideo.Decrypt.cs
│   │   ├── YouTubeVideo.Format.cs
│   │   ├── YouTubeVideo.cs
│   │   ├── YoutubeVideo.Descramble.cs
│   │   └── libvideo.csproj
│   ├── libvideo.compat/
│   │   ├── AdaptiveType.cs
│   │   ├── AudioExtractionException.cs
│   │   ├── AudioType.cs
│   │   ├── DownloadUrlResolver.cs
│   │   ├── VideoInfo.cs
│   │   ├── VideoNotAvailableException.cs
│   │   ├── VideoType.cs
│   │   ├── YoutubeParseException.cs
│   │   └── libvideo.compat.csproj
│   ├── libvideo.debug/
│   │   ├── CustomYoutubeClient.cs
│   │   ├── Program.cs
│   │   └── libvideo.debug.csproj
│   └── libvideo.sln
└── tests/
    ├── Compat/
    │   ├── Compat/
    │   │   ├── App.config
    │   │   ├── Compat.csproj
    │   │   ├── Program.cs
    │   │   └── Properties/
    │   │       └── AssemblyInfo.cs
    │   └── Compat.sln
    ├── Core/
    │   ├── Core/
    │   │   ├── Core.csproj
    │   │   ├── Properties/
    │   │   │   └── AssemblyInfo.cs
    │   │   ├── UnitTests.cs
    │   │   └── packages.config
    │   └── Core.sln
    └── Speed.Test/
        ├── Speed.Test/
        │   ├── App.config
        │   ├── Program.cs
        │   ├── Properties/
        │   │   └── AssemblyInfo.cs
        │   ├── Speed.Test.csproj
        │   └── packages.config
        └── Speed.Test.sln
Download .txt
SYMBOL INDEX (213 symbols across 43 files)

FILE: samples/Valks/Valks/Program.cs
  class MainClass (line 7) | class MainClass
    method Main (line 9) | public static void Main(string[] args)
    method GetDefaultFolder (line 54) | static string GetDefaultFolder()

FILE: src/libvideo.compat/AdaptiveType.cs
  type AdaptiveType (line 9) | public enum AdaptiveType

FILE: src/libvideo.compat/AudioExtractionException.cs
  class AudioExtractionException (line 9) | public class AudioExtractionException : Exception
    method AudioExtractionException (line 11) | public AudioExtractionException(string message)

FILE: src/libvideo.compat/AudioType.cs
  type AudioType (line 3) | public enum AudioType

FILE: src/libvideo.compat/DownloadUrlResolver.cs
  class DownloadUrlResolver (line 11) | public static class DownloadUrlResolver
    method DecryptDownloadUrl (line 15) | public static void DecryptDownloadUrl(VideoInfo info)
    method GetDownloadUrls (line 21) | public static IEnumerable<VideoInfo> GetDownloadUrls(string videoUrl, ...
    method GetDownloadUrlsAsync (line 31) | public async static Task<IEnumerable<VideoInfo>> GetDownloadUrlsAsync(
    method TryNormalizeYoutubeUrl (line 41) | public static bool TryNormalizeYoutubeUrl(string url, out string norma...

FILE: src/libvideo.compat/VideoInfo.cs
  class VideoInfo (line 6) | public class VideoInfo
    method VideoInfo (line 10) | internal VideoInfo(YouTubeVideo video)
    method ToString (line 127) | public override string ToString()

FILE: src/libvideo.compat/VideoNotAvailableException.cs
  class VideoNotAvailableException (line 9) | public class VideoNotAvailableException : Exception
    method VideoNotAvailableException (line 11) | public VideoNotAvailableException()
    method VideoNotAvailableException (line 14) | public VideoNotAvailableException(string message)

FILE: src/libvideo.compat/VideoType.cs
  type VideoType (line 3) | public enum VideoType

FILE: src/libvideo.compat/YoutubeParseException.cs
  class YoutubeParseException (line 9) | public class YoutubeParseException : Exception
    method YoutubeParseException (line 11) | public YoutubeParseException(string message, Exception innerException)

FILE: src/libvideo.debug/CustomYoutubeClient.cs
  class CustomHandler (line 12) | class CustomHandler
    method GetHandler (line 14) | public HttpMessageHandler GetHandler()
  class CustomYouTube (line 26) | class CustomYouTube : YouTube
    method MakeClient (line 31) | protected override HttpClient MakeClient(HttpMessageHandler handler)
    method MakeHandler (line 35) | protected override HttpMessageHandler MakeHandler()
    method CreateDownloadAsync (line 39) | public async Task CreateDownloadAsync(Uri uri, string filePath, IProgr...
    method GetContentLengthAsync (line 77) | private async Task<long?> GetContentLengthAsync(string requestUri, boo...
  class Test (line 88) | class Test
    method Run (line 90) | public void Run()

FILE: src/libvideo.debug/Program.cs
  class Program (line 6) | class Program
    method Main (line 8) | static void Main()
    method TestVideoLib (line 22) | public static void TestVideoLib(string[] queries)

FILE: src/libvideo/AdaptiveKind.cs
  type AdaptiveKind (line 3) | public enum AdaptiveKind

FILE: src/libvideo/AudioFormat.cs
  type AudioFormat (line 3) | public enum AudioFormat

FILE: src/libvideo/Client.cs
  class Client (line 12) | public static class Client
    method For (line 14) | public static Client<T> For<T>(ServiceBase<T> baseService)
    method SourceFactory (line 25) | private Task<string> SourceFactory(string address) =>
    method Client (line 28) | internal Client(ServiceBase<T> baseService)
    method Dispose (line 43) | public void Dispose()
    method Dispose (line 49) | protected virtual void Dispose(bool disposing)
    method GetVideo (line 64) | public T GetVideo(string videoUri) =>
    method GetAllVideos (line 67) | public IEnumerable<T> GetAllVideos(string videoUri) =>
    method GetVideoAsync (line 70) | public Task<T> GetVideoAsync(string videoUri) =>
    method GetAllVideosAsync (line 73) | public Task<IEnumerable<T>> GetAllVideosAsync(string videoUri) =>
  class Client (line 18) | public class Client<T> : IService<T>, IAsyncService<T>, IDisposable
    method For (line 14) | public static Client<T> For<T>(ServiceBase<T> baseService)
    method SourceFactory (line 25) | private Task<string> SourceFactory(string address) =>
    method Client (line 28) | internal Client(ServiceBase<T> baseService)
    method Dispose (line 43) | public void Dispose()
    method Dispose (line 49) | protected virtual void Dispose(bool disposing)
    method GetVideo (line 64) | public T GetVideo(string videoUri) =>
    method GetAllVideos (line 67) | public IEnumerable<T> GetAllVideos(string videoUri) =>
    method GetVideoAsync (line 70) | public Task<T> GetVideoAsync(string videoUri) =>
    method GetAllVideosAsync (line 73) | public Task<IEnumerable<T>> GetAllVideosAsync(string videoUri) =>

FILE: src/libvideo/DelegatingClient.cs
  class DelegatingClient (line 9) | public class DelegatingClient : IDisposable
    method DelegatingClient (line 14) | public DelegatingClient()
    method Dispose (line 26) | public void Dispose()
    method Dispose (line 32) | protected virtual void Dispose(bool disposing)
    method MakeClient (line 50) | private HttpClient MakeClient() =>
    method MakeHandler (line 53) | protected virtual HttpMessageHandler MakeHandler()
    method MakeClient (line 67) | protected virtual HttpClient MakeClient(HttpMessageHandler handler)
    method Get (line 76) | public HttpResponseMessage Get(string uri) =>
    method GetByteArray (line 79) | public byte[] GetByteArray(string uri) =>
    method GetStream (line 82) | public Stream GetStream(string uri) =>
    method GetString (line 85) | public string GetString(string uri) =>
    method GetAsync (line 95) | public Task<HttpResponseMessage> GetAsync(string uri) =>
    method GetByteArrayAsync (line 98) | public Task<byte[]> GetByteArrayAsync(string uri) =>
    method GetStreamAsync (line 101) | public Task<Stream> GetStreamAsync(string uri) =>
    method GetStringAsync (line 104) | public Task<string> GetStringAsync(string uri) =>

FILE: src/libvideo/Exceptions/BadQueryException.cs
  class BadQueryException (line 5) | internal class BadQueryException : Exception
    method BadQueryException (line 7) | public BadQueryException()
    method BadQueryException (line 11) | public BadQueryException(string message)
    method BadQueryException (line 15) | public BadQueryException(string message, Exception innerException)

FILE: src/libvideo/Exceptions/UnavaibleVideoException.cs
  class UnavailableStreamException (line 5) | public class UnavailableStreamException : Exception
    method UnavailableStreamException (line 7) | public UnavailableStreamException()
    method UnavailableStreamException (line 11) | public UnavailableStreamException(string message)
    method UnavailableStreamException (line 15) | public UnavailableStreamException(string message, Exception innerExcep...

FILE: src/libvideo/Helpers/EmptyArray.cs
  class EmptyArray (line 9) | internal static class EmptyArray<T>

FILE: src/libvideo/Helpers/Html.cs
  class Html (line 10) | internal static class Html
    method GetNode (line 13) | public static string GetNode(string name, string source) =>
    method GetUrisFromManifest (line 18) | public static IEnumerable<string> GetUrisFromManifest(string source)

FILE: src/libvideo/Helpers/Json.cs
  class Json (line 7) | internal static class Json
    method GetKey (line 9) | public static string GetKey(string key, string source)
    method TryGetKey (line 18) | public static bool TryGetKey(string key, string source, out string tar...
    method GetNullableProperty (line 23) | public static JsonElement? GetNullableProperty(this JsonElement jsonEl...
    method Extract (line 33) | public static string Extract(string source)
    method GetKey (line 78) | private static bool GetKey(string key, string source, out string target)

FILE: src/libvideo/Helpers/KeyCollection.cs
  class Query (line 10) | internal partial class Query : IDictionary<string, string>
    class KeyCollection (line 12) | public class KeyCollection : ICollection<string>, IReadOnlyCollection<...
      method KeyCollection (line 16) | public KeyCollection(Query query)
      method Add (line 25) | public void Add(string item)
      method Clear (line 30) | public void Clear()
      method Contains (line 35) | public bool Contains(string item)
      method CopyTo (line 46) | public void CopyTo(string[] array, int arrayIndex)
      method GetEnumerator (line 52) | public IEnumerator<string> GetEnumerator()
      method Remove (line 58) | public bool Remove(string item)
      method GetEnumerator (line 63) | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

FILE: src/libvideo/Helpers/Operations.cs
  type Operations (line 9) | internal struct Operations
    method Operations (line 11) | public Operations(string reverse,

FILE: src/libvideo/Helpers/Query.cs
  class Query (line 8) | internal partial class Query : IDictionary<string, string>, IReadOnlyDic...
    method Query (line 14) | public Query(string uri)
    method Add (line 110) | void ICollection<KeyValuePair<string, string>>.Add(KeyValuePair<string...
    method Add (line 115) | public void Add(string key, string value)
    method Clear (line 121) | public void Clear()
    method Contains (line 130) | bool ICollection<KeyValuePair<string, string>>.Contains(KeyValuePair<s...
    method ContainsKey (line 143) | public bool ContainsKey(string key)
    method CopyTo (line 153) | void ICollection<KeyValuePair<string, string>>.CopyTo(KeyValuePair<str...
    method GetEnumerator (line 158) | public IEnumerator<KeyValuePair<string, string>> GetEnumerator()
    method Remove (line 164) | bool ICollection<KeyValuePair<string, string>>.Remove(KeyValuePair<str...
    method Remove (line 169) | public bool Remove(string key)
    method TryGetValue (line 186) | public bool TryGetValue(string key, out string value)
    method GetEnumerator (line 202) | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
    method ToString (line 204) | public override string ToString()
    method EnsureCapacity (line 228) | private void EnsureCapacity(int capacity)

FILE: src/libvideo/Helpers/Require.cs
  class Require (line 9) | internal static class Require
    method NotNull (line 11) | public static void NotNull<T>(T obj, string name)

FILE: src/libvideo/Helpers/Text.cs
  class Text (line 9) | internal static class Text
    method StringBetween (line 11) | public static string StringBetween(string prefix, string suffix, strin...
    method SkipWhitespace (line 26) | public static int SkipWhitespace(this string text, int start)

FILE: src/libvideo/Helpers/UnscrambledQuery.cs
  type UnscrambledQuery (line 9) | internal readonly struct UnscrambledQuery
    method UnscrambledQuery (line 11) | public UnscrambledQuery(string uri, bool encrypted)

FILE: src/libvideo/Helpers/ValueCollection.cs
  class Query (line 10) | internal partial class Query
    class ValueCollection (line 12) | public class ValueCollection : ICollection<string>, IReadOnlyCollectio...
      method ValueCollection (line 16) | public ValueCollection(Query query)
      method Add (line 25) | public void Add(string item)
      method Clear (line 30) | public void Clear()
      method Contains (line 35) | public bool Contains(string item)
      method CopyTo (line 46) | public void CopyTo(string[] array, int arrayIndex)
      method GetEnumerator (line 52) | public IEnumerator<string> GetEnumerator()
      method Remove (line 58) | public bool Remove(string item)
      method GetEnumerator (line 63) | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

FILE: src/libvideo/IAsyncService.cs
  type IAsyncService (line 9) | internal interface IAsyncService<T> where T : Video
    method GetVideoAsync (line 11) | Task<T> GetVideoAsync(string uri);
    method GetAllVideosAsync (line 12) | Task<IEnumerable<T>> GetAllVideosAsync(string uri);

FILE: src/libvideo/IService.cs
  type IService (line 9) | internal interface IService<T> where T : Video
    method GetVideo (line 11) | T GetVideo(string uri);
    method GetAllVideos (line 12) | IEnumerable<T> GetAllVideos(string uri);

FILE: src/libvideo/ServiceBase.cs
  class ServiceBase (line 11) | public abstract class ServiceBase<T> : IService<T>, IAsyncService<T>
    method VideoSelector (line 14) | internal virtual T VideoSelector(IEnumerable<T> videos) =>
    method GetVideo (line 18) | public T GetVideo(string videoUri) =>
    method GetVideo (line 21) | internal T GetVideo(string videoUri,
    method GetAllVideos (line 25) | public IEnumerable<T> GetAllVideos(string videoUri) =>
    method GetAllVideos (line 28) | internal IEnumerable<T> GetAllVideos(string videoUri,
    method GetVideoAsync (line 33) | public async Task<T> GetVideoAsync(string videoUri)
    method GetVideoAsync (line 43) | internal async Task<T> GetVideoAsync(
    method GetAllVideosAsync (line 48) | public async Task<IEnumerable<T>> GetAllVideosAsync(string videoUri)
    method GetAllVideosAsync (line 58) | internal abstract Task<IEnumerable<T>> GetAllVideosAsync(
    method MakeClient (line 61) | internal HttpClient MakeClient() =>
    method MakeHandler (line 64) | protected virtual HttpMessageHandler MakeHandler()
    method MakeClient (line 80) | protected virtual HttpClient MakeClient(HttpMessageHandler handler)

FILE: src/libvideo/Video.cs
  class VideoInfo (line 8) | public class VideoInfo
    method VideoInfo (line 10) | public VideoInfo(string title, int second, string author)
  class Video (line 20) | public abstract class Video
    method Video (line 22) | internal Video()
    method GetUriAsync (line 32) | public virtual Task<string> GetUriAsync() =>
    method GetBytes (line 35) | public byte[] GetBytes() =>
    method GetBytesAsync (line 38) | public async Task<byte[]> GetBytesAsync()
    method Stream (line 48) | public Stream Stream() => StreamAsync().GetAwaiter().GetResult();
    method StreamAsync (line 50) | public async Task<Stream> StreamAsync()
    method Head (line 59) | public Stream Head() => HeadAsync().GetAwaiter().GetResult();
    method HeadAsync (line 60) | public async Task<Stream> HeadAsync()

FILE: src/libvideo/VideoClient.cs
  class VideoClient (line 11) | public class VideoClient : IDisposable
    method VideoClient (line 16) | public VideoClient()
    method Dispose (line 28) | public void Dispose()
    method Dispose (line 34) | protected virtual void Dispose(bool disposing)
    method MakeClient (line 52) | private HttpClient MakeClient() => MakeClient(MakeHandler());
    method MakeHandler (line 54) | protected virtual HttpMessageHandler MakeHandler() => new HttpClientHa...
    method MakeClient (line 56) | protected virtual HttpClient MakeClient(HttpMessageHandler handler)
    method GetBytes (line 66) | public byte[] GetBytes(Video video) => GetBytesAsync(video).GetAwaiter...
    method GetBytesAsync (line 68) | public async Task<byte[]> GetBytesAsync(Video video)
    method Stream (line 79) | public Stream Stream(Video video) => StreamAsync(video).GetAwaiter().G...
    method StreamAsync (line 81) | public async Task<Stream> StreamAsync(Video video)
    method GetContentLengthAsync (line 92) | public async Task<long?> GetContentLengthAsync(string requestUri)
    method HeadAsync (line 99) | public async Task<HttpResponseMessage> HeadAsync(string requestUri)

FILE: src/libvideo/VideoFormat.cs
  type VideoFormat (line 3) | public enum VideoFormat

FILE: src/libvideo/VisitorDataTokenGenerator.cs
  class VisitorDataTokenGenerator (line 10) | internal class VisitorDataTokenGenerator : IDisposable
    method GetVisitorDataFromYouTube (line 16) | public static async Task<string> GetVisitorDataFromYouTube(HttpClient ...
    method Dispose (line 63) | public void Dispose()

FILE: src/libvideo/WebSites.cs
  type WebSites (line 9) | public enum WebSites

FILE: src/libvideo/YouTube.cs
  class YouTube (line 18) | public class YouTube : ServiceBase<YouTubeVideo>
    method GetAllVideosAsync (line 25) | internal override async Task<IEnumerable<YouTubeVideo>> GetAllVideosAs...
    method GetSignatureKey (line 54) | public static string GetSignatureKey()
    method TryNormalize (line 59) | private bool TryNormalize(string videoUri, out string normalized)
    method ParseVideos (line 86) | private IEnumerable<YouTubeVideo> ParseVideos(string source, string js...
    method ParsePlayerJson (line 205) | private string ParsePlayerJson(string source)
    method ParseJsPlayer (line 228) | private string ParseJsPlayer(string source)
    method Unscramble (line 262) | private UnscrambledQuery Unscramble(string queryString)
    method GetSignatureAndHost (line 292) | private string GetSignatureAndHost(string key, string signature, Query...
    method UnscrambleManifestUri (line 304) | private UnscrambledQuery UnscrambleManifestUri(string manifestUri)
    method GetPlayerResponseIOSAsync (line 327) | private async Task<string> GetPlayerResponseIOSAsync(string id)

FILE: src/libvideo/YouTubeVideo.Decrypt.cs
  class YouTubeVideo (line 11) | public partial class YouTubeVideo
    method DecryptAsync (line 13) | private async Task<string> DecryptAsync(string uri, Func<DelegatingCli...
    method DecryptSignature (line 35) | private string DecryptSignature(string js, string signature)
    method GetDecryptionFunctionLines (line 77) | private string[] GetDecryptionFunctionLines(string js)
    class Decryptor (line 83) | private class Decryptor
      method AddFunction (line 93) | public void AddFunction(string js, string function)
      method ExecuteFunction (line 116) | public string ExecuteFunction(string signature, string line, string ...
      method Reverse (line 143) | private string Reverse(string signature)
      method Slice (line 154) | private string Slice(string signature, int index) => signature.Subst...
      method Swap (line 156) | private string Swap(string signature, int index)
      type FunctionType (line 165) | private enum FunctionType

FILE: src/libvideo/YouTubeVideo.Format.cs
  class YouTubeVideo (line 4) | public partial class YouTubeVideo

FILE: src/libvideo/YouTubeVideo.cs
  class YouTubeVideo (line 7) | public partial class YouTubeVideo : Video
    method YouTubeVideo (line 15) | internal YouTubeVideo(VideoInfo info, UnscrambledQuery query, string j...
    method GetUri (line 35) | public string GetUri(Func<DelegatingClient> makeClient) => GetUriAsync...
    method GetUriAsync (line 37) | public override Task<string> GetUriAsync() => GetUriAsync(() => new De...
    method GetUriAsync (line 39) | public async Task<string> GetUriAsync(Func<DelegatingClient> makeClient)
    method GetContentLength (line 73) | private async Task<long?> GetContentLength(Query query)

FILE: src/libvideo/YoutubeVideo.Descramble.cs
  class YouTubeVideo (line 8) | public partial class YouTubeVideo
    method NDescrambleAsync (line 10) | private async Task<string> NDescrambleAsync(string uri, Func<Delegatin...
    method DescrambleNSignature (line 31) | private string DescrambleNSignature(string js, string signature)
    method GetDescrambleFunctionBody (line 45) | private Tuple<string, string> GetDescrambleFunctionBody(string js)

FILE: tests/Compat/Compat/Program.cs
  class Program (line 10) | class Program
    method Main (line 12) | static void Main(string[] args)
    method CompileThis (line 20) | static void CompileThis()

FILE: tests/Core/Core/UnitTests.cs
  class UnitTests (line 12) | public class UnitTests
    method YouTube_GetAllVideos (line 24) | [Theory]
    method YouTube_ThrowOnInvalidUri (line 43) | [Theory]

FILE: tests/Speed.Test/Speed.Test/Program.cs
  class Program (line 12) | class Program
    method Main (line 16) | static void Main(string[] args)
    method RunChecked (line 67) | static void RunChecked(Action action)
Condensed preview — 69 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (151K chars).
[
  {
    "path": ".gitattributes",
    "chars": 2518,
    "preview": "###############################################################################\n# Set default behavior to automatically "
  },
  {
    "path": ".gitignore",
    "chars": 266,
    "preview": "# 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/bu"
  },
  {
    "path": "LICENSE",
    "chars": 1312,
    "preview": "BSD 2-Clause License\n\nCopyright (c) 2021, OMANSAK\nAll rights reserved.\n\nRedistribution and use in source and binary form"
  },
  {
    "path": "README.md",
    "chars": 3178,
    "preview": "# libvideo\n\n![icon](icons/icon_200.png)\n\n[![NuGet](https://img.shields.io/nuget/dt/VideoLibrary.svg)](https://www.nuget."
  },
  {
    "path": "changelog.md",
    "chars": 116,
    "preview": "# 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",
    "chars": 5657,
    "preview": "# Documentation\n\nHere you'll find a more in-depth explanation of our API.\n\nTo get information about a video:\n\n```csharp\n"
  },
  {
    "path": "samples/Valks/Valks/Program.cs",
    "chars": 1785,
    "preview": "using VideoLibrary;\nusing System;\nusing System.IO;\n\nnamespace Valks\n{\n    class MainClass\n    {\n        public static vo"
  },
  {
    "path": "samples/Valks/Valks/Properties/AssemblyInfo.cs",
    "chars": 973,
    "preview": "using System.Reflection;\nusing System.Runtime.CompilerServices;\n\n// Information about this assembly is defined by the fo"
  },
  {
    "path": "samples/Valks/Valks/Valks.csproj",
    "chars": 1966,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project DefaultTargets=\"Build\" ToolsVersion=\"4.0\" xmlns=\"http://schemas.microso"
  },
  {
    "path": "samples/Valks/Valks.sln",
    "chars": 846,
    "preview": "\nMicrosoft Visual Studio Solution File, Format Version 11.00\n# Visual Studio 2010\nProject(\"{FAE04EC0-301F-11D3-BF4B-00C"
  },
  {
    "path": "src/libvideo/AdaptiveKind.cs",
    "chars": 112,
    "preview": "namespace VideoLibrary\n{\n    public enum AdaptiveKind\n    {\n        None,\n        Audio,\n        Video\n    }\n}\n"
  },
  {
    "path": "src/libvideo/AudioFormat.cs",
    "chars": 142,
    "preview": "namespace VideoLibrary\n{\n    public enum AudioFormat\n    {\n        Aac = 0,\n        Vorbis = 1,\n        Opus = 2,\n     "
  },
  {
    "path": "src/libvideo/Client.cs",
    "chars": 1986,
    "preview": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Net;\nusing System.Net.Http;\nusing Syste"
  },
  {
    "path": "src/libvideo/DelegatingClient.cs",
    "chars": 2623,
    "preview": "using System;\nusing System.IO;\nusing System.Net;\nusing System.Net.Http;\nusing System.Threading.Tasks;\n\nnamespace VideoL"
  },
  {
    "path": "src/libvideo/Exceptions/BadQueryException.cs",
    "chars": 405,
    "preview": "using System;\n\nnamespace VideoLibrary.Exceptions\n{\n    internal class BadQueryException : Exception\n    {\n        publi"
  },
  {
    "path": "src/libvideo/Exceptions/UnavaibleVideoException.cs",
    "chars": 439,
    "preview": "using System;\n\nnamespace VideoLibrary.Exceptions\n{\n    public class UnavailableStreamException : Exception\n    {\n      "
  },
  {
    "path": "src/libvideo/Helpers/EmptyArray.cs",
    "chars": 258,
    "preview": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\n\nna"
  },
  {
    "path": "src/libvideo/Helpers/Html.cs",
    "chars": 1040,
    "preview": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Net;\nusing System.Text;\nusing System.Th"
  },
  {
    "path": "src/libvideo/Helpers/Json.cs",
    "chars": 3444,
    "preview": "using System;\nusing System.Text;\nusing System.Text.Json;\n\nnamespace VideoLibrary.Helpers\n{\n    internal static class Jso"
  },
  {
    "path": "src/libvideo/Helpers/KeyCollection.cs",
    "chars": 1754,
    "preview": "using System;\nusing System.Collections;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing S"
  },
  {
    "path": "src/libvideo/Helpers/Operations.cs",
    "chars": 513,
    "preview": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\n\nna"
  },
  {
    "path": "src/libvideo/Helpers/Query.cs",
    "chars": 6678,
    "preview": "using System;\nusing System.Collections;\nusing System.Collections.Generic;\nusing System.Text;\n\nnamespace VideoLibrary.He"
  },
  {
    "path": "src/libvideo/Helpers/Require.cs",
    "chars": 389,
    "preview": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\n\nna"
  },
  {
    "path": "src/libvideo/Helpers/Text.cs",
    "chars": 850,
    "preview": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\n\nna"
  },
  {
    "path": "src/libvideo/Helpers/UnscrambledQuery.cs",
    "chars": 438,
    "preview": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\n\nna"
  },
  {
    "path": "src/libvideo/Helpers/ValueCollection.cs",
    "chars": 1734,
    "preview": "using System;\nusing System.Collections;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing S"
  },
  {
    "path": "src/libvideo/IAsyncService.cs",
    "chars": 316,
    "preview": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\n\nna"
  },
  {
    "path": "src/libvideo/IService.cs",
    "chars": 289,
    "preview": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\n\nna"
  },
  {
    "path": "src/libvideo/ServiceBase.cs",
    "chars": 3192,
    "preview": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Net;\nusing System.Net.Http;\nusing Syste"
  },
  {
    "path": "src/libvideo/Video.cs",
    "chars": 2905,
    "preview": "using System;\nusing System.IO;\nusing System.Text;\nusing System.Threading.Tasks;\n\nnamespace VideoLibrary\n{\n    public cl"
  },
  {
    "path": "src/libvideo/VideoClient.cs",
    "chars": 2748,
    "preview": "using System;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.Linq;\nusing System.Net.Http;\nusing System"
  },
  {
    "path": "src/libvideo/VideoFormat.cs",
    "chars": 111,
    "preview": "namespace VideoLibrary\n{\n    public enum VideoFormat\n    {\n        Mp4,\n        WebM,\n        Unknown\n    }\n}\n"
  },
  {
    "path": "src/libvideo/VisitorDataTokenGenerator.cs",
    "chars": 2325,
    "preview": "using System;\nusing System.Linq;\nusing System.Net.Http;\nusing System.Net.Http.Headers;\nusing System.Text.Json;\nusing Sy"
  },
  {
    "path": "src/libvideo/WebSites.cs",
    "chars": 202,
    "preview": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\n\nna"
  },
  {
    "path": "src/libvideo/YouTube.cs",
    "chars": 15470,
    "preview": "using System;\r\nusing System.Collections.Generic;\r\nusing System.Linq;\r\nusing System.Net;\r\nusing System.Net.Http;\r\nusing S"
  },
  {
    "path": "src/libvideo/YouTubeVideo.Decrypt.cs",
    "chars": 6682,
    "preview": "using System;\nusing System.Collections.Generic;\nusing System.Globalization;\nusing System.Text;\nusing System.Text.Regular"
  },
  {
    "path": "src/libvideo/YouTubeVideo.Format.cs",
    "chars": 11406,
    "preview": "namespace VideoLibrary\n{\n    // TODO Add/Fix itags\n    public partial class YouTubeVideo\n    {\n        public int Fps\n "
  },
  {
    "path": "src/libvideo/YouTubeVideo.cs",
    "chars": 2615,
    "preview": "using System;\nusing System.Threading.Tasks;\nusing VideoLibrary.Helpers;\n\nnamespace VideoLibrary\n{\n    public partial cla"
  },
  {
    "path": "src/libvideo/YoutubeVideo.Descramble.cs",
    "chars": 2853,
    "preview": "using System;\nusing System.Text.RegularExpressions;\nusing System.Threading.Tasks;\nusing VideoLibrary.Helpers;\n\nnamespac"
  },
  {
    "path": "src/libvideo/libvideo.csproj",
    "chars": 2174,
    "preview": "<Project Sdk=\"Microsoft.NET.Sdk\">\n\n\t<PropertyGroup>\n\t\t<Authors>Bar Arnon,OMANSAK</Authors>\n\t\t<Copyright>Copyright 2018 "
  },
  {
    "path": "src/libvideo.compat/AdaptiveType.cs",
    "chars": 216,
    "preview": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\n\nnam"
  },
  {
    "path": "src/libvideo.compat/AudioExtractionException.cs",
    "chars": 310,
    "preview": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\n\nnam"
  },
  {
    "path": "src/libvideo.compat/AudioType.cs",
    "chars": 105,
    "preview": "namespace YoutubeExtractor\n{\n    public enum AudioType\n    {\n        Aac, Vorbis, Opus, Unknown\n    }\n}\n"
  },
  {
    "path": "src/libvideo.compat/DownloadUrlResolver.cs",
    "chars": 2139,
    "preview": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\nusin"
  },
  {
    "path": "src/libvideo.compat/VideoInfo.cs",
    "chars": 3548,
    "preview": "using System;\nusing VideoLibrary;\n\nnamespace YoutubeExtractor\n{\n    public class VideoInfo\n    {\n        private readonl"
  },
  {
    "path": "src/libvideo.compat/VideoNotAvailableException.cs",
    "chars": 371,
    "preview": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\n\nnam"
  },
  {
    "path": "src/libvideo.compat/VideoType.cs",
    "chars": 96,
    "preview": "namespace YoutubeExtractor\n{\n    public enum VideoType\n    {\n        Mp4, WebM, Unknown\n    }\n}\n"
  },
  {
    "path": "src/libvideo.compat/YoutubeParseException.cs",
    "chars": 346,
    "preview": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\n\nnam"
  },
  {
    "path": "src/libvideo.compat/libvideo.compat.csproj",
    "chars": 2954,
    "preview": "<Project Sdk=\"Microsoft.NET.Sdk\">\n\n\t<PropertyGroup>\n\t\t<Authors>Bar Arnon,OMANSAK</Authors>\n\t\t<Copyright>Copyright 2018 "
  },
  {
    "path": "src/libvideo.debug/CustomYoutubeClient.cs",
    "chars": 4486,
    "preview": "using System;\nusing System.IO;\nusing System.Linq;\nusing System.Net;\nusing System.Net.Http;\nusing System.Net.Http.Header"
  },
  {
    "path": "src/libvideo.debug/Program.cs",
    "chars": 2136,
    "preview": "using System;\nusing System.Diagnostics;\n\nnamespace VideoLibrary.Debug\n{\n    class Program\n    {\n        static void Mai"
  },
  {
    "path": "src/libvideo.debug/libvideo.debug.csproj",
    "chars": 367,
    "preview": "<Project Sdk=\"Microsoft.NET.Sdk\">\n\n  <PropertyGroup>\n    <OutputType>Exe</OutputType>\n    <RootNamespace>VideoLibrary.D"
  },
  {
    "path": "src/libvideo.sln",
    "chars": 2079,
    "preview": "\nMicrosoft Visual Studio Solution File, Format Version 12.00\n# Visual Studio 15\nVisualStudioVersion = 15.0.27703.2000\nM"
  },
  {
    "path": "tests/Compat/Compat/App.config",
    "chars": 527,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<configuration>\n    <startup> \n        <supportedRuntime version=\"v4.0\" sku=\".NE"
  },
  {
    "path": "tests/Compat/Compat/Compat.csproj",
    "chars": 2782,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project ToolsVersion=\"14.0\" DefaultTargets=\"Build\" xmlns=\"http://schemas.micros"
  },
  {
    "path": "tests/Compat/Compat/Program.cs",
    "chars": 2102,
    "preview": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\nusi"
  },
  {
    "path": "tests/Compat/Compat/Properties/AssemblyInfo.cs",
    "chars": 1385,
    "preview": "using System.Reflection;\nusing System.Runtime.CompilerServices;\nusing System.Runtime.InteropServices;\n\n// General Infor"
  },
  {
    "path": "tests/Compat/Compat.sln",
    "chars": 961,
    "preview": "\nMicrosoft Visual Studio Solution File, Format Version 12.00\n# Visual Studio 14\nVisualStudioVersion = 14.0.23107.0\nMini"
  },
  {
    "path": "tests/Core/Core/Core.csproj",
    "chars": 5373,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project ToolsVersion=\"14.0\" DefaultTargets=\"Build;Test\" xmlns=\"http://schemas.m"
  },
  {
    "path": "tests/Core/Core/Properties/AssemblyInfo.cs",
    "chars": 1381,
    "preview": "using System.Reflection;\nusing System.Runtime.CompilerServices;\nusing System.Runtime.InteropServices;\n\n// General Infor"
  },
  {
    "path": "tests/Core/Core/UnitTests.cs",
    "chars": 1702,
    "preview": "using System;\nusing System.Collections.Generic;\nusing System.Diagnostics;\nusing System.Linq;\nusing System.Text;\nusing S"
  },
  {
    "path": "tests/Core/Core/packages.config",
    "chars": 517,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<packages>\n  <package id=\"xunit\" version=\"2.0.0\" targetFramework=\"net452\" />\n  <"
  },
  {
    "path": "tests/Core/Core.sln",
    "chars": 955,
    "preview": "\nMicrosoft Visual Studio Solution File, Format Version 12.00\n# Visual Studio 14\nVisualStudioVersion = 14.0.23107.0\nMini"
  },
  {
    "path": "tests/Speed.Test/Speed.Test/App.config",
    "chars": 527,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<configuration>\n    <startup> \n        <supportedRuntime version=\"v4.0\" sku=\".NE"
  },
  {
    "path": "tests/Speed.Test/Speed.Test/Program.cs",
    "chars": 2616,
    "preview": "using VideoLibrary;\nusing System;\nusing System.Collections.Generic;\nusing System.Diagnostics;\nusing System.Linq;\nusing "
  },
  {
    "path": "tests/Speed.Test/Speed.Test/Properties/AssemblyInfo.cs",
    "chars": 1393,
    "preview": "using System.Reflection;\nusing System.Runtime.CompilerServices;\nusing System.Runtime.InteropServices;\n\n// General Infor"
  },
  {
    "path": "tests/Speed.Test/Speed.Test/Speed.Test.csproj",
    "chars": 3316,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project ToolsVersion=\"14.0\" DefaultTargets=\"Build\" xmlns=\"http://schemas.micros"
  },
  {
    "path": "tests/Speed.Test/Speed.Test/packages.config",
    "chars": 239,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<packages>\n  <package id=\"Newtonsoft.Json\" version=\"13.0.1\" targetFramework=\"net"
  },
  {
    "path": "tests/Speed.Test/Speed.Test.sln",
    "chars": 973,
    "preview": "\nMicrosoft Visual Studio Solution File, Format Version 12.00\n# Visual Studio 14\nVisualStudioVersion = 14.0.23107.0\nMini"
  }
]

About this extraction

This page contains the full source code of the i3arnon/libvideo GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 69 files (137.3 KB), approximately 32.0k tokens, and a symbol index with 213 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!