├── etc
└── bitcoin.txt
├── img
├── YouTube.png
├── telegram.png
├── YouTubeApiSharp.ico
└── bitcoin-donate-black.png
├── src
├── ExampleApplication
│ ├── YouTubeApiSharp.ico
│ ├── ExampleApplication.csproj
│ └── Program.cs
└── YouTubeApiSharp
│ ├── YouTubeApiSharp
│ ├── YouTubeApiSharp.ico
│ ├── Type
│ │ ├── AdaptiveType.cs
│ │ ├── AudioType.cs
│ │ └── VideoType.cs
│ ├── Utilities
│ │ └── Utilities.cs
│ ├── Exception
│ │ ├── VideoNotAvailableException.cs
│ │ └── ParseException.cs
│ ├── API
│ │ ├── ChannelItemsSearch.cs
│ │ ├── Continuation.cs
│ │ ├── PlaylistItemsSearchComponents.cs
│ │ ├── PlaylistSearchComponents.cs
│ │ ├── VideoSearchComponents.cs
│ │ ├── ChannelSearchComponents.cs
│ │ ├── PlaylistItemsSearch.cs
│ │ ├── Decipherer.cs
│ │ ├── PlaylistSearch.cs
│ │ ├── ChannelSearch.cs
│ │ ├── VideoSearch.cs
│ │ ├── DownloadUrlResolver.cs
│ │ └── Model.cs
│ ├── YouTubeApiSharp.csproj
│ ├── Helper
│ │ └── Helper.cs
│ ├── Web
│ │ └── Web.cs
│ ├── Log
│ │ └── Log.cs
│ ├── Http
│ │ └── HttpHelper.cs
│ ├── Download
│ │ └── VideoDownloader.cs
│ └── Constants
│ │ └── VideoInfo.cs
│ └── YouTubeApiSharp.sln
├── LICENSE
├── README.md
└── .gitignore
/etc/bitcoin.txt:
--------------------------------------------------------------------------------
1 | bitcoin:bc1qe856dksmn0fp3tynxyw9p79mksp6aykjm894ny
--------------------------------------------------------------------------------
/img/YouTube.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snakx/YouTubeApiSharp/HEAD/img/YouTube.png
--------------------------------------------------------------------------------
/img/telegram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snakx/YouTubeApiSharp/HEAD/img/telegram.png
--------------------------------------------------------------------------------
/img/YouTubeApiSharp.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snakx/YouTubeApiSharp/HEAD/img/YouTubeApiSharp.ico
--------------------------------------------------------------------------------
/img/bitcoin-donate-black.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snakx/YouTubeApiSharp/HEAD/img/bitcoin-donate-black.png
--------------------------------------------------------------------------------
/src/ExampleApplication/YouTubeApiSharp.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snakx/YouTubeApiSharp/HEAD/src/ExampleApplication/YouTubeApiSharp.ico
--------------------------------------------------------------------------------
/src/YouTubeApiSharp/YouTubeApiSharp/YouTubeApiSharp.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snakx/YouTubeApiSharp/HEAD/src/YouTubeApiSharp/YouTubeApiSharp/YouTubeApiSharp.ico
--------------------------------------------------------------------------------
/src/YouTubeApiSharp/YouTubeApiSharp/Type/AdaptiveType.cs:
--------------------------------------------------------------------------------
1 | namespace YouTubeApiSharp
2 | {
3 | public enum AdaptiveType
4 | {
5 | None,
6 | Audio,
7 | Video
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/YouTubeApiSharp/YouTubeApiSharp/Type/AudioType.cs:
--------------------------------------------------------------------------------
1 | namespace YouTubeApiSharp
2 | {
3 | public enum AudioType
4 | {
5 | Aac,
6 | Mp3,
7 | Vorbis,
8 | Opus,
9 |
10 | ///
11 | /// The audio type is unknown. This can occur if YoutubeExtractor is not up-to-date.
12 | ///
13 | Unknown
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/ExampleApplication/ExampleApplication.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | netcoreapp3.1
6 | YouTubeApiSharp.ico
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/YouTubeApiSharp/YouTubeApiSharp/Utilities/Utilities.cs:
--------------------------------------------------------------------------------
1 | using System.Net;
2 |
3 | namespace YouTubeApiSharp
4 | {
5 | static class Utilities
6 | {
7 | public static string HtmlDecode(string value)
8 | {
9 | try
10 | {
11 | return WebUtility.HtmlDecode(value);
12 | }
13 | catch { return value; }
14 | }
15 |
16 | public static bool IsValid(this string val)
17 | {
18 | return !string.IsNullOrEmpty(val) && !string.IsNullOrWhiteSpace(val);
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/YouTubeApiSharp/YouTubeApiSharp/Type/VideoType.cs:
--------------------------------------------------------------------------------
1 | namespace YouTubeApiSharp
2 | {
3 | ///
4 | /// The video type. Also known as video container.
5 | ///
6 | public enum VideoType
7 | {
8 | ///
9 | /// Video for mobile devices (3GP).
10 | ///
11 | Mobile,
12 |
13 | Flash,
14 | Mp4,
15 | WebM,
16 |
17 | ///
18 | /// The video type is unknown. This can occur if YoutubeExtractor is not up-to-date.
19 | ///
20 | Unknown
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/YouTubeApiSharp/YouTubeApiSharp/Exception/VideoNotAvailableException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace YouTubeApiSharp
4 | {
5 | ///
6 | /// The exception that is thrown when the video is not available for viewing.
7 | /// This can happen when the uploader restricts the video to specific countries.
8 | ///
9 | public class VideoNotAvailableException : Exception
10 | {
11 | public VideoNotAvailableException()
12 | { }
13 |
14 | public VideoNotAvailableException(string message)
15 | : base(message)
16 | { }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/YouTubeApiSharp/YouTubeApiSharp/Exception/ParseException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace YouTubeApiSharp
4 | {
5 | ///
6 | ///
7 | /// The exception that is thrown when the YouTube page could not be parsed.
8 | /// This happens, when YouTube changes the structure of their page.
9 | ///
10 | /// Please report when this exception happens at www.github.com/flagbug/YoutubeExtractor/issues
11 | ///
12 | public class ParseException : Exception
13 | {
14 | public ParseException(string message, Exception innerException)
15 | : base(message, innerException)
16 | { }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/YouTubeApiSharp/YouTubeApiSharp/API/ChannelItemsSearch.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text.RegularExpressions;
4 | using System.Threading.Tasks;
5 |
6 | namespace YouTubeApiSharp
7 | {
8 | public class ChannelItemsSearch
9 | {
10 | public async Task> GetChannelItems(string Channelurl, int ChannelItems)
11 | {
12 | // Do search
13 | // Search address
14 | string content = await Web.getContentFromUrlWithProperty(Channelurl);
15 |
16 | string chUrl = string.Empty;
17 |
18 | // Search string
19 | string pattern = "playAllButton.*?\"commandMetadata\":\\{\"webCommandMetadata\":\\{\"url\":\"(?.*?)\"";
20 | MatchCollection result = Regex.Matches(content, pattern, RegexOptions.Singleline);
21 |
22 | if (result.Count > 0)
23 | chUrl = "http://youtube.com" + result[0].Groups[1].Value.Replace(@"\u0026", "&");
24 | else
25 | chUrl = string.Empty;
26 |
27 | return await new PlaylistItemsSearch().GetPlaylistItems(chUrl, ChannelItems);
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Torsten Klinger
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/YouTubeApiSharp/YouTubeApiSharp/YouTubeApiSharp.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 | 0.0.5
6 | Torsten Klinger
7 | A complete Private YouTube API for .NET (C#, VB.NET).
8 | https://github.com/snakx/YouTubeApiSharp
9 | https://github.com/snakx/YouTubeApiSharp
10 | Copyright © Torsten Klinger
11 | youtube, youtubeapisharp, youtubesearch, youtubeextractor, youtube-api, video, audio, download, extract, standard, net, core, xamarin
12 | YouTube.png
13 | true
14 | MIT
15 | YouTubeApiSharp.ico
16 | 0.0.1.1
17 | 0.0.1.1
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | True
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/src/YouTubeApiSharp/YouTubeApiSharp/API/Continuation.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Net;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace YouTubeApiSharp
8 | {
9 | class Continuation
10 | {
11 | static WebClient webclient;
12 | public static async Task Scrape(string initContToken)
13 | {
14 | var continuationToken = initContToken;
15 | var continuationPost = @"{
16 | 'context': {
17 | 'client': {
18 | 'clientName': '1',
19 | 'clientVersion': '2.20200701.03.01'
20 | }
21 | },
22 | 'continuation': '" + continuationToken + @"'
23 | }";
24 |
25 | try
26 | {
27 | webclient = new WebClient();
28 | webclient.Encoding = Encoding.Default;
29 |
30 | Task uploadStringTask = webclient.UploadStringTaskAsync(new Uri("https://www.youtube.com/youtubei/v1/search?key=AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8"), continuationPost);
31 | var content = await uploadStringTask;
32 |
33 | return content.Replace('\r', ' ').Replace('\n', ' ');
34 | }
35 | catch
36 | {
37 | return string.Empty;
38 | }
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/YouTubeApiSharp/YouTubeApiSharp/Helper/Helper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 |
5 | namespace YouTubeApiSharp
6 | {
7 | public class Helper
8 | {
9 | public static string Folder { get; set; }
10 |
11 | public static String ExtractValue(String Source, String Start, String End)
12 | {
13 | int start, end;
14 |
15 | try
16 | {
17 | if (Source.Contains(Start) && Source.Contains(End))
18 | {
19 | start = Source.IndexOf(Start, 0) + Start.Length;
20 | end = Source.IndexOf(End, start);
21 |
22 | return Source.Substring(start, end - start);
23 | }
24 | else
25 | return printZero();
26 | }
27 | catch (Exception ex)
28 | {
29 | if (Log.getMode())
30 | Log.println(Folder, ex.ToString());
31 |
32 | return printZero();
33 | }
34 | }
35 |
36 | public static String printZero()
37 | {
38 | return " ";
39 | }
40 |
41 | public static string makeFilenameValid(string file)
42 | {
43 | char replacementChar = '_';
44 |
45 | var blacklist = new HashSet(Path.GetInvalidFileNameChars());
46 |
47 | var output = file.ToCharArray();
48 |
49 | for (int i = 0, ln = output.Length; i < ln; i++)
50 | {
51 | if (blacklist.Contains(output[i]))
52 | output[i] = replacementChar;
53 | }
54 |
55 | return new string(output);
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/YouTubeApiSharp/YouTubeApiSharp.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.31105.61
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "YouTubeApiSharp", "YouTubeApiSharp\YouTubeApiSharp.csproj", "{C6362D0E-C06C-43BF-BB58-2C34018F306F}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExampleApplication", "..\ExampleApplication\ExampleApplication.csproj", "{03B7159A-4B81-489C-ADF3-58567B189FEF}"
9 | EndProject
10 | Global
11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
12 | Debug|Any CPU = Debug|Any CPU
13 | Release|Any CPU = Release|Any CPU
14 | EndGlobalSection
15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
16 | {C6362D0E-C06C-43BF-BB58-2C34018F306F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
17 | {C6362D0E-C06C-43BF-BB58-2C34018F306F}.Debug|Any CPU.Build.0 = Debug|Any CPU
18 | {C6362D0E-C06C-43BF-BB58-2C34018F306F}.Release|Any CPU.ActiveCfg = Release|Any CPU
19 | {C6362D0E-C06C-43BF-BB58-2C34018F306F}.Release|Any CPU.Build.0 = Release|Any CPU
20 | {03B7159A-4B81-489C-ADF3-58567B189FEF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21 | {03B7159A-4B81-489C-ADF3-58567B189FEF}.Debug|Any CPU.Build.0 = Debug|Any CPU
22 | {03B7159A-4B81-489C-ADF3-58567B189FEF}.Release|Any CPU.ActiveCfg = Release|Any CPU
23 | {03B7159A-4B81-489C-ADF3-58567B189FEF}.Release|Any CPU.Build.0 = Release|Any CPU
24 | EndGlobalSection
25 | GlobalSection(SolutionProperties) = preSolution
26 | HideSolutionNode = FALSE
27 | EndGlobalSection
28 | GlobalSection(ExtensibilityGlobals) = postSolution
29 | SolutionGuid = {A727C847-517D-464A-8F60-CDF7223713D8}
30 | EndGlobalSection
31 | EndGlobal
32 |
--------------------------------------------------------------------------------
/src/YouTubeApiSharp/YouTubeApiSharp/API/PlaylistItemsSearchComponents.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace YouTubeApiSharp
4 | {
5 | public class PlaylistItemsSearchComponents
6 | {
7 | private String Title;
8 | private String Author;
9 | private String Duration;
10 | private String Url;
11 | private String Thumbnail;
12 |
13 | public PlaylistItemsSearchComponents(String Title, String Author, String Duration, String Url, String Thumbnail)
14 | {
15 | this.setTitle(Title);
16 | this.setAuthor(Author);
17 | this.setDuration(Duration);
18 | this.setUrl(Url);
19 | this.setThumbnail(Thumbnail);
20 | }
21 |
22 | public String getTitle()
23 | {
24 | return Title;
25 | }
26 |
27 | public void setTitle(String title)
28 | {
29 | Title = title;
30 | }
31 |
32 | public String getAuthor()
33 | {
34 | return Author;
35 | }
36 |
37 | public void setAuthor(String author)
38 | {
39 | Author = author;
40 | }
41 |
42 | public String getDuration()
43 | {
44 | return Duration;
45 | }
46 |
47 | public void setDuration(String duration)
48 | {
49 | Duration = duration;
50 | }
51 |
52 | public String getUrl()
53 | {
54 | return Url;
55 | }
56 |
57 | public void setUrl(String url)
58 | {
59 | Url = url;
60 | }
61 |
62 | public String getThumbnail()
63 | {
64 | return Thumbnail;
65 | }
66 |
67 | public void setThumbnail(String thumbnail)
68 | {
69 | Thumbnail = thumbnail;
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/YouTubeApiSharp/YouTubeApiSharp/Web/Web.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net;
3 | using System.Text;
4 | using System.Threading.Tasks;
5 |
6 | namespace YouTubeApiSharp
7 | {
8 | class Web
9 | {
10 | static WebClient webclient;
11 |
12 | public static async Task getContentFromUrl(String Url)
13 | {
14 | try
15 | {
16 | webclient = new WebClient();
17 | webclient.Encoding = Encoding.Default;
18 |
19 | Task downloadStringTask = webclient.DownloadStringTaskAsync(new Uri(Url));
20 | var content = await downloadStringTask;
21 |
22 | return content.Replace('\r', ' ').Replace('\n', ' ');
23 | }
24 | catch (Exception ex)
25 | {
26 | return ex.ToString();
27 | }
28 | }
29 |
30 | public static async Task getContentFromUrlWithProperty(String Url)
31 | {
32 | try
33 | {
34 | webclient = new WebClient();
35 | webclient.Encoding = Encoding.Default;
36 | webclient.Headers[HttpRequestHeader.UserAgent] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3731.0 Safari/537.36 Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/75.0.3731.0 Chrome/75.0.3731.0 Safari/537.36";
37 |
38 | Task downloadStringTask = webclient.DownloadStringTaskAsync(new Uri(Url));
39 | var content = await downloadStringTask;
40 |
41 | return content.Replace('\r', ' ').Replace('\n', ' ');
42 | }
43 | catch (Exception ex)
44 | {
45 | return ex.ToString();
46 | }
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # YouTubeApiSharp
2 |
3 |
4 |
5 |
6 |
7 | ## Overview
8 | A complete Private YouTube API for .NET (C#, VB.NET).
9 |
10 | ## Target platforms
11 |
12 | - .NET Standard 2.0
13 | - WinRT
14 | - Windows
15 | - Linux
16 | - macOS
17 | - Windows Phone
18 | - Xamarin.Android
19 | - Xamarin.iOS
20 |
21 | ## NuGet
22 |
23 | [YouTubeApiSharp at NuGet](http://nuget.org/packages/YouTubeApiSharp)
24 |
25 | Install-Package YouTubeApiSharp
26 |
27 | ## License
28 |
29 | The YouTubeApiSharp code is licensed under the [MIT License](http://opensource.org/licenses/MIT).
30 |
31 | ## Example code
32 |
33 | **Get the download URLs**
34 |
35 | ```c#
36 |
37 | // Our test youtube link
38 | string link = "insert youtube link";
39 |
40 | /*
41 | * Get the available video formats.
42 | * We'll work with them in the video and audio download examples.
43 | */
44 | IEnumerable videoInfos = DownloadUrlResolver.GetDownloadUrls(link);
45 |
46 | ```
47 |
48 | **Download the video**
49 |
50 | ```c#
51 |
52 | // Select the first .mp4 video with 360p resolution
53 | VideoInfo video = videoInfos
54 | .First(info => info.VideoType == VideoType.Mp4 && info.Resolution == 360);
55 |
56 | // Decrypt only if needed
57 | if (video.RequiresDecryption)
58 | {
59 | DownloadUrlResolver.DecryptDownloadUrl(video);
60 | }
61 |
62 | // Create the video downloader.
63 | VideoDownloader dl = new VideoDownloader();
64 | dl.DownloadFile(video.DownloadUrl, video.Title, true, Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), video.VideoExtension);
65 |
66 | ```
67 |
68 | ## Community
69 |
70 |
71 |
72 |
73 |
--------------------------------------------------------------------------------
/src/YouTubeApiSharp/YouTubeApiSharp/Log/Log.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 |
4 | namespace YouTubeApiSharp
5 | {
6 | public class Log
7 | {
8 | static bool isEnabled;
9 |
10 | public static bool getMode()
11 | {
12 | return isEnabled;
13 | }
14 |
15 | ///
16 | /// Enable / Disable loggin. NOTE: If you enable logging you have to set
17 | /// a folder variable at Helper.Folder!
18 | ///
19 | ///
20 | ///
21 | public static bool setMode(bool Mode)
22 | {
23 | return isEnabled = Mode;
24 | }
25 |
26 | public static void println(String Folder, String Message)
27 | {
28 | #if DEBUG
29 | Console.WriteLine("[" + DateTime.Now + "]" + " " + Message);
30 | #endif
31 |
32 | try
33 | {
34 | // Data folder path
35 | string dataPath = Folder;
36 |
37 | if (!Directory.Exists(dataPath))
38 | {
39 | Directory.CreateDirectory(dataPath);
40 | }
41 |
42 | // Logfile
43 | dataPath = Path.Combine(dataPath, "log");
44 |
45 | if (!Directory.Exists(dataPath))
46 | {
47 | Directory.CreateDirectory(dataPath);
48 | }
49 |
50 | if (!File.Exists(Path.Combine(dataPath, "log.txt")))
51 | {
52 | using (File.Create(Path.Combine(dataPath, "log.txt")))
53 | { };
54 | }
55 |
56 | File.AppendAllText(Path.Combine(dataPath, "log.txt"), "[" + DateTime.Now + "]" + " " + Message + Environment.NewLine);
57 | }
58 | catch (Exception ex)
59 | {
60 | #if DEBUG
61 | Console.WriteLine("[" + DateTime.Now + "]" + " " + ex.ToString());
62 | #endif
63 | throw new Exception(ex.ToString());
64 | }
65 | }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/YouTubeApiSharp/YouTubeApiSharp/API/PlaylistSearchComponents.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace YouTubeApiSharp
4 | {
5 | public class PlaylistSearchComponents
6 | {
7 | private String Id;
8 | private String Title;
9 | private String Author;
10 | private String VideoCount;
11 | private String Thumbnail;
12 | private String Url;
13 |
14 | public PlaylistSearchComponents(String Id, String Title, String Author, String VideoCount, String Thumbnail, String Url)
15 | {
16 | this.setId(Id);
17 | this.setTitle(Title);
18 | this.setAuthor(Author);
19 | this.setVideoCount(VideoCount);
20 | this.setThumbnail(Thumbnail);
21 | this.setUrl(Url);
22 | }
23 |
24 | public String getId()
25 | {
26 | return Id;
27 | }
28 |
29 | public void setId(String id)
30 | {
31 | Id = id;
32 | }
33 |
34 | public String getTitle()
35 | {
36 | return Title;
37 | }
38 |
39 | public void setTitle(String title)
40 | {
41 | Title = title;
42 | }
43 |
44 | public String getAuthor()
45 | {
46 | return Author;
47 | }
48 |
49 | public void setAuthor(String author)
50 | {
51 | Author = author;
52 | }
53 |
54 | public String getVideoCount()
55 | {
56 | return VideoCount;
57 | }
58 |
59 | public void setVideoCount(String videocount)
60 | {
61 | VideoCount = videocount;
62 | }
63 |
64 | public String getThumbnail()
65 | {
66 | return Thumbnail;
67 | }
68 |
69 | public void setThumbnail(String thumbnail)
70 | {
71 | Thumbnail = thumbnail;
72 | }
73 |
74 | public String getUrl()
75 | {
76 | return Url;
77 | }
78 |
79 | public void setUrl(String url)
80 | {
81 | Url = url;
82 | }
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/src/YouTubeApiSharp/YouTubeApiSharp/API/VideoSearchComponents.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace YouTubeApiSharp
4 | {
5 | public class VideoSearchComponents
6 | {
7 | private String Title;
8 | private String Author;
9 | private String Description;
10 | private String Duration;
11 | private String Url;
12 | private String Thumbnail;
13 | private String ViewCount;
14 |
15 | public VideoSearchComponents(String Title, String Author, String Description, String Duration, String Url, String Thumbnail, String ViewCount)
16 | {
17 | this.setTitle(Title);
18 | this.setAuthor(Author);
19 | this.setDescription(Description);
20 | this.setDuration(Duration);
21 | this.setUrl(Url);
22 | this.setThumbnail(Thumbnail);
23 | this.setViewCount(ViewCount);
24 | }
25 |
26 | public String getTitle()
27 | {
28 | return Title;
29 | }
30 |
31 | public void setTitle(String title)
32 | {
33 | Title = title;
34 | }
35 |
36 | public String getAuthor()
37 | {
38 | return Author;
39 | }
40 |
41 | public void setAuthor(String author)
42 | {
43 | Author = author;
44 | }
45 |
46 | public String getDescription()
47 | {
48 | return Description;
49 | }
50 |
51 | public void setDescription(String description)
52 | {
53 | Description = description;
54 | }
55 |
56 | public String getDuration()
57 | {
58 | return Duration;
59 | }
60 |
61 | public void setDuration(String duration)
62 | {
63 | Duration = duration;
64 | }
65 |
66 | public String getUrl()
67 | {
68 | return Url;
69 | }
70 |
71 | public void setUrl(String url)
72 | {
73 | Url = url;
74 | }
75 |
76 | public String getThumbnail()
77 | {
78 | return Thumbnail;
79 | }
80 |
81 | public void setThumbnail(String thumbnail)
82 | {
83 | Thumbnail = thumbnail;
84 | }
85 |
86 | public String getViewCount()
87 | {
88 | return ViewCount;
89 | }
90 |
91 | public void setViewCount(String viewcount)
92 | {
93 | ViewCount = viewcount;
94 | }
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/src/YouTubeApiSharp/YouTubeApiSharp/API/ChannelSearchComponents.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace YouTubeApiSharp
4 | {
5 | public class ChannelSearchComponents
6 | {
7 | private String Id;
8 | private String Title;
9 | private String Description;
10 | private String VideoCount;
11 | private String SubscriberCount;
12 | private String Thumbnail;
13 | private String Url;
14 |
15 | public ChannelSearchComponents(String Id, String Title, String Description, String VideoCount, String SubscriberCount, String Url, String Thumbnail)
16 | {
17 | this.setId(Id);
18 | this.setTitle(Title);
19 | this.setDescription(Description);
20 | this.setVideoCount(VideoCount);
21 | this.setSubscriberCount(SubscriberCount);
22 | this.setUrl(Url);
23 | this.setThumbnail(Thumbnail);
24 | }
25 |
26 | public String getId()
27 | {
28 | return Id;
29 | }
30 |
31 | public void setId(String id)
32 | {
33 | Id = id;
34 | }
35 |
36 | public String getTitle()
37 | {
38 | return Title;
39 | }
40 |
41 | public void setTitle(String title)
42 | {
43 | Title = title;
44 | }
45 |
46 | public String getDescription()
47 | {
48 | return Description;
49 | }
50 |
51 | public void setDescription(String description)
52 | {
53 | Description = description;
54 | }
55 |
56 | public String getVideoCount()
57 | {
58 | return VideoCount;
59 | }
60 |
61 | public void setVideoCount(String videocount)
62 | {
63 | VideoCount = videocount;
64 | }
65 |
66 | public String getSubscriberCount()
67 | {
68 | return SubscriberCount;
69 | }
70 |
71 | public void setSubscriberCount(String subscribercount)
72 | {
73 | SubscriberCount = subscribercount;
74 | }
75 |
76 | public String getThumbnail()
77 | {
78 | return Thumbnail;
79 | }
80 |
81 | public void setThumbnail(String thumbnail)
82 | {
83 | Thumbnail = thumbnail;
84 | }
85 |
86 | public String getUrl()
87 | {
88 | return Url;
89 | }
90 |
91 | public void setUrl(String url)
92 | {
93 | Url = url;
94 | }
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/src/YouTubeApiSharp/YouTubeApiSharp/Http/HttpHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Net;
6 | using System.Text;
7 | using System.Text.RegularExpressions;
8 |
9 | namespace YouTubeApiSharp
10 | {
11 | internal static class HttpHelper
12 | {
13 | public static string DownloadString(string url)
14 | {
15 | #if PORTABLE
16 | var request = WebRequest.Create(url);
17 | request.Method = "GET";
18 |
19 | System.Threading.Tasks.Task task = System.Threading.Tasks.Task.Factory.FromAsync(
20 | request.BeginGetResponse,
21 | asyncResult => request.EndGetResponse(asyncResult),
22 | null);
23 |
24 | return task.ContinueWith(t => ReadStreamFromResponse(t.Result)).Result;
25 | #else
26 | using (var client = new WebClient())
27 | {
28 | client.Encoding = System.Text.Encoding.UTF8;
29 | return client.DownloadString(url);
30 | }
31 | #endif
32 | }
33 |
34 | public static string HtmlDecode(string value)
35 | {
36 | #if PORTABLE
37 | return System.Net.WebUtility.HtmlDecode(value);
38 | #else
39 | return WebUtility.HtmlDecode(value);
40 | #endif
41 | }
42 |
43 | public static IDictionary ParseQueryString(string s)
44 | {
45 | // remove anything other than query string from url
46 | if (s.Contains("?"))
47 | {
48 | s = s.Substring(s.IndexOf('?') + 1);
49 | }
50 |
51 | var dictionary = new Dictionary();
52 |
53 | foreach (string vp in Regex.Split(s, "&"))
54 | {
55 | string[] strings = Regex.Split(vp, "=");
56 |
57 | string key = strings[0];
58 | string value = string.Empty;
59 |
60 | if (strings.Length == 2)
61 | value = strings[1];
62 | else if (strings.Length > 2)
63 | value = string.Join("=", strings.Skip(1).Take(strings.Length).ToArray());
64 |
65 | dictionary.Add(key, value);
66 | }
67 |
68 | return dictionary;
69 | }
70 |
71 | public static string ReplaceQueryStringParameter(string currentPageUrl, string paramToReplace, string newValue)
72 | {
73 | var query = ParseQueryString(currentPageUrl);
74 |
75 | query[paramToReplace] = newValue;
76 |
77 | var resultQuery = new StringBuilder();
78 | bool isFirst = true;
79 |
80 | foreach (KeyValuePair pair in query)
81 | {
82 | if (!isFirst)
83 | {
84 | resultQuery.Append("&");
85 | }
86 |
87 | resultQuery.Append(pair.Key);
88 | resultQuery.Append("=");
89 | resultQuery.Append(pair.Value);
90 |
91 | isFirst = false;
92 | }
93 |
94 | var uriBuilder = new UriBuilder(currentPageUrl)
95 | {
96 | Query = resultQuery.ToString()
97 | };
98 |
99 | return uriBuilder.ToString();
100 | }
101 |
102 | public static string UrlDecode(string url)
103 | {
104 | #if PORTABLE
105 | return System.Net.WebUtility.UrlDecode(url);
106 | #else
107 | return WebUtility.UrlDecode(url);
108 | #endif
109 | }
110 |
111 | private static string ReadStreamFromResponse(WebResponse response)
112 | {
113 | using (Stream responseStream = response.GetResponseStream())
114 | {
115 | using (var sr = new StreamReader(responseStream))
116 | {
117 | return sr.ReadToEnd();
118 | }
119 | }
120 | }
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/src/YouTubeApiSharp/YouTubeApiSharp/API/PlaylistItemsSearch.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text.RegularExpressions;
4 | using System.Threading.Tasks;
5 |
6 | namespace YouTubeApiSharp
7 | {
8 | public class PlaylistItemsSearch
9 | {
10 | static List items;
11 |
12 | private String Title;
13 | private String Author;
14 | private String Duration;
15 | private String Url;
16 | private String Thumbnail;
17 |
18 | bool hasMore = false;
19 | string indexUrl = string.Empty;
20 | string tempUrl = string.Empty;
21 | int index = 0;
22 |
23 | public async Task> GetPlaylistItems(string Playlisturl, int PlaylistItems)
24 | {
25 | items = new List();
26 |
27 | // Do search
28 | // Search address
29 | HASMORE:
30 | string content = string.Empty;
31 | if (!hasMore)
32 | content = await Web.getContentFromUrlWithProperty(Playlisturl);
33 | else
34 | content = await Web.getContentFromUrlWithProperty(indexUrl);
35 |
36 | // Search string
37 | string pattern = "playlistPanelVideoRenderer\":\\{\"title\":\\{\"accessibility\":\\{\"accessibilityData\":\\{\"label\":.*?\\,\"simpleText\":\"(?.*?)\".*?runs\":\\[\\{\"text\":\"(?.*?)\".*?\":\\{\"thumbnails\":\\[\\{\"url\":\"(?.*?)\".*?\"}},\"simpleText\":\"(?.*?)\".*?videoId\":\"(?.*?)\"";
38 | MatchCollection result = Regex.Matches(content, pattern, RegexOptions.Singleline);
39 |
40 | for (int ctr = 0; ctr <= result.Count - 1; ctr++)
41 | {
42 | index += 1;
43 | if (Log.getMode())
44 | Log.println(Helper.Folder, "Match: " + result[ctr].Value);
45 |
46 | // Title
47 | Title = result[ctr].Groups[1].Value.Replace(@"\u0026", "&");
48 |
49 | if (Log.getMode())
50 | Log.println(Helper.Folder, "Title: " + Title);
51 |
52 | // Author
53 | Author = result[ctr].Groups[2].Value.Replace(@"\u0026", "&");
54 |
55 | if (Log.getMode())
56 | Log.println(Helper.Folder, "Author: " + Author);
57 |
58 | // Duration
59 | Duration = result[ctr].Groups[4].Value;
60 |
61 | if (Log.getMode())
62 | Log.println(Helper.Folder, "Duration: " + Duration);
63 |
64 | // Thumbnail
65 | Thumbnail = result[ctr].Groups[3].Value;
66 |
67 | if (Log.getMode())
68 | Log.println(Helper.Folder, "Thumbnail: " + Thumbnail);
69 |
70 | // Url
71 | Url = "http://youtube.com/watch?v=" + result[ctr].Groups[5].Value;
72 |
73 | if (Log.getMode())
74 | Log.println(Helper.Folder, "Url: " + Url);
75 |
76 | if (hasMore)
77 | {
78 | var c = items.FindAll(item => item.getUrl().Contains(Url)).Count; // Item not in list already
79 | if (c == 0)
80 | {
81 | // Add item to list
82 | items.Add(new PlaylistItemsSearchComponents(Utilities.HtmlDecode(Title),
83 | Utilities.HtmlDecode(Author), Duration, Url, Thumbnail));
84 | }
85 | }
86 | else
87 | {
88 | // Add item to list
89 | items.Add(new PlaylistItemsSearchComponents(Utilities.HtmlDecode(Title),
90 | Utilities.HtmlDecode(Author), Duration, Url, Thumbnail));
91 | }
92 |
93 | // Temp url for parsing more items
94 | tempUrl = Url;
95 | }
96 |
97 | if (index < PlaylistItems) // Load more items
98 | {
99 | hasMore = true;
100 | indexUrl = tempUrl + "&list=" + Helper.ExtractValue(Playlisturl + ";", "list=", ";") + "&index=" + index;
101 | goto HASMORE;
102 | }
103 |
104 | return items;
105 | }
106 | }
107 | }
--------------------------------------------------------------------------------
/src/YouTubeApiSharp/YouTubeApiSharp/API/Decipherer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Text;
4 | using System.Text.RegularExpressions;
5 |
6 | namespace YouTubeApiSharp
7 | {
8 | internal static class Decipherer
9 | {
10 | public static string DecipherWithVersion(string cipher, string cipherVersion)
11 | {
12 | string jsUrl = string.Format("https://www.youtube.com{0}", cipherVersion);
13 | string js = HttpHelper.DownloadString(jsUrl);
14 |
15 | //Find "yv" in this: c&&a.set(b,encodeURIComponent(yv(
16 | string functNamePattern = @"(\w+)=function\(\w+\){(\w+)=\2\.split\(\x22{2}\);.*?return\s+\2\.join\(\x22{2}\)}"; //Regex Formed To Find Word or DollarSign
17 |
18 | var funcName = Regex.Match(js, functNamePattern).Groups[0].Value;
19 |
20 | if (funcName.Contains("$"))
21 | {
22 | funcName = "\\" + funcName; //Due To Dollar Sign Introduction, Need To Escape
23 | }
24 |
25 | //string funcPattern = @"(?!h\.)" + @funcName + @"=function\(\w+\)\{(.*?)\}"; //Escape funcName string
26 | //var funcBody = Regex.Match(js, funcPattern, RegexOptions.Singleline).Value; //Entire sig function
27 | var lines = funcName.Split(';'); //Each line in sig function
28 |
29 | string idReverse = "", idSlice = "", idCharSwap = ""; //Hold name for each cipher method
30 | string functionIdentifier = "";
31 | string operations = "";
32 |
33 | foreach (var line in lines.Skip(1).Take(lines.Length - 2)) //Matches the funcBody with each cipher method. Only runs till all three are defined.
34 | {
35 | if (!string.IsNullOrEmpty(idReverse) && !string.IsNullOrEmpty(idSlice) &&
36 | !string.IsNullOrEmpty(idCharSwap))
37 | {
38 | break; //Break loop if all three cipher methods are defined
39 | }
40 |
41 | functionIdentifier = GetFunctionFromLine(line);
42 | string reReverse = string.Format(@"{0}:\bfunction\b\(\w+\)", functionIdentifier); //Regex for reverse (one parameter)
43 | string reSlice = string.Format(@"{0}:\bfunction\b\([a],b\).(\breturn\b)?.?\w+\.", functionIdentifier); //Regex for slice (return or not)
44 | string reSwap = string.Format(@"{0}:\bfunction\b\(\w+\,\w\).\bvar\b.\bc=a\b", functionIdentifier); //Regex for the char swap.
45 |
46 | if (Regex.Match(js, reReverse).Success)
47 | {
48 | if (String.IsNullOrEmpty(idReverse))
49 | idReverse = functionIdentifier; //If def matched the regex for reverse then the current function is a defined as the reverse
50 | }
51 |
52 | if (Regex.Match(js, reSlice).Success)
53 | {
54 | if (String.IsNullOrEmpty(idSlice))
55 | idSlice = functionIdentifier; //If def matched the regex for slice then the current function is defined as the slice.
56 | }
57 |
58 | if (Regex.Match(js, reSwap).Success)
59 | {
60 | if (String.IsNullOrEmpty(idCharSwap))
61 | idCharSwap = functionIdentifier; //If def matched the regex for charSwap then the current function is defined as swap.
62 | }
63 | }
64 |
65 | foreach (var line in lines.Skip(1).Take(lines.Length - 2))
66 | {
67 | Match m;
68 | functionIdentifier = GetFunctionFromLine(line);
69 |
70 | if ((m = Regex.Match(line, @"\(\w+,(?\d+)\)")).Success && functionIdentifier == idCharSwap)
71 | {
72 | operations += "w" + m.Groups["index"].Value + " "; //operation is a swap (w)
73 | }
74 |
75 | if ((m = Regex.Match(line, @"\(\w+,(?\d+)\)")).Success && functionIdentifier == idSlice)
76 | {
77 | operations += "s" + m.Groups["index"].Value + " "; //operation is a slice
78 | }
79 |
80 | if (functionIdentifier == idReverse) //No regex required for reverse (reverse method has no parameters)
81 | {
82 | operations += "r "; //operation is a reverse
83 | }
84 | }
85 |
86 | operations = operations.Trim();
87 |
88 | return DecipherWithOperations(cipher, operations);
89 | }
90 |
91 | private static string ApplyOperation(string cipher, string op)
92 | {
93 | switch (op[0])
94 | {
95 | case 'r':
96 | return new string(cipher.ToCharArray().Reverse().ToArray());
97 |
98 | case 'w':
99 | {
100 | int index = GetOpIndex(op);
101 | return SwapFirstChar(cipher, index);
102 | }
103 |
104 | case 's':
105 | {
106 | int index = GetOpIndex(op);
107 | return cipher.Substring(index);
108 | }
109 |
110 | default:
111 | throw new NotImplementedException("Couldn't find cipher operation.");
112 | }
113 | }
114 |
115 | private static string DecipherWithOperations(string cipher, string operations)
116 | {
117 | return operations.Split(new[] { " " }, StringSplitOptions.RemoveEmptyEntries)
118 | .Aggregate(cipher, ApplyOperation);
119 | }
120 |
121 | private static string GetFunctionFromLine(string currentLine)
122 | {
123 | Regex matchFunctionReg = new Regex(@"\w+\.(?\w+)\("); //lc.ac(b,c) want the ac part.
124 | Match rgMatch = matchFunctionReg.Match(currentLine);
125 | string matchedFunction = rgMatch.Groups["functionID"].Value;
126 | return matchedFunction; //return 'ac'
127 | }
128 |
129 | private static int GetOpIndex(string op)
130 | {
131 | string parsed = new Regex(@".(\d+)").Match(op).Result("$1");
132 | int index = Int32.Parse(parsed);
133 |
134 | return index;
135 | }
136 |
137 | private static string SwapFirstChar(string cipher, int index)
138 | {
139 | var builder = new StringBuilder(cipher);
140 | builder[0] = cipher[index];
141 | builder[index] = cipher[0];
142 |
143 | return builder.ToString();
144 | }
145 | }
146 | }
--------------------------------------------------------------------------------
/src/YouTubeApiSharp/YouTubeApiSharp/Download/VideoDownloader.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.ComponentModel;
3 | using System.IO;
4 | using System.Net;
5 |
6 | namespace YouTubeApiSharp
7 | {
8 | public class VideoDownloader
9 | {
10 | internal string Folder;
11 |
12 | ///
13 | /// Download video file
14 | ///
15 | ///
16 | ///
17 | ///
18 | ///
19 | ///
20 | public void DownloadFile(string URL, string Title, bool isVideo, string Folder, string file_extension)
21 | {
22 | WebClient DownloadFile = new WebClient();
23 |
24 | DownloadFile.DownloadProgressChanged += (sender, e) => DownloadFileProgressChanged(sender, e, DownloadFile);
25 |
26 | // Event when download completed
27 | DownloadFile.DownloadFileCompleted += new AsyncCompletedEventHandler(DownloadFileCompleted);
28 |
29 | // Start download
30 | this.Folder = Folder;
31 | var directory = Folder;
32 |
33 | // Check if directory exists
34 | try
35 | {
36 | if (!System.IO.Directory.Exists(directory))
37 | System.IO.Directory.CreateDirectory(directory);
38 | }
39 | catch { }
40 |
41 | string file = string.Empty;
42 | if (isVideo)
43 | {
44 | file = Path.Combine(directory, Helper.makeFilenameValid(Title).Replace("/", "")
45 | .Replace(".", "")
46 | .Replace("|", "")
47 | .Replace("?", "")
48 | .Replace("!", "") + file_extension);
49 | }
50 | else
51 | {
52 | file = Path.Combine(directory, Helper.makeFilenameValid(Title).Replace("/", "")
53 | .Replace(".", "")
54 | .Replace("|", "")
55 | .Replace("?", "")
56 | .Replace("!", "") + ".m4a");
57 | }
58 |
59 | #if DEBUG
60 | Console.WriteLine(file);
61 | #endif
62 |
63 | if (Log.getMode())
64 | Log.println(Helper.Folder, file);
65 |
66 | DownloadFile.DownloadFileAsync(new Uri(URL), file, file + "|" + Title);
67 |
68 | #if DEBUG
69 | Console.WriteLine("Download started");
70 | #endif
71 |
72 | if (Log.getMode())
73 | Log.println(Helper.Folder, "Download started");
74 | }
75 |
76 | ///
77 | /// Download progress
78 | ///
79 | ///
80 | ///
81 | ///
82 | private void DownloadFileProgressChanged(object sender, DownloadProgressChangedEventArgs e, WebClient webClient)
83 | {
84 | // Progress
85 | string s = e.UserState as String;
86 | string[] s_ = s.Split(new char[] { '|' });
87 |
88 | long ProgressPercentage = 0;
89 |
90 | try
91 | {
92 | var contentLength = webClient.ResponseHeaders.Get("Content-Length");
93 | var totalBytesToReceive = Convert.ToInt64(contentLength);
94 |
95 | ProgressPercentage = 100 * e.BytesReceived / totalBytesToReceive;
96 | }
97 | catch
98 | {
99 | ProgressPercentage = 0;
100 | }
101 |
102 | #if DEBUG
103 | Console.WriteLine(ProgressPercentage + " %");
104 | #endif
105 |
106 | if (Log.getMode())
107 | Log.println(Helper.Folder, ProgressPercentage + " %");
108 | }
109 |
110 | ///
111 | /// Download file completed
112 | ///
113 | ///
114 | ///
115 | private void DownloadFileCompleted(object sender, AsyncCompletedEventArgs e)
116 | {
117 | // Split arguments
118 | string s = e.UserState as String;
119 |
120 | string[] s_ = s.Split(new char[] { '|' });
121 |
122 | // Directory
123 | var directory = this.Folder;
124 |
125 | // File
126 | string mediaFile = s_[0];
127 |
128 | // Filename
129 | var filename = mediaFile.Replace(Convert.ToString(directory), "").Replace("/", "");
130 |
131 | if (filename.EndsWith(".m4a"))
132 | {
133 | // Write m4a tags
134 | try
135 | {
136 | string performer = "";
137 | string title = "";
138 |
139 | if (filename.Contains("-"))
140 | {
141 | string[] split = filename.Split(new char[] { '-' });
142 |
143 | performer = split[0].TrimStart().TrimEnd();
144 |
145 | foreach (var s__ in split)
146 | {
147 | if (!s__.Contains(split[0]))
148 | title += s__;
149 | }
150 |
151 | title = title.TrimStart().TrimEnd().Replace(".m4a", "").Replace(".mp3", "").Replace(".mp4", "").Replace(".m4u", "");
152 | }
153 | else
154 | {
155 | if (filename.Contains(" "))
156 | {
157 | string[] split = filename.Split(new char[] { ' ' });
158 |
159 | performer = split[0].TrimStart().TrimEnd();
160 |
161 | foreach (var s__ in split)
162 | {
163 | if (!s__.Contains(split[0]))
164 | title += s__ + " ";
165 | }
166 |
167 | title = title.TrimStart().TrimEnd().Replace(".m4a", "").Replace(".mp3", "").Replace(".mp4", "").Replace(".m4u", "");
168 | }
169 | else
170 | {
171 | performer = filename;
172 | title = " ";
173 | }
174 | }
175 |
176 | TagLib.File file = TagLib.File.Create(mediaFile);
177 |
178 | file.Tag.Performers = new string[] { performer };
179 | file.Tag.Title = title;
180 |
181 | file.Save();
182 | }
183 | catch (Exception ex)
184 | {
185 | #if DEBUG
186 | Console.WriteLine(ex.ToString());
187 | #endif
188 |
189 | if (Log.getMode())
190 | Log.println(Helper.Folder, ex.ToString());
191 | }
192 | }
193 | #if DEBUG
194 | Console.WriteLine("Download completed: " + Helper.makeFilenameValid(filename));
195 | #endif
196 |
197 | if (Log.getMode())
198 | Log.println(Helper.Folder, "Download completed: "+ Helper.makeFilenameValid(filename));
199 | }
200 | }
201 | }
202 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Mono auto generated files
17 | mono_crash.*
18 |
19 | # Build results
20 | [Dd]ebug/
21 | [Dd]ebugPublic/
22 | [Rr]elease/
23 | [Rr]eleases/
24 | x64/
25 | x86/
26 | [Aa][Rr][Mm]/
27 | [Aa][Rr][Mm]64/
28 | bld/
29 | [Bb]in/
30 | [Oo]bj/
31 | #[Ll]og/
32 | [Ll]ogs/
33 |
34 | # Visual Studio 2015/2017 cache/options directory
35 | .vs/
36 | # Uncomment if you have tasks that create the project's static files in wwwroot
37 | #wwwroot/
38 |
39 | # Visual Studio 2017 auto generated files
40 | Generated\ Files/
41 |
42 | # MSTest test Results
43 | [Tt]est[Rr]esult*/
44 | [Bb]uild[Ll]og.*
45 |
46 | # NUnit
47 | *.VisualState.xml
48 | TestResult.xml
49 | nunit-*.xml
50 |
51 | # Build Results of an ATL Project
52 | [Dd]ebugPS/
53 | [Rr]eleasePS/
54 | dlldata.c
55 |
56 | # Benchmark Results
57 | BenchmarkDotNet.Artifacts/
58 |
59 | # .NET Core
60 | project.lock.json
61 | project.fragment.lock.json
62 | artifacts/
63 |
64 | # StyleCop
65 | StyleCopReport.xml
66 |
67 | # Files built by Visual Studio
68 | *_i.c
69 | *_p.c
70 | *_h.h
71 | *.ilk
72 | *.meta
73 | *.obj
74 | *.iobj
75 | *.pch
76 | *.pdb
77 | *.ipdb
78 | *.pgc
79 | *.pgd
80 | *.rsp
81 | *.sbr
82 | *.tlb
83 | *.tli
84 | *.tlh
85 | *.tmp
86 | *.tmp_proj
87 | *_wpftmp.csproj
88 | *.log
89 | *.vspscc
90 | *.vssscc
91 | .builds
92 | *.pidb
93 | *.svclog
94 | *.scc
95 |
96 | # Chutzpah Test files
97 | _Chutzpah*
98 |
99 | # Visual C++ cache files
100 | ipch/
101 | *.aps
102 | *.ncb
103 | *.opendb
104 | *.opensdf
105 | *.sdf
106 | *.cachefile
107 | *.VC.db
108 | *.VC.VC.opendb
109 |
110 | # Visual Studio profiler
111 | *.psess
112 | *.vsp
113 | *.vspx
114 | *.sap
115 |
116 | # Visual Studio Trace Files
117 | *.e2e
118 |
119 | # TFS 2012 Local Workspace
120 | $tf/
121 |
122 | # Guidance Automation Toolkit
123 | *.gpState
124 |
125 | # ReSharper is a .NET coding add-in
126 | _ReSharper*/
127 | *.[Rr]e[Ss]harper
128 | *.DotSettings.user
129 |
130 | # TeamCity is a build add-in
131 | _TeamCity*
132 |
133 | # DotCover is a Code Coverage Tool
134 | *.dotCover
135 |
136 | # AxoCover is a Code Coverage Tool
137 | .axoCover/*
138 | !.axoCover/settings.json
139 |
140 | # Visual Studio code coverage results
141 | *.coverage
142 | *.coveragexml
143 |
144 | # NCrunch
145 | _NCrunch_*
146 | .*crunch*.local.xml
147 | nCrunchTemp_*
148 |
149 | # MightyMoose
150 | *.mm.*
151 | AutoTest.Net/
152 |
153 | # Web workbench (sass)
154 | .sass-cache/
155 |
156 | # Installshield output folder
157 | [Ee]xpress/
158 |
159 | # DocProject is a documentation generator add-in
160 | DocProject/buildhelp/
161 | DocProject/Help/*.HxT
162 | DocProject/Help/*.HxC
163 | DocProject/Help/*.hhc
164 | DocProject/Help/*.hhk
165 | DocProject/Help/*.hhp
166 | DocProject/Help/Html2
167 | DocProject/Help/html
168 |
169 | # Click-Once directory
170 | publish/
171 |
172 | # Publish Web Output
173 | *.[Pp]ublish.xml
174 | *.azurePubxml
175 | # Note: Comment the next line if you want to checkin your web deploy settings,
176 | # but database connection strings (with potential passwords) will be unencrypted
177 | *.pubxml
178 | *.publishproj
179 |
180 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
181 | # checkin your Azure Web App publish settings, but sensitive information contained
182 | # in these scripts will be unencrypted
183 | PublishScripts/
184 |
185 | # NuGet Packages
186 | *.nupkg
187 | # NuGet Symbol Packages
188 | *.snupkg
189 | # The packages folder can be ignored because of Package Restore
190 | **/[Pp]ackages/*
191 | # except build/, which is used as an MSBuild target.
192 | !**/[Pp]ackages/build/
193 | # Uncomment if necessary however generally it will be regenerated when needed
194 | #!**/[Pp]ackages/repositories.config
195 | # NuGet v3's project.json files produces more ignorable files
196 | *.nuget.props
197 | *.nuget.targets
198 |
199 | # Microsoft Azure Build Output
200 | csx/
201 | *.build.csdef
202 |
203 | # Microsoft Azure Emulator
204 | ecf/
205 | rcf/
206 |
207 | # Windows Store app package directories and files
208 | AppPackages/
209 | BundleArtifacts/
210 | Package.StoreAssociation.xml
211 | _pkginfo.txt
212 | *.appx
213 | *.appxbundle
214 | *.appxupload
215 |
216 | # Visual Studio cache files
217 | # files ending in .cache can be ignored
218 | *.[Cc]ache
219 | # but keep track of directories ending in .cache
220 | !?*.[Cc]ache/
221 |
222 | # Others
223 | ClientBin/
224 | ~$*
225 | *~
226 | *.dbmdl
227 | *.dbproj.schemaview
228 | *.jfm
229 | *.pfx
230 | *.publishsettings
231 | orleans.codegen.cs
232 |
233 | # Including strong name files can present a security risk
234 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
235 | #*.snk
236 |
237 | # Since there are multiple workflows, uncomment next line to ignore bower_components
238 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
239 | #bower_components/
240 |
241 | # RIA/Silverlight projects
242 | Generated_Code/
243 |
244 | # Backup & report files from converting an old project file
245 | # to a newer Visual Studio version. Backup files are not needed,
246 | # because we have git ;-)
247 | _UpgradeReport_Files/
248 | Backup*/
249 | UpgradeLog*.XML
250 | UpgradeLog*.htm
251 | ServiceFabricBackup/
252 | *.rptproj.bak
253 |
254 | # SQL Server files
255 | *.mdf
256 | *.ldf
257 | *.ndf
258 |
259 | # Business Intelligence projects
260 | *.rdl.data
261 | *.bim.layout
262 | *.bim_*.settings
263 | *.rptproj.rsuser
264 | *- [Bb]ackup.rdl
265 | *- [Bb]ackup ([0-9]).rdl
266 | *- [Bb]ackup ([0-9][0-9]).rdl
267 |
268 | # Microsoft Fakes
269 | FakesAssemblies/
270 |
271 | # GhostDoc plugin setting file
272 | *.GhostDoc.xml
273 |
274 | # Node.js Tools for Visual Studio
275 | .ntvs_analysis.dat
276 | node_modules/
277 |
278 | # Visual Studio 6 build log
279 | *.plg
280 |
281 | # Visual Studio 6 workspace options file
282 | *.opt
283 |
284 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
285 | *.vbw
286 |
287 | # Visual Studio LightSwitch build output
288 | **/*.HTMLClient/GeneratedArtifacts
289 | **/*.DesktopClient/GeneratedArtifacts
290 | **/*.DesktopClient/ModelManifest.xml
291 | **/*.Server/GeneratedArtifacts
292 | **/*.Server/ModelManifest.xml
293 | _Pvt_Extensions
294 |
295 | # Paket dependency manager
296 | .paket/paket.exe
297 | paket-files/
298 |
299 | # FAKE - F# Make
300 | .fake/
301 |
302 | # CodeRush personal settings
303 | .cr/personal
304 |
305 | # Python Tools for Visual Studio (PTVS)
306 | __pycache__/
307 | *.pyc
308 |
309 | # Cake - Uncomment if you are using it
310 | # tools/**
311 | # !tools/packages.config
312 |
313 | # Tabs Studio
314 | *.tss
315 |
316 | # Telerik's JustMock configuration file
317 | *.jmconfig
318 |
319 | # BizTalk build output
320 | *.btp.cs
321 | *.btm.cs
322 | *.odx.cs
323 | *.xsd.cs
324 |
325 | # OpenCover UI analysis results
326 | OpenCover/
327 |
328 | # Azure Stream Analytics local run output
329 | ASALocalRun/
330 |
331 | # MSBuild Binary and Structured Log
332 | *.binlog
333 |
334 | # NVidia Nsight GPU debugger configuration file
335 | *.nvuser
336 |
337 | # MFractors (Xamarin productivity tool) working folder
338 | .mfractor/
339 |
340 | # Local History for Visual Studio
341 | .localhistory/
342 |
343 | # BeatPulse healthcheck temp database
344 | healthchecksdb
345 |
346 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
347 | MigrationBackup/
348 |
349 | # Ionide (cross platform F# VS Code tools) working folder
350 | .ionide/
351 |
--------------------------------------------------------------------------------
/src/YouTubeApiSharp/YouTubeApiSharp/API/PlaylistSearch.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text.RegularExpressions;
4 | using System.Threading.Tasks;
5 |
6 | namespace YouTubeApiSharp
7 | {
8 | public class PlaylistSearch
9 | {
10 | static List items;
11 |
12 | private static String Id;
13 | private static String Title;
14 | private static String Author;
15 | private static String VideoCount;
16 | private static String Thumbnail;
17 | private static String Url;
18 |
19 | static string continuationCommand = string.Empty;
20 |
21 | public async Task> GetPlaylists(string querystring, int querypages)
22 | {
23 | items = new List();
24 |
25 | // Do search
26 | for (int i = 1; i <= querypages; i++)
27 | {
28 | string content = string.Empty;
29 | if (i == 1) // First page
30 | {
31 | // Search address
32 | content = await Web.getContentFromUrlWithProperty("https://www.youtube.com/results?search_query=" + querystring.Replace(" ", "+") + "&sp=EgIQAw%253D%253D");
33 |
34 | // Continuation command
35 | continuationCommand = Helper.ExtractValue(content, "\"continuationCommand\":{\"token\":\"", "\"").Replace("%3D", "=").Replace("%2F", "/");
36 | if (Log.getMode())
37 | Log.println(Helper.Folder, "continuationCommand: " + continuationCommand);
38 |
39 | // Search string
40 | string pattern = "playlistRenderer\":\\{\"playlistId\":\"(?.*?)\",\"title\":\\{\"simpleText\":\"(?.*?)\"},\"thumbnails\":\\[\\{\"thumbnails\":\\[\\{\"url\":\"(?.*?)\".*?videoCount\":\"(?.*?)\".*?\\{\"webCommandMetadata\":\\{\"url\":\"(?.*?)\".*?\"shortBylineText\":\\{\"runs\":\\[\\{\"text\":\"(?.*?)\"";
41 | MatchCollection result = Regex.Matches(content, pattern, RegexOptions.Singleline);
42 |
43 | for (int ctr = 0; ctr <= result.Count - 1; ctr++)
44 | {
45 | if (Log.getMode())
46 | Log.println(Helper.Folder, "Match: " + result[ctr].Value);
47 |
48 | // Id
49 | Id = result[ctr].Groups[1].Value;
50 |
51 | if (Log.getMode())
52 | Log.println(Helper.Folder, "Id: " + Id);
53 |
54 | // Title
55 | Title = result[ctr].Groups[2].Value.Replace(@"\u0026", "&");
56 |
57 | if (Log.getMode())
58 | Log.println(Helper.Folder, "Title: " + Title);
59 |
60 | // Author
61 | Author = result[ctr].Groups[6].Value.Replace(@"\u0026", "&");
62 |
63 | if (Log.getMode())
64 | Log.println(Helper.Folder, "Author: " + Author);
65 |
66 | // VideoCount
67 | VideoCount = result[ctr].Groups[4].Value;
68 |
69 | if (Log.getMode())
70 | Log.println(Helper.Folder, "VideoCount: " + VideoCount);
71 |
72 | // Thumbnail
73 | Thumbnail = result[ctr].Groups[3].Value;
74 |
75 | if (Log.getMode())
76 | Log.println(Helper.Folder, "Thumbnail: " + Thumbnail);
77 |
78 | // Url
79 | Url = "http://youtube.com" + result[ctr].Groups[5].Value.Replace("\\u0026", "&");
80 |
81 | if (Log.getMode())
82 | Log.println(Helper.Folder, "Url: " + Url);
83 |
84 | // Add item to list
85 | items.Add(new PlaylistSearchComponents(Id, Utilities.HtmlDecode(Title),
86 | Utilities.HtmlDecode(Author), VideoCount, Thumbnail, Url));
87 | }
88 | }
89 | else // Next page
90 | {
91 | // Search address
92 | content = await Continuation.Scrape(continuationCommand);
93 |
94 | // Continuation command
95 | continuationCommand = Helper.ExtractValue(content.Replace(" ", ""), "\"continuationCommand\": { \"token\": \"", "\"").Replace("%3D", "=").Replace("%2F", "/");
96 | if (Log.getMode())
97 | Log.println(Helper.Folder, "continuationCommand: " + continuationCommand);
98 |
99 | content = content.Replace(" ", "");
100 |
101 | // Search string
102 | string pattern = "playlistRenderer\":\\ { \"playlistId\": \"(?.*?)\", \"title\": \\{ \"simpleText\": \"(?.*?)\" }, \"thumbnails\": \\[ \\{ \"thumbnails\": \\[ \\{ \"url\": \"(?.*?)\".*?videoCount\": \"(?.*?)\".*?\\{ \"webCommandMetadata\": \\{ \"url\": \"(?.*?)\".*?\"shortBylineText\": \\{ \"runs\": \\[ \\{ \"text\": \"(?.*?)\"";
103 | MatchCollection result = Regex.Matches(content, pattern, RegexOptions.Singleline);
104 |
105 | for (int ctr = 0; ctr <= result.Count - 1; ctr++)
106 | {
107 | if (Log.getMode())
108 | Log.println(Helper.Folder, "Match: " + result[ctr].Value);
109 |
110 | // Id
111 | Id = result[ctr].Groups[1].Value;
112 |
113 | if (Log.getMode())
114 | Log.println(Helper.Folder, "Id: " + Id);
115 |
116 | // Title
117 | Title = result[ctr].Groups[2].Value.Replace(@"\u0026", "&");
118 |
119 | if (Log.getMode())
120 | Log.println(Helper.Folder, "Title: " + Title);
121 |
122 | // Author
123 | Author = result[ctr].Groups[6].Value.Replace(@"\u0026", "&");
124 |
125 | if (Log.getMode())
126 | Log.println(Helper.Folder, "Author: " + Author);
127 |
128 | // VideoCount
129 | VideoCount = result[ctr].Groups[4].Value;
130 |
131 | if (Log.getMode())
132 | Log.println(Helper.Folder, "VideoCount: " + VideoCount);
133 |
134 | // Thumbnail
135 | Thumbnail = result[ctr].Groups[3].Value;
136 |
137 | if (Log.getMode())
138 | Log.println(Helper.Folder, "Thumbnail: " + Thumbnail);
139 |
140 | // Url
141 | Url = "http://youtube.com" + result[ctr].Groups[5].Value.Replace("\\u0026", "&");
142 |
143 | if (Log.getMode())
144 | Log.println(Helper.Folder, "Url: " + Url);
145 |
146 | var c = items.FindAll(item => item.getUrl().Contains(Url)).Count; // Item not in list already
147 | if (c == 0)
148 | {
149 | // Add item to list
150 | items.Add(new PlaylistSearchComponents(Id, Utilities.HtmlDecode(Title),
151 | Utilities.HtmlDecode(Author), VideoCount, Thumbnail, Url));
152 | }
153 | }
154 | }
155 | }
156 |
157 | return items;
158 | }
159 | }
160 | }
161 |
--------------------------------------------------------------------------------
/src/ExampleApplication/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 |
5 | using YouTubeApiSharp;
6 |
7 | namespace ExampleApplication
8 | {
9 | class Program
10 | {
11 | static void Main(string[] args)
12 | {
13 | /* ********** */
14 | /* Unit Tests */
15 | /* ********** */
16 |
17 | /* Search video */
18 | Search();
19 |
20 | /* Download audio, video */
21 | //Download();
22 |
23 | /* Search playlist */
24 | //Search_Playlist();
25 |
26 | /* Search playlist items */
27 | //Search_PlaylistItems();
28 |
29 | /* Search channel */
30 | //Search_Channel();
31 |
32 | /* Search channel items */
33 | //Search_ChannelItems();
34 |
35 | Console.ReadLine();
36 | }
37 |
38 | static async void Search()
39 | {
40 | // Disable logging
41 | Log.setMode(false);
42 |
43 | // Keyword
44 | string querystring = "Kurdo";
45 |
46 | // Number of result pages
47 | int querypages = 1;
48 |
49 | ////////////////////////////////
50 | // Start searching
51 | ////////////////////////////////
52 |
53 | VideoSearch videos = new VideoSearch();
54 | var items = await videos.GetVideos(querystring, querypages);
55 |
56 | int i = 1;
57 |
58 | foreach (var item in items)
59 | {
60 | Console.WriteLine("# " + i);
61 | Console.WriteLine("Title: " + item.getTitle());
62 | Console.WriteLine("Author: " + item.getAuthor());
63 | Console.WriteLine("Description: " + item.getDescription());
64 | Console.WriteLine("Duration: " + item.getDuration());
65 | Console.WriteLine("Url: " + item.getUrl());
66 | Console.WriteLine("Thumbnail: " + item.getThumbnail());
67 | Console.WriteLine("ViewCount: " + item.getViewCount());
68 | Console.WriteLine("");
69 | i++;
70 | }
71 | }
72 |
73 | static async void Search_PlaylistItems()
74 | {
75 | // Disable logging
76 | Log.setMode(false);
77 |
78 | // Url
79 | string playlisturl = "https://www.youtube.com/watch?v=hvP9_UEVIw4&list=PL90240A2D521E753B";
80 |
81 | // Items
82 | int plitems = 786;
83 |
84 | ////////////////////////////////
85 | // Start searching
86 | ////////////////////////////////
87 |
88 | PlaylistItemsSearch playlistItems = new PlaylistItemsSearch();
89 | var items = await playlistItems.GetPlaylistItems(playlisturl, plitems);
90 |
91 | int i = 0;
92 |
93 | foreach (var item in items)
94 | {
95 | i += 1;
96 | Console.WriteLine("#" + i);
97 | Console.WriteLine("Title: " + item.getTitle());
98 | Console.WriteLine("Author: " + item.getAuthor());
99 | Console.WriteLine("Duration: " + item.getDuration());
100 | Console.WriteLine("Thumbnail: " + item.getThumbnail());
101 | Console.WriteLine("Url: " + item.getUrl());
102 | Console.WriteLine("");
103 | }
104 | }
105 |
106 | static async void Search_ChannelItems()
107 | {
108 | // Disable logging
109 | Log.setMode(false);
110 |
111 | // Url
112 | string channelurl = "https://www.youtube.com/channel/UCHte7RKGIYJXDZKShCNz9gw";
113 |
114 | // Items
115 | int chitems = 184;
116 |
117 | ////////////////////////////////
118 | // Start searching
119 | ////////////////////////////////
120 |
121 | ChannelItemsSearch channelItems = new ChannelItemsSearch();
122 | var items = await channelItems.GetChannelItems(channelurl, chitems);
123 |
124 | int i = 0;
125 |
126 | foreach (var item in items)
127 | {
128 | i += 1;
129 | Console.WriteLine("#" + i);
130 | Console.WriteLine("Title: " + item.getTitle());
131 | Console.WriteLine("Author: " + item.getAuthor());
132 | Console.WriteLine("Duration: " + item.getDuration());
133 | Console.WriteLine("Thumbnail: " + item.getThumbnail());
134 | Console.WriteLine("Url: " + item.getUrl());
135 | Console.WriteLine("");
136 | }
137 | }
138 |
139 | static async void Search_Playlist()
140 | {
141 | // Disable logging
142 | Log.setMode(false);
143 |
144 | // Keyword
145 | string querystring = "Kurdo";
146 |
147 | // Number of result pages
148 | int querypages = 2;
149 |
150 | ////////////////////////////////
151 | // Start searching
152 | ////////////////////////////////
153 |
154 | PlaylistSearch playlist = new PlaylistSearch();
155 | var items = await playlist.GetPlaylists(querystring, querypages);
156 |
157 | int i = 0;
158 |
159 | foreach (var item in items)
160 | {
161 | i += 1;
162 | Console.WriteLine("#" + i);
163 | Console.WriteLine("Id: " + item.getId());
164 | Console.WriteLine("Title: " + item.getTitle());
165 | Console.WriteLine("Author: " + item.getAuthor());
166 | Console.WriteLine("VideoCount: " + item.getVideoCount());
167 | Console.WriteLine("Thumbnail: " + item.getThumbnail());
168 | Console.WriteLine("Url: " + item.getUrl());
169 | Console.WriteLine("");
170 | }
171 | }
172 |
173 | static async void Search_Channel()
174 | {
175 | // Disable logging
176 | Log.setMode(false);
177 |
178 | // Keyword
179 | string querystring = "Kurdo";
180 |
181 | // Number of result pages
182 | int querypages = 2;
183 |
184 | ////////////////////////////////
185 | // Start searching
186 | ////////////////////////////////
187 |
188 | ChannelSearch channel = new ChannelSearch();
189 | var items = await channel.GetChannels(querystring, querypages);
190 |
191 | int i = 0;
192 |
193 | foreach (var item in items)
194 | {
195 | i += 1;
196 | Console.WriteLine("#" + i);
197 | Console.WriteLine("Id: " + item.getId());
198 | Console.WriteLine("Title: " + item.getTitle());
199 | Console.WriteLine("Description: " + item.getDescription());
200 | Console.WriteLine("VideoCount: " + item.getVideoCount());
201 | Console.WriteLine("SubscriberCount: " + item.getSubscriberCount());
202 | Console.WriteLine("Thumbnail: " + item.getThumbnail());
203 | Console.WriteLine("Url: " + item.getUrl());
204 | Console.WriteLine("");
205 | }
206 | }
207 |
208 | static void Download()
209 | {
210 | // Disable logging
211 | Log.setMode(false);
212 |
213 | // YouTube url
214 |
215 | // Protected
216 | string link = "https://www.youtube.com/watch?v=LN--3zgY5oM";
217 |
218 | // Free
219 | //string link = "https://www.youtube.com/watch?v=curXTlNnGho";
220 |
221 | IEnumerable videoInfos = DownloadUrlResolver.GetDownloadUrls(link, false);
222 |
223 | foreach (var v in videoInfos)
224 | Console.WriteLine(v.ToString() + ", Audio bitrate: " + v.AudioBitrate + ", Adaptive type: " + v.AdaptiveType);
225 |
226 | DownloadVideo(videoInfos);
227 | }
228 |
229 | private static void DownloadVideo(IEnumerable videoInfos)
230 | {
231 | // Select the first .mp4 video with 360p resolution
232 | VideoInfo video = videoInfos
233 | .First(info => info.VideoType == VideoType.Mp4 && info.Resolution == 360);
234 |
235 | // Decrypt only if needed
236 | if (video.RequiresDecryption)
237 | {
238 | DownloadUrlResolver.DecryptDownloadUrl(video);
239 | }
240 |
241 | // Create the video downloader.
242 | VideoDownloader dl = new VideoDownloader();
243 | dl.DownloadFile(video.DownloadUrl, video.Title, true, Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), video.VideoExtension);
244 | }
245 | }
246 | }
247 |
--------------------------------------------------------------------------------
/src/YouTubeApiSharp/YouTubeApiSharp/API/ChannelSearch.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text.RegularExpressions;
4 | using System.Threading.Tasks;
5 |
6 | namespace YouTubeApiSharp
7 | {
8 | public class ChannelSearch
9 | {
10 | static List items;
11 |
12 | private String Id;
13 | private String Title;
14 | private String Description;
15 | private String VideoCount;
16 | private String SubscriberCount;
17 | private String Thumbnail;
18 | private String Url;
19 |
20 | static string continuationCommand = string.Empty;
21 |
22 | public async Task> GetChannels(string querystring, int querypages)
23 | {
24 | items = new List();
25 |
26 | // Do search
27 | for (int i = 1; i <= querypages; i++)
28 | {
29 | string content = string.Empty;
30 | if (i == 1) // First page
31 | {
32 | // Search address
33 | content = await Web.getContentFromUrlWithProperty("https://www.youtube.com/results?search_query=" + querystring.Replace(" ", "+") + "&sp=EgIQAg%253D%253D");
34 |
35 | // Continuation command
36 | continuationCommand = Helper.ExtractValue(content, "\"continuationCommand\":{\"token\":\"", "\"").Replace("%3D", "=").Replace("%2F", "/");
37 | if (Log.getMode())
38 | Log.println(Helper.Folder, "continuationCommand: " + continuationCommand);
39 |
40 | content = Helper.ExtractValue(content, "ytInitialData", "ytInitialPlayerResponse");
41 |
42 | // Search string
43 | string pattern = "channelRenderer\":\\{\"channelId\":\"(?.*?)\",\"title\":{\"simpleText\":\"(?.*?)\".*?\"canonicalBaseUrl\":\"(?.*?)\"}}.*?\\{\"thumbnails\":\\[\\{\"url\":\"(?.*?)\".*?videoCountText\":\\{\"runs\":\\[\\{\"text\":\"(?.*?)\".*?clickTrackingParams";
44 | MatchCollection result = Regex.Matches(content, pattern, RegexOptions.Singleline);
45 |
46 | for (int ctr = 0; ctr <= result.Count - 1; ctr++)
47 | {
48 | if (Log.getMode())
49 | Log.println(Helper.Folder, "Match: " + result[ctr].Value);
50 |
51 | // Id
52 | Id = result[ctr].Groups[1].Value;
53 |
54 | if (Log.getMode())
55 | Log.println(Helper.Folder, "Id: " + Id);
56 |
57 | // Title
58 | Title = result[ctr].Groups[2].Value.Replace(@"\u0026", "&");
59 |
60 | if (Log.getMode())
61 | Log.println(Helper.Folder, "Title: " + Title);
62 |
63 | // Description
64 | Description = Helper.ExtractValue(result[ctr].Value, "\"descriptionSnippet\":{\"runs\":[{\"text\":\"", "\"}]},").Replace(@"\u0026", "&");
65 |
66 | if (Log.getMode())
67 | Log.println(Helper.Folder, "Description: " + Description);
68 |
69 | // VideoCount
70 | VideoCount = result[ctr].Groups[5].Value;
71 |
72 | if (VideoCount.Contains(" ")) // -> 1 Video
73 | VideoCount = VideoCount.Replace(" Video", "");
74 |
75 | if (Log.getMode())
76 | Log.println(Helper.Folder, "VideoCount: " + VideoCount);
77 |
78 | // SubscriberCount
79 | SubscriberCount = Helper.ExtractValue(result[ctr].Value, "subscriberCountText\":{\"accessibility\":{\"accessibilityData\":{\"label\":\"", " ");
80 |
81 | if (Log.getMode())
82 | Log.println(Helper.Folder, "SubscriberCount: " + SubscriberCount);
83 |
84 | // Thumbnail
85 | if (result[ctr].Groups[4].Value.StartsWith("https"))
86 | Thumbnail = result[ctr].Groups[4].Value;
87 | else
88 | Thumbnail = "https:" + result[ctr].Groups[4].Value;
89 |
90 | if (Log.getMode())
91 | Log.println(Helper.Folder, "Thumbnail: " + Thumbnail);
92 |
93 | // Url
94 | Url = "http://youtube.com" + result[ctr].Groups[3].Value;
95 |
96 | if (Log.getMode())
97 | Log.println(Helper.Folder, "Url: " + Url);
98 |
99 | // Add item to list
100 | items.Add(new ChannelSearchComponents(Id, Utilities.HtmlDecode(Title),
101 | Utilities.HtmlDecode(Description), VideoCount, SubscriberCount, Url, Thumbnail));
102 | }
103 | }
104 | else // Next page
105 | {
106 | // Search address
107 | content = await Continuation.Scrape(continuationCommand);
108 |
109 | // Continuation command
110 | continuationCommand = Helper.ExtractValue(content.Replace(" ", ""), "\"continuationCommand\": { \"token\": \"", "\"").Replace("%3D", "=").Replace("%2F", "/");
111 | if (Log.getMode())
112 | Log.println(Helper.Folder, "continuationCommand: " + continuationCommand);
113 |
114 | content = content.Replace(" ", "");
115 |
116 | // Search string
117 | string pattern = "channelRenderer\":\\ { \"channelId\": \"(?.*?)\", \"title\": { \"simpleText\": \"(?.*?)\".*?\"canonicalBaseUrl\": \"(?.*?)\" } }.*?\\{ \"thumbnails\": \\[ \\{ \"url\": \"(?.*?)\".*?videoCountText\": \\{ \"runs\": \\[ \\{ \"text\": \"(?.*?)\".*?clickTrackingParams";
118 | MatchCollection result = Regex.Matches(content, pattern, RegexOptions.Singleline);
119 |
120 | for (int ctr = 0; ctr <= result.Count - 1; ctr++)
121 | {
122 | if (Log.getMode())
123 | Log.println(Helper.Folder, "Match: " + result[ctr].Value);
124 |
125 | // Id
126 | Id = result[ctr].Groups[1].Value;
127 |
128 | if (Log.getMode())
129 | Log.println(Helper.Folder, "Id: " + Id);
130 |
131 | // Title
132 | Title = result[ctr].Groups[2].Value.Replace(@"\u0026", "&");
133 |
134 | if (Log.getMode())
135 | Log.println(Helper.Folder, "Title: " + Title);
136 |
137 | // Description
138 | Description = Helper.ExtractValue(result[ctr].Value.Replace(" ", ""), "\"descriptionSnippet\": { \"runs\": [ { \"text\": \"", "\" } ] },").Replace(@"\u0026", "&");
139 |
140 | if (Log.getMode())
141 | Log.println(Helper.Folder, "Description: " + Description);
142 |
143 | // VideoCount
144 | VideoCount = result[ctr].Groups[5].Value;
145 |
146 | if (VideoCount.Contains(" ")) // -> 1 Video
147 | VideoCount = VideoCount.Replace(" Video", "");
148 |
149 | if (Log.getMode())
150 | Log.println(Helper.Folder, "VideoCount: " + VideoCount);
151 |
152 | // SubscriberCount
153 | SubscriberCount = Helper.ExtractValue(result[ctr].Value.Replace(" ", ""), "subscriberCountText\":{\"accessibility\":{\"accessibilityData\":{\"label\":\"", " ");
154 |
155 | if (Log.getMode())
156 | Log.println(Helper.Folder, "SubscriberCount: " + SubscriberCount);
157 |
158 | // Thumbnail
159 | if (result[ctr].Groups[4].Value.StartsWith("https"))
160 | Thumbnail = result[ctr].Groups[4].Value;
161 | else
162 | Thumbnail = "https:" + result[ctr].Groups[4].Value;
163 |
164 | if (Log.getMode())
165 | Log.println(Helper.Folder, "Thumbnail: " + Thumbnail);
166 |
167 | // Url
168 | Url = "http://youtube.com" + result[ctr].Groups[3].Value;
169 |
170 | if (Log.getMode())
171 | Log.println(Helper.Folder, "Url: " + Url);
172 |
173 | var c = items.FindAll(item => item.getUrl().Contains(Url)).Count; // Item not in list already
174 | if (c == 0)
175 | {
176 | // Add item to list
177 | items.Add(new ChannelSearchComponents(Id, Utilities.HtmlDecode(Title),
178 | Utilities.HtmlDecode(Description), VideoCount, SubscriberCount, Url, Thumbnail));
179 | }
180 | }
181 | }
182 | }
183 |
184 | return items;
185 | }
186 | }
187 | }
188 |
--------------------------------------------------------------------------------
/src/YouTubeApiSharp/YouTubeApiSharp/Constants/VideoInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace YouTubeApiSharp
4 | {
5 | public class VideoInfo
6 | {
7 | internal static IEnumerable Defaults = new List
8 | {
9 | /* Non-adaptive */
10 | new VideoInfo (5, VideoType.Flash, 240, false, AudioType.Mp3, 64, AdaptiveType.None),
11 | new VideoInfo (6, VideoType.Flash, 270, false, AudioType.Mp3, 64, AdaptiveType.None),
12 | new VideoInfo (13, VideoType.Mobile, 0, false, AudioType.Aac, 0, AdaptiveType.None),
13 | new VideoInfo (17, VideoType.Mobile, 144, false, AudioType.Aac, 24, AdaptiveType.None),
14 | new VideoInfo (18, VideoType.Mp4, 360, false, AudioType.Aac, 96, AdaptiveType.None),
15 | new VideoInfo (22, VideoType.Mp4, 720, false, AudioType.Aac, 192, AdaptiveType.None),
16 | new VideoInfo (34, VideoType.Flash, 360, false, AudioType.Aac, 128, AdaptiveType.None),
17 | new VideoInfo (35, VideoType.Flash, 480, false, AudioType.Aac, 128, AdaptiveType.None),
18 | new VideoInfo (36, VideoType.Mobile, 240, false, AudioType.Aac, 38, AdaptiveType.None),
19 | new VideoInfo (37, VideoType.Mp4, 1080, false, AudioType.Aac, 192, AdaptiveType.None),
20 | new VideoInfo (38, VideoType.Mp4, 3072, false, AudioType.Aac, 192, AdaptiveType.None),
21 | new VideoInfo (43, VideoType.WebM, 360, false, AudioType.Vorbis, 128, AdaptiveType.None),
22 | new VideoInfo (44, VideoType.WebM, 480, false, AudioType.Vorbis, 128, AdaptiveType.None),
23 | new VideoInfo (45, VideoType.WebM, 720, false, AudioType.Vorbis, 192, AdaptiveType.None),
24 | new VideoInfo (46, VideoType.WebM, 1080, false, AudioType.Vorbis, 192, AdaptiveType.None),
25 |
26 | /* 3d */
27 | new VideoInfo (82, VideoType.Mp4, 360, true, AudioType.Aac, 96, AdaptiveType.None),
28 | new VideoInfo (83, VideoType.Mp4, 240, true, AudioType.Aac, 96, AdaptiveType.None),
29 | new VideoInfo (84, VideoType.Mp4, 720, true, AudioType.Aac, 152, AdaptiveType.None),
30 | new VideoInfo (85, VideoType.Mp4, 520, true, AudioType.Aac, 152, AdaptiveType.None),
31 | new VideoInfo (100, VideoType.WebM, 360, true, AudioType.Vorbis, 128, AdaptiveType.None),
32 | new VideoInfo (101, VideoType.WebM, 360, true, AudioType.Vorbis, 192, AdaptiveType.None),
33 | new VideoInfo (102, VideoType.WebM, 720, true, AudioType.Vorbis, 192, AdaptiveType.None),
34 |
35 | /* Adaptive (aka DASH) - Video */
36 | new VideoInfo (133, VideoType.Mp4, 240, false, AudioType.Unknown, 0, AdaptiveType.Video),
37 | new VideoInfo (134, VideoType.Mp4, 360, false, AudioType.Unknown, 0, AdaptiveType.Video),
38 | new VideoInfo (135, VideoType.Mp4, 480, false, AudioType.Unknown, 0, AdaptiveType.Video),
39 | new VideoInfo (136, VideoType.Mp4, 720, false, AudioType.Unknown, 0, AdaptiveType.Video),
40 | new VideoInfo (137, VideoType.Mp4, 1080, false, AudioType.Unknown, 0, AdaptiveType.Video),
41 | new VideoInfo (138, VideoType.Mp4, 2160, false, AudioType.Unknown, 0, AdaptiveType.Video),
42 | new VideoInfo (160, VideoType.Mp4, 144, false, AudioType.Unknown, 0, AdaptiveType.Video),
43 | new VideoInfo (242, VideoType.WebM, 240, false, AudioType.Unknown, 0, AdaptiveType.Video),
44 | new VideoInfo (243, VideoType.WebM, 360, false, AudioType.Unknown, 0, AdaptiveType.Video),
45 | new VideoInfo (244, VideoType.WebM, 480, false, AudioType.Unknown, 0, AdaptiveType.Video),
46 | new VideoInfo (247, VideoType.WebM, 720, false, AudioType.Unknown, 0, AdaptiveType.Video),
47 | new VideoInfo (248, VideoType.WebM, 1080, false, AudioType.Unknown, 0, AdaptiveType.Video),
48 | new VideoInfo (264, VideoType.Mp4, 1440, false, AudioType.Unknown, 0, AdaptiveType.Video),
49 | new VideoInfo (271, VideoType.WebM, 1440, false, AudioType.Unknown, 0, AdaptiveType.Video),
50 | new VideoInfo (272, VideoType.WebM, 2160, false, AudioType.Unknown, 0, AdaptiveType.Video),
51 | new VideoInfo (278, VideoType.WebM, 144, false, AudioType.Unknown, 0, AdaptiveType.Video),
52 |
53 | /* Adaptive (aka DASH) - Audio */
54 | new VideoInfo (139, VideoType.Mp4, 0, false, AudioType.Aac, 48, AdaptiveType.Audio),
55 | new VideoInfo (140, VideoType.Mp4, 0, false, AudioType.Aac, 128, AdaptiveType.Audio),
56 | new VideoInfo (141, VideoType.Mp4, 0, false, AudioType.Aac, 256, AdaptiveType.Audio),
57 | new VideoInfo (171, VideoType.WebM, 0, false, AudioType.Vorbis, 128, AdaptiveType.Audio),
58 | new VideoInfo (172, VideoType.WebM, 0, false, AudioType.Vorbis, 192, AdaptiveType.Audio),
59 | new VideoInfo (249, VideoType.WebM, 0, false, AudioType.Opus, 58, AdaptiveType.Audio),
60 | new VideoInfo (250, VideoType.WebM, 0, false, AudioType.Opus, 75, AdaptiveType.Audio),
61 | new VideoInfo (251, VideoType.WebM, 0, false, AudioType.Opus, 146, AdaptiveType.Audio)
62 | };
63 |
64 | internal VideoInfo(int formatCode)
65 | : this(formatCode, VideoType.Unknown, 0, false, AudioType.Unknown, 0, AdaptiveType.None)
66 | { }
67 |
68 | internal VideoInfo(VideoInfo info)
69 | : this(info.FormatCode, info.VideoType, info.Resolution, info.Is3D, info.AudioType, info.AudioBitrate, info.AdaptiveType)
70 | { }
71 |
72 | private VideoInfo(int formatCode, VideoType videoType, int resolution, bool is3D, AudioType audioType, int audioBitrate, AdaptiveType adaptiveType)
73 | {
74 | this.FormatCode = formatCode;
75 | this.VideoType = videoType;
76 | this.Resolution = resolution;
77 | this.Is3D = is3D;
78 | this.AudioType = audioType;
79 | this.AudioBitrate = audioBitrate;
80 | this.AdaptiveType = adaptiveType;
81 | }
82 |
83 | ///
84 | /// Gets an enum indicating whether the format is adaptive or not.
85 | ///
86 | ///
87 | /// AdaptiveType.Audio or AdaptiveType.Video if the format is adaptive;
88 | /// otherwise, AdaptiveType.None.
89 | ///
90 | public AdaptiveType AdaptiveType { get; set; }
91 |
92 | ///
93 | /// The approximate audio bitrate in kbit/s.
94 | ///
95 | /// The approximate audio bitrate in kbit/s, or 0 if the bitrate is unknown.
96 | public int AudioBitrate { get; set; }
97 |
98 | ///
99 | /// Gets the audio extension.
100 | ///
101 | /// The audio extension, or null if the audio extension is unknown.
102 | public string AudioExtension
103 | {
104 | get
105 | {
106 | switch (this.AudioType)
107 | {
108 | case AudioType.Aac:
109 | return ".aac";
110 |
111 | case AudioType.Mp3:
112 | return ".mp3";
113 |
114 | case AudioType.Vorbis:
115 | return ".ogg";
116 | }
117 |
118 | return null;
119 | }
120 | }
121 |
122 | ///
123 | /// Gets the audio type (encoding).
124 | ///
125 | public AudioType AudioType { get; private set; }
126 |
127 | ///
128 | /// Gets a value indicating whether the audio of this video can be extracted by YoutubeExtractor.
129 | ///
130 | ///
131 | /// true if the audio of this video can be extracted by YoutubeExtractor; otherwise, false.
132 | ///
133 | public bool CanExtractAudio
134 | {
135 | get { return this.VideoType == VideoType.Flash; }
136 | }
137 |
138 | ///
139 | /// Gets the download URL.
140 | ///
141 | public string DownloadUrl { get; internal set; }
142 |
143 | ///
144 | /// Gets the format code, that is used by YouTube internally to differentiate between
145 | /// quality profiles.
146 | ///
147 | public int FormatCode { get; private set; }
148 |
149 | public bool Is3D { get; private set; }
150 |
151 | ///
152 | /// Gets a value indicating whether this video info requires a signature decryption before
153 | /// the download URL can be used.
154 | ///
155 | /// This can be achieved with the
156 | ///
157 | public bool RequiresDecryption { get; internal set; }
158 |
159 | ///
160 | /// Gets the resolution of the video.
161 | ///
162 | /// The resolution of the video, or 0 if the resolution is unkown.
163 | public int Resolution { get; private set; }
164 |
165 | ///
166 | /// Gets the video title.
167 | ///
168 | public string Title { get; internal set; }
169 |
170 | ///
171 | /// Gets the video extension.
172 | ///
173 | /// The video extension, or null if the video extension is unknown.
174 | public string VideoExtension
175 | {
176 | get
177 | {
178 | switch (this.VideoType)
179 | {
180 | case VideoType.Mp4:
181 | return ".mp4";
182 |
183 | case VideoType.Mobile:
184 | return ".3gp";
185 |
186 | case VideoType.Flash:
187 | return ".m4a";
188 |
189 | case VideoType.WebM:
190 | return ".webm";
191 | }
192 |
193 | return null;
194 | }
195 | }
196 |
197 | ///
198 | /// Gets the video type (container).
199 | ///
200 | public VideoType VideoType { get; private set; }
201 |
202 | ///
203 | /// We use this in the method to
204 | /// decrypt the signature
205 | ///
206 | ///
207 | internal string HtmlPlayerVersion { get; set; }
208 |
209 | ///
210 | /// FizeSize
211 | ///
212 | public int FileSize { get; set; }
213 |
214 | public string FileSizeHumanReadable { get; set; }
215 |
216 | ///
217 | /// Quality Label
218 | ///
219 | public string FormatNote { get; set; }
220 |
221 | public int FPS { get; set; }
222 |
223 | public int Height { get; set; }
224 |
225 | public int Width { get; set; }
226 |
227 | public float AverageBitrate { get; set; }
228 |
229 | public override string ToString()
230 | {
231 | return string.Format("Full Title: {0}, Type: {1}, Resolution: {2}p", this.Title + this.VideoExtension, this.VideoType, this.Resolution);
232 | }
233 | }
234 | }
235 |
--------------------------------------------------------------------------------
/src/YouTubeApiSharp/YouTubeApiSharp/API/VideoSearch.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text.RegularExpressions;
4 | using System.Threading.Tasks;
5 |
6 | namespace YouTubeApiSharp
7 | {
8 | public class VideoSearch
9 | {
10 | static List items;
11 |
12 | static string title;
13 | static string author;
14 | static string description;
15 | static string duration;
16 | static string url;
17 | static string thumbnail;
18 | static string viewcount;
19 |
20 | static string continuationCommand = string.Empty;
21 |
22 | ///
23 | /// Search videos
24 | ///
25 | ///
26 | ///
27 | ///
28 | public async Task> GetVideos(string querystring, int querypages)
29 | {
30 | items = new List();
31 |
32 | // Do search
33 | for (int i = 1; i <= querypages; i++)
34 | {
35 | string content = string.Empty;
36 | if (i == 1) // First page
37 | {
38 | // Search address
39 | content = await Web.getContentFromUrl("https://www.youtube.com/results?search_query=" + querystring);
40 |
41 | // Continuation command
42 | continuationCommand = Helper.ExtractValue(content, "\"continuationCommand\":{\"token\":\"", "\"").Replace("%3D","=").Replace("%2F","/");
43 | if (Log.getMode())
44 | Log.println(Helper.Folder, "continuationCommand: " + continuationCommand);
45 |
46 | content = Helper.ExtractValue(content, "ytInitialData", "ytInitialPlayerResponse");
47 |
48 | // Search string
49 | string pattern = "videoRenderer.*?maxOneLine";
50 | MatchCollection result = Regex.Matches(content, pattern, RegexOptions.Singleline);
51 |
52 | for (int ctr = 0; ctr <= result.Count - 1; ctr++)
53 | {
54 | if (Log.getMode())
55 | Log.println(Helper.Folder, "Match: " + result[ctr].Value);
56 |
57 | // Title
58 | title = Helper.ExtractValue(result[ctr].Value, "\"title\":{\"runs\":[{\"text\":\"", "\"}]").Replace(@"\u0026", "&");
59 |
60 | if (Log.getMode())
61 | Log.println(Helper.Folder, "Title: " + title);
62 |
63 | // Author
64 | author = Helper.ExtractValue(result[ctr].Value, "\"ownerText\":{\"runs\":[{\"text\":\"", "\",\"").Replace(@"\u0026", "&");
65 |
66 | if (Log.getMode())
67 | Log.println(Helper.Folder, "Author: " + author);
68 |
69 | // Description
70 | description = Helper.ExtractValue(result[ctr].Value, "\"snippetText\":{\"runs\":[", "]},").Replace(@"\u0026", " &");
71 |
72 | if (Log.getMode())
73 | Log.println(Helper.Folder, "Description: " + description);
74 |
75 | // Duration
76 | duration = Helper.ExtractValue(result[ctr].Value, "lengthText\"", "viewCountText");
77 | duration = Helper.ExtractValue(duration, "simpleText\":\"", "\"");
78 |
79 | if (Log.getMode())
80 | Log.println(Helper.Folder, "Duration: " + duration);
81 |
82 | // Url
83 | url = string.Concat("http://www.youtube.com/watch?v=", Helper.ExtractValue(result[ctr].Value, "videoId\":\"", "\""));
84 |
85 | if (Log.getMode())
86 | Log.println(Helper.Folder, "Url: " + url);
87 |
88 | // Thumbnail
89 | thumbnail = Helper.ExtractValue(result[ctr].Value, "\"thumbnail\":{\"thumbnails\":[{\"url\":\"", "\"").Replace(@"\u0026", "&");
90 |
91 | if (Log.getMode())
92 | Log.println(Helper.Folder, "Thumbnail: " + thumbnail);
93 |
94 | // View count
95 | {
96 | string strView = Helper.ExtractValue(result[ctr].Value, "\"viewCountText\":{\"simpleText\":\"", "\"},\"");
97 | if (strView.IsValid())//if (!string.IsNullOrEmpty(strView) && !string.IsNullOrWhiteSpace(strView))
98 | {
99 | string[] strParsedArr =
100 | strView.Split(new string[] { " " }, StringSplitOptions.RemoveEmptyEntries);
101 |
102 | string parsedText = strParsedArr[0];
103 | parsedText = parsedText.Trim().Replace(",", ".");
104 |
105 | viewcount = parsedText;
106 | }
107 | else
108 | {
109 | viewcount = "n.a.";
110 | }
111 | }
112 |
113 | if (Log.getMode())
114 | Log.println(Helper.Folder, "Viewcount: " + viewcount);
115 |
116 | // Remove playlists
117 | if (title != "__title__" && title.IsValid()/*title != " "*/)
118 | {
119 | if (duration.IsValid())//if (duration != "" && duration != " ")
120 | {
121 | // Add item to list
122 | items.Add(new VideoSearchComponents(Utilities.HtmlDecode(title),
123 | Utilities.HtmlDecode(author), Utilities.HtmlDecode(description), duration, url, thumbnail, viewcount));
124 | }
125 | }
126 | }
127 | }
128 | else // Next page
129 | {
130 | // Search address
131 | content = await Continuation.Scrape(continuationCommand);
132 |
133 | // Continuation command
134 | continuationCommand = Helper.ExtractValue(content.Replace(" ", ""), "\"continuationCommand\": { \"token\": \"", "\"").Replace("%3D", "=").Replace("%2F", "/");
135 | if (Log.getMode())
136 | Log.println(Helper.Folder, "continuationCommand: " + continuationCommand);
137 |
138 | // Search string
139 | string pattern = "videoRenderer.*?maxOneLine";
140 | MatchCollection result = Regex.Matches(content, pattern, RegexOptions.Singleline);
141 |
142 | for (int ctr = 0; ctr <= result.Count - 1; ctr++)
143 | {
144 | var r = result[ctr].Value.Replace(" ", "");
145 |
146 | if (Log.getMode())
147 | Log.println(Helper.Folder, "Match: " + result[ctr].Value);
148 |
149 | // Title
150 | title = Helper.ExtractValue(r, "\"title\": { \"runs\": [ { \"text\": \"", "\" } ],").Replace(@"\u0026", "&");
151 |
152 | if (Log.getMode())
153 | Log.println(Helper.Folder, "Title: " + title);
154 |
155 | // Author
156 | author = Helper.ExtractValue(r, "\"ownerText\": { \"runs\": [ { \"text\": \"", "\",").Replace(@"\u0026", "&");
157 |
158 | if (Log.getMode())
159 | Log.println(Helper.Folder, "Author: " + author);
160 |
161 | // Description
162 | description = Helper.ExtractValue(result[ctr].Value, "\"snippetText\":{\"runs\":[", "]},").Replace(@"\u0026", " &");
163 |
164 | if (Log.getMode())
165 | Log.println(Helper.Folder, "Description: " + description);
166 |
167 | // Duration
168 | duration = Helper.ExtractValue(r, "lengthText\"", "viewCountText");
169 | duration = Helper.ExtractValue(duration, "simpleText\": \"", "\"");
170 |
171 | if (Log.getMode())
172 | Log.println(Helper.Folder, "Duration: " + duration);
173 |
174 | // Url
175 | url = string.Concat("http://www.youtube.com/watch?v=", Helper.ExtractValue(r, "videoId\": \"", "\""));
176 |
177 | if (Log.getMode())
178 | Log.println(Helper.Folder, "Url: " + url);
179 |
180 | // Thumbnail
181 | thumbnail = Helper.ExtractValue(r, "\"thumbnail\": { \"thumbnails\": [ { \"url\": \"", "\"").Replace(@"\u0026", "&");
182 |
183 | if (Log.getMode())
184 | Log.println(Helper.Folder, "Thumbnail: " + thumbnail);
185 |
186 | // View count
187 | {
188 | string strView = Helper.ExtractValue(r, "}, \"viewCountText\": { \"simpleText\": \"", "\" }, \"");
189 | if (strView.IsValid())//if (!string.IsNullOrEmpty(strView) && !string.IsNullOrWhiteSpace(strView))
190 | {
191 | string[] strParsedArr =
192 | strView.Split(new string[] { " " }, StringSplitOptions.RemoveEmptyEntries);
193 |
194 | string parsedText = strParsedArr[0];
195 | parsedText = parsedText.Trim().Replace(",", ".");
196 |
197 | viewcount = parsedText;
198 | }
199 | else
200 | {
201 | viewcount = "n.a.";
202 | }
203 | }
204 |
205 | if (Log.getMode())
206 | Log.println(Helper.Folder, "Viewcount: " + viewcount);
207 |
208 | // Remove playlists
209 | if (title != "__title__" && title.IsValid()/*title != " "*/)
210 | {
211 | if (duration.IsValid())//if (duration != "" && duration != " ")
212 | {
213 | var c = items.FindAll(item => item.getUrl().Contains(url)).Count; // Item not in list already
214 | if (c == 0)
215 | {
216 | // Add item to list
217 | items.Add(new VideoSearchComponents(Utilities.HtmlDecode(title),
218 | Utilities.HtmlDecode(author), Utilities.HtmlDecode(description), duration, url, thumbnail, viewcount));
219 | }
220 | }
221 | }
222 | }
223 | }
224 | }
225 |
226 | return items;
227 | }
228 | }
229 | }
230 |
--------------------------------------------------------------------------------
/src/YouTubeApiSharp/YouTubeApiSharp/API/DownloadUrlResolver.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Diagnostics;
4 | using System.Globalization;
5 | using System.Linq;
6 | using System.Net;
7 | using System.Text.RegularExpressions;
8 |
9 | namespace YouTubeApiSharp
10 | {
11 | ///
12 | /// Provides a method to get the download link of a YouTube video.
13 | ///
14 | public static class DownloadUrlResolver
15 | {
16 | private const string RateBypassFlag = "ratebypass";
17 | private const string SignatureQuery = "sig";
18 |
19 | ///
20 | /// Decrypts the signature in the property and sets it
21 | /// to the decrypted URL. Use this method, if you have decryptSignature in the method set to false.
23 | ///
24 | /// The video info which's downlaod URL should be decrypted.
25 | ///
26 | /// There was an error while deciphering the signature.
27 | ///
28 | public static void DecryptDownloadUrl(VideoInfo videoInfo)
29 | {
30 | string decipheredSignature;
31 | IDictionary strs = HttpHelper.ParseQueryString(videoInfo.DownloadUrl);
32 | if (strs.ContainsKey(SignatureQuery))
33 | {
34 | string item = strs[SignatureQuery];
35 | try
36 | {
37 | decipheredSignature = GetDecipheredSignature(videoInfo.HtmlPlayerVersion, item);
38 | }
39 | catch (Exception exception)
40 | {
41 | throw new ParseException("Could not decipher signature", exception);
42 | }
43 | videoInfo.DownloadUrl = HttpHelper.ReplaceQueryStringParameter(videoInfo.DownloadUrl, SignatureQuery, decipheredSignature);
44 | videoInfo.RequiresDecryption = false;
45 | }
46 | }
47 |
48 | ///
49 | /// Gets a list of s for the specified URL.
50 | ///
51 | /// The URL of the YouTube video.
52 | ///
53 | /// A value indicating whether the video signatures should be decrypted or not. Decrypting
54 | /// consists of a HTTP request for each , so you may want to set
55 | /// this to false and call on your selected later.
57 | ///
58 | /// A list of s that can be used to download the video.
59 | ///
60 | /// The parameter is null.
61 | ///
62 | ///
63 | /// The parameter is not a valid YouTube URL.
64 | ///
65 | /// The video is not available.
66 | ///
67 | /// An error occurred while downloading the YouTube page html.
68 | ///
69 | /// The Youtube page could not be parsed.
70 | public static IEnumerable GetDownloadUrls(string videoUrl, bool decryptSignature = true)
71 | {
72 | if (videoUrl == null)
73 | {
74 | throw new ArgumentNullException("videoUrl");
75 | }
76 |
77 | if (!TryNormalizeYoutubeUrl(videoUrl, out videoUrl))
78 | {
79 | throw new ArgumentException("URL is not a valid youtube URL!");
80 | }
81 |
82 | try
83 | {
84 | var model = LoadModel(videoUrl);
85 |
86 | string videoTitle = GetVideoTitle(model);
87 |
88 | IEnumerable infos = GetVideoInfos(model).ToList();
89 |
90 | string html5PlayerVersion = GetHtml5PlayerVersion(model.Microformat.PlayerMicroformatRenderer.Embed.IframeUrl);
91 |
92 | foreach (VideoInfo videoInfo in infos)
93 | {
94 | videoInfo.Title = videoTitle;
95 | videoInfo.HtmlPlayerVersion = html5PlayerVersion;
96 |
97 | //It takes a long time to decrypt all of item.
98 | /*if (decryptSignature && info.RequiresDecryption)
99 | {
100 | DecryptDownloadUrl(info);
101 | }*/
102 | }
103 |
104 | return infos;
105 | }
106 | catch (Exception ex)
107 | {
108 | if (ex is WebException || ex is VideoNotAvailableException)
109 | {
110 | throw;
111 | }
112 |
113 | #if DEBUG
114 | Console.WriteLine(ex.ToString());
115 | #endif
116 |
117 | ThrowYoutubeParseException(ex, videoUrl);
118 | }
119 |
120 | return null; // Will never happen, but the compiler requires it
121 | }
122 |
123 | #if PORTABLE
124 |
125 | public static System.Threading.Tasks.Task> GetDownloadUrlsAsync(string videoUrl, bool decryptSignature = true)
126 | {
127 | return System.Threading.Tasks.Task.Run(() => GetDownloadUrls(videoUrl, decryptSignature));
128 | }
129 |
130 | #endif
131 |
132 | ///
133 | /// Normalizes the given YouTube URL to the format http://youtube.com/watch?v={youtube-id}
134 | /// and returns whether the normalization was successful or not.
135 | ///
136 | /// The YouTube URL to normalize.
137 | /// The normalized YouTube URL.
138 | ///
139 | /// true, if the normalization was successful; false, if the URL is invalid.
140 | ///
141 | public static bool TryNormalizeYoutubeUrl(string url, out string normalizedUrl)
142 | {
143 | url = url.Trim();
144 | url = url.Replace("youtu.be/", "youtube.com/watch?v=");
145 | url = url.Replace("www.youtube", "youtube");
146 | url = url.Replace("youtube.com/embed/", "youtube.com/watch?v=");
147 | if (url.Contains("/v/"))
148 | {
149 | url = string.Concat("http://youtube.com", (new Uri(url)).AbsolutePath.Replace("/v/", "/watch?v="));
150 | }
151 | url = url.Replace("/watch#", "/watch?");
152 |
153 |
154 | if (!HttpHelper.ParseQueryString(url).TryGetValue("v", out string v))
155 | {
156 | normalizedUrl = null;
157 | return false;
158 | }
159 |
160 | normalizedUrl = "https://youtube.com/watch?v=" + v;
161 |
162 | return true;
163 | }
164 |
165 | private static List GetAdaptiveStreamMap(Model model)
166 | {
167 | return model.StreamingData.AdaptiveFormats.ToList();
168 | }
169 |
170 | private static string GetDecipheredSignature(string htmlPlayerVersion, string signature)
171 | {
172 | return Decipherer.DecipherWithVersion(signature, htmlPlayerVersion);
173 | }
174 |
175 | private static string GetHtml5PlayerVersion(string url)
176 | {
177 | string h5url = string.Empty;
178 |
179 | try
180 | {
181 | // Assests
182 | string st = HttpHelper.DownloadString(url);
183 | //#if DEBUG
184 | // Console.WriteLine(st);
185 | //#endif
186 | Regex rg = new Regex("\"assets\":.+?\"js\":\\s*\"([^\"]+)\"");
187 |
188 | h5url = rg.Match(st).Result("$1").Replace("\\/", "/");
189 |
190 | if (string.IsNullOrEmpty(h5url.Trim()))
191 | {
192 | // JSUrl
193 | string str = HttpHelper.DownloadString(url);
194 | Regex regex = new Regex(@"['""]PLAYER_CONFIG['""]\s*:\s*(\{.*\})");
195 | var src = regex.Match(str).Groups[1].Value.Replace("\\/", "/");
196 | Regex reg = new Regex("\"jsUrl\":\"(?.*?)\"");
197 | var player = reg.Match(src).Groups[1].Value;
198 |
199 | h5url = reg.Match(src).Groups[1].Value;
200 |
201 | if (String.IsNullOrEmpty(h5url))
202 | {
203 | h5url = Helper.ExtractValue(str, "jsUrl\":\"", "\"");
204 | }
205 | }
206 | }
207 | catch
208 | {
209 | // JSUrl
210 | string str = HttpHelper.DownloadString(url);
211 | Regex regex = new Regex(@"['""]PLAYER_CONFIG['""]\s*:\s*(\{.*\})");
212 | var src = regex.Match(str).Groups[1].Value.Replace("\\/", "/");
213 | Regex reg = new Regex("\"jsUrl\":\"(?.*?)\"");
214 | var player = reg.Match(src).Groups[1].Value;
215 |
216 | h5url = reg.Match(src).Groups[1].Value;
217 |
218 | if (String.IsNullOrEmpty(h5url))
219 | {
220 | h5url = Helper.ExtractValue(str, "jsUrl\":\"", "\"");
221 | }
222 | }
223 |
224 | //#if DEBUG
225 | // Console.WriteLine(h5url);
226 | //#endif
227 |
228 | // If h5url is still empty make a dirty fallback
229 | if (String.IsNullOrEmpty(h5url))
230 | {
231 | h5url = "/s/player/c299662f/player_ias.vflset/de_DE/base.js";
232 | }
233 |
234 | return h5url;
235 | }
236 |
237 | private static List GetStreamMap(Model model)
238 | {
239 | return model.StreamingData.Formats.ToList();
240 | }
241 |
242 | private static IEnumerable GetVideoInfos(Model model)
243 | {
244 | var streamingFormats = GetStreamMap(model);
245 | streamingFormats.AddRange(GetAdaptiveStreamMap(model));
246 |
247 | foreach (var fmt in streamingFormats)
248 | {
249 | if (!fmt.Itag.HasValue) continue;
250 |
251 | var itag = (int)fmt.Itag.Value;
252 |
253 | VideoInfo videoInfo = VideoInfo.Defaults.SingleOrDefault(info => info.FormatCode == itag);
254 |
255 | videoInfo = videoInfo ?? new VideoInfo(itag);
256 |
257 | if (!string.IsNullOrEmpty(fmt.Url))
258 | {
259 | videoInfo.DownloadUrl = HttpHelper.UrlDecode(HttpHelper.UrlDecode(fmt.Url));
260 | }
261 | else if (!string.IsNullOrEmpty(fmt.Cipher) || !string.IsNullOrEmpty(fmt.SignatureCipher))
262 | {
263 | IDictionary cipher = null;
264 |
265 | if (!string.IsNullOrEmpty(fmt.Cipher))
266 | cipher = HttpHelper.ParseQueryString(fmt.Cipher);
267 | if (!string.IsNullOrEmpty(fmt.SignatureCipher))
268 | cipher = HttpHelper.ParseQueryString(fmt.SignatureCipher);
269 |
270 | if (!cipher.ContainsKey("url"))
271 | continue;
272 | if (!cipher.ContainsKey("s"))
273 | continue;
274 |
275 | var url = cipher["url"];
276 | var sig = cipher["s"];
277 |
278 | url = HttpHelper.UrlDecode(url);
279 | url = HttpHelper.UrlDecode(url);
280 |
281 | sig = HttpHelper.UrlDecode(sig);
282 | sig = HttpHelper.UrlDecode(sig);
283 |
284 | url = url.Replace("&s=", "&sig=");
285 | videoInfo.DownloadUrl = HttpHelper.ReplaceQueryStringParameter(url, SignatureQuery, sig);
286 | videoInfo.RequiresDecryption = true;
287 | }
288 | else
289 | continue;
290 |
291 | if (!HttpHelper.ParseQueryString(videoInfo.DownloadUrl).ContainsKey(RateBypassFlag))
292 | {
293 | videoInfo.DownloadUrl = string.Concat(videoInfo.DownloadUrl, string.Format("&{0}={1}", "ratebypass", "yes"));
294 | }
295 |
296 | if (fmt.AudioSampleRate.HasValue)
297 | videoInfo.AudioBitrate = (int)fmt.AudioSampleRate.Value;
298 |
299 | if (fmt.ContentLength.HasValue)
300 | {
301 | videoInfo.FileSize = (int)fmt.ContentLength.Value;
302 | videoInfo.FileSizeHumanReadable = ContentLengthtoHumanReadable(Convert.ToString(fmt.ContentLength.Value));
303 | }
304 |
305 |
306 | if (!string.IsNullOrEmpty(fmt.QualityLabel))
307 | videoInfo.FormatNote = fmt.QualityLabel;
308 | else
309 | videoInfo.FormatNote = fmt.Quality;
310 |
311 | if (fmt.Fps.HasValue)
312 | videoInfo.FPS = (int)fmt.Fps.Value;
313 |
314 | if (fmt.Height.HasValue)
315 | videoInfo.Height = (int)fmt.Height.Value;
316 |
317 | if (fmt.Width.HasValue)
318 | videoInfo.Width = (int)fmt.Width.Value;
319 |
320 | // bitrate for itag 43 is always 2147483647
321 | if (itag != 43)
322 | {
323 | if (fmt.AverageBitrate.HasValue)
324 | videoInfo.AverageBitrate = fmt.AverageBitrate.Value / 1000f;
325 | else if (fmt.Bitrate.HasValue)
326 | videoInfo.AverageBitrate = fmt.Bitrate.Value / 1000f;
327 | }
328 |
329 | if (fmt.Height.HasValue)
330 | videoInfo.Height = (int)fmt.Height.Value;
331 |
332 | if (fmt.Width.HasValue)
333 | videoInfo.Width = (int)fmt.Width.Value;
334 |
335 | yield return videoInfo;
336 | }
337 | }
338 |
339 | private static string GetVideoTitle(Model model)
340 | {
341 | return model.VideoDetails.Title.Replace("+", " ");
342 | }
343 |
344 | private static Model LoadModel(string videoUrl)
345 | {
346 | //var videoId = videoUrl.Replace("https://youtube.com/watch?v=", "");
347 | //var url = $"https://www.youtube.com/get_video_info?html5=1&video_id={videoId}&eurl=https://youtube.googleapis.com/v/{videoId}&c=TVHTML5&cver=6.20180913";
348 |
349 | //try
350 | //{
351 | // return Model.FromJson(GetVideoInfo(videoUrl));
352 | //}
353 | //catch
354 | //{
355 | // return Model.FromJson(HttpHelper.UrlDecode(HttpHelper.ParseQueryString(HttpHelper.DownloadString(url))["player_response"]));
356 | //}
357 |
358 | // https://github.com/snakx/YouTubeApiSharp/issues/1
359 |
360 | return Model.FromJson(GetVideoInfo(videoUrl));
361 | }
362 |
363 | private static string GetVideoInfo(String videoUrl)
364 | {
365 | String content = HttpHelper.DownloadString(videoUrl);
366 | String streamingData = Helper.ExtractValue(content, "ytInitialPlayerResponse = ", ";");
367 | return streamingData;
368 | }
369 |
370 | private static void ThrowYoutubeParseException(Exception innerException, string videoUrl)
371 | {
372 | throw new ParseException("Could not parse the Youtube page for URL " + videoUrl + "\n" +
373 | "This may be due to a change of the Youtube page structure.\n" +
374 | "Please report this bug at thisistorsten@gmail.com", innerException);
375 | }
376 |
377 | private static string ContentLengthtoHumanReadable(string contentLength)
378 | {
379 | try
380 | {
381 | if (string.IsNullOrEmpty(contentLength))
382 | return "Unknown";
383 |
384 | long ContentLength = 0;
385 | long result;
386 | var cLength = long.TryParse(contentLength, out ContentLength);
387 |
388 | string File_Size;
389 | string ext;
390 |
391 | if (ContentLength >= 1073741824)
392 | {
393 | result = ContentLength / 1073741824;
394 | ext = "GB";
395 | }
396 | else if (ContentLength >= 1048576)
397 | {
398 | result = ContentLength / 1048576;
399 | ext = "MB";
400 | }
401 | else
402 | {
403 | result = ContentLength / 1024;
404 | ext = "KB";
405 | }
406 | File_Size = result.ToString("0.00", CultureInfo.GetCultureInfo("en-US").NumberFormat);
407 | return File_Size + " " + ext;
408 | }
409 | catch
410 | {
411 | return "Unknown";
412 | }
413 | }
414 | }
415 | }
416 |
--------------------------------------------------------------------------------
/src/YouTubeApiSharp/YouTubeApiSharp/API/Model.cs:
--------------------------------------------------------------------------------
1 | //
2 | //
3 | // To parse this JSON data, add NuGet 'Newtonsoft.Json' then do:
4 | //
5 | // using YoutubeExtractor;
6 | //
7 | // var Model = Model.FromJson(jsonString);
8 |
9 | namespace YouTubeApiSharp
10 | {
11 | using Newtonsoft.Json;
12 | using Newtonsoft.Json.Converters;
13 | using System;
14 | using System.Collections.Generic;
15 | using System.Globalization;
16 | public partial class Model
17 | {
18 | [JsonProperty("playabilityStatus", NullValueHandling = NullValueHandling.Ignore)]
19 | public PlayabilityStatus PlayabilityStatus { get; set; }
20 |
21 | [JsonProperty("streamingData", NullValueHandling = NullValueHandling.Ignore)]
22 | public StreamingData StreamingData { get; set; }
23 |
24 | [JsonProperty("playbackTracking", NullValueHandling = NullValueHandling.Ignore)]
25 | public PlaybackTracking PlaybackTracking { get; set; }
26 |
27 | [JsonProperty("videoDetails", NullValueHandling = NullValueHandling.Ignore)]
28 | public VideoDetails VideoDetails { get; set; }
29 |
30 | [JsonProperty("annotations", NullValueHandling = NullValueHandling.Ignore)]
31 | public List Annotations { get; set; }
32 |
33 | [JsonProperty("playerConfig", NullValueHandling = NullValueHandling.Ignore)]
34 | public PlayerConfig PlayerConfig { get; set; }
35 |
36 | [JsonProperty("storyboards", NullValueHandling = NullValueHandling.Ignore)]
37 | public Storyboards Storyboards { get; set; }
38 |
39 | [JsonProperty("microformat", NullValueHandling = NullValueHandling.Ignore)]
40 | public Microformat Microformat { get; set; }
41 |
42 | [JsonProperty("trackingParams", NullValueHandling = NullValueHandling.Ignore)]
43 | public string TrackingParams { get; set; }
44 |
45 | [JsonProperty("attestation", NullValueHandling = NullValueHandling.Ignore)]
46 | public Attestation Attestation { get; set; }
47 |
48 | [JsonProperty("endscreen", NullValueHandling = NullValueHandling.Ignore)]
49 |
50 | public Endscreen Endscreen { get; set; }
51 | }
52 |
53 | public partial class Annotation
54 | {
55 | [JsonProperty("playerAnnotationsUrlsRenderer", NullValueHandling = NullValueHandling.Ignore)]
56 | public PlayerAnnotationsUrlsRenderer PlayerAnnotationsUrlsRenderer { get; set; }
57 | }
58 |
59 | public partial class PlayerAnnotationsUrlsRenderer
60 | {
61 | [JsonProperty("invideoUrl", NullValueHandling = NullValueHandling.Ignore)]
62 | public string InvideoUrl { get; set; }
63 |
64 | [JsonProperty("loadPolicy", NullValueHandling = NullValueHandling.Ignore)]
65 | public string LoadPolicy { get; set; }
66 |
67 | [JsonProperty("allowInPlaceSwitch", NullValueHandling = NullValueHandling.Ignore)]
68 | public bool? AllowInPlaceSwitch { get; set; }
69 | }
70 |
71 | public partial class Attestation
72 | {
73 | [JsonProperty("playerAttestationRenderer", NullValueHandling = NullValueHandling.Ignore)]
74 | public PlayerAttestationRenderer PlayerAttestationRenderer { get; set; }
75 | }
76 |
77 | public partial class PlayerAttestationRenderer
78 | {
79 | [JsonProperty("challenge", NullValueHandling = NullValueHandling.Ignore)]
80 | public string Challenge { get; set; }
81 |
82 | [JsonProperty("botguardData", NullValueHandling = NullValueHandling.Ignore)]
83 | public BotguardData BotguardData { get; set; }
84 | }
85 |
86 | public partial class BotguardData
87 | {
88 | [JsonProperty("program", NullValueHandling = NullValueHandling.Ignore)]
89 | public string Program { get; set; }
90 |
91 | [JsonProperty("interpreterUrl", NullValueHandling = NullValueHandling.Ignore)]
92 | public string InterpreterUrl { get; set; }
93 | }
94 |
95 | public partial class Endscreen
96 | {
97 | [JsonProperty("endscreenRenderer", NullValueHandling = NullValueHandling.Ignore)]
98 | public EndscreenRenderer EndscreenRenderer { get; set; }
99 | }
100 |
101 | public partial class EndscreenRenderer
102 | {
103 | [JsonProperty("elements", NullValueHandling = NullValueHandling.Ignore)]
104 | public List Elements { get; set; }
105 |
106 | [JsonProperty("startMs", NullValueHandling = NullValueHandling.Ignore)]
107 | [JsonConverter(typeof(ParseStringConverter))]
108 | public long? StartMs { get; set; }
109 |
110 | [JsonProperty("trackingParams", NullValueHandling = NullValueHandling.Ignore)]
111 | public string TrackingParams { get; set; }
112 | }
113 |
114 | public partial class Element
115 | {
116 | [JsonProperty("endscreenElementRenderer", NullValueHandling = NullValueHandling.Ignore)]
117 | public EndscreenElementRenderer EndscreenElementRenderer { get; set; }
118 | }
119 |
120 | public partial class EndscreenElementRenderer
121 | {
122 | [JsonProperty("style", NullValueHandling = NullValueHandling.Ignore)]
123 | public string Style { get; set; }
124 |
125 | [JsonProperty("image", NullValueHandling = NullValueHandling.Ignore)]
126 | public ImageClass Image { get; set; }
127 |
128 | [JsonProperty("icon", NullValueHandling = NullValueHandling.Ignore)]
129 | public Icon Icon { get; set; }
130 |
131 | [JsonProperty("left", NullValueHandling = NullValueHandling.Ignore)]
132 | public double? Left { get; set; }
133 |
134 | [JsonProperty("width", NullValueHandling = NullValueHandling.Ignore)]
135 | public double? Width { get; set; }
136 |
137 | [JsonProperty("top", NullValueHandling = NullValueHandling.Ignore)]
138 | public double? Top { get; set; }
139 |
140 | [JsonProperty("aspectRatio", NullValueHandling = NullValueHandling.Ignore)]
141 | public double? AspectRatio { get; set; }
142 |
143 | [JsonProperty("startMs", NullValueHandling = NullValueHandling.Ignore)]
144 | [JsonConverter(typeof(ParseStringConverter))]
145 | public long? StartMs { get; set; }
146 |
147 | [JsonProperty("endMs", NullValueHandling = NullValueHandling.Ignore)]
148 | [JsonConverter(typeof(ParseStringConverter))]
149 | public long? EndMs { get; set; }
150 |
151 | [JsonProperty("title", NullValueHandling = NullValueHandling.Ignore)]
152 | public Title Title { get; set; }
153 |
154 | [JsonProperty("metadata", NullValueHandling = NullValueHandling.Ignore)]
155 | public Description Metadata { get; set; }
156 |
157 | [JsonProperty("callToAction", NullValueHandling = NullValueHandling.Ignore)]
158 | public Description CallToAction { get; set; }
159 |
160 | [JsonProperty("dismiss", NullValueHandling = NullValueHandling.Ignore)]
161 | public Description Dismiss { get; set; }
162 |
163 | [JsonProperty("endpoint", NullValueHandling = NullValueHandling.Ignore)]
164 | public Endpoint Endpoint { get; set; }
165 |
166 | [JsonProperty("hovercardButton", NullValueHandling = NullValueHandling.Ignore)]
167 | public HovercardButton HovercardButton { get; set; }
168 |
169 | [JsonProperty("trackingParams", NullValueHandling = NullValueHandling.Ignore)]
170 | public string TrackingParams { get; set; }
171 |
172 | [JsonProperty("isSubscribe", NullValueHandling = NullValueHandling.Ignore)]
173 | public bool? IsSubscribe { get; set; }
174 |
175 | [JsonProperty("id", NullValueHandling = NullValueHandling.Ignore)]
176 | public String Id { get; set; }
177 |
178 | [JsonProperty("videoDuration", NullValueHandling = NullValueHandling.Ignore)]
179 | public Description VideoDuration { get; set; }
180 |
181 | [JsonProperty("playlistLength", NullValueHandling = NullValueHandling.Ignore)]
182 | public Description PlaylistLength { get; set; }
183 | }
184 |
185 | public partial class Description
186 | {
187 | [JsonProperty("simpleText", NullValueHandling = NullValueHandling.Ignore)]
188 | public string SimpleText { get; set; }
189 | }
190 |
191 | public partial class Endpoint
192 | {
193 | [JsonProperty("clickTrackingParams", NullValueHandling = NullValueHandling.Ignore)]
194 | public string ClickTrackingParams { get; set; }
195 |
196 | [JsonProperty("browseEndpoint", NullValueHandling = NullValueHandling.Ignore)]
197 | public BrowseEndpoint BrowseEndpoint { get; set; }
198 |
199 | [JsonProperty("watchEndpoint", NullValueHandling = NullValueHandling.Ignore)]
200 | public WatchEndpoint WatchEndpoint { get; set; }
201 | }
202 |
203 | public partial class BrowseEndpoint
204 | {
205 | [JsonProperty("browseId", NullValueHandling = NullValueHandling.Ignore)]
206 | public string BrowseId { get; set; }
207 | }
208 |
209 | public partial class WatchEndpoint
210 | {
211 | [JsonProperty("videoId", NullValueHandling = NullValueHandling.Ignore)]
212 | public string VideoId { get; set; }
213 |
214 | [JsonProperty("playlistId", NullValueHandling = NullValueHandling.Ignore)]
215 | public string PlaylistId { get; set; }
216 | }
217 |
218 | public partial class HovercardButton
219 | {
220 | [JsonProperty("subscribeButtonRenderer", NullValueHandling = NullValueHandling.Ignore)]
221 | public SubscribeButtonRenderer SubscribeButtonRenderer { get; set; }
222 | }
223 |
224 | public partial class SubscribeButtonRenderer
225 | {
226 | [JsonProperty("buttonText", NullValueHandling = NullValueHandling.Ignore)]
227 | public ButtonText ButtonText { get; set; }
228 |
229 | [JsonProperty("subscribed", NullValueHandling = NullValueHandling.Ignore)]
230 | public bool? Subscribed { get; set; }
231 |
232 | [JsonProperty("enabled", NullValueHandling = NullValueHandling.Ignore)]
233 | public bool? Enabled { get; set; }
234 |
235 | [JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)]
236 | public string Type { get; set; }
237 |
238 | [JsonProperty("channelId", NullValueHandling = NullValueHandling.Ignore)]
239 | public string ChannelId { get; set; }
240 |
241 | [JsonProperty("showPreferences", NullValueHandling = NullValueHandling.Ignore)]
242 | public bool? ShowPreferences { get; set; }
243 |
244 | [JsonProperty("subscribedButtonText", NullValueHandling = NullValueHandling.Ignore)]
245 | public ButtonText SubscribedButtonText { get; set; }
246 |
247 | [JsonProperty("unsubscribedButtonText", NullValueHandling = NullValueHandling.Ignore)]
248 | public ButtonText UnsubscribedButtonText { get; set; }
249 |
250 | [JsonProperty("trackingParams", NullValueHandling = NullValueHandling.Ignore)]
251 | public string TrackingParams { get; set; }
252 |
253 | [JsonProperty("unsubscribeButtonText", NullValueHandling = NullValueHandling.Ignore)]
254 | public ButtonText UnsubscribeButtonText { get; set; }
255 |
256 | [JsonProperty("serviceEndpoints", NullValueHandling = NullValueHandling.Ignore)]
257 | public List ServiceEndpoints { get; set; }
258 |
259 | [JsonProperty("subscribeAccessibility", NullValueHandling = NullValueHandling.Ignore)]
260 | public Accessibility SubscribeAccessibility { get; set; }
261 |
262 | [JsonProperty("unsubscribeAccessibility", NullValueHandling = NullValueHandling.Ignore)]
263 | public Accessibility UnsubscribeAccessibility { get; set; }
264 | }
265 |
266 | public partial class ButtonText
267 | {
268 | [JsonProperty("runs", NullValueHandling = NullValueHandling.Ignore)]
269 | public List Runs { get; set; }
270 | }
271 |
272 | public partial class Run
273 | {
274 | [JsonProperty("text", NullValueHandling = NullValueHandling.Ignore)]
275 | public string Text { get; set; }
276 | }
277 |
278 | public partial class ServiceEndpointElement
279 | {
280 | [JsonProperty("clickTrackingParams", NullValueHandling = NullValueHandling.Ignore)]
281 | public string ClickTrackingParams { get; set; }
282 |
283 | [JsonProperty("subscribeEndpoint", NullValueHandling = NullValueHandling.Ignore)]
284 | public SubscribeEndpoint SubscribeEndpoint { get; set; }
285 |
286 | [JsonProperty("signalServiceEndpoint", NullValueHandling = NullValueHandling.Ignore)]
287 | public SignalServiceEndpoint SignalServiceEndpoint { get; set; }
288 | }
289 |
290 | public partial class SignalServiceEndpoint
291 | {
292 | [JsonProperty("signal", NullValueHandling = NullValueHandling.Ignore)]
293 | public string Signal { get; set; }
294 |
295 | [JsonProperty("actions", NullValueHandling = NullValueHandling.Ignore)]
296 | public List Actions { get; set; }
297 | }
298 |
299 | public partial class Action
300 | {
301 | [JsonProperty("clickTrackingParams", NullValueHandling = NullValueHandling.Ignore)]
302 | public string ClickTrackingParams { get; set; }
303 |
304 | [JsonProperty("openPopupAction", NullValueHandling = NullValueHandling.Ignore)]
305 | public OpenPopupAction OpenPopupAction { get; set; }
306 | }
307 |
308 | public partial class OpenPopupAction
309 | {
310 | [JsonProperty("popup", NullValueHandling = NullValueHandling.Ignore)]
311 | public Popup Popup { get; set; }
312 |
313 | [JsonProperty("popupType", NullValueHandling = NullValueHandling.Ignore)]
314 | public string PopupType { get; set; }
315 | }
316 |
317 | public partial class Popup
318 | {
319 | [JsonProperty("confirmDialogRenderer", NullValueHandling = NullValueHandling.Ignore)]
320 | public ConfirmDialogRenderer ConfirmDialogRenderer { get; set; }
321 | }
322 |
323 | public partial class ConfirmDialogRenderer
324 | {
325 | [JsonProperty("trackingParams", NullValueHandling = NullValueHandling.Ignore)]
326 | public string TrackingParams { get; set; }
327 |
328 | [JsonProperty("dialogMessages", NullValueHandling = NullValueHandling.Ignore)]
329 | public List DialogMessages { get; set; }
330 |
331 | [JsonProperty("confirmButton", NullValueHandling = NullValueHandling.Ignore)]
332 | public Button ConfirmButton { get; set; }
333 |
334 | [JsonProperty("cancelButton", NullValueHandling = NullValueHandling.Ignore)]
335 | public Button CancelButton { get; set; }
336 |
337 | [JsonProperty("primaryIsCancel", NullValueHandling = NullValueHandling.Ignore)]
338 | public bool? PrimaryIsCancel { get; set; }
339 | }
340 |
341 | public partial class Button
342 | {
343 | [JsonProperty("buttonRenderer", NullValueHandling = NullValueHandling.Ignore)]
344 | public ButtonRenderer ButtonRenderer { get; set; }
345 | }
346 |
347 | public partial class ButtonRenderer
348 | {
349 | [JsonProperty("style", NullValueHandling = NullValueHandling.Ignore)]
350 | public string Style { get; set; }
351 |
352 | [JsonProperty("size", NullValueHandling = NullValueHandling.Ignore)]
353 | public string Size { get; set; }
354 |
355 | [JsonProperty("text", NullValueHandling = NullValueHandling.Ignore)]
356 | public ButtonText Text { get; set; }
357 |
358 | [JsonProperty("trackingParams", NullValueHandling = NullValueHandling.Ignore)]
359 | public string TrackingParams { get; set; }
360 |
361 | [JsonProperty("serviceEndpoint", NullValueHandling = NullValueHandling.Ignore)]
362 | public ButtonRendererServiceEndpoint ServiceEndpoint { get; set; }
363 | }
364 |
365 | public partial class ButtonRendererServiceEndpoint
366 | {
367 | [JsonProperty("clickTrackingParams", NullValueHandling = NullValueHandling.Ignore)]
368 | public string ClickTrackingParams { get; set; }
369 |
370 | [JsonProperty("unsubscribeEndpoint", NullValueHandling = NullValueHandling.Ignore)]
371 | public SubscribeEndpoint UnsubscribeEndpoint { get; set; }
372 | }
373 |
374 | public partial class SubscribeEndpoint
375 | {
376 | [JsonProperty("channelIds", NullValueHandling = NullValueHandling.Ignore)]
377 | public List ChannelIds { get; set; }
378 |
379 | [JsonProperty("params", NullValueHandling = NullValueHandling.Ignore)]
380 | public string Params { get; set; }
381 | }
382 |
383 | public partial class Accessibility
384 | {
385 | [JsonProperty("accessibilityData", NullValueHandling = NullValueHandling.Ignore)]
386 | public AccessibilityData AccessibilityData { get; set; }
387 | }
388 |
389 | public partial class AccessibilityData
390 | {
391 | [JsonProperty("label", NullValueHandling = NullValueHandling.Ignore)]
392 | public string Label { get; set; }
393 | }
394 |
395 | public partial class Icon
396 | {
397 | [JsonProperty("thumbnails", NullValueHandling = NullValueHandling.Ignore)]
398 | public List Thumbnails { get; set; }
399 | }
400 |
401 | public partial class IconThumbnail
402 | {
403 | [JsonProperty("url", NullValueHandling = NullValueHandling.Ignore)]
404 | public string Url { get; set; }
405 | }
406 |
407 | public partial class ImageClass
408 | {
409 | [JsonProperty("thumbnails", NullValueHandling = NullValueHandling.Ignore)]
410 | public List Thumbnails { get; set; }
411 | }
412 |
413 | public partial class ThumbnailThumbnail
414 | {
415 | [JsonProperty("url", NullValueHandling = NullValueHandling.Ignore)]
416 | public string Url { get; set; }
417 |
418 | [JsonProperty("width", NullValueHandling = NullValueHandling.Ignore)]
419 | public long? Width { get; set; }
420 |
421 | [JsonProperty("height", NullValueHandling = NullValueHandling.Ignore)]
422 | public long? Height { get; set; }
423 | }
424 |
425 | public partial class Title
426 | {
427 | [JsonProperty("accessibility", NullValueHandling = NullValueHandling.Ignore)]
428 | public Accessibility Accessibility { get; set; }
429 |
430 | [JsonProperty("simpleText", NullValueHandling = NullValueHandling.Ignore)]
431 | public string SimpleText { get; set; }
432 | }
433 |
434 | public partial class Microformat
435 | {
436 | [JsonProperty("playerMicroformatRenderer", NullValueHandling = NullValueHandling.Ignore)]
437 | public PlayerMicroformatRenderer PlayerMicroformatRenderer { get; set; }
438 | }
439 |
440 | public partial class PlayerMicroformatRenderer
441 | {
442 | [JsonProperty("thumbnail", NullValueHandling = NullValueHandling.Ignore)]
443 | public ImageClass Thumbnail { get; set; }
444 |
445 | [JsonProperty("embed", NullValueHandling = NullValueHandling.Ignore)]
446 | public Embed Embed { get; set; }
447 |
448 | [JsonProperty("title", NullValueHandling = NullValueHandling.Ignore)]
449 | public Description Title { get; set; }
450 |
451 | [JsonProperty("description", NullValueHandling = NullValueHandling.Ignore)]
452 | public Description Description { get; set; }
453 |
454 | [JsonProperty("lengthSeconds", NullValueHandling = NullValueHandling.Ignore)]
455 | [JsonConverter(typeof(ParseStringConverter))]
456 | public long? LengthSeconds { get; set; }
457 |
458 | [JsonProperty("ownerProfileUrl", NullValueHandling = NullValueHandling.Ignore)]
459 | public string OwnerProfileUrl { get; set; }
460 |
461 | [JsonProperty("ownerGplusProfileUrl", NullValueHandling = NullValueHandling.Ignore)]
462 | public string OwnerGplusProfileUrl { get; set; }
463 |
464 | [JsonProperty("externalChannelId", NullValueHandling = NullValueHandling.Ignore)]
465 | public string ExternalChannelId { get; set; }
466 |
467 | [JsonProperty("availableCountries", NullValueHandling = NullValueHandling.Ignore)]
468 | public List AvailableCountries { get; set; }
469 |
470 | [JsonProperty("isUnlisted", NullValueHandling = NullValueHandling.Ignore)]
471 | public bool? IsUnlisted { get; set; }
472 |
473 | [JsonProperty("hasYpcMetadata", NullValueHandling = NullValueHandling.Ignore)]
474 | public bool? HasYpcMetadata { get; set; }
475 |
476 | [JsonProperty("viewCount", NullValueHandling = NullValueHandling.Ignore)]
477 | [JsonConverter(typeof(ParseStringConverter))]
478 | public long? ViewCount { get; set; }
479 |
480 | [JsonProperty("category", NullValueHandling = NullValueHandling.Ignore)]
481 | public string Category { get; set; }
482 |
483 | [JsonProperty("publishDate", NullValueHandling = NullValueHandling.Ignore)]
484 | public DateTimeOffset? PublishDate { get; set; }
485 |
486 | [JsonProperty("ownerChannelName", NullValueHandling = NullValueHandling.Ignore)]
487 | public string OwnerChannelName { get; set; }
488 |
489 | [JsonProperty("uploadDate", NullValueHandling = NullValueHandling.Ignore)]
490 | public DateTimeOffset? UploadDate { get; set; }
491 | }
492 |
493 | public partial class Embed
494 | {
495 | [JsonProperty("iframeUrl", NullValueHandling = NullValueHandling.Ignore)]
496 | public string IframeUrl { get; set; }
497 |
498 | [JsonProperty("flashUrl", NullValueHandling = NullValueHandling.Ignore)]
499 | public string FlashUrl { get; set; }
500 |
501 | [JsonProperty("width", NullValueHandling = NullValueHandling.Ignore)]
502 | public long? Width { get; set; }
503 |
504 | [JsonProperty("height", NullValueHandling = NullValueHandling.Ignore)]
505 | public long? Height { get; set; }
506 |
507 | [JsonProperty("flashSecureUrl", NullValueHandling = NullValueHandling.Ignore)]
508 | public string FlashSecureUrl { get; set; }
509 | }
510 |
511 | public partial class PlayabilityStatus
512 | {
513 | [JsonProperty("status", NullValueHandling = NullValueHandling.Ignore)]
514 | public string Status { get; set; }
515 |
516 | [JsonProperty("playableInEmbed", NullValueHandling = NullValueHandling.Ignore)]
517 | public bool? PlayableInEmbed { get; set; }
518 | }
519 |
520 | public partial class PlaybackTracking
521 | {
522 | [JsonProperty("videostatsPlaybackUrl", NullValueHandling = NullValueHandling.Ignore)]
523 | public PtrackingUrlClass VideostatsPlaybackUrl { get; set; }
524 |
525 | [JsonProperty("videostatsDelayplayUrl", NullValueHandling = NullValueHandling.Ignore)]
526 | public PtrackingUrlClass VideostatsDelayplayUrl { get; set; }
527 |
528 | [JsonProperty("videostatsWatchtimeUrl", NullValueHandling = NullValueHandling.Ignore)]
529 | public PtrackingUrlClass VideostatsWatchtimeUrl { get; set; }
530 |
531 | [JsonProperty("ptrackingUrl", NullValueHandling = NullValueHandling.Ignore)]
532 | public PtrackingUrlClass PtrackingUrl { get; set; }
533 |
534 | [JsonProperty("qoeUrl", NullValueHandling = NullValueHandling.Ignore)]
535 | public PtrackingUrlClass QoeUrl { get; set; }
536 |
537 | [JsonProperty("setAwesomeUrl", NullValueHandling = NullValueHandling.Ignore)]
538 | public AtrUrlClass SetAwesomeUrl { get; set; }
539 |
540 | [JsonProperty("atrUrl", NullValueHandling = NullValueHandling.Ignore)]
541 | public AtrUrlClass AtrUrl { get; set; }
542 |
543 | [JsonProperty("youtubeRemarketingUrl", NullValueHandling = NullValueHandling.Ignore)]
544 | public AtrUrlClass YoutubeRemarketingUrl { get; set; }
545 | }
546 |
547 | public partial class AtrUrlClass
548 | {
549 | [JsonProperty("baseUrl", NullValueHandling = NullValueHandling.Ignore)]
550 | public string BaseUrl { get; set; }
551 |
552 | [JsonProperty("elapsedMediaTimeSeconds", NullValueHandling = NullValueHandling.Ignore)]
553 | public long? ElapsedMediaTimeSeconds { get; set; }
554 | }
555 |
556 | public partial class PtrackingUrlClass
557 | {
558 | [JsonProperty("baseUrl", NullValueHandling = NullValueHandling.Ignore)]
559 | public string BaseUrl { get; set; }
560 | }
561 |
562 | public partial class PlayerConfig
563 | {
564 | [JsonProperty("audioConfig", NullValueHandling = NullValueHandling.Ignore)]
565 | public AudioConfig AudioConfig { get; set; }
566 |
567 | [JsonProperty("streamSelectionConfig", NullValueHandling = NullValueHandling.Ignore)]
568 | public StreamSelectionConfig StreamSelectionConfig { get; set; }
569 |
570 | [JsonProperty("mediaCommonConfig", NullValueHandling = NullValueHandling.Ignore)]
571 | public MediaCommonConfig MediaCommonConfig { get; set; }
572 | }
573 |
574 | public partial class AudioConfig
575 | {
576 | [JsonProperty("loudnessDb", NullValueHandling = NullValueHandling.Ignore)]
577 | public double? LoudnessDb { get; set; }
578 |
579 | [JsonProperty("perceptualLoudnessDb", NullValueHandling = NullValueHandling.Ignore)]
580 | public double? PerceptualLoudnessDb { get; set; }
581 |
582 | [JsonProperty("enablePerFormatLoudness", NullValueHandling = NullValueHandling.Ignore)]
583 | public bool? EnablePerFormatLoudness { get; set; }
584 | }
585 |
586 | public partial class MediaCommonConfig
587 | {
588 | [JsonProperty("dynamicReadaheadConfig", NullValueHandling = NullValueHandling.Ignore)]
589 | public DynamicReadaheadConfig DynamicReadaheadConfig { get; set; }
590 | }
591 |
592 | public partial class DynamicReadaheadConfig
593 | {
594 | [JsonProperty("maxReadAheadMediaTimeMs", NullValueHandling = NullValueHandling.Ignore)]
595 | public long? MaxReadAheadMediaTimeMs { get; set; }
596 |
597 | [JsonProperty("minReadAheadMediaTimeMs", NullValueHandling = NullValueHandling.Ignore)]
598 | public long? MinReadAheadMediaTimeMs { get; set; }
599 |
600 | [JsonProperty("readAheadGrowthRateMs", NullValueHandling = NullValueHandling.Ignore)]
601 | public long? ReadAheadGrowthRateMs { get; set; }
602 | }
603 |
604 | public partial class StreamSelectionConfig
605 | {
606 | [JsonProperty("maxBitrate", NullValueHandling = NullValueHandling.Ignore)]
607 | [JsonConverter(typeof(ParseStringConverter))]
608 | public long? MaxBitrate { get; set; }
609 | }
610 |
611 | public partial class Storyboards
612 | {
613 | [JsonProperty("playerStoryboardSpecRenderer", NullValueHandling = NullValueHandling.Ignore)]
614 | public PlayerStoryboardSpecRenderer PlayerStoryboardSpecRenderer { get; set; }
615 | }
616 |
617 | public partial class PlayerStoryboardSpecRenderer
618 | {
619 | [JsonProperty("spec", NullValueHandling = NullValueHandling.Ignore)]
620 | public string Spec { get; set; }
621 | }
622 |
623 | public partial class StreamingData
624 | {
625 | [JsonProperty("expiresInSeconds", NullValueHandling = NullValueHandling.Ignore)]
626 | [JsonConverter(typeof(ParseStringConverter))]
627 | public long? ExpiresInSeconds { get; set; }
628 |
629 | [JsonProperty("formats", NullValueHandling = NullValueHandling.Ignore)]
630 | public List Formats { get; set; }
631 |
632 | [JsonProperty("adaptiveFormats", NullValueHandling = NullValueHandling.Ignore)]
633 | public List AdaptiveFormats { get; set; }
634 | }
635 |
636 | public partial class Format
637 | {
638 | [JsonProperty("itag", NullValueHandling = NullValueHandling.Ignore)]
639 | public long? Itag { get; set; }
640 |
641 | [JsonProperty("url", NullValueHandling = NullValueHandling.Ignore)]
642 | public string Url { get; set; }
643 |
644 | [JsonProperty("mimeType", NullValueHandling = NullValueHandling.Ignore)]
645 | public string MimeType { get; set; }
646 |
647 | [JsonProperty("bitrate", NullValueHandling = NullValueHandling.Ignore)]
648 | public long? Bitrate { get; set; }
649 |
650 | [JsonProperty("width", NullValueHandling = NullValueHandling.Ignore)]
651 | public long? Width { get; set; }
652 |
653 | [JsonProperty("height", NullValueHandling = NullValueHandling.Ignore)]
654 | public long? Height { get; set; }
655 |
656 | [JsonProperty("initRange", NullValueHandling = NullValueHandling.Ignore)]
657 | public Range InitRange { get; set; }
658 |
659 | [JsonProperty("indexRange", NullValueHandling = NullValueHandling.Ignore)]
660 | public Range IndexRange { get; set; }
661 |
662 | [JsonProperty("lastModified", NullValueHandling = NullValueHandling.Ignore)]
663 | public string LastModified { get; set; }
664 |
665 | [JsonProperty("contentLength", NullValueHandling = NullValueHandling.Ignore)]
666 | [JsonConverter(typeof(ParseStringConverter))]
667 | public long? ContentLength { get; set; }
668 |
669 | [JsonProperty("quality", NullValueHandling = NullValueHandling.Ignore)]
670 | public string Quality { get; set; }
671 |
672 | [JsonProperty("fps", NullValueHandling = NullValueHandling.Ignore)]
673 | public long? Fps { get; set; }
674 |
675 | [JsonProperty("qualityLabel", NullValueHandling = NullValueHandling.Ignore)]
676 | public string QualityLabel { get; set; }
677 |
678 | [JsonProperty("projectionType", NullValueHandling = NullValueHandling.Ignore)]
679 | public ProjectionType? ProjectionType { get; set; }
680 |
681 | [JsonProperty("averageBitrate", NullValueHandling = NullValueHandling.Ignore)]
682 | public long? AverageBitrate { get; set; }
683 |
684 | [JsonProperty("approxDurationMs", NullValueHandling = NullValueHandling.Ignore)]
685 | [JsonConverter(typeof(ParseStringConverter))]
686 | public long? ApproxDurationMs { get; set; }
687 |
688 | [JsonProperty("colorInfo", NullValueHandling = NullValueHandling.Ignore)]
689 | public ColorInfo ColorInfo { get; set; }
690 |
691 | [JsonProperty("highReplication", NullValueHandling = NullValueHandling.Ignore)]
692 | public bool? HighReplication { get; set; }
693 |
694 | [JsonProperty("audioQuality", NullValueHandling = NullValueHandling.Ignore)]
695 | public string AudioQuality { get; set; }
696 |
697 | [JsonProperty("audioSampleRate", NullValueHandling = NullValueHandling.Ignore)]
698 | [JsonConverter(typeof(ParseStringConverter))]
699 | public long? AudioSampleRate { get; set; }
700 |
701 | [JsonProperty("audioChannels", NullValueHandling = NullValueHandling.Ignore)]
702 | public long? AudioChannels { get; set; }
703 |
704 | [JsonProperty("cipher", NullValueHandling = NullValueHandling.Ignore)]
705 | public string Cipher { get; set; }
706 |
707 | [JsonProperty("signatureCipher", NullValueHandling = NullValueHandling.Ignore)]
708 | public string SignatureCipher { get; set; }
709 | }
710 |
711 | public partial class ColorInfo
712 | {
713 | [JsonProperty("primaries", NullValueHandling = NullValueHandling.Ignore)]
714 | public string Primaries { get; set; }
715 |
716 | [JsonProperty("transferCharacteristics", NullValueHandling = NullValueHandling.Ignore)]
717 | public string TransferCharacteristics { get; set; }
718 |
719 | [JsonProperty("matrixCoefficients", NullValueHandling = NullValueHandling.Ignore)]
720 | public string MatrixCoefficients { get; set; }
721 | }
722 |
723 | public partial class Range
724 | {
725 | [JsonProperty("start", NullValueHandling = NullValueHandling.Ignore)]
726 | [JsonConverter(typeof(ParseStringConverter))]
727 | public long? Start { get; set; }
728 |
729 | [JsonProperty("end", NullValueHandling = NullValueHandling.Ignore)]
730 | [JsonConverter(typeof(ParseStringConverter))]
731 | public long? End { get; set; }
732 | }
733 |
734 | public partial class VideoDetails
735 | {
736 | [JsonProperty("videoId", NullValueHandling = NullValueHandling.Ignore)]
737 | public string VideoId { get; set; }
738 |
739 | [JsonProperty("title", NullValueHandling = NullValueHandling.Ignore)]
740 | public string Title { get; set; }
741 |
742 | [JsonProperty("lengthSeconds", NullValueHandling = NullValueHandling.Ignore)]
743 | [JsonConverter(typeof(ParseStringConverter))]
744 | public long? LengthSeconds { get; set; }
745 |
746 | [JsonProperty("keywords", NullValueHandling = NullValueHandling.Ignore)]
747 | public List Keywords { get; set; }
748 |
749 | [JsonProperty("channelId", NullValueHandling = NullValueHandling.Ignore)]
750 | public string ChannelId { get; set; }
751 |
752 | [JsonProperty("isOwnerViewing", NullValueHandling = NullValueHandling.Ignore)]
753 | public bool? IsOwnerViewing { get; set; }
754 |
755 | [JsonProperty("shortDescription", NullValueHandling = NullValueHandling.Ignore)]
756 | public string ShortDescription { get; set; }
757 |
758 | [JsonProperty("isCrawlable", NullValueHandling = NullValueHandling.Ignore)]
759 | public bool? IsCrawlable { get; set; }
760 |
761 | [JsonProperty("thumbnail", NullValueHandling = NullValueHandling.Ignore)]
762 | public ImageClass Thumbnail { get; set; }
763 |
764 | [JsonProperty("averageRating", NullValueHandling = NullValueHandling.Ignore)]
765 | public double? AverageRating { get; set; }
766 |
767 | [JsonProperty("allowRatings", NullValueHandling = NullValueHandling.Ignore)]
768 | public bool? AllowRatings { get; set; }
769 |
770 | [JsonProperty("viewCount", NullValueHandling = NullValueHandling.Ignore)]
771 | [JsonConverter(typeof(ParseStringConverter))]
772 | public long? ViewCount { get; set; }
773 |
774 | [JsonProperty("author", NullValueHandling = NullValueHandling.Ignore)]
775 | public string Author { get; set; }
776 |
777 | [JsonProperty("isPrivate", NullValueHandling = NullValueHandling.Ignore)]
778 | public bool? IsPrivate { get; set; }
779 |
780 | [JsonProperty("isUnpluggedCorpus", NullValueHandling = NullValueHandling.Ignore)]
781 | public bool? IsUnpluggedCorpus { get; set; }
782 |
783 | [JsonProperty("isLiveContent", NullValueHandling = NullValueHandling.Ignore)]
784 | public bool? IsLiveContent { get; set; }
785 | }
786 |
787 | public enum ProjectionType { Rectangular };
788 |
789 | public partial class Model
790 | {
791 | public static Model FromJson(string json) => JsonConvert.DeserializeObject(json, Converter.Settings);
792 | }
793 |
794 | public static class Serialize
795 | {
796 | public static string ToJson(this Model self) => JsonConvert.SerializeObject(self, Converter.Settings);
797 | }
798 |
799 | internal static class Converter
800 | {
801 | public static readonly JsonSerializerSettings Settings = new JsonSerializerSettings
802 | {
803 | MetadataPropertyHandling = MetadataPropertyHandling.Ignore,
804 | DateParseHandling = DateParseHandling.None,
805 | Converters =
806 | {
807 | ProjectionTypeConverter.Singleton,
808 | new IsoDateTimeConverter { DateTimeStyles = DateTimeStyles.AssumeUniversal }
809 | },
810 | };
811 | }
812 |
813 | internal class ParseStringConverter : JsonConverter
814 | {
815 | public override bool CanConvert(Type t) => t == typeof(long) || t == typeof(long?);
816 |
817 | public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer)
818 | {
819 | if (reader.TokenType == JsonToken.Null) return null;
820 | var value = serializer.Deserialize(reader);
821 | long l;
822 | if (Int64.TryParse(value, out l))
823 | {
824 | return l;
825 | }
826 | throw new Exception("Cannot unmarshal type long");
827 | }
828 |
829 | public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer)
830 | {
831 | if (untypedValue == null)
832 | {
833 | serializer.Serialize(writer, null);
834 | return;
835 | }
836 | var value = (long)untypedValue;
837 | serializer.Serialize(writer, value.ToString());
838 | return;
839 | }
840 |
841 | public static readonly ParseStringConverter Singleton = new ParseStringConverter();
842 | }
843 |
844 | internal class ProjectionTypeConverter : JsonConverter
845 | {
846 | public override bool CanConvert(Type t) => t == typeof(ProjectionType) || t == typeof(ProjectionType?);
847 |
848 | public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer)
849 | {
850 | if (reader.TokenType == JsonToken.Null) return null;
851 | var value = serializer.Deserialize(reader);
852 | if (value == "RECTANGULAR")
853 | {
854 | return ProjectionType.Rectangular;
855 | }
856 | throw new Exception("Cannot unmarshal type ProjectionType");
857 | }
858 |
859 | public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer)
860 | {
861 | if (untypedValue == null)
862 | {
863 | serializer.Serialize(writer, null);
864 | return;
865 | }
866 | var value = (ProjectionType)untypedValue;
867 | if (value == ProjectionType.Rectangular)
868 | {
869 | serializer.Serialize(writer, "RECTANGULAR");
870 | return;
871 | }
872 | throw new Exception("Cannot marshal type ProjectionType");
873 | }
874 |
875 | public static readonly ProjectionTypeConverter Singleton = new ProjectionTypeConverter();
876 | }
877 | }
--------------------------------------------------------------------------------