diff --git a/YTManager/Controllers/Videos.cs b/YTManager/Controllers/Videos.cs index 7fd4b65..0dc61d3 100644 --- a/YTManager/Controllers/Videos.cs +++ b/YTManager/Controllers/Videos.cs @@ -26,6 +26,12 @@ namespace YTManager.Controllers { // Channel on youtube that owns this video public string Channel; + // Duration of the video in seconds. + public int Seconds; + + // What tags are relevant with this video. + public List Tags; + // Populate this struct using a model video. public Video_ForAPI(Models.Video video) { Title = video.Title; @@ -33,6 +39,8 @@ namespace YTManager.Controllers { ID = video.YoutubeID; Thumbnail = video.ThumbnailURL; Channel = video.Channel.Title; + Seconds = (int)video.Duration.TotalSeconds; + Tags = video.Tags?.Select(t => t.Name).ToList(); } } @@ -50,9 +58,10 @@ namespace YTManager.Controllers { public async Task GetVideos() { // Get all the relevant videos. var vids = await db.Videos - .Include(v => v.Channel) .OrderByDescending(i => i.AddedToYT) .Take(max_per_query) + .Include(v => v.Channel) + .Include(v => v.Tags) .ToListAsync(); // Convert them to what we will send out. @@ -69,10 +78,11 @@ namespace YTManager.Controllers { public async Task Get_Channel_Videos([FromRoute] string channelName) { // Get all the relevant videos. var vids = await db.Videos - .Include(v => v.Channel) .Where(v => v.Channel.Title == channelName) .OrderByDescending(i => i.AddedToYT) .Take(max_per_query) + .Include(v => v.Channel) + .Include(v => v.Tags) .ToListAsync(); // Convert them to what we will send out. diff --git a/YTManager/Migrations/20180228202611_added_vid_duration.Designer.cs b/YTManager/Migrations/20180228202611_added_vid_duration.Designer.cs new file mode 100644 index 0000000..b8811d6 --- /dev/null +++ b/YTManager/Migrations/20180228202611_added_vid_duration.Designer.cs @@ -0,0 +1,116 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.EntityFrameworkCore.Storage.Internal; +using System; +using YTManager; + +namespace YTManager.Migrations +{ + [DbContext(typeof(MediaDB))] + [Migration("20180228202611_added_vid_duration")] + partial class added_vid_duration + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn) + .HasAnnotation("ProductVersion", "2.0.1-rtm-125"); + + modelBuilder.Entity("YTManager.Models.Channel", b => + { + b.Property("PrimaryKey") + .ValueGeneratedOnAdd(); + + b.Property("AddedtoDB"); + + b.Property("Description") + .IsRequired(); + + b.Property("Refreshed"); + + b.Property("ThumbnailURL") + .IsRequired(); + + b.Property("Title") + .IsRequired(); + + b.Property("YoutubeID") + .IsRequired(); + + b.HasKey("PrimaryKey"); + + b.ToTable("Channels"); + }); + + modelBuilder.Entity("YTManager.Models.Tag", b => + { + b.Property("PrimaryKey") + .ValueGeneratedOnAdd(); + + b.Property("Name") + .IsRequired(); + + b.Property("VideoPrimaryKey"); + + b.HasKey("PrimaryKey"); + + b.HasIndex("VideoPrimaryKey"); + + b.ToTable("Tags"); + }); + + modelBuilder.Entity("YTManager.Models.Video", b => + { + b.Property("PrimaryKey") + .ValueGeneratedOnAdd(); + + b.Property("AddedToYT"); + + b.Property("AddedtoDB"); + + b.Property("ChannelPrimaryKey"); + + b.Property("Description") + .IsRequired(); + + b.Property("Duration"); + + b.Property("ThumbnailURL") + .IsRequired(); + + b.Property("Title") + .IsRequired(); + + b.Property("YoutubeID") + .IsRequired(); + + b.HasKey("PrimaryKey"); + + b.HasIndex("ChannelPrimaryKey"); + + b.ToTable("Videos"); + }); + + modelBuilder.Entity("YTManager.Models.Tag", b => + { + b.HasOne("YTManager.Models.Video") + .WithMany("Tags") + .HasForeignKey("VideoPrimaryKey"); + }); + + modelBuilder.Entity("YTManager.Models.Video", b => + { + b.HasOne("YTManager.Models.Channel", "Channel") + .WithMany("Videos") + .HasForeignKey("ChannelPrimaryKey") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/YTManager/Migrations/20180228202611_added_vid_duration.cs b/YTManager/Migrations/20180228202611_added_vid_duration.cs new file mode 100644 index 0000000..77256fc --- /dev/null +++ b/YTManager/Migrations/20180228202611_added_vid_duration.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using System; +using System.Collections.Generic; + +namespace YTManager.Migrations +{ + public partial class added_vid_duration : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Duration", + table: "Videos", + nullable: false, + defaultValue: "00:00:00"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Duration", + table: "Videos"); + } + } +} diff --git a/YTManager/Migrations/MediaDBModelSnapshot.cs b/YTManager/Migrations/MediaDBModelSnapshot.cs index 92114cd..9cbb35d 100644 --- a/YTManager/Migrations/MediaDBModelSnapshot.cs +++ b/YTManager/Migrations/MediaDBModelSnapshot.cs @@ -77,6 +77,8 @@ namespace YTManager.Migrations b.Property("Description") .IsRequired(); + b.Property("Duration"); + b.Property("ThumbnailURL") .IsRequired(); diff --git a/YTManager/Models/Video.cs b/YTManager/Models/Video.cs index b34f1e7..d1c6036 100644 --- a/YTManager/Models/Video.cs +++ b/YTManager/Models/Video.cs @@ -32,6 +32,10 @@ namespace YTManager.Models { [Required] public DateTime AddedtoDB { get; set; } + // How long the video is + [Required] + public TimeSpan Duration { get; set; } + // What channel this video comes from. [Required] public Channel Channel { get; set; } diff --git a/YTManager/Properties/launchSettings.json b/YTManager/Properties/launchSettings.json index 9dc51d0..4333360 100644 --- a/YTManager/Properties/launchSettings.json +++ b/YTManager/Properties/launchSettings.json @@ -20,7 +20,7 @@ "commandName": "Project", "launchUrl": "api/values", "environmentVariables": { - "POSTGRESQL_DBSTR": "Server=192.168.1.211;Port=32768;Database=postgres;User Id=postgres;", + "POSTGRESQL_DBSTR": "Server=192.168.1.2;Port=32768;Database=postgres;User Id=postgres;", "ASPNETCORE_ENVIRONMENT": "Development" }, "applicationUrl": "http://localhost:62214/" diff --git a/YTManager/Tasks/FetchVideos.cs b/YTManager/Tasks/FetchVideos.cs index b13182f..b7f79ad 100644 --- a/YTManager/Tasks/FetchVideos.cs +++ b/YTManager/Tasks/FetchVideos.cs @@ -8,7 +8,7 @@ using Microsoft.EntityFrameworkCore; namespace YTManager.Tasks { public class FetchVideos { // Get a bunch of videos from youtube that the channel generated. - private static async Task> Get_YTVideos(string channelID) { + public static async Task> Get_YTVideos(string channelID, int max = 50) { // YT API access key var youtubeService = new YouTubeService(new Google.Apis.Services.BaseClientService.Initializer() { @@ -16,15 +16,18 @@ namespace YTManager.Tasks { ApplicationName = "testingapppp" }); + // Max cannot be larger than 50, so clamp it. + max = max > 50 ? 50 : max; + // Search youtube for all the relevant data of the channel. - var query = youtubeService.Search.List("snippet"); - query.ChannelId = channelID; - query.Order = SearchResource.ListRequest.OrderEnum.Date; - query.MaxResults = 50; - var response = await query.ExecuteAsync(); + var videos_query = youtubeService.Search.List("snippet"); + videos_query.ChannelId = channelID; + videos_query.Order = SearchResource.ListRequest.OrderEnum.Date; + videos_query.MaxResults = max; + var videos_response = await videos_query.ExecuteAsync(); // Convert the response into models. - return response.Items? + var videos = videos_response.Items? .Where(i => i.Id.Kind == "youtube#video") .Select(newvid => new Models.Video { @@ -35,6 +38,28 @@ namespace YTManager.Tasks { AddedtoDB = DateTime.Now, ThumbnailURL = newvid.Snippet.Thumbnails.Medium.Url }).ToList(); + + // Search youtube to get the length and tags for each video. + var duration_query = youtubeService.Videos.List("contentDetails,snippet"); + duration_query.Id = string.Join(',', videos.Select(v => v.YoutubeID)); + var duration_response = await duration_query.ExecuteAsync(); + + // Pair each video with the result. + foreach(var contentdetail in duration_response.Items.Where(i => i.Kind == "youtube#video")){ + var vid = videos.Single(v => v.YoutubeID == contentdetail.Id); + + // Yes, really, ISO8601 time span => c#'s TimeSpan is not in TimeSpan, it's in xmlconvert! Wtf?! + vid.Duration = System.Xml.XmlConvert.ToTimeSpan(contentdetail.ContentDetails.Duration); + + // Copy over all the created tags if any tags were provided. + if (contentdetail.Snippet.Tags == null) + vid.Tags = new List(); + else + vid.Tags = contentdetail.Snippet.Tags.Select(t => new Models.Tag { Name = t }).ToList(); + } + + // Send back the parsed vids. + return videos; } // Gets some info about a youtube channel.