Skip to content
Draft
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: 2 additions & 0 deletions src/YTSearch.NET.TestConsole/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ static async Task Main(string[] args)

//var a = await client.SearchYouTubeChannelAsync("rick astley");
var a = await client.GetVideoMetadataAsync("qUeud6DvOWI");
//var a = (await client.SearchYoutubeVideoAsync("never gonna give you up")).Results.First().Url;
//var a = (await client.SearchYoutubeVideoAsync("beautiful girls"));

Debugger.Break();

Expand Down
2 changes: 1 addition & 1 deletion src/YTSearch.NET.Tests/YouTubeSearchTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public async Task SearchYoutube_TitleSearchWithoutFlags_CorrectVideoDetails()
Assert.That(a.Url, Is.EqualTo("https://www.youtube.com/watch?v=dQw4w9WgXcQ"));
Assert.That(a.Length, Is.EqualTo(TimeSpan.FromSeconds(213)));
Assert.That(a.Author, Is.EqualTo("Rick Astley"));
Assert.That(a.Published, Is.EqualTo("13 years ago"));
Assert.That(a.Published, Is.EqualTo("14 years ago"));
});
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@
using System.Text;
using System.Text.Json.Nodes;
using System.Threading.Tasks;
using YTSearch.NET.Models;

namespace YTSearch.NET
namespace YTSearch.NET.Models
{
public class SearchedYouTubeChannel
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
using System;
using YTSearch.NET.Models;

namespace YTSearch.NET
namespace YTSearch.NET.Models
{
/// <summary>
/// Class <c>SearchedYoutubeVideo</c> inherits from <seealso cref="BaseYouTubeVideo"/> and shows extra video information extracted from video searchess
/// Class <c>SearchedYoutubeVideo</c> inherits from <seealso cref="YouTubeVideoBase"/> and shows extra video information extracted from video searchess
/// </summary>
public class SearchedYouTubeVideo : BaseYouTubeVideo
public class SearchedYouTubeVideo : YouTubeVideoBase
{
public SearchedYouTubeVideo(string? title, string? videoId, Thumbnail[] thumbnails, TimeSpan length, string? author, int? views, string? published)
public SearchedYouTubeVideo(string? title, string? videoId, Thumbnail[] thumbnails, TimeSpan length, string? author, int? views, string? published)
: base(title, videoId, thumbnails, length, author, views)
{
Published = published;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
using System.Collections.Generic;

namespace YTSearch.NET
namespace YTSearch.NET.Models
{
public class YouTubeChannelSearchResult : YouTubeSearchResult
{
public YouTubeChannelSearchResult(string query, string url, ICollection<SearchedYouTubeChannel> results)
public YouTubeChannelSearchResult(string query, string url, ICollection<SearchedYouTubeChannel> results)
: base(query, url)
{
Results = results;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
using System.Text;
using System.Threading.Tasks;

namespace YTSearch.NET
namespace YTSearch.NET.Models
{
public abstract class YouTubeSearchResult
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
using System;
using YTSearch.NET.Models;

namespace YTSearch.NET
namespace YTSearch.NET.Models
{
/// <summary>
/// Abstract class <c>BaseYouTubeVideo</c> shows basic extracted information for a YouTube video
/// </summary>
public abstract class BaseYouTubeVideo

public abstract class YouTubeVideoBase
{
public BaseYouTubeVideo(string? title, string? videoId, Thumbnail[] thumbnails, TimeSpan length, string? author, int? views)
public YouTubeVideoBase(string? title, string? videoId, Thumbnail[] thumbnails, TimeSpan length, string? author, int? views)
{
Title = title;
VideoId = videoId;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
using System.Collections.Generic;

namespace YTSearch.NET
namespace YTSearch.NET.Models
{
public class YouTubeVideoSearchResult : YouTubeSearchResult
{
#region Search result
/// <summary>
/// Search result class
/// </summary>
public YouTubeVideoSearchResult(string query, string url, ICollection<SearchedYouTubeVideo> results)
:base(query, url)
public YouTubeVideoSearchResult(string query, string url, ICollection<SearchedYouTubeVideo> results)
: base(query, url)
{
Results = results;
}
Expand Down
4 changes: 2 additions & 2 deletions src/YTSearch.NET/QueriedYouTubeVideo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
namespace YTSearch.NET
{
/// <summary>
/// Class <c>QueriedYouTubeVideo</c> inherits from <seealso cref="BaseYouTubeVideo"/> and shows extra video information extracted from a YouTube video
/// Class <c>QueriedYouTubeVideo</c> inherits from <seealso cref="YouTubeVideoBase"/> and shows extra video information extracted from a YouTube video
/// </summary>
public class QueriedYouTubeVideo : BaseYouTubeVideo
public class QueriedYouTubeVideo : YouTubeVideoBase
{
public QueriedYouTubeVideo(string? title, string? videoId, Thumbnail[] thumbnails, TimeSpan length, string? author, int? views, string[]? keywords, string? description, bool? isCrawlable, bool? isRatingEnabled, bool? isPrivate, bool? isLiveContent, DateTime publishedDate, DateTime uploadedDate, bool? isFamilyFriendly, string[]? availableCountries, bool? isUnlisted, string? category)
: base(title, videoId, thumbnails, length, author, views)
Expand Down
85 changes: 85 additions & 0 deletions src/YTSearch.NET/Utils/SearchUtils.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Text.Json.Nodes;
using YTSearch.NET.Models;

namespace YTSearch.NET.Utils
{
internal static class SearchUtils
{
internal static int? KMBToInt(string? kmb)
{
if (kmb == null) return null;

try
{
if (kmb.ToLower().Contains('k'))
{
return (int)(float.Parse(kmb.ToLower().Replace("k", "")) * 1000);
}
else if (kmb.ToLower().Contains('m'))
{
return (int)(float.Parse(kmb.ToLower().Replace("m", "")) * 1000000);
}
else if (kmb.ToLower().Contains('b'))
{
return (int)(float.Parse(kmb.ToLower().Replace("b", "")) * 1000000000);
}
else
{
return int.Parse(kmb);
}
}
catch
{
return null;
}
}

internal static TimeSpan ParseVideoLength(string? timespan)
{
var output = TimeSpan.Zero;
if (timespan != null)
{
try
{
switch (timespan.Split(':').Length)
{
case 1:
output = TimeSpan.ParseExact(timespan, "%s", CultureInfo.InvariantCulture);
break;
case 2:
output = TimeSpan.ParseExact(timespan, @"m\:ss", CultureInfo.InvariantCulture);
break;
default:
output = TimeSpan.ParseExact(timespan, "g", CultureInfo.InvariantCulture);
break;
}
}
catch
{
output = TimeSpan.Zero;
}
}

return output;
}

internal static Thumbnail[] ParseThumbnails(JsonNode? jsonNode)
{
var thumbnails = new List<Thumbnail>();
jsonNode?.AsArray().ToList().ForEach(thumbnail =>
{
var width = (int?)thumbnail?["width"];
var height = (int?)thumbnail?["height"];
var url = (string?)thumbnail?["url"];
thumbnails.Add(new Thumbnail(width, height, url));
});

return thumbnails.ToArray();
}
}
}
88 changes: 10 additions & 78 deletions src/YTSearch.NET/YouTubeSearchClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using System.Threading.Tasks;
using System.Web;
using YTSearch.NET.Models;
using YTSearch.NET.Utils;

namespace YTSearch.NET
{
Expand Down Expand Up @@ -47,8 +48,8 @@ public async Task<YouTubeVideoSearchResult> SearchYoutubeVideoAsync(string query

var title = (string?)video?["title"]?["runs"]?[0]?["text"];
var videoId = (string?)video?["videoId"];
var thumbnails = ParseThumbnails(video?["thumbnail"]?["thumbnails"]);
var length = ParseVideoLength((string?)video?["lengthText"]?["simpleText"]);
var thumbnails = SearchUtils.ParseThumbnails(video?["thumbnail"]?["thumbnails"]);
var length = SearchUtils.ParseVideoLength((string?)video?["lengthText"]?["simpleText"]);
var author = (string?)video?["ownerText"]?["runs"]?[0]?["text"];
int? views = int.TryParse(((string?)video?["viewCountText"]?["simpleText"])?.Replace(",", "")?.Replace("views", ""), out int parsed) ? parsed : (int?)null; // (int?) cast to target netstandard2.1
var published = (string?)video?["publishedTimeText"]?["simpleText"];
Expand Down Expand Up @@ -83,7 +84,7 @@ public async Task<YouTubeChannelSearchResult> SearchYouTubeChannelAsync(string q

var channelId = (string?)channel?["channelId"];
var name = (string?)channel?["title"]?["simpleText"];
var thumbnails = ParseThumbnails(channel?["thumbnail"]?["thumbnails"]);
var thumbnails = SearchUtils.ParseThumbnails(channel?["thumbnail"]?["thumbnails"]);
var descriptionSnippet = channel?["descriptionSnippet"]?["runs"]?[0]?["text"];

// YouTube does this weird thing where they send subscriber count in the view/video count text, so we have to check for them
Expand All @@ -92,16 +93,16 @@ public async Task<YouTubeChannelSearchResult> SearchYouTubeChannelAsync(string q

if ((string?)channel?["videoCountText"]?["simpleText"] != null && ((string?)channel?["videoCountText"]?["simpleText"]).Contains("subscriber"))
{
subscriberCount = KMBToInt(((string?)channel?["videoCountText"]?["simpleText"])?.Split(' ')[0]);
subscriberCount = SearchUtils.KMBToInt(((string?)channel?["videoCountText"]?["simpleText"])?.Split(' ')[0]);
}
else
{
subscriberCount = KMBToInt(((string?)channel?["subscriberCountText"]?["simpleText"])?.Split(' ')[0]);
subscriberCount = SearchUtils.KMBToInt(((string?)channel?["subscriberCountText"]?["simpleText"])?.Split(' ')[0]);
}

if ((string?)channel?["videoCountText"]?["runs"]?[0]?["text"] != null && ((string?)channel?["videoCountText"]?["runs"]?[0]?["text"]).Contains("video"))
{
videoCount = KMBToInt(((string?)channel?["videoCountText"]?["runs"]?[0]?["text"])?.Split(' ')[0]);
videoCount = SearchUtils.KMBToInt(((string?)channel?["videoCountText"]?["runs"]?[0]?["text"])?.Split(' ')[0]);
}

searchResults.Add(new SearchedYouTubeChannel(channelId, name, thumbnails, descriptionSnippet, videoCount, subscriberCount));
Expand Down Expand Up @@ -138,7 +139,7 @@ public async Task<YouTubeVideoQueryResult> GetVideoMetadataAsync(Uri uri)

var title = (string?)videoDetail?["title"];
var videoId = (string?)videoDetail?["videoId"];
var thumbnails = ParseThumbnails(videoDetail?["thumbnail"]?["thumbnails"]);
var thumbnails = SearchUtils.ParseThumbnails(videoDetail?["thumbnail"]?["thumbnails"]);
var length = TimeSpan.FromSeconds(int.Parse((string?)videoDetail?["lengthSeconds"]));
var author = (string?)videoDetail?["author"];
var views = int.Parse((string?)videoDetail?["viewCount"]);
Expand Down Expand Up @@ -179,77 +180,8 @@ public async Task<YouTubeVideoQueryResult> GetVideoMetadataAsync(Uri uri)

}
#endregion
#region Channel Metadata

private static Thumbnail[] ParseThumbnails(JsonNode? jsonNode)
{
var thumbnails = new List<Thumbnail>();
jsonNode?.AsArray().ToList().ForEach(thumbnail =>
{
var width = (int?)thumbnail?["width"];
var height = (int?)thumbnail?["height"];
var url = (string?)thumbnail?["url"];
thumbnails.Add(new Thumbnail(width, height, url));
});

return thumbnails.ToArray();
}

private static TimeSpan ParseVideoLength(string? timespan)
{
var output = TimeSpan.Zero;
if (timespan != null)
{
try
{
switch (timespan.Split(':').Length)
{
case 1:
output = TimeSpan.ParseExact(timespan, "%s", CultureInfo.InvariantCulture);
break;
case 2:
output = TimeSpan.ParseExact(timespan, @"m\:ss", CultureInfo.InvariantCulture);
break;
default:
output = TimeSpan.ParseExact(timespan, "g", CultureInfo.InvariantCulture);
break;
}
}
catch
{
output = TimeSpan.Zero;
}
}

return output;
}

private static int? KMBToInt(string? kmb)
{
if(kmb == null) return null;

try
{
if (kmb.ToLower().Contains('k'))
{
return (int)(float.Parse(kmb.ToLower().Replace("k", "")) * 1000);
}
else if (kmb.ToLower().Contains('m'))
{
return (int)(float.Parse(kmb.ToLower().Replace("m", "")) * 1000000);
}
else if (kmb.ToLower().Contains('b'))
{
return (int)(float.Parse(kmb.ToLower().Replace("b", "")) * 1000000000);
}
else
{
return int.Parse(kmb);
}
}
catch
{
return null;
}
}
#endregion
}
}