mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-06-01 04:34:50 -04:00
Start to remove transcoder dependence on kyoo
This commit is contained in:
parent
19485a110a
commit
0a0939fa3d
@ -44,9 +44,14 @@ namespace Kyoo.Abstractions.Models.Permissions
|
|||||||
Create,
|
Create,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Allow the user to delete this kind od data.
|
/// Allow the user to delete this kind of data.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Delete
|
Delete,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Allow the user to play this file.
|
||||||
|
/// </summary>
|
||||||
|
Play,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -271,8 +271,8 @@ namespace Kyoo.Abstractions.Models
|
|||||||
public VideoLinks Links =>
|
public VideoLinks Links =>
|
||||||
new()
|
new()
|
||||||
{
|
{
|
||||||
Direct = $"/video/episode/{Slug}/direct",
|
Direct = $"/episode/{Slug}/direct",
|
||||||
Hls = $"/video/episode/{Slug}/master.m3u8",
|
Hls = $"/episode/{Slug}/master.m3u8",
|
||||||
};
|
};
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -68,8 +68,7 @@ public class WatchStatusRepository : IWatchStatusRepository
|
|||||||
DatabaseContext db = scope.ServiceProvider.GetRequiredService<DatabaseContext>();
|
DatabaseContext db = scope.ServiceProvider.GetRequiredService<DatabaseContext>();
|
||||||
WatchStatusRepository repo =
|
WatchStatusRepository repo =
|
||||||
scope.ServiceProvider.GetRequiredService<WatchStatusRepository>();
|
scope.ServiceProvider.GetRequiredService<WatchStatusRepository>();
|
||||||
List<Guid> users = await db
|
List<Guid> users = await db.ShowWatchStatus.IgnoreQueryFilters()
|
||||||
.ShowWatchStatus.IgnoreQueryFilters()
|
|
||||||
.Where(x => x.ShowId == ep.ShowId && x.Status == WatchStatus.Completed)
|
.Where(x => x.ShowId == ep.ShowId && x.Status == WatchStatus.Completed)
|
||||||
.Select(x => x.UserId)
|
.Select(x => x.UserId)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
52
back/src/Kyoo.Core/Controllers/Transcoder.cs
Normal file
52
back/src/Kyoo.Core/Controllers/Transcoder.cs
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
// 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.Threading.Tasks;
|
||||||
|
using AspNetCore.Proxy;
|
||||||
|
using AspNetCore.Proxy.Options;
|
||||||
|
using Kyoo.Abstractions.Models.Utils;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace Kyoo.Core.Controllers;
|
||||||
|
|
||||||
|
public class Transcoder : Controller
|
||||||
|
{
|
||||||
|
public Task Proxy(string route, string path)
|
||||||
|
{
|
||||||
|
HttpProxyOptions proxyOptions = HttpProxyOptionsBuilder
|
||||||
|
.Instance.WithBeforeSend(
|
||||||
|
(ctx, req) =>
|
||||||
|
{
|
||||||
|
req.Headers.Add("X-Path", path);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.WithHandleFailure(
|
||||||
|
async (context, exception) =>
|
||||||
|
{
|
||||||
|
context.Response.StatusCode = StatusCodes.Status503ServiceUnavailable;
|
||||||
|
await context.Response.WriteAsJsonAsync(
|
||||||
|
new RequestError("Service unavailable")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.Build();
|
||||||
|
return this.HttpProxyAsync($"http://transcoder:7666/{route}", proxyOptions);
|
||||||
|
}
|
||||||
|
}
|
@ -110,8 +110,7 @@ namespace Kyoo.Core
|
|||||||
options.SuppressMapClientErrors = true;
|
options.SuppressMapClientErrors = true;
|
||||||
options.InvalidModelStateResponseFactory = ctx =>
|
options.InvalidModelStateResponseFactory = ctx =>
|
||||||
{
|
{
|
||||||
string[] errors = ctx
|
string[] errors = ctx.ModelState.SelectMany(x => x.Value!.Errors)
|
||||||
.ModelState.SelectMany(x => x.Value!.Errors)
|
|
||||||
.Select(x => x.ErrorMessage)
|
.Select(x => x.ErrorMessage)
|
||||||
.ToArray();
|
.ToArray();
|
||||||
return new BadRequestObjectResult(new RequestError(errors));
|
return new BadRequestObjectResult(new RequestError(errors));
|
||||||
|
@ -24,6 +24,7 @@ using Kyoo.Abstractions.Models.Attributes;
|
|||||||
using Kyoo.Abstractions.Models.Permissions;
|
using Kyoo.Abstractions.Models.Permissions;
|
||||||
using Kyoo.Abstractions.Models.Utils;
|
using Kyoo.Abstractions.Models.Utils;
|
||||||
using Kyoo.Authentication;
|
using Kyoo.Authentication;
|
||||||
|
using Kyoo.Core.Controllers;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using static Kyoo.Abstractions.Models.Utils.Constants;
|
using static Kyoo.Abstractions.Models.Utils.Constants;
|
||||||
@ -38,26 +39,12 @@ namespace Kyoo.Core.Api
|
|||||||
[ApiController]
|
[ApiController]
|
||||||
[PartialPermission(nameof(Episode))]
|
[PartialPermission(nameof(Episode))]
|
||||||
[ApiDefinition("Episodes", Group = ResourcesGroup)]
|
[ApiDefinition("Episodes", Group = ResourcesGroup)]
|
||||||
public class EpisodeApi : CrudThumbsApi<Episode>
|
public class EpisodeApi(
|
||||||
|
ILibraryManager libraryManager,
|
||||||
|
IThumbnailsManager thumbnails,
|
||||||
|
Transcoder transcoder
|
||||||
|
) : CrudThumbsApi<Episode>(libraryManager.Episodes, thumbnails)
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// The library manager used to modify or retrieve information in the data store.
|
|
||||||
/// </summary>
|
|
||||||
private readonly ILibraryManager _libraryManager;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Create a new <see cref="EpisodeApi"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="libraryManager">
|
|
||||||
/// The library manager used to modify or retrieve information in the data store.
|
|
||||||
/// </param>
|
|
||||||
/// <param name="thumbnails">The thumbnail manager used to retrieve images paths.</param>
|
|
||||||
public EpisodeApi(ILibraryManager libraryManager, IThumbnailsManager thumbnails)
|
|
||||||
: base(libraryManager.Episodes, thumbnails)
|
|
||||||
{
|
|
||||||
_libraryManager = libraryManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get episode's show
|
/// Get episode's show
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -77,7 +64,7 @@ namespace Kyoo.Core.Api
|
|||||||
[FromQuery] Include<Show> fields
|
[FromQuery] Include<Show> fields
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
return await _libraryManager.Shows.Get(
|
return await libraryManager.Shows.Get(
|
||||||
identifier.IsContainedIn<Show, Episode>(x => x.Episodes!),
|
identifier.IsContainedIn<Show, Episode>(x => x.Episodes!),
|
||||||
fields
|
fields
|
||||||
);
|
);
|
||||||
@ -104,15 +91,15 @@ namespace Kyoo.Core.Api
|
|||||||
[FromQuery] Include<Season> fields
|
[FromQuery] Include<Season> fields
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
Season? ret = await _libraryManager.Seasons.GetOrDefault(
|
Season? ret = await libraryManager.Seasons.GetOrDefault(
|
||||||
identifier.IsContainedIn<Season, Episode>(x => x.Episodes!),
|
identifier.IsContainedIn<Season, Episode>(x => x.Episodes!),
|
||||||
fields
|
fields
|
||||||
);
|
);
|
||||||
if (ret != null)
|
if (ret != null)
|
||||||
return ret;
|
return ret;
|
||||||
Episode? episode = await identifier.Match(
|
Episode? episode = await identifier.Match(
|
||||||
id => _libraryManager.Episodes.GetOrDefault(id),
|
id => libraryManager.Episodes.GetOrDefault(id),
|
||||||
slug => _libraryManager.Episodes.GetOrDefault(slug)
|
slug => libraryManager.Episodes.GetOrDefault(slug)
|
||||||
);
|
);
|
||||||
return episode == null ? NotFound() : NoContent();
|
return episode == null ? NotFound() : NoContent();
|
||||||
}
|
}
|
||||||
@ -136,9 +123,9 @@ namespace Kyoo.Core.Api
|
|||||||
{
|
{
|
||||||
Guid id = await identifier.Match(
|
Guid id = await identifier.Match(
|
||||||
id => Task.FromResult(id),
|
id => Task.FromResult(id),
|
||||||
async slug => (await _libraryManager.Episodes.Get(slug)).Id
|
async slug => (await libraryManager.Episodes.Get(slug)).Id
|
||||||
);
|
);
|
||||||
return await _libraryManager.WatchStatus.GetEpisodeStatus(id, User.GetIdOrThrow());
|
return await libraryManager.WatchStatus.GetEpisodeStatus(id, User.GetIdOrThrow());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -170,9 +157,9 @@ namespace Kyoo.Core.Api
|
|||||||
{
|
{
|
||||||
Guid id = await identifier.Match(
|
Guid id = await identifier.Match(
|
||||||
id => Task.FromResult(id),
|
id => Task.FromResult(id),
|
||||||
async slug => (await _libraryManager.Episodes.Get(slug)).Id
|
async slug => (await libraryManager.Episodes.Get(slug)).Id
|
||||||
);
|
);
|
||||||
return await _libraryManager.WatchStatus.SetEpisodeStatus(
|
return await libraryManager.WatchStatus.SetEpisodeStatus(
|
||||||
id,
|
id,
|
||||||
User.GetIdOrThrow(),
|
User.GetIdOrThrow(),
|
||||||
status,
|
status,
|
||||||
@ -199,9 +186,100 @@ namespace Kyoo.Core.Api
|
|||||||
{
|
{
|
||||||
Guid id = await identifier.Match(
|
Guid id = await identifier.Match(
|
||||||
id => Task.FromResult(id),
|
id => Task.FromResult(id),
|
||||||
async slug => (await _libraryManager.Episodes.Get(slug)).Id
|
async slug => (await libraryManager.Episodes.Get(slug)).Id
|
||||||
);
|
);
|
||||||
await _libraryManager.WatchStatus.DeleteEpisodeStatus(id, User.GetIdOrThrow());
|
await libraryManager.WatchStatus.DeleteEpisodeStatus(id, User.GetIdOrThrow());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Direct stream
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Retrieve the raw video stream, in the same container as the one on the server. No transcoding or
|
||||||
|
/// transmuxing is done.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="identifier">The ID or slug of the <see cref="Episode"/>.</param>
|
||||||
|
/// <returns>The video file of this episode.</returns>
|
||||||
|
/// <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.Status404NotFound)]
|
||||||
|
public async Task GetDirectStream(Identifier identifier)
|
||||||
|
{
|
||||||
|
string path = await identifier.Match(
|
||||||
|
async id => (await libraryManager.Episodes.Get(id)).Path,
|
||||||
|
async slug => (await libraryManager.Episodes.Get(slug)).Path
|
||||||
|
);
|
||||||
|
await transcoder.Proxy("/direct", path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get master playlist
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Get a master playlist containing all possible video qualities and audios available for this resource.
|
||||||
|
/// Note that the direct stream is missing (since the direct is not an hls stream) and
|
||||||
|
/// subtitles/fonts are not included to support more codecs than just webvtt.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="identifier">The ID or slug of the <see cref="Episode"/>.</param>
|
||||||
|
/// <returns>The master playlist of this episode.</returns>
|
||||||
|
/// <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.Status404NotFound)]
|
||||||
|
public async Task GetMaster(Identifier identifier)
|
||||||
|
{
|
||||||
|
string path = await identifier.Match(
|
||||||
|
async id => (await libraryManager.Episodes.Get(id)).Path,
|
||||||
|
async slug => (await libraryManager.Episodes.Get(slug)).Path
|
||||||
|
);
|
||||||
|
await transcoder.Proxy("/master.m3u8", path);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("{identifier:id}/{quality}/index.m3u8")]
|
||||||
|
[PartialPermission(Kind.Play)]
|
||||||
|
public async Task GetVideoIndex(Identifier identifier, string quality)
|
||||||
|
{
|
||||||
|
string path = await identifier.Match(
|
||||||
|
async id => (await libraryManager.Episodes.Get(id)).Path,
|
||||||
|
async slug => (await libraryManager.Episodes.Get(slug)).Path
|
||||||
|
);
|
||||||
|
await transcoder.Proxy($"/{quality}/index.m3u8", path);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("{identifier:id}/audio/{audio}/index.m3u8")]
|
||||||
|
[PartialPermission(Kind.Play)]
|
||||||
|
public async Task GetAudioIndex(Identifier identifier, string audio)
|
||||||
|
{
|
||||||
|
string path = await identifier.Match(
|
||||||
|
async id => (await libraryManager.Episodes.Get(id)).Path,
|
||||||
|
async slug => (await libraryManager.Episodes.Get(slug)).Path
|
||||||
|
);
|
||||||
|
await transcoder.Proxy($"/audio/{audio}/index.m3u8", path);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("{identifier:id}/audio/{audio}/{segment}")]
|
||||||
|
[PartialPermission(Kind.Play)]
|
||||||
|
public async Task GetAudioSegment(Identifier identifier, string audio, string segment)
|
||||||
|
{
|
||||||
|
string path = await identifier.Match(
|
||||||
|
async id => (await libraryManager.Episodes.Get(id)).Path,
|
||||||
|
async slug => (await libraryManager.Episodes.Get(slug)).Path
|
||||||
|
);
|
||||||
|
await transcoder.Proxy($"/audio/{audio}/{segment}", path);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("{identifier:id}/info")]
|
||||||
|
[PartialPermission(Kind.Play)]
|
||||||
|
public async Task GetInfo(Identifier identifier)
|
||||||
|
{
|
||||||
|
string path = await identifier.Match(
|
||||||
|
async id => (await libraryManager.Episodes.Get(id)).Path,
|
||||||
|
async slug => (await libraryManager.Episodes.Get(slug)).Path
|
||||||
|
);
|
||||||
|
await transcoder.Proxy("/info", path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,14 +16,14 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using AspNetCore.Proxy;
|
using Kyoo.Abstractions.Controllers;
|
||||||
using AspNetCore.Proxy.Options;
|
|
||||||
using Kyoo.Abstractions.Models.Permissions;
|
using Kyoo.Abstractions.Models.Permissions;
|
||||||
using Kyoo.Abstractions.Models.Utils;
|
using Kyoo.Abstractions.Models.Utils;
|
||||||
|
using Kyoo.Core.Controllers;
|
||||||
using Kyoo.Utils;
|
using Kyoo.Utils;
|
||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
namespace Kyoo.Core.Api
|
namespace Kyoo.Core.Api
|
||||||
@ -32,20 +32,9 @@ namespace Kyoo.Core.Api
|
|||||||
/// Proxy to other services
|
/// Proxy to other services
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ApiController]
|
[ApiController]
|
||||||
public class ProxyApi : Controller
|
[Obsolete("Use /episode/id/master.m3u8 or routes like that")]
|
||||||
|
public class ProxyApi(ILibraryManager library, Transcoder transcoder) : Controller
|
||||||
{
|
{
|
||||||
private readonly HttpProxyOptions _proxyOptions = HttpProxyOptionsBuilder
|
|
||||||
.Instance.WithHandleFailure(
|
|
||||||
async (context, exception) =>
|
|
||||||
{
|
|
||||||
context.Response.StatusCode = StatusCodes.Status503ServiceUnavailable;
|
|
||||||
await context.Response.WriteAsJsonAsync(
|
|
||||||
new RequestError("Service unavailable")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Transcoder proxy
|
/// Transcoder proxy
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -54,15 +43,28 @@ namespace Kyoo.Core.Api
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
/// <param name="rest">The path of the transcoder.</param>
|
/// <param name="rest">The path of the transcoder.</param>
|
||||||
/// <returns>The return value of the transcoder.</returns>
|
/// <returns>The return value of the transcoder.</returns>
|
||||||
[Route("video/{**rest}")]
|
[Route("video/{type}/{id:id}/{**rest}")]
|
||||||
[Permission("video", Kind.Read)]
|
[Permission("video", Kind.Read)]
|
||||||
public Task Proxy(string rest, [FromQuery] Dictionary<string, string> query)
|
[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
|
||||||
|
)
|
||||||
{
|
{
|
||||||
// TODO: Use an env var to configure transcoder:7666.
|
string path = await (
|
||||||
return this.HttpProxyAsync(
|
type is "movie" or "movies"
|
||||||
$"http://transcoder:7666/{rest}" + query.ToQueryString(),
|
? id.Match(
|
||||||
_proxyOptions
|
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 transcoder.Proxy(rest + query.ToQueryString(), path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,12 +16,9 @@ import (
|
|||||||
// Retrieve the raw video stream, in the same container as the one on the server. No transcoding or
|
// Retrieve the raw video stream, in the same container as the one on the server. No transcoding or
|
||||||
// transmuxing is done.
|
// transmuxing is done.
|
||||||
//
|
//
|
||||||
// Path: /:resource/:slug/direct
|
// Path: /direct
|
||||||
func DirectStream(c echo.Context) error {
|
func DirectStream(c echo.Context) error {
|
||||||
resource := c.Param("resource")
|
path, err := GetPath(c)
|
||||||
slug := c.Param("slug")
|
|
||||||
|
|
||||||
path, err := GetPath(resource, slug)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -34,17 +31,13 @@ func DirectStream(c echo.Context) error {
|
|||||||
// Note that the direct stream is missing (since the direct is not an hls stream) and
|
// Note that the direct stream is missing (since the direct is not an hls stream) and
|
||||||
// subtitles/fonts are not included to support more codecs than just webvtt.
|
// subtitles/fonts are not included to support more codecs than just webvtt.
|
||||||
//
|
//
|
||||||
// Path: /:resource/:slug/master.m3u8
|
// Path: /master.m3u8
|
||||||
func (h *Handler) GetMaster(c echo.Context) error {
|
func (h *Handler) GetMaster(c echo.Context) error {
|
||||||
resource := c.Param("resource")
|
|
||||||
slug := c.Param("slug")
|
|
||||||
|
|
||||||
client, err := GetClientId(c)
|
client, err := GetClientId(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
path, err := GetPath(c)
|
||||||
path, err := GetPath(resource, slug)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -62,21 +55,17 @@ func (h *Handler) GetMaster(c echo.Context) error {
|
|||||||
// This route can take a few seconds to respond since it will way for at least one segment to be
|
// This route can take a few seconds to respond since it will way for at least one segment to be
|
||||||
// available.
|
// available.
|
||||||
//
|
//
|
||||||
// Path: /:resource/:slug/:quality/index.m3u8
|
// Path: /:quality/index.m3u8
|
||||||
func (h *Handler) GetVideoIndex(c echo.Context) error {
|
func (h *Handler) GetVideoIndex(c echo.Context) error {
|
||||||
resource := c.Param("resource")
|
|
||||||
slug := c.Param("slug")
|
|
||||||
quality, err := src.QualityFromString(c.Param("quality"))
|
quality, err := src.QualityFromString(c.Param("quality"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
client, err := GetClientId(c)
|
client, err := GetClientId(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
path, err := GetPath(c)
|
||||||
path, err := GetPath(resource, slug)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -94,21 +83,17 @@ func (h *Handler) GetVideoIndex(c echo.Context) error {
|
|||||||
// This route can take a few seconds to respond since it will way for at least one segment to be
|
// This route can take a few seconds to respond since it will way for at least one segment to be
|
||||||
// available.
|
// available.
|
||||||
//
|
//
|
||||||
// Path: /:resource/:slug/audio/:audio/index.m3u8
|
// Path: /audio/:audio/index.m3u8
|
||||||
func (h *Handler) GetAudioIndex(c echo.Context) error {
|
func (h *Handler) GetAudioIndex(c echo.Context) error {
|
||||||
resource := c.Param("resource")
|
|
||||||
slug := c.Param("slug")
|
|
||||||
audio, err := strconv.ParseInt(c.Param("audio"), 10, 32)
|
audio, err := strconv.ParseInt(c.Param("audio"), 10, 32)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
client, err := GetClientId(c)
|
client, err := GetClientId(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
path, err := GetPath(c)
|
||||||
path, err := GetPath(resource, slug)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -124,10 +109,8 @@ func (h *Handler) GetAudioIndex(c echo.Context) error {
|
|||||||
//
|
//
|
||||||
// Retrieve a chunk of a transmuxed video.
|
// Retrieve a chunk of a transmuxed video.
|
||||||
//
|
//
|
||||||
// Path: /:resource/:slug/:quality/segments-:chunk.ts
|
// Path: /:quality/segments-:chunk.ts
|
||||||
func (h *Handler) GetVideoSegment(c echo.Context) error {
|
func (h *Handler) GetVideoSegment(c echo.Context) error {
|
||||||
resource := c.Param("resource")
|
|
||||||
slug := c.Param("slug")
|
|
||||||
quality, err := src.QualityFromString(c.Param("quality"))
|
quality, err := src.QualityFromString(c.Param("quality"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -136,13 +119,11 @@ func (h *Handler) GetVideoSegment(c echo.Context) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
client, err := GetClientId(c)
|
client, err := GetClientId(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
path, err := GetPath(c)
|
||||||
path, err := GetPath(resource, slug)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -158,10 +139,8 @@ func (h *Handler) GetVideoSegment(c echo.Context) error {
|
|||||||
//
|
//
|
||||||
// Retrieve a chunk of a transcoded audio.
|
// Retrieve a chunk of a transcoded audio.
|
||||||
//
|
//
|
||||||
// Path: /:resource/:slug/audio/:audio/segments-:chunk.ts
|
// Path: /audio/:audio/segments-:chunk.ts
|
||||||
func (h *Handler) GetAudioSegment(c echo.Context) error {
|
func (h *Handler) GetAudioSegment(c echo.Context) error {
|
||||||
resource := c.Param("resource")
|
|
||||||
slug := c.Param("slug")
|
|
||||||
audio, err := strconv.ParseInt(c.Param("audio"), 10, 32)
|
audio, err := strconv.ParseInt(c.Param("audio"), 10, 32)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -170,13 +149,11 @@ func (h *Handler) GetAudioSegment(c echo.Context) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
client, err := GetClientId(c)
|
client, err := GetClientId(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
path, err := GetPath(c)
|
||||||
path, err := GetPath(resource, slug)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -192,12 +169,9 @@ func (h *Handler) GetAudioSegment(c echo.Context) error {
|
|||||||
//
|
//
|
||||||
// Identify metadata about a file.
|
// Identify metadata about a file.
|
||||||
//
|
//
|
||||||
// Path: /:resource/:slug/info
|
// Path: /info
|
||||||
func (h *Handler) GetInfo(c echo.Context) error {
|
func (h *Handler) GetInfo(c echo.Context) error {
|
||||||
resource := c.Param("resource")
|
path, err := GetPath(c)
|
||||||
slug := c.Param("slug")
|
|
||||||
|
|
||||||
path, err := GetPath(resource, slug)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -208,10 +182,10 @@ func (h *Handler) GetInfo(c echo.Context) error {
|
|||||||
}
|
}
|
||||||
// Run extractors to have them in cache
|
// Run extractors to have them in cache
|
||||||
h.extractor.RunExtractor(ret.Path, ret.Sha, &ret.Subtitles)
|
h.extractor.RunExtractor(ret.Path, ret.Sha, &ret.Subtitles)
|
||||||
go h.thumbnails.ExtractThumbnail(
|
// go h.thumbnails.ExtractThumbnail(
|
||||||
ret.Path,
|
// ret.Path,
|
||||||
fmt.Sprintf("%s/%s/thumbnails.png", resource, slug),
|
// fmt.Sprintf("%s/%s/thumbnails.png", resource, slug),
|
||||||
)
|
// )
|
||||||
return c.JSON(http.StatusOK, ret)
|
return c.JSON(http.StatusOK, ret)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -340,13 +314,13 @@ func main() {
|
|||||||
thumbnails: src.NewThumbnailsCreator(),
|
thumbnails: src.NewThumbnailsCreator(),
|
||||||
}
|
}
|
||||||
|
|
||||||
e.GET("/:resource/:slug/direct", DirectStream)
|
e.GET("/direct", DirectStream)
|
||||||
e.GET("/:resource/:slug/master.m3u8", h.GetMaster)
|
e.GET("/master.m3u8", h.GetMaster)
|
||||||
e.GET("/:resource/:slug/:quality/index.m3u8", h.GetVideoIndex)
|
e.GET("/:quality/index.m3u8", h.GetVideoIndex)
|
||||||
e.GET("/:resource/:slug/audio/:audio/index.m3u8", h.GetAudioIndex)
|
e.GET("/audio/:audio/index.m3u8", h.GetAudioIndex)
|
||||||
e.GET("/:resource/:slug/:quality/:chunk", h.GetVideoSegment)
|
e.GET("/:quality/:chunk", h.GetVideoSegment)
|
||||||
e.GET("/:resource/:slug/audio/:audio/:chunk", h.GetAudioSegment)
|
e.GET("/audio/:audio/:chunk", h.GetAudioSegment)
|
||||||
e.GET("/:resource/:slug/info", h.GetInfo)
|
e.GET("/info", h.GetInfo)
|
||||||
e.GET("/:resource/:slug/thumbnails.png", h.GetThumbnails)
|
e.GET("/:resource/:slug/thumbnails.png", h.GetThumbnails)
|
||||||
e.GET("/:resource/:slug/thumbnails.vtt", h.GetThumbnailsVtt)
|
e.GET("/:resource/:slug/thumbnails.vtt", h.GetThumbnailsVtt)
|
||||||
e.GET("/:sha/attachment/:name", h.GetAttachment)
|
e.GET("/:sha/attachment/:name", h.GetAttachment)
|
||||||
|
@ -1,11 +1,8 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -18,42 +15,12 @@ type Item struct {
|
|||||||
Path string `json:"path"`
|
Path string `json:"path"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetPath(resource string, slug string) (string, error) {
|
func GetPath(c echo.Context) (string, error) {
|
||||||
url := os.Getenv("API_URL")
|
key := c.Request().Header.Get("X-Path")
|
||||||
if url == "" {
|
|
||||||
url = "http://back:5000"
|
|
||||||
}
|
|
||||||
key := os.Getenv("KYOO_APIKEYS")
|
|
||||||
if key == "" {
|
if key == "" {
|
||||||
return "", errors.New("missing api keys")
|
return "", echo.NewHTTPError(http.StatusBadRequest, "Missing resouce path. You need to set the X-Path header.")
|
||||||
}
|
}
|
||||||
key = strings.Split(key, ",")[0]
|
return key, nil
|
||||||
|
|
||||||
req, err := http.NewRequest("GET", strings.Join([]string{url, resource, slug}, "/"), nil)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
req.Header.Set("X-API-KEY", key)
|
|
||||||
res, err := client.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
if res.StatusCode != 200 {
|
|
||||||
return "", echo.NewHTTPError(
|
|
||||||
http.StatusNotFound,
|
|
||||||
fmt.Sprintf("No %s found with the slug %s.", resource, slug),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer res.Body.Close()
|
|
||||||
ret := Item{}
|
|
||||||
err = json.NewDecoder(res.Body).Decode(&ret)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret.Path, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func SanitizePath(path string) error {
|
func SanitizePath(path string) error {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user