mirror of
https://github.com/mxpv/podsync.git
synced 2024-05-11 05:55:04 +00:00
Generate download links inside controller action
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml.Serialization;
|
||||
@@ -22,6 +23,12 @@ namespace Podsync.Controllers
|
||||
[HandleException]
|
||||
public class FeedController : Controller
|
||||
{
|
||||
private static readonly IDictionary<string, string> Extensions = new Dictionary<string, string>
|
||||
{
|
||||
["video/mp4"] = "mp4",
|
||||
["audio/mp4"] = "m4a"
|
||||
};
|
||||
|
||||
private readonly XmlSerializer _serializer = new XmlSerializer(typeof(Rss));
|
||||
|
||||
private readonly IRssBuilder _rssBuilder;
|
||||
@@ -94,18 +101,27 @@ namespace Podsync.Controllers
|
||||
|
||||
try
|
||||
{
|
||||
rss = await _rssBuilder.Query(Request.GetBaseUrl(), feedId);
|
||||
rss = await _rssBuilder.Query(feedId);
|
||||
}
|
||||
catch (KeyNotFoundException)
|
||||
{
|
||||
return NotFound(feedId);
|
||||
}
|
||||
|
||||
var selfHost = Request.GetBaseUrl();
|
||||
|
||||
// Set atom link to this feed
|
||||
// See https://validator.w3.org/feed/docs/warning/MissingAtomSelfLink.html
|
||||
var selfLink = new Uri($"{Request.Scheme}://{Request.Host}{Request.Path}");
|
||||
var selfLink = new Uri(selfHost, Request.Path);
|
||||
rss.Channels.ForEach(x => x.AtomLink = selfLink);
|
||||
|
||||
// No magic here, just make download links to DownloadController.Download
|
||||
rss.Channels.SelectMany(x => x.Items).ForEach(item =>
|
||||
{
|
||||
var ext = Extensions[item.ContentType];
|
||||
item.DownloadLink = new Uri(selfHost, $"download/{feedId}/{item.Id}.{ext}");
|
||||
});
|
||||
|
||||
// Serialize feed to string
|
||||
string body;
|
||||
using (var writer = new Utf8StringWriter())
|
||||
|
||||
@@ -30,12 +30,12 @@ namespace Podsync.Services.Builder
|
||||
get { throw new NotSupportedException(); }
|
||||
}
|
||||
|
||||
public override Task<Rss> Query(Uri baseUrl, string feedId, FeedMetadata feed)
|
||||
public override Task<Rss> Query(FeedMetadata feed)
|
||||
{
|
||||
IRssBuilder builder;
|
||||
if (_builders.TryGetValue(feed.Provider, out builder))
|
||||
{
|
||||
return builder.Query(baseUrl, feedId, feed);
|
||||
return builder.Query(feed);
|
||||
}
|
||||
|
||||
throw new NotSupportedException("Not supported provider");
|
||||
|
||||
@@ -10,8 +10,8 @@ namespace Podsync.Services.Builder
|
||||
{
|
||||
Provider Provider { get; }
|
||||
|
||||
Task<Rss> Query(Uri baseUrl, string feedId);
|
||||
Task<Rss> Query(string feedId);
|
||||
|
||||
Task<Rss> Query(Uri baseUrl, string feedId, FeedMetadata metadata);
|
||||
Task<Rss> Query(FeedMetadata metadata);
|
||||
}
|
||||
}
|
||||
@@ -17,13 +17,13 @@ namespace Podsync.Services.Builder
|
||||
|
||||
public abstract Provider Provider { get; }
|
||||
|
||||
public async Task<Rss> Query(Uri baseUrl, string feedId)
|
||||
public async Task<Rss> Query(string feedId)
|
||||
{
|
||||
var metadata = await _storageService.Load(feedId);
|
||||
|
||||
return await Query(baseUrl, feedId, metadata);
|
||||
return await Query(metadata);
|
||||
}
|
||||
|
||||
public abstract Task<Rss> Query(Uri baseUrl, string feedId, FeedMetadata metadata);
|
||||
public abstract Task<Rss> Query(FeedMetadata metadata);
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ namespace Podsync.Services.Builder
|
||||
|
||||
public override Provider Provider { get; } = Provider.Vimeo;
|
||||
|
||||
public override async Task<Rss> Query(Uri baseUrl, string feedId, FeedMetadata metadata)
|
||||
public override async Task<Rss> Query(FeedMetadata metadata)
|
||||
{
|
||||
var linkType = metadata.LinkType;
|
||||
|
||||
@@ -94,17 +94,15 @@ namespace Podsync.Services.Builder
|
||||
{
|
||||
return new Item
|
||||
{
|
||||
Id = video.Id,
|
||||
Title = video.Title,
|
||||
Description = video.Description,
|
||||
PubDate = video.CreatedAt,
|
||||
Link = video.Link,
|
||||
Duration = video.Duration,
|
||||
Content = new MediaContent
|
||||
{
|
||||
Length = video.Size,
|
||||
MediaType = "video/mp4",
|
||||
Url = null
|
||||
}
|
||||
FileSize = video.Size,
|
||||
ContentType = "video/mp4",
|
||||
Author = video.Author
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -13,18 +13,16 @@ namespace Podsync.Services.Builder
|
||||
{
|
||||
public class YouTubeRssBuilder : RssBuilderBase
|
||||
{
|
||||
private readonly ILinkService _linkService;
|
||||
private readonly IYouTubeClient _youTube;
|
||||
|
||||
public YouTubeRssBuilder(ILinkService linkService, IYouTubeClient youTube, IStorageService storageService) : base(storageService)
|
||||
public YouTubeRssBuilder(IYouTubeClient youTube, IStorageService storageService) : base(storageService)
|
||||
{
|
||||
_linkService = linkService;
|
||||
_youTube = youTube;
|
||||
}
|
||||
|
||||
public override Provider Provider { get; } = Provider.YouTube;
|
||||
|
||||
public override async Task<Rss> Query(Uri baseUrl, string feedId, FeedMetadata metadata)
|
||||
public override async Task<Rss> Query(FeedMetadata metadata)
|
||||
{
|
||||
if (metadata.Provider != Provider.YouTube)
|
||||
{
|
||||
@@ -57,7 +55,7 @@ namespace Podsync.Services.Builder
|
||||
// Get video descriptions
|
||||
var videos = await _youTube.GetVideos(new VideoQuery { Id = string.Join(",", ids) });
|
||||
|
||||
channel.Items = videos.Select(youtubeVideo => MakeItem(youtubeVideo, baseUrl, feedId, metadata));
|
||||
channel.Items = videos.Select(youtubeVideo => MakeItem(youtubeVideo, metadata));
|
||||
|
||||
var rss = new Rss
|
||||
{
|
||||
@@ -102,46 +100,36 @@ namespace Podsync.Services.Builder
|
||||
};
|
||||
}
|
||||
|
||||
private Item MakeItem(Video video, Uri baseUrl, string feedId, FeedMetadata feed)
|
||||
private Item MakeItem(Video video, FeedMetadata feed)
|
||||
{
|
||||
string contentType;
|
||||
string extension;
|
||||
GetMediaInfo(feed.Quality, out contentType, out extension);
|
||||
|
||||
var downloadUri = _linkService.Download(baseUrl, feedId, video.VideoId, extension);
|
||||
string contentType = GetContentType(feed.Quality);
|
||||
|
||||
return new Item
|
||||
{
|
||||
Id = video.VideoId,
|
||||
Title = video.Title,
|
||||
Description = video.Description,
|
||||
PubDate = video.PublishedAt,
|
||||
Link = video.Link,
|
||||
Duration = video.Duration,
|
||||
Content = new MediaContent
|
||||
{
|
||||
Length = video.Size,
|
||||
MediaType = contentType,
|
||||
Url = downloadUri
|
||||
}
|
||||
FileSize = video.Size,
|
||||
ContentType = contentType
|
||||
};
|
||||
}
|
||||
|
||||
private static void GetMediaInfo(ResolveType resolveType, out string contentType, out string extension)
|
||||
private static string GetContentType(ResolveType resolveType)
|
||||
{
|
||||
if (resolveType == ResolveType.VideoHigh || resolveType == ResolveType.VideoLow)
|
||||
{
|
||||
contentType = "video/mp4";
|
||||
extension = ".mp4";
|
||||
return "video/mp4";
|
||||
}
|
||||
else if (resolveType == ResolveType.AudioHigh || resolveType == ResolveType.AudioLow)
|
||||
|
||||
if (resolveType == ResolveType.AudioHigh || resolveType == ResolveType.AudioLow)
|
||||
{
|
||||
contentType = "audio/mp4";
|
||||
extension = ".m4a";
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException("Unsupported resolve type");
|
||||
return "audio/mp4";
|
||||
}
|
||||
|
||||
throw new ArgumentException("Unsupported resolve type");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Xml;
|
||||
using System.Xml.Schema;
|
||||
using System.Xml.Serialization;
|
||||
@@ -22,7 +23,13 @@ namespace Podsync.Services.Feed
|
||||
|
||||
public TimeSpan Duration { get; set; }
|
||||
|
||||
public MediaContent Content { get; set; }
|
||||
public string Id { get; set; }
|
||||
|
||||
public long FileSize { get; set; }
|
||||
|
||||
public Uri DownloadLink { get; set; }
|
||||
|
||||
public string ContentType { get; set; }
|
||||
|
||||
public XmlSchema GetSchema()
|
||||
{
|
||||
@@ -52,17 +59,22 @@ namespace Podsync.Services.Feed
|
||||
|
||||
writer.WriteStartElement("guid");
|
||||
writer.WriteAttributeString("isPermaLink", "true");
|
||||
writer.WriteString(Link.ToString());
|
||||
writer.WriteString(Link?.ToString() ?? Id);
|
||||
writer.WriteEndElement();
|
||||
|
||||
/*
|
||||
<enclosure url="http://podsync.net/download/youtube/yp202t46OIE.mp4" length="48300000" type="video/mp4"/>
|
||||
*/
|
||||
|
||||
if (DownloadLink == null)
|
||||
{
|
||||
throw new InvalidDataException("Can't generate RSS item with no download link");
|
||||
}
|
||||
|
||||
writer.WriteStartElement("enclosure");
|
||||
writer.WriteAttributeString("url", Content.Url.ToString());
|
||||
writer.WriteAttributeString("length", Content.Length.ToString());
|
||||
writer.WriteAttributeString("type", Content.MediaType);
|
||||
writer.WriteAttributeString("url", DownloadLink.ToString());
|
||||
writer.WriteAttributeString("length", FileSize.ToString());
|
||||
writer.WriteAttributeString("type", ContentType);
|
||||
writer.WriteEndElement();
|
||||
|
||||
/*
|
||||
@@ -70,9 +82,9 @@ namespace Podsync.Services.Feed
|
||||
*/
|
||||
|
||||
writer.WriteStartElement("content", Namespaces.Media);
|
||||
writer.WriteAttributeString("url", Content.Url.ToString());
|
||||
writer.WriteAttributeString("fileSize", Content.Length.ToString());
|
||||
writer.WriteAttributeString("type", Content.MediaType);
|
||||
writer.WriteAttributeString("url", DownloadLink.ToString());
|
||||
writer.WriteAttributeString("fileSize", FileSize.ToString());
|
||||
writer.WriteAttributeString("type", ContentType);
|
||||
writer.WriteEndElement();
|
||||
|
||||
/*
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Podsync.Services.Feed
|
||||
{
|
||||
public struct MediaContent
|
||||
{
|
||||
public Uri Url { get; set; }
|
||||
|
||||
public long Length { get; set; }
|
||||
|
||||
public string MediaType { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,8 @@ namespace Podsync.Services.Videos.Vimeo
|
||||
{
|
||||
public class Video
|
||||
{
|
||||
public string Id { get; set; }
|
||||
|
||||
public string Title { get; set; }
|
||||
|
||||
public string Description { get; set; }
|
||||
@@ -17,5 +19,7 @@ namespace Podsync.Services.Videos.Vimeo
|
||||
public long Size { get; set; }
|
||||
|
||||
public TimeSpan Duration { get; set; }
|
||||
|
||||
public string Author { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -101,15 +101,21 @@ namespace Podsync.Services.Videos.Vimeo
|
||||
v.duration.ToObject<long>() *
|
||||
0.38848958333);
|
||||
|
||||
// Extract id from uri like '/videos/50522981'
|
||||
var uri = v.uri.ToString();
|
||||
var id = uri.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries)[1];
|
||||
|
||||
var video = new Video
|
||||
{
|
||||
Id = id,
|
||||
Title = v.name,
|
||||
Description = v.description,
|
||||
Link = new Uri(v.link?.ToString()),
|
||||
Thumbnail = new Uri(v.pictures?.sizes[0]?.link?.ToString()),
|
||||
CreatedAt = DateTime.Parse(v.created_time?.ToString()),
|
||||
Duration = TimeSpan.FromSeconds(v.duration?.ToObject<int>()),
|
||||
Size = size
|
||||
Size = size,
|
||||
Author = v.user.name
|
||||
};
|
||||
|
||||
output.Add(video);
|
||||
|
||||
@@ -38,7 +38,7 @@ namespace Podsync.Tests.Services.Builder
|
||||
|
||||
_storageService.Setup(x => x.Load(feedId)).ReturnsAsync(feed);
|
||||
|
||||
var rss = await _builder.Query(new Uri("http://localhost:2020"), feedId);
|
||||
var rss = await _builder.Query(feedId);
|
||||
|
||||
Assert.NotEmpty(rss.Channels);
|
||||
|
||||
@@ -52,12 +52,12 @@ namespace Podsync.Tests.Services.Builder
|
||||
|
||||
foreach (var item in channel.Items)
|
||||
{
|
||||
Assert.NotNull(item.Id);
|
||||
Assert.NotNull(item.Title);
|
||||
Assert.NotNull(item.Link);
|
||||
Assert.True(item.Duration.TotalSeconds > 0);
|
||||
Assert.NotNull(item.Content);
|
||||
Assert.True(item.Content.Length > 0);
|
||||
Assert.NotNull(item.Content.MediaType);
|
||||
Assert.True(item.FileSize > 0);
|
||||
Assert.NotNull(item.ContentType);
|
||||
Assert.NotNull(item.PubDate);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace Podsync.Tests.Services.Builder
|
||||
var linkService = new LinkService();
|
||||
var client = new YouTubeClient(linkService, Options);
|
||||
|
||||
_builder = new YouTubeRssBuilder(linkService, client, _storageService.Object);
|
||||
_builder = new YouTubeRssBuilder(client, _storageService.Object);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
@@ -41,7 +41,7 @@ namespace Podsync.Tests.Services.Builder
|
||||
|
||||
_storageService.Setup(x => x.Load(feedId)).ReturnsAsync(feed);
|
||||
|
||||
var rss = await _builder.Query(new Uri("http://localhost:2020"), feedId);
|
||||
var rss = await _builder.Query(feedId);
|
||||
|
||||
Assert.NotEmpty(rss.Channels);
|
||||
|
||||
@@ -58,10 +58,8 @@ namespace Podsync.Tests.Services.Builder
|
||||
Assert.NotNull(item.Title);
|
||||
Assert.NotNull(item.Link);
|
||||
Assert.True(item.Duration.TotalSeconds > 0);
|
||||
Assert.NotNull(item.Content);
|
||||
Assert.True(item.Content.Length > 0);
|
||||
Assert.NotNull(item.Content.MediaType);
|
||||
Assert.NotNull(item.Content.Url);
|
||||
Assert.True(item.FileSize > 0);
|
||||
Assert.NotNull(item.ContentType);
|
||||
Assert.NotNull(item.PubDate);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,12 +20,9 @@ namespace Podsync.Tests.Services.Feed
|
||||
Title = "Steve Gillespie - Getting Arrested (Stand up Comedy)",
|
||||
Link = new Uri("https://youtube.com/watch?v=Jj22gfTnpAI"),
|
||||
PubDate = DateTime.Parse("Mon, 07 Nov 2016 20:02:26 GMT"),
|
||||
Content = new MediaContent
|
||||
{
|
||||
Url = new Uri("http://podsync.net/download/youtube/Jj22gfTnpAI.mp4"),
|
||||
Length = 52850000,
|
||||
MediaType = "video/mp4"
|
||||
},
|
||||
DownloadLink = new Uri("http://podsync.net/download/youtube/Jj22gfTnpAI.mp4"),
|
||||
FileSize = 52850000,
|
||||
ContentType = "video/mp4",
|
||||
Duration = new TimeSpan(0, 0, 2, 31)
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user