From ceab79255fc8a4a5fbe4c9945aa5999632630845 Mon Sep 17 00:00:00 2001 From: Michael Gordeev Date: Sat, 30 Nov 2019 23:15:53 +0300 Subject: [PATCH] Implemented CC retrieval --- .../YouTube.API.Test/ClosedCaptionsTest.cs | 19 +++++++ .../Models/ClosedCaptionInfo.cs | 3 ++ .../Models/ClosedCaptionTrack.cs | 19 +++++++ .../Resources/CaptionsResource.cs | 50 ++++++++++++++++++- .../Resources/VideoPlaybackResource.cs | 3 +- .../YouTubeScraper/YouTubeService.cs | 1 + 6 files changed, 92 insertions(+), 3 deletions(-) create mode 100644 YouTubeScraper/YouTube.API.Test/ClosedCaptionsTest.cs create mode 100644 YouTubeScraper/YouTubeScraper/Models/ClosedCaptionTrack.cs diff --git a/YouTubeScraper/YouTube.API.Test/ClosedCaptionsTest.cs b/YouTubeScraper/YouTube.API.Test/ClosedCaptionsTest.cs new file mode 100644 index 0000000..ef0aafc --- /dev/null +++ b/YouTubeScraper/YouTube.API.Test/ClosedCaptionsTest.cs @@ -0,0 +1,19 @@ +using NUnit.Framework; +using System.Linq; +using YouTube.Models; + +namespace YouTube.API.Test +{ + public class ClosedCaptionsTest + { + [Test] + public void ValidCaptionsTest() + { + YouTubeService service = new YouTubeService(); + ClosedCaptionInfo info = service.VideoPlayback.List("VC5-YkjMHuw").Execute().ClosedCaptions.FirstOrDefault(); + ClosedCaptionTrack track = service.Captions.Load(info).Execute(); + Assert.IsNotNull(track); + Assert.IsNotEmpty(track.Captions); + } + } +} diff --git a/YouTubeScraper/YouTubeScraper/Models/ClosedCaptionInfo.cs b/YouTubeScraper/YouTubeScraper/Models/ClosedCaptionInfo.cs index 01aa425..9eb4fc3 100644 --- a/YouTubeScraper/YouTubeScraper/Models/ClosedCaptionInfo.cs +++ b/YouTubeScraper/YouTubeScraper/Models/ClosedCaptionInfo.cs @@ -1,4 +1,5 @@ using System.Globalization; +using YoutubeExplode.Models.ClosedCaptions; namespace YouTube.Models { @@ -7,5 +8,7 @@ namespace YouTube.Models public CultureInfo Language { get; set; } public string Url { get; set; } public bool AutoGenerated { get; set; } + + internal ClosedCaptionTrackInfo TrackInfo { get; set; } } } diff --git a/YouTubeScraper/YouTubeScraper/Models/ClosedCaptionTrack.cs b/YouTubeScraper/YouTubeScraper/Models/ClosedCaptionTrack.cs new file mode 100644 index 0000000..418aa20 --- /dev/null +++ b/YouTubeScraper/YouTubeScraper/Models/ClosedCaptionTrack.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace YouTube.Models +{ + public class ClosedCaptionTrack + { + public ClosedCaptionInfo Info { get; set; } + public IReadOnlyList Captions { get; set; } + + public class ClosedCaption + { + public TimeSpan Offset { get; set; } + public TimeSpan Duration { get; set; } + public string Content { get; set; } + } + } +} diff --git a/YouTubeScraper/YouTubeScraper/Resources/CaptionsResource.cs b/YouTubeScraper/YouTubeScraper/Resources/CaptionsResource.cs index 51fa553..f421fe7 100644 --- a/YouTubeScraper/YouTubeScraper/Resources/CaptionsResource.cs +++ b/YouTubeScraper/YouTubeScraper/Resources/CaptionsResource.cs @@ -1,11 +1,57 @@ using Google.Apis.Services; +using System.Collections.Generic; +using System.Threading.Tasks; +using YouTube.Models; +using YoutubeExplode; namespace YouTube.Resources { - public class CaptionsResource + public class CaptionsResource : Google.Apis.YouTube.v3.CaptionsResource { IClientService Service { get; } - public CaptionsResource(IClientService service) => + public CaptionsResource(IClientService service) : base(service) => Service = service; + + public LoadRequest Load(ClosedCaptionInfo captionInfo) => + new LoadRequest(Service, captionInfo); + + public class LoadRequest + { + public ClosedCaptionInfo CaptionInfo { get; set; } + IClientService Service { get; set; } + + public LoadRequest(IClientService service, ClosedCaptionInfo captionInfo) + { + CaptionInfo = captionInfo; + Service = service; + } + + public async Task ExecuteAsync() + { + YoutubeClient client = new YoutubeClient(Service.HttpClient); + var response = await client.GetClosedCaptionTrackAsync(CaptionInfo.TrackInfo); + List captions = new List(); + foreach (var i in response.Captions) + captions.Add(new ClosedCaptionTrack.ClosedCaption + { + Offset = i.Offset, + Duration = i.Duration, + Content = i.Text + }); + + return new ClosedCaptionTrack + { + Info = CaptionInfo, + Captions = captions.AsReadOnly() + }; + } + + public ClosedCaptionTrack Execute() + { + Task task = ExecuteAsync(); + task.Wait(); + return task.Result; + } + } } } diff --git a/YouTubeScraper/YouTubeScraper/Resources/VideoPlaybackResource.cs b/YouTubeScraper/YouTubeScraper/Resources/VideoPlaybackResource.cs index 8c88a00..ae05805 100644 --- a/YouTubeScraper/YouTubeScraper/Resources/VideoPlaybackResource.cs +++ b/YouTubeScraper/YouTubeScraper/Resources/VideoPlaybackResource.cs @@ -87,7 +87,8 @@ namespace YouTube.Resources { AutoGenerated = i.IsAutoGenerated, Url = i.Url, - Language = new CultureInfo(i.Language.Code) + Language = new CultureInfo(i.Language.Code), + TrackInfo = i }); item.ClosedCaptions = captions.AsReadOnly(); diff --git a/YouTubeScraper/YouTubeScraper/YouTubeService.cs b/YouTubeScraper/YouTubeScraper/YouTubeService.cs index c7f5aa8..27b4808 100644 --- a/YouTubeScraper/YouTubeScraper/YouTubeService.cs +++ b/YouTubeScraper/YouTubeScraper/YouTubeService.cs @@ -6,6 +6,7 @@ namespace YouTube { public DashManifestsResource DashManifests => new DashManifestsResource(this); public VideoPlaybackResource VideoPlayback => new VideoPlaybackResource(this); + public new CaptionsResource Captions => new CaptionsResource(this); public HistoryResource History { get; } public WatchLaterResource WatchLater { get; } // TODO: Add Activities override for recomendations and subscriptions and implementation of cc retrieval