mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-05-24 02:02:36 -04:00
Move transcoder api to /video with path b64 encoded to prevent db hit
This commit is contained in:
parent
f871f4e1bf
commit
59e1832c3e
42
back/src/Kyoo.Core/Controllers/Base64RouteConstraint.cs
Normal file
42
back/src/Kyoo.Core/Controllers/Base64RouteConstraint.cs
Normal file
@ -0,0 +1,42 @@
|
||||
// Kyoo - A portable and vast media library solution.
|
||||
// Copyright (c) Kyoo.
|
||||
//
|
||||
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||
//
|
||||
// Kyoo is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// any later version.
|
||||
//
|
||||
// Kyoo is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
|
||||
namespace Kyoo.Core.Controllers;
|
||||
|
||||
public class Base64RouteConstraint : IRouteConstraint
|
||||
{
|
||||
static Regex Base64Reg = new("^[-A-Za-z0-9+/]*={0,3}$");
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Match(
|
||||
HttpContext? httpContext,
|
||||
IRouter? route,
|
||||
string routeKey,
|
||||
RouteValueDictionary values,
|
||||
RouteDirection routeDirection
|
||||
)
|
||||
{
|
||||
return values.TryGetValue(routeKey, out object? val)
|
||||
&& val is string str
|
||||
&& Base64Reg.IsMatch(str);
|
||||
}
|
||||
}
|
@ -76,6 +76,7 @@ public static class ServiceExtensions
|
||||
services.Configure<RouteOptions>(x =>
|
||||
{
|
||||
x.ConstraintMap.Add("id", typeof(IdentifierRouteConstraint));
|
||||
x.ConstraintMap.Add("base64", typeof(Base64RouteConstraint));
|
||||
});
|
||||
|
||||
services.AddResponseCompression(x =>
|
||||
|
@ -1,95 +0,0 @@
|
||||
// Kyoo - A portable and vast media library solution.
|
||||
// Copyright (c) Kyoo.
|
||||
//
|
||||
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||
//
|
||||
// Kyoo is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// any later version.
|
||||
//
|
||||
// Kyoo is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using AspNetCore.Proxy;
|
||||
using AspNetCore.Proxy.Options;
|
||||
using Kyoo.Abstractions.Controllers;
|
||||
using Kyoo.Abstractions.Models.Permissions;
|
||||
using Kyoo.Abstractions.Models.Utils;
|
||||
using Kyoo.Utils;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Kyoo.Core.Api;
|
||||
|
||||
/// <summary>
|
||||
/// Proxy to other services
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Obsolete("Use /episode/id/master.m3u8 or routes like that")]
|
||||
public class ProxyApi(ILibraryManager library) : Controller
|
||||
{
|
||||
private Task _Proxy(string route, (string path, string route) info)
|
||||
{
|
||||
HttpProxyOptions proxyOptions = HttpProxyOptionsBuilder
|
||||
.Instance.WithBeforeSend(
|
||||
(ctx, req) =>
|
||||
{
|
||||
req.Headers.Add("X-Path", info.path);
|
||||
req.Headers.Add("X-Route", info.route);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
)
|
||||
.WithHandleFailure(
|
||||
async (context, exception) =>
|
||||
{
|
||||
context.Response.StatusCode = StatusCodes.Status503ServiceUnavailable;
|
||||
await context.Response.WriteAsJsonAsync(
|
||||
new RequestError("Service unavailable")
|
||||
);
|
||||
}
|
||||
)
|
||||
.Build();
|
||||
return this.HttpProxyAsync($"{Transcoder.TranscoderUrl}{route}", proxyOptions);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Transcoder proxy
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Simply proxy requests to the transcoder
|
||||
/// </remarks>
|
||||
/// <param name="rest">The path of the transcoder.</param>
|
||||
/// <returns>The return value of the transcoder.</returns>
|
||||
[Route("video/{type}/{id:id}/{**rest}")]
|
||||
[Permission("video", Kind.Read)]
|
||||
[Obsolete("Use /episode/id/master.m3u8 or routes like that")]
|
||||
public async Task Proxy(
|
||||
string type,
|
||||
Identifier id,
|
||||
string rest,
|
||||
[FromQuery] Dictionary<string, string> query
|
||||
)
|
||||
{
|
||||
string path = await (
|
||||
type is "movie" or "movies"
|
||||
? id.Match(
|
||||
async id => (await library.Movies.Get(id)).Path,
|
||||
async slug => (await library.Movies.Get(slug)).Path
|
||||
)
|
||||
: id.Match(
|
||||
async id => (await library.Episodes.Get(id)).Path,
|
||||
async slug => (await library.Episodes.Get(slug)).Path
|
||||
)
|
||||
);
|
||||
await _Proxy(rest + query.ToQueryString(), (path, $"{type}/{id}"));
|
||||
}
|
||||
}
|
122
back/src/Kyoo.Core/Views/Content/VideoApi.cs
Normal file
122
back/src/Kyoo.Core/Views/Content/VideoApi.cs
Normal file
@ -0,0 +1,122 @@
|
||||
// Kyoo - A portable and vast media library solution.
|
||||
// Copyright (c) Kyoo.
|
||||
//
|
||||
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||
//
|
||||
// Kyoo is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// any later version.
|
||||
//
|
||||
// Kyoo is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using AspNetCore.Proxy;
|
||||
using AspNetCore.Proxy.Options;
|
||||
using Kyoo.Abstractions.Models.Attributes;
|
||||
using Kyoo.Abstractions.Models.Permissions;
|
||||
using Kyoo.Abstractions.Models.Utils;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using static Kyoo.Abstractions.Models.Utils.Constants;
|
||||
|
||||
namespace Kyoo.Core.Api;
|
||||
|
||||
/// <summary>
|
||||
/// Private routes of the transcoder.
|
||||
/// Url for these routes will be returned from /info or /master.m3u8 routes.
|
||||
/// This should not be called manually
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("videos")]
|
||||
[Route("video", Order = AlternativeRoute)]
|
||||
[Permission("video", Kind.Read, Group = Group.Overall)]
|
||||
[ApiDefinition("Video", Group = OtherGroup)]
|
||||
public class VideoApi : Controller
|
||||
{
|
||||
public static string TranscoderUrl =
|
||||
Environment.GetEnvironmentVariable("TRANSCODER_URL") ?? "http://transcoder:7666";
|
||||
|
||||
private Task _Proxy(string route)
|
||||
{
|
||||
HttpProxyOptions proxyOptions = HttpProxyOptionsBuilder
|
||||
.Instance.WithHandleFailure(
|
||||
async (context, exception) =>
|
||||
{
|
||||
context.Response.StatusCode = StatusCodes.Status503ServiceUnavailable;
|
||||
await context.Response.WriteAsJsonAsync(
|
||||
new RequestError("Service unavailable")
|
||||
);
|
||||
}
|
||||
)
|
||||
.Build();
|
||||
return this.HttpProxyAsync($"{TranscoderUrl}/{route}", proxyOptions);
|
||||
}
|
||||
|
||||
[HttpGet("{path:base64}/direct")]
|
||||
[PartialPermission(Kind.Play)]
|
||||
[ProducesResponseType(StatusCodes.Status206PartialContent)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task GetDirectStream(string path)
|
||||
{
|
||||
await _Proxy($"{path}/direct");
|
||||
}
|
||||
|
||||
[HttpGet("{path:base64}/master.m3u8")]
|
||||
[PartialPermission(Kind.Play)]
|
||||
[ProducesResponseType(StatusCodes.Status206PartialContent)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task GetMaster(string path)
|
||||
{
|
||||
await _Proxy($"{path}/master.m3u8");
|
||||
}
|
||||
|
||||
[HttpGet("{path:base64}/{quality}/index.m3u8")]
|
||||
[PartialPermission(Kind.Play)]
|
||||
public async Task GetVideoIndex(string path, string quality)
|
||||
{
|
||||
await _Proxy($"{path}/{quality}/index.m3u8");
|
||||
}
|
||||
|
||||
[HttpGet("{path:base64}/{quality}/{segment}")]
|
||||
[PartialPermission(Kind.Play)]
|
||||
public async Task GetVideoSegment(string path, string quality, string segment)
|
||||
{
|
||||
await _Proxy($"{path}/{quality}/{segment}");
|
||||
}
|
||||
|
||||
[HttpGet("{path:base64}/audio/{audio}/index.m3u8")]
|
||||
[PartialPermission(Kind.Play)]
|
||||
public async Task GetAudioIndex(string path, string audio)
|
||||
{
|
||||
await _Proxy($"{path}/audio/{audio}/index.m3u8");
|
||||
}
|
||||
|
||||
[HttpGet("{path:base64}/audio/{audio}/{segment}")]
|
||||
[PartialPermission(Kind.Play)]
|
||||
public async Task GetAudioSegment(string path, string audio, string segment)
|
||||
{
|
||||
await _Proxy($"{path}/audio/{audio}/{segment}");
|
||||
}
|
||||
|
||||
[HttpGet("{path:base64}/attachment/{name}")]
|
||||
[PartialPermission(Kind.Play)]
|
||||
public async Task GetAttachment(string path, string name)
|
||||
{
|
||||
await _Proxy($"{path}/attachment/{name}");
|
||||
}
|
||||
|
||||
[HttpGet("{path:base64}/subtitle/{name}")]
|
||||
[PartialPermission(Kind.Play)]
|
||||
public async Task GetSubtitle(string path, string name)
|
||||
{
|
||||
await _Proxy($"{path}/subtitle/{name}");
|
||||
}
|
||||
}
|
@ -17,6 +17,7 @@
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using AspNetCore.Proxy;
|
||||
using AspNetCore.Proxy.Options;
|
||||
@ -29,27 +30,13 @@ using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Kyoo.Core.Api;
|
||||
|
||||
public static class Transcoder
|
||||
{
|
||||
public static string TranscoderUrl =
|
||||
Environment.GetEnvironmentVariable("TRANSCODER_URL") ?? "http://transcoder:7666";
|
||||
}
|
||||
|
||||
public abstract class TranscoderApi<T>(IRepository<T> repository) : CrudThumbsApi<T>(repository)
|
||||
where T : class, IResource, IThumbnails, IQuery
|
||||
{
|
||||
private Task _Proxy(string route, (string path, string route) info)
|
||||
private Task _Proxy(string route)
|
||||
{
|
||||
HttpProxyOptions proxyOptions = HttpProxyOptionsBuilder
|
||||
.Instance.WithBeforeSend(
|
||||
(ctx, req) =>
|
||||
{
|
||||
req.Headers.Add("X-Path", info.path);
|
||||
req.Headers.Add("X-Route", info.route);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
)
|
||||
.WithHandleFailure(
|
||||
.Instance.WithHandleFailure(
|
||||
async (context, exception) =>
|
||||
{
|
||||
context.Response.StatusCode = StatusCodes.Status503ServiceUnavailable;
|
||||
@ -59,10 +46,16 @@ public abstract class TranscoderApi<T>(IRepository<T> repository) : CrudThumbsAp
|
||||
}
|
||||
)
|
||||
.Build();
|
||||
return this.HttpProxyAsync($"{Transcoder.TranscoderUrl}{route}", proxyOptions);
|
||||
return this.HttpProxyAsync($"{VideoApi.TranscoderUrl}/{route}", proxyOptions);
|
||||
}
|
||||
|
||||
protected abstract Task<(string path, string route)> GetPath(Identifier identifier);
|
||||
protected abstract Task<string> GetPath(Identifier identifier);
|
||||
|
||||
private async Task<string> _GetPath64(Identifier identifier)
|
||||
{
|
||||
string path = await GetPath(identifier);
|
||||
return Convert.ToBase64String(Encoding.UTF8.GetBytes(path));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Direct stream
|
||||
@ -76,11 +69,12 @@ public abstract class TranscoderApi<T>(IRepository<T> repository) : CrudThumbsAp
|
||||
/// <response code="404">No episode with the given ID or slug could be found.</response>
|
||||
[HttpGet("{identifier:id}/direct")]
|
||||
[PartialPermission(Kind.Play)]
|
||||
[ProducesResponseType(StatusCodes.Status206PartialContent)]
|
||||
[ProducesResponseType(StatusCodes.Status302Found)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task GetDirectStream(Identifier identifier)
|
||||
public async Task<IActionResult> GetDirectStream(Identifier identifier)
|
||||
{
|
||||
await _Proxy("/direct", await GetPath(identifier));
|
||||
// TODO: Remove the /api and use a proxy rewrite instead.
|
||||
return Redirect($"/api/video/{await _GetPath64(identifier)}/direct");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -96,39 +90,12 @@ public abstract class TranscoderApi<T>(IRepository<T> repository) : CrudThumbsAp
|
||||
/// <response code="404">No episode with the given ID or slug could be found.</response>
|
||||
[HttpGet("{identifier:id}/master.m3u8")]
|
||||
[PartialPermission(Kind.Play)]
|
||||
[ProducesResponseType(StatusCodes.Status206PartialContent)]
|
||||
[ProducesResponseType(StatusCodes.Status302Found)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task GetMaster(Identifier identifier)
|
||||
public async Task<IActionResult> GetMaster(Identifier identifier)
|
||||
{
|
||||
await _Proxy("/master.m3u8", await GetPath(identifier));
|
||||
}
|
||||
|
||||
[HttpGet("{identifier:id}/{quality}/index.m3u8")]
|
||||
[PartialPermission(Kind.Play)]
|
||||
public async Task GetVideoIndex(Identifier identifier, string quality)
|
||||
{
|
||||
await _Proxy($"/{quality}/index.m3u8", await GetPath(identifier));
|
||||
}
|
||||
|
||||
[HttpGet("{identifier:id}/{quality}/{segment}")]
|
||||
[PartialPermission(Kind.Play)]
|
||||
public async Task GetVideoSegment(Identifier identifier, string quality, string segment)
|
||||
{
|
||||
await _Proxy($"/{quality}/{segment}", await GetPath(identifier));
|
||||
}
|
||||
|
||||
[HttpGet("{identifier:id}/audio/{audio}/index.m3u8")]
|
||||
[PartialPermission(Kind.Play)]
|
||||
public async Task GetAudioIndex(Identifier identifier, string audio)
|
||||
{
|
||||
await _Proxy($"/audio/{audio}/index.m3u8", await GetPath(identifier));
|
||||
}
|
||||
|
||||
[HttpGet("{identifier:id}/audio/{audio}/{segment}")]
|
||||
[PartialPermission(Kind.Play)]
|
||||
public async Task GetAudioSegment(Identifier identifier, string audio, string segment)
|
||||
{
|
||||
await _Proxy($"/audio/{audio}/{segment}", await GetPath(identifier));
|
||||
// TODO: Remove the /api and use a proxy rewrite instead.
|
||||
return Redirect($"/api/video/{await _GetPath64(identifier)}/master.m3u8");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -144,7 +111,7 @@ public abstract class TranscoderApi<T>(IRepository<T> repository) : CrudThumbsAp
|
||||
[PartialPermission(Kind.Read)]
|
||||
public async Task GetInfo(Identifier identifier)
|
||||
{
|
||||
await _Proxy("/info", await GetPath(identifier));
|
||||
await _Proxy($"{await _GetPath64(identifier)}/info");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -160,7 +127,7 @@ public abstract class TranscoderApi<T>(IRepository<T> repository) : CrudThumbsAp
|
||||
[PartialPermission(Kind.Read)]
|
||||
public async Task GetThumbnails(Identifier identifier)
|
||||
{
|
||||
await _Proxy("/thumbnails.png", await GetPath(identifier));
|
||||
await _Proxy($"{await _GetPath64(identifier)}/thumbnails.png");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -177,6 +144,6 @@ public abstract class TranscoderApi<T>(IRepository<T> repository) : CrudThumbsAp
|
||||
[PartialPermission(Kind.Read)]
|
||||
public async Task GetThumbnailsVtt(Identifier identifier)
|
||||
{
|
||||
await _Proxy("/thumbnails.vtt", await GetPath(identifier));
|
||||
await _Proxy($"{await _GetPath64(identifier)}/thumbnails.vtt");
|
||||
}
|
||||
}
|
||||
|
@ -210,12 +210,12 @@ public class EpisodeApi(ILibraryManager libraryManager)
|
||||
await libraryManager.WatchStatus.DeleteEpisodeStatus(id, User.GetIdOrThrow());
|
||||
}
|
||||
|
||||
protected override async Task<(string path, string route)> GetPath(Identifier identifier)
|
||||
protected override async Task<string> GetPath(Identifier identifier)
|
||||
{
|
||||
string path = await identifier.Match(
|
||||
async id => (await Repository.Get(id)).Path,
|
||||
async slug => (await Repository.Get(slug)).Path
|
||||
);
|
||||
return (path, $"/episodes/{identifier}");
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
@ -221,12 +221,12 @@ public class MovieApi(ILibraryManager libraryManager) : TranscoderApi<Movie>(lib
|
||||
await libraryManager.WatchStatus.DeleteMovieStatus(id, User.GetIdOrThrow());
|
||||
}
|
||||
|
||||
protected override async Task<(string path, string route)> GetPath(Identifier identifier)
|
||||
protected override async Task<string> GetPath(Identifier identifier)
|
||||
{
|
||||
string path = await identifier.Match(
|
||||
async id => (await Repository.Get(id)).Path,
|
||||
async slug => (await Repository.Get(slug)).Path
|
||||
);
|
||||
return (path, $"/movies/{identifier}");
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user