Skip to content
This repository was archived by the owner on Sep 28, 2020. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion YoutubeExtractor/YoutubeExtractor/AudioDownloader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ private void ExtractAudio(string path)
{
if (this.AudioExtractionProgressChanged != null)
{
this.AudioExtractionProgressChanged(this, new ProgressEventArgs(args.ProgressPercentage));
this.AudioExtractionProgressChanged(this, args);
}
};

Expand Down
1 change: 1 addition & 0 deletions YoutubeExtractor/YoutubeExtractor/AudioType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ public enum AudioType
Aac,
Mp3,
Vorbis,
Opus,

/// <summary>
/// The audio type is unknown. This can occur if YoutubeExtractor is not up-to-date.
Expand Down
2 changes: 1 addition & 1 deletion YoutubeExtractor/YoutubeExtractor/Decipherer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public static string DecipherWithVersion(string cipher, string cipherVersion)
string js = HttpHelper.DownloadString(jsUrl);

//Find "C" in this: var A = B.sig||C (B.s)
string functNamePattern = @"\.sig\s*\|\|([a-zA-Z0-9\$]+)\("; //Regex Formed To Find Word or DollarSign
string functNamePattern = @"\""signature"",\s?([a-zA-Z0-9\$]+)\(";

var funcName = Regex.Match(js, functNamePattern).Groups[1].Value;

Expand Down
97 changes: 96 additions & 1 deletion YoutubeExtractor/YoutubeExtractor/DownloadUrlResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Linq;
using System.Net;
using System.Text.RegularExpressions;
using System.Xml;
using Newtonsoft.Json.Linq;

namespace YoutubeExtractor
Expand Down Expand Up @@ -91,10 +92,22 @@ public static IEnumerable<VideoInfo> GetDownloadUrls(string videoUrl, bool decry

IEnumerable<ExtractionInfo> downloadUrls = ExtractDownloadUrls(json);

IEnumerable<VideoInfo> infos = GetVideoInfos(downloadUrls, videoTitle).ToList();
List<VideoInfo> infos = GetVideoInfos(downloadUrls, videoTitle).ToList();

string dashManifestUrl = GetDashManifest(json);

string htmlPlayerVersion = GetHtml5PlayerVersion(json);

// Query dash manifest URL for additional formats
if (!string.IsNullOrEmpty(dashManifestUrl)) {
string signature = ExtractSignatureFromManifest(dashManifestUrl);
if (!string.IsNullOrEmpty(signature)) {
string decrypt = GetDecipheredSignature(signature, htmlPlayerVersion);
dashManifestUrl = dashManifestUrl.Replace(signature, decrypt).Replace("/s/", "/signature/");
}
ParseDashManifest(dashManifestUrl, infos, videoTitle);
}

foreach (VideoInfo info in infos)
{
info.HtmlPlayerVersion = htmlPlayerVersion;
Expand Down Expand Up @@ -228,6 +241,20 @@ private static string GetDecipheredSignature(string htmlPlayerVersion, string si
return Decipherer.DecipherWithVersion(signature, htmlPlayerVersion);
}

/// <summary>
/// Extracts the signature from the DASH Manifest which is located after /s/.
/// </summary>
/// <param name="manifestUrl">The DASH Manifest URL to extract from.</param>
/// <returns>The extracted signature.</returns>
private static string ExtractSignatureFromManifest(string manifestUrl) {
string[] Params = manifestUrl.Split('/');
for (int i = 0; i < Params.Length; i++) {
if (Params[i] == "s" && i < Params.Length - 1)
return Params[i + 1];
}
return string.Empty;
}

private static string GetHtml5PlayerVersion(JObject json)
{
var regex = new Regex(@"player-(.+?).js");
Expand Down Expand Up @@ -287,13 +314,41 @@ private static IEnumerable<VideoInfo> GetVideoInfos(IEnumerable<ExtractionInfo>
return downLoadInfos;
}

public static VideoInfo GetSingleVideoInfo(int formatCode, string queryUrl, string videoTitle, bool requiresDecryption) {
var Params = HttpHelper.ParseQueryString(queryUrl);

VideoInfo info = VideoInfo.Defaults.SingleOrDefault(videoInfo => videoInfo.FormatCode == formatCode);

if (info != null) {
long fileSize = Params.ContainsKey("clen") ? long.Parse(Params["clen"]) : 0;
info = new VideoInfo(info) {
DownloadUrl = queryUrl,
Title = videoTitle,
RequiresDecryption = requiresDecryption,
FileSize = fileSize
};
} else {
info = new VideoInfo(formatCode) {
DownloadUrl = queryUrl
};
}

return info;
}

private static string GetVideoTitle(JObject json)
{
JToken title = json["args"]["title"];

return title == null ? String.Empty : title.ToString();
}

private static string GetDashManifest(JObject json) {
JToken manifest = json["args"]["dashmpd"];

return manifest == null ? String.Empty : manifest.ToString();
}

private static bool IsVideoUnavailable(string pageSource)
{
const string unavailableContainer = "<div id=\"watch-player-unavailable\">";
Expand All @@ -317,6 +372,46 @@ private static JObject LoadJson(string url)
return JObject.Parse(extractedJson);
}

private static void ParseDashManifest(string dashManifestUrl, List<VideoInfo> previousFormats, string videoTitle) {
string pageSource = HttpHelper.DownloadString(dashManifestUrl);

XmlDocument doc = new XmlDocument();
XmlNamespaceManager docNamespace = new XmlNamespaceManager(doc.NameTable);
docNamespace.AddNamespace("urn", "urn:mpeg:DASH:schema:MPD:2011");
docNamespace.AddNamespace("yd", "http://youtube.com/yt/2012/10/10");
doc.LoadXml(pageSource);
XmlNodeList ManifestList = doc.SelectNodes("//urn:Representation", docNamespace);
foreach (XmlElement item in ManifestList) {
int FormatCode = int.Parse(item.GetAttribute("id"));
XmlNode BaseUrl = item.GetElementsByTagName("BaseURL").Item(0);
VideoInfo info = GetSingleVideoInfo(FormatCode, BaseUrl.InnerText, videoTitle, false);
if (item.HasAttribute("height"))
info.Resolution = int.Parse(item.GetAttribute("height"));
if (item.HasAttribute("frameRate"))
info.FrameRate = int.Parse(item.GetAttribute("frameRate"));

VideoInfo DeleteItem = previousFormats.SingleOrDefault(v => v.FormatCode == FormatCode);
if (DeleteItem != null)
previousFormats.Remove(DeleteItem);
previousFormats.Add(info);
}
}

/// <summary>
/// Non-DASH videos don't provide file size. Queries the server to know the stream size.
/// </summary>
/// <param name="info">The information of the stream to get the size for.</param>
/// <returns>The stream size in bytes.</returns>
public static void QueryStreamSize(VideoInfo info) {
if (info.RequiresDecryption)
DecryptDownloadUrl(info);

var request = (HttpWebRequest)WebRequest.Create(info.DownloadUrl);
using (WebResponse response = request.GetResponse()) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should use request.BeginGetResponse() here instead.

info.FileSize = (int)response.ContentLength;
}
}

private static void ThrowYoutubeParseException(Exception innerException, string videoUrl)
{
throw new YoutubeParseException("Could not parse the Youtube page for URL " + videoUrl + "\n" +
Expand Down
2 changes: 1 addition & 1 deletion YoutubeExtractor/YoutubeExtractor/FlvFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ public void ExtractStreams()

if (this.ConversionProgressChanged != null)
{
this.ConversionProgressChanged(this, new ProgressEventArgs(progress));
this.ConversionProgressChanged(this, new ProgressEventArgs((int)this.fileOffset, progress));
}
}

Expand Down
8 changes: 7 additions & 1 deletion YoutubeExtractor/YoutubeExtractor/ProgressEventArgs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ namespace YoutubeExtractor
{
public class ProgressEventArgs : EventArgs
{
public ProgressEventArgs(double progressPercentage)
public ProgressEventArgs(int progressBytes, double progressPercentage)
{
this.ProgressBytes = progressBytes;
this.ProgressPercentage = progressPercentage;
}

Expand All @@ -14,6 +15,11 @@ public ProgressEventArgs(double progressPercentage)
/// </summary>
public bool Cancel { get; set; }

/// <summary>
/// Gets the progress in bytes downloaded.
/// </summary>
public int ProgressBytes { get; private set; }

/// <summary>
/// Gets the progress percentage in a range from 0.0 to 100.0.
/// </summary>
Expand Down
8 changes: 7 additions & 1 deletion YoutubeExtractor/YoutubeExtractor/VideoDownloader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ public VideoDownloader(VideoInfo video, string savePath, int? bytesToDownload =
/// </summary>
public event EventHandler<ProgressEventArgs> DownloadProgressChanged;

/// <summary>
/// Returns the size of the current download in bytes.
/// </summary>
public int DownloadSize { get; private set; }

/// <summary>
/// Starts the video download.
/// </summary>
Expand All @@ -46,6 +51,7 @@ public override void Execute()
{
using (Stream source = response.GetResponseStream())
{
DownloadSize = (int)response.ContentLength;
using (FileStream target = File.Open(this.SavePath, FileMode.Create, FileAccess.Write))
{
var buffer = new byte[1024];
Expand All @@ -59,7 +65,7 @@ public override void Execute()

copiedBytes += bytes;

var eventArgs = new ProgressEventArgs((copiedBytes * 1.0 / response.ContentLength) * 100);
var eventArgs = new ProgressEventArgs(copiedBytes, (copiedBytes * 1.0 / response.ContentLength) * 100);

if (this.DownloadProgressChanged != null)
{
Expand Down
34 changes: 30 additions & 4 deletions YoutubeExtractor/YoutubeExtractor/VideoInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,24 +49,39 @@ public class VideoInfo
new VideoInfo(271, VideoType.WebM, 1440, false, AudioType.Unknown, 0, AdaptiveType.Video),
new VideoInfo(272, VideoType.WebM, 2160, false, AudioType.Unknown, 0, AdaptiveType.Video),
new VideoInfo(278, VideoType.WebM, 144, false, AudioType.Unknown, 0, AdaptiveType.Video),
new VideoInfo(302, VideoType.WebM, 720, false, AudioType.Unknown, 0, AdaptiveType.Video, 60),
new VideoInfo(303, VideoType.WebM, 1080, false, AudioType.Unknown, 0, AdaptiveType.Video, 60),
new VideoInfo(308, VideoType.WebM, 1440, false, AudioType.Unknown, 0, AdaptiveType.Video, 60),
new VideoInfo(313, VideoType.WebM, 2160, false, AudioType.Unknown, 0, AdaptiveType.Video),
new VideoInfo(315, VideoType.WebM, 2160, false, AudioType.Unknown, 0, AdaptiveType.Video, 60),
new VideoInfo(298, VideoType.Mp4, 720, false, AudioType.Unknown, 0, AdaptiveType.Video, 60),
new VideoInfo(299, VideoType.Mp4, 1080, false, AudioType.Unknown, 0, AdaptiveType.Video, 60),
new VideoInfo(266, VideoType.Mp4, 2160, false, AudioType.Unknown, 0, AdaptiveType.Video),

/* Adaptive (aka DASH) - Audio */
new VideoInfo(139, VideoType.Mp4, 0, false, AudioType.Aac, 48, AdaptiveType.Audio),
new VideoInfo(140, VideoType.Mp4, 0, false, AudioType.Aac, 128, AdaptiveType.Audio),
new VideoInfo(141, VideoType.Mp4, 0, false, AudioType.Aac, 256, AdaptiveType.Audio),
new VideoInfo(171, VideoType.WebM, 0, false, AudioType.Vorbis, 128, AdaptiveType.Audio),
new VideoInfo(172, VideoType.WebM, 0, false, AudioType.Vorbis, 192, AdaptiveType.Audio)
new VideoInfo(172, VideoType.WebM, 0, false, AudioType.Vorbis, 192, AdaptiveType.Audio),
new VideoInfo(249, VideoType.WebM, 0, false, AudioType.Opus, 50, AdaptiveType.Audio),
new VideoInfo(250, VideoType.WebM, 0, false, AudioType.Opus, 70, AdaptiveType.Audio),
new VideoInfo(251, VideoType.WebM, 0, false, AudioType.Opus, 160, AdaptiveType.Audio),
};

internal VideoInfo(int formatCode)
: this(formatCode, VideoType.Unknown, 0, false, AudioType.Unknown, 0, AdaptiveType.None)
: this(formatCode, VideoType.Unknown, 0, false, AudioType.Unknown, 0, AdaptiveType.None, 0)
{ }

internal VideoInfo(VideoInfo info)
: this(info.FormatCode, info.VideoType, info.Resolution, info.Is3D, info.AudioType, info.AudioBitrate, info.AdaptiveType)
: this(info.FormatCode, info.VideoType, info.Resolution, info.Is3D, info.AudioType, info.AudioBitrate, info.AdaptiveType, info.FrameRate)
{ }

private VideoInfo(int formatCode, VideoType videoType, int resolution, bool is3D, AudioType audioType, int audioBitrate, AdaptiveType adaptiveType)
: this(formatCode, videoType, resolution, is3D, audioType, audioBitrate, adaptiveType, 0)
{ }

private VideoInfo(int formatCode, VideoType videoType, int resolution, bool is3D, AudioType audioType, int audioBitrate, AdaptiveType adaptiveType, int frameRate)
{
this.FormatCode = formatCode;
this.VideoType = videoType;
Expand All @@ -75,6 +90,7 @@ private VideoInfo(int formatCode, VideoType videoType, int resolution, bool is3D
this.AudioType = audioType;
this.AudioBitrate = audioBitrate;
this.AdaptiveType = adaptiveType;
this.FrameRate = frameRate;
}

/// <summary>
Expand All @@ -92,6 +108,11 @@ private VideoInfo(int formatCode, VideoType videoType, int resolution, bool is3D
/// <value>The approximate audio bitrate in kbit/s, or 0 if the bitrate is unknown.</value>
public int AudioBitrate { get; private set; }

/// <summary>
/// The frame rate of the video, usually 60 or 0 for unspecified.
/// </summary>
public int FrameRate { get; internal set; }

/// <summary>
/// Gets the audio extension.
/// </summary>
Expand Down Expand Up @@ -153,11 +174,16 @@ public bool CanExtractAudio
/// </summary>
public bool RequiresDecryption { get; internal set; }

/// <summary>
/// Gets the size of the stream in bytes.
/// </summary>
public long FileSize { get; internal set; }

/// <summary>
/// Gets the resolution of the video.
/// </summary>
/// <value>The resolution of the video, or 0 if the resolution is unkown.</value>
public int Resolution { get; private set; }
public int Resolution { get; internal set; }

/// <summary>
/// Gets the video title.
Expand Down
6 changes: 4 additions & 2 deletions YoutubeExtractor/YoutubeExtractor/YoutubeExtractor.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,14 @@
<DebugSymbols>true</DebugSymbols>
</PropertyGroup>
<ItemGroup>
<Reference Include="Newtonsoft.Json, Version=4.5.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.5.0.8\lib\net35\Newtonsoft.Json.dll</HintPath>
<Reference Include="Newtonsoft.Json, Version=9.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\..\..\NaturalGroundingPlayer\packages\Newtonsoft.Json.9.0.1\lib\net35\Newtonsoft.Json.dll</HintPath>
Copy link

@Code1110 Code1110 Mar 1, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be <HintPath>..\packages\Newtonsoft.Json.9.0.1\lib\net35\Newtonsoft.Json.dll</HintPath> instead.

<Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Web" />
<Reference Include="System.XML" />
</ItemGroup>
<ItemGroup>
<Compile Include="AacAudioExtractor.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Newtonsoft.Json" version="5.0.8" targetFramework="net35" />
<package id="Newtonsoft.Json" version="9.0.1" targetFramework="net35" />
</packages>