diff --git a/back/src/Kyoo.Core/Controllers/Base64RouteConstraint.cs b/back/src/Kyoo.Core/Controllers/Base64RouteConstraint.cs
new file mode 100644
index 00000000..e69177a1
--- /dev/null
+++ b/back/src/Kyoo.Core/Controllers/Base64RouteConstraint.cs
@@ -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 .
+
+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}$");
+
+ ///
+ 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);
+ }
+}
diff --git a/back/src/Kyoo.Core/Extensions/ServiceExtensions.cs b/back/src/Kyoo.Core/Extensions/ServiceExtensions.cs
index 452aed44..5dd49309 100644
--- a/back/src/Kyoo.Core/Extensions/ServiceExtensions.cs
+++ b/back/src/Kyoo.Core/Extensions/ServiceExtensions.cs
@@ -76,6 +76,7 @@ public static class ServiceExtensions
services.Configure(x =>
{
x.ConstraintMap.Add("id", typeof(IdentifierRouteConstraint));
+ x.ConstraintMap.Add("base64", typeof(Base64RouteConstraint));
});
services.AddResponseCompression(x =>
diff --git a/back/src/Kyoo.Core/Views/Content/ProxyApi.cs b/back/src/Kyoo.Core/Views/Content/ProxyApi.cs
deleted file mode 100644
index 28b36253..00000000
--- a/back/src/Kyoo.Core/Views/Content/ProxyApi.cs
+++ /dev/null
@@ -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 .
-
-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;
-
-///
-/// Proxy to other services
-///
-[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);
- }
-
- ///
- /// Transcoder proxy
- ///
- ///
- /// Simply proxy requests to the transcoder
- ///
- /// The path of the transcoder.
- /// The return value of the transcoder.
- [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 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}"));
- }
-}
diff --git a/back/src/Kyoo.Core/Views/Content/VideoApi.cs b/back/src/Kyoo.Core/Views/Content/VideoApi.cs
new file mode 100644
index 00000000..3415986a
--- /dev/null
+++ b/back/src/Kyoo.Core/Views/Content/VideoApi.cs
@@ -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 .
+
+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;
+
+///
+/// Private routes of the transcoder.
+/// Url for these routes will be returned from /info or /master.m3u8 routes.
+/// This should not be called manually
+///
+[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}");
+ }
+}
diff --git a/back/src/Kyoo.Core/Views/Helper/Transcoder.cs b/back/src/Kyoo.Core/Views/Helper/Transcoder.cs
index 337f437b..43f25a8d 100644
--- a/back/src/Kyoo.Core/Views/Helper/Transcoder.cs
+++ b/back/src/Kyoo.Core/Views/Helper/Transcoder.cs
@@ -17,6 +17,7 @@
// along with Kyoo. If not, see .
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(IRepository repository) : CrudThumbsApi(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(IRepository 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 GetPath(Identifier identifier);
+
+ private async Task _GetPath64(Identifier identifier)
+ {
+ string path = await GetPath(identifier);
+ return Convert.ToBase64String(Encoding.UTF8.GetBytes(path));
+ }
///
/// Direct stream
@@ -76,11 +69,12 @@ public abstract class TranscoderApi(IRepository repository) : CrudThumbsAp
/// No episode with the given ID or slug could be found.
[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 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");
}
///
@@ -96,39 +90,12 @@ public abstract class TranscoderApi(IRepository repository) : CrudThumbsAp
/// No episode with the given ID or slug could be found.
[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 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");
}
///
@@ -144,7 +111,7 @@ public abstract class TranscoderApi(IRepository repository) : CrudThumbsAp
[PartialPermission(Kind.Read)]
public async Task GetInfo(Identifier identifier)
{
- await _Proxy("/info", await GetPath(identifier));
+ await _Proxy($"{await _GetPath64(identifier)}/info");
}
///
@@ -160,7 +127,7 @@ public abstract class TranscoderApi(IRepository 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");
}
///
@@ -177,6 +144,6 @@ public abstract class TranscoderApi(IRepository 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");
}
}
diff --git a/back/src/Kyoo.Core/Views/Resources/EpisodeApi.cs b/back/src/Kyoo.Core/Views/Resources/EpisodeApi.cs
index 9fb268a0..b26c196c 100644
--- a/back/src/Kyoo.Core/Views/Resources/EpisodeApi.cs
+++ b/back/src/Kyoo.Core/Views/Resources/EpisodeApi.cs
@@ -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 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;
}
}
diff --git a/back/src/Kyoo.Core/Views/Resources/MovieApi.cs b/back/src/Kyoo.Core/Views/Resources/MovieApi.cs
index 1d95ab6f..274b4e31 100644
--- a/back/src/Kyoo.Core/Views/Resources/MovieApi.cs
+++ b/back/src/Kyoo.Core/Views/Resources/MovieApi.cs
@@ -221,12 +221,12 @@ public class MovieApi(ILibraryManager libraryManager) : TranscoderApi(lib
await libraryManager.WatchStatus.DeleteMovieStatus(id, User.GetIdOrThrow());
}
- protected override async Task<(string path, string route)> GetPath(Identifier identifier)
+ protected override async Task 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;
}
}