Skip to content

Commit f5040af

Browse files
Restore video service as it is used in the community website
1 parent 11c27c4 commit f5040af

File tree

10 files changed

+569
-1
lines changed

10 files changed

+569
-1
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
namespace OurUmbraco.Community.Videos
2+
{
3+
4+
public static class CommunityVideosConstants
5+
{
6+
7+
public static class Playlists
8+
{
9+
10+
public const string UmbraCoffeePlaylistId = "PL8U_DpU2bvqeIGMNay5O7wjD5Fv16PzzI";
11+
12+
}
13+
14+
}
15+
16+
}
Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Configuration;
4+
using System.IO;
5+
using System.Linq;
6+
using System.Net;
7+
using System.Web.Hosting;
8+
using Newtonsoft.Json;
9+
using Skybrud.Essentials.Json;
10+
using Skybrud.Social.Google.Common;
11+
using Skybrud.Social.Google.YouTube.Models.Videos;
12+
using Skybrud.Social.Google.YouTube.Options.PlaylistItems;
13+
using Skybrud.Social.Google.YouTube.Options.Playlists;
14+
using Skybrud.Social.Google.YouTube.Options.Videos;
15+
using Umbraco.Core;
16+
using Umbraco.Core.IO;
17+
using Umbraco.Core.Logging;
18+
19+
namespace OurUmbraco.Community.Videos
20+
{
21+
22+
public class CommunityVideosService
23+
{
24+
protected GoogleService Api { get; private set; }
25+
26+
public CommunityVideosService()
27+
{
28+
Api = GoogleService.CreateFromServerKey(ConfigurationManager.AppSettings["GoogleServerKey"]);
29+
}
30+
31+
private bool ExistsOnDiskAndIsUpToDate(string id)
32+
{
33+
34+
string dir = IOHelper.MapPath("~/App_Data/TEMP/YouTube/");
35+
string path = dir + "Video_" + id + ".json";
36+
return File.Exists(path) && File.GetLastWriteTimeUtc(path) >= DateTime.UtcNow.AddDays(-1);
37+
38+
}
39+
40+
public YouTubeVideo LoadYouTubeVideo(string videoId)
41+
{
42+
string dir = IOHelper.MapPath("~/App_Data/TEMP/YouTube/");
43+
string path = dir + "Video_" + videoId + ".json";
44+
return File.Exists(path) ? JsonUtils.LoadJsonObject(path, YouTubeVideo.Parse) : null;
45+
}
46+
47+
public Playlist[] GetPlaylists()
48+
{
49+
try
50+
{
51+
var path = HostingEnvironment.MapPath("~/config/YouTubePlaylists.json");
52+
using (var file = File.OpenText(path))
53+
{
54+
var jsonSerializer = new JsonSerializer();
55+
var youtube = (YouTubeInfo)jsonSerializer.Deserialize(file, typeof(YouTubeInfo));
56+
return youtube.PlayLists;
57+
}
58+
}
59+
catch (Exception ex)
60+
{
61+
LogHelper.Error<CommunityVideosService>("Unable to parse config file", ex);
62+
}
63+
64+
return new List<Playlist>().ToArray();
65+
}
66+
67+
public YouTubeVideo[] LoadYouTubePlaylistVideos()
68+
{
69+
var videos = new List<YouTubeVideo>();
70+
71+
var playlists = GetPlaylists();
72+
foreach (var playlist in playlists)
73+
videos.AddRange(LoadYouTubePlaylistVideo(playlist.Id));
74+
75+
return videos.ToArray();
76+
}
77+
78+
public YouTubeVideo[] LoadYouTubePlaylistVideo(string playlistId)
79+
{
80+
string dir = IOHelper.MapPath("~/App_Data/TEMP/YouTube/");
81+
string path = dir + "Playlist_" + playlistId + "_Videos.json";
82+
return File.Exists(path) ? JsonUtils.LoadJsonArray(path, YouTubeVideo.Parse) : new YouTubeVideo[0];
83+
}
84+
85+
public void UpdateYouTubePlaylistVideos()
86+
{
87+
if (ConfigurationManager.AppSettings["GoogleServerKey"] == "it's a secret.. and no, this is not the secret, this key will transformed on the build server")
88+
{
89+
return;
90+
}
91+
92+
var playlists = GetPlaylists();
93+
foreach (var playlist in playlists)
94+
UpdateYouTubePlaylistVideo(playlist.Id);
95+
}
96+
97+
public void UpdateYouTubePlaylistVideo(string playlistId)
98+
{
99+
100+
// Make sure we have a TEMP directory
101+
string dir = IOHelper.MapPath("~/App_Data/TEMP/YouTube/");
102+
Directory.CreateDirectory(dir);
103+
104+
// Make an initial request to get information about the playlist
105+
var response1 = Api.YouTube.Playlists.GetPlaylists(new YouTubeGetPlaylistListOptions
106+
{
107+
Ids = new[] { playlistId }
108+
});
109+
110+
// Get a reference to the playlist (using "Single" as there should be exactly one playlist)
111+
var playlist = response1.Body.Items.Single();
112+
113+
// Save the playlist to the disk
114+
JsonUtils.SaveJsonObject(dir + "Playlist_" + playlist.Id + ".json", playlist);
115+
116+
// List of all video IDs
117+
List<string> ids = new List<string>();
118+
119+
// Initialize the options for getting the playlist items
120+
var playlistItemsOptions = new YouTubeGetPlaylistItemListOptions(playlistId)
121+
{
122+
123+
// Maximum allowed value is 50
124+
MaxResults = 50
125+
126+
};
127+
128+
int page = 0;
129+
while (page < 10)
130+
{
131+
132+
// Get the playlist items
133+
var response2 = Api.YouTube.PlaylistItems.GetPlaylistItems(playlistItemsOptions);
134+
135+
// Append each video ID to the list
136+
foreach (var item in response2.Body.Items)
137+
{
138+
ids.Add(item.VideoId);
139+
}
140+
141+
// Break the loop if there are no additional pages
142+
if (String.IsNullOrWhiteSpace(response2.Body.NextPageToken))
143+
{
144+
break;
145+
}
146+
147+
// Update the options with the page token
148+
playlistItemsOptions.PageToken = response2.Body.NextPageToken;
149+
150+
page++;
151+
152+
}
153+
154+
// Iterate through groups of IDs (maximum 50 items per group)
155+
foreach (var group in ids.Where(x => !ExistsOnDiskAndIsUpToDate(x)).InGroupsOf(50))
156+
{
157+
158+
// Initialize the video options
159+
var videosOptions = new YouTubeGetVideoListOptions
160+
{
161+
Ids = group.ToArray(),
162+
Part = YouTubeVideoParts.Snippet + YouTubeVideoParts.ContentDetails + YouTubeVideoParts.Statistics
163+
};
164+
165+
// Make a request to the APi to get video information
166+
var res3 = Api.YouTube.Videos.GetVideos(videosOptions);
167+
168+
// Iterate through the videos
169+
foreach (var video in res3.Body.Items)
170+
{
171+
172+
// Save the video to the disk
173+
string path = dir + "Video_" + video.Id + ".json";
174+
JsonUtils.SaveJsonObject(path, video);
175+
176+
// Download the thumnails for each video
177+
var thumbnailUrl = GetThumbnail(video).Url;
178+
179+
const string mediaRoot = "~/media/YouTube";
180+
var thumbnailFile = IOHelper.MapPath($"{mediaRoot}/{video.Id}.jpg");
181+
182+
var mediaPath = IOHelper.MapPath(mediaRoot);
183+
if (Directory.Exists(mediaPath) == false)
184+
Directory.CreateDirectory(mediaPath);
185+
186+
if (File.Exists(thumbnailFile))
187+
continue;
188+
189+
using (var client = new WebClient())
190+
client.DownloadFile(thumbnailUrl, thumbnailFile);
191+
}
192+
}
193+
194+
// Load the videos from the individual files, and save them to a common file
195+
JsonUtils.SaveJsonArray(dir + "Playlist_" + playlistId + "_Videos.json", ids.Select(LoadYouTubeVideo).WhereNotNull());
196+
}
197+
198+
private static YouTubeVideoThumbnail GetThumbnail(YouTubeVideo video)
199+
{
200+
var thumbnails = new List<YouTubeVideoThumbnail>();
201+
202+
if (video.Snippet.Thumbnails.HasDefault)
203+
thumbnails.Add(video.Snippet.Thumbnails.Default);
204+
205+
if (video.Snippet.Thumbnails.HasStandard)
206+
thumbnails.Add(video.Snippet.Thumbnails.Standard);
207+
208+
if (video.Snippet.Thumbnails.HasMedium)
209+
thumbnails.Add(video.Snippet.Thumbnails.Medium);
210+
211+
if (video.Snippet.Thumbnails.HasHigh)
212+
thumbnails.Add(video.Snippet.Thumbnails.High);
213+
214+
if (video.Snippet.Thumbnails.HasMaxRes)
215+
thumbnails.Add(video.Snippet.Thumbnails.MaxRes);
216+
217+
var thumbnail = thumbnails.OrderBy(x => x.Width).FirstOrDefault(x => x.Width >= 350);
218+
return thumbnail;
219+
}
220+
}
221+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
using OurUmbraco.Community.Videos.Models;
2+
using System.Linq;
3+
using System.Web.Http;
4+
using Umbraco.Web.WebApi;
5+
6+
namespace OurUmbraco.Community.Videos.Controllers.Http
7+
{
8+
public class VideoController : UmbracoApiController
9+
{
10+
private readonly CommunityVideosService _videoService;
11+
12+
public VideoController()
13+
{
14+
_videoService = new CommunityVideosService();
15+
}
16+
17+
public IHttpActionResult GetAll()
18+
{
19+
var videos = _videoService.LoadYouTubePlaylistVideos();
20+
var dtos = videos.Select(x => new YouTubeVideoDto
21+
{
22+
Id = x.Id,
23+
Title = x.Snippet.Title,
24+
Description = x.Snippet.Description,
25+
Length = x.ContentDetails.Duration.Value.TotalMinutes,
26+
PublishedAt = x.Snippet.PublishedAt,
27+
Likes = x.Statistics.LikeCount,
28+
Plays = x.Statistics.ViewCount,
29+
Tags = x.Snippet.Tags,
30+
ThumbnailUrl = $"https://our.umbraco.com/media/YouTube/{x.Id}.jpg"
31+
});
32+
33+
return Ok(dtos);
34+
}
35+
}
36+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Runtime.Serialization;
4+
5+
namespace OurUmbraco.Community.Videos.Models
6+
{
7+
[DataContract(Name = "youTubeVideo")]
8+
public class YouTubeVideoDto
9+
{
10+
[DataMember(Name = "id")]
11+
public string Id { get; set; }
12+
13+
[DataMember(Name = "title")]
14+
public string Title { get; set; }
15+
16+
[DataMember(Name = "description")]
17+
public string Description { get; set; }
18+
19+
[DataMember(Name = "length")]
20+
public Double Length { get; set; }
21+
22+
[DataMember(Name = "publishedAt")]
23+
public DateTime PublishedAt { get; set; }
24+
25+
[DataMember(Name = "likes")]
26+
public long Likes { get; set; }
27+
28+
[DataMember(Name = "plays")]
29+
public long Plays { get; set; }
30+
31+
[DataMember(Name = "tags")]
32+
public IEnumerable<string> Tags { get; set; }
33+
34+
[DataMember(Name = "thumbnailUrl")]
35+
public string ThumbnailUrl { get; set; }
36+
}
37+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using System.Collections.Generic;
2+
using Newtonsoft.Json;
3+
4+
namespace OurUmbraco.Community.Videos
5+
{
6+
public class YouTubeInfo
7+
{
8+
public Playlist[] PlayLists { get; set; }
9+
}
10+
11+
public class Playlist
12+
{
13+
public string Id { get; set; }
14+
public string Title { get; set; }
15+
16+
[JsonProperty("members")]
17+
public List<int> MemberIds { get; set; }
18+
}
19+
}

OurUmbraco/NotificationsCore/Notifications/ScheduleHangfireJobs.cs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
using OurUmbraco.Community.Mastodon;
66
using OurUmbraco.Documentation;
77
using OurUmbraco.Our.Services;
8+
using OurUmbraco.Community.Videos;
9+
using OurUmbraco.Videos;
810

911
namespace OurUmbraco.NotificationsCore.Notifications
1012
{
@@ -48,7 +50,17 @@ public void FetchStaticApiDocumentation(PerformContext context)
4850
public void FetchMastodonPosts(PerformContext context)
4951
{
5052
var mastodonService = new MastodonService();
51-
RecurringJob.AddOrUpdate<MastodonService>("️💥 Get Masto posts", x => x.GetStatuses(10), Cron.MinuteInterval(2));
53+
RecurringJob.AddOrUpdate<MastodonService>("️🐘 Get Masto posts", x => x.GetStatuses(10), Cron.MinuteInterval(2));
54+
}
55+
56+
public void UpdateVimeoVideos()
57+
{
58+
RecurringJob.AddOrUpdate<VideosService>("📽️ Update Vimeo videos", x => x.UpdateVimeoVideos("umbraco"), Cron.HourInterval(1));
59+
}
60+
61+
public void UpdateCommunityVideos()
62+
{
63+
RecurringJob.AddOrUpdate<CommunityVideosService>("📽️ Update community videos", x => x.UpdateYouTubePlaylistVideos(), Cron.HourInterval(1));
5264
}
5365
}
5466
}

OurUmbraco/Our/GoogleOAuth/UmbracoStandardOwinStartup.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ public override void Configuration(IAppBuilder app)
6060
scheduler.UpdateGitHubIssues(null);
6161
scheduler.FetchStaticApiDocumentation(null);
6262
scheduler.FetchMastodonPosts(null);
63+
scheduler.UpdateVimeoVideos();
64+
scheduler.UpdateCommunityVideos();
6365
}
6466
}
6567
}

OurUmbraco/OurUmbraco.csproj

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -629,6 +629,11 @@
629629
<Compile Include="Community\Karma\Controllers\Http\KarmaController.cs" />
630630
<Compile Include="Community\Mastodon\MastodonService.cs" />
631631
<Compile Include="Community\People\Controllers\Http\MvpController.cs" />
632+
<Compile Include="Community\Videos\CommunityVideosConstants.cs" />
633+
<Compile Include="Community\Videos\CommunityVideosService.cs" />
634+
<Compile Include="Community\Videos\Controllers\Http\VideoController.cs" />
635+
<Compile Include="Community\Videos\Models\YouTubeVideoDto.cs" />
636+
<Compile Include="Community\Videos\YouTubePlayList.cs" />
632637
<Compile Include="Documentation\Controllers\StaticSiteController.cs" />
633638
<Compile Include="Documentation\StaticApiDocumentationService.cs" />
634639
<Compile Include="Documentation\Models\AzureDevopsArtifacts.cs" />
@@ -1054,6 +1059,8 @@
10541059
<Compile Include="UmbracoAuthorizationFilter.cs" />
10551060
<Compile Include="Version\Config.cs" />
10561061
<Compile Include="Version\uWikiFileVersion.cs" />
1062+
<Compile Include="Videos\VideosCategory.cs" />
1063+
<Compile Include="Videos\VideosService.cs" />
10571064
<Compile Include="Wiki\Api\WikiController.cs" />
10581065
<Compile Include="Wiki\BusinessLogic\Data.cs" />
10591066
<Compile Include="Wiki\BusinessLogic\Events.cs" />

0 commit comments

Comments
 (0)