Cleaning up the video & the subtitle api

This commit is contained in:
Zoe Roux 2020-10-26 06:22:03 +01:00
parent c07494c9e8
commit 2dd0806517
9 changed files with 140 additions and 128 deletions

View File

@ -32,6 +32,7 @@ namespace Kyoo.Controllers
Task<Episode> GetEpisode(int id);
Task<Episode> GetEpisode(int showID, int seasonNumber, int episodeNumber);
Task<Genre> GetGenre(int id);
Task<Track> GetTrack(int id);
Task<Studio> GetStudio(int id);
Task<People> GetPeople(int id);
@ -42,7 +43,7 @@ namespace Kyoo.Controllers
Task<Season> GetSeason(string showSlug, int seasonNumber);
Task<Episode> GetEpisode(string showSlug, int seasonNumber, int episodeNumber);
Task<Episode> GetMovieEpisode(string movieSlug);
Task<Track> GetTrack(int id);
Task<Track> GetTrack(string slug);
Task<Genre> GetGenre(string slug);
Task<Studio> GetStudio(string slug);
Task<People> GetPeople(string slug);

View File

@ -109,7 +109,12 @@ namespace Kyoo.Controllers
{
return EpisodeRepository.Get(showID, seasonNumber, episodeNumber);
}
public Task<Track> GetTrack(string slug)
{
return TrackRepository.Get(slug);
}
public Task<Genre> GetGenre(int id)
{
return GenreRepository.Get(id);

View File

@ -93,7 +93,9 @@ namespace Kyoo.Models
public static string GetSlug(string showSlug, int seasonNumber, int episodeNumber)
{
return showSlug + "-s" + seasonNumber + "e" + episodeNumber;
if (seasonNumber == -1)
return showSlug;
return $"{showSlug}-s{seasonNumber}e{episodeNumber}";
}
public void OnMerge(object merged)

View File

@ -39,18 +39,21 @@ namespace Kyoo.Controllers
public override Task<Track> Get(string slug)
{
Match match = Regex.Match(slug,
@"(?<show>.*)-s(?<season>\d*)-e(?<episode>\d*).(?<language>.{0,3})(?<forced>-forced)?(\..*)?");
@"(?<show>.*)-s(?<season>\d+)e(?<episode>\d+)\.(?<language>.{0,3})(?<forced>-forced)?(\..*)?");
if (!match.Success)
{
if (int.TryParse(slug, out int id))
return Get(id);
throw new ArgumentException("Invalid track slug. Format: {episodeSlug}.{language}[-forced][.{extension}]");
match = Regex.Match(slug, @"(?<show>.*)\.(?<language>.{0,3})(?<forced>-forced)?(\..*)?");
if (!match.Success)
throw new ArgumentException("Invalid track slug. " +
"Format: {episodeSlug}.{language}[-forced][.{extension}]");
}
string showSlug = match.Groups["show"].Value;
int seasonNumber = int.Parse(match.Groups["season"].Value);
int episodeNumber = int.Parse(match.Groups["episode"].Value);
int seasonNumber = match.Groups["season"].Success ? int.Parse(match.Groups["season"].Value) : -1;
int episodeNumber = match.Groups["episode"].Success ? int.Parse(match.Groups["episode"].Value) : -1;
string language = match.Groups["language"].Value;
bool forced = match.Groups["forced"].Success;
return _database.Tracks.FirstOrDefaultAsync(x => x.Episode.Show.Slug == showSlug
@ -59,6 +62,7 @@ namespace Kyoo.Controllers
&& x.Language == language
&& x.IsForced == forced);
}
public Task<ICollection<Track>> Search(string query)
{
throw new InvalidOperationException("Tracks do not support the search method.");

View File

@ -79,7 +79,7 @@ namespace Kyoo.Controllers
public Task<string> Transcode(Episode episode)
{
return null; // Not implemented yet.
return Task.FromResult<string>(null); // Not implemented yet.
}
}
}

View File

@ -32,7 +32,6 @@ namespace Kyoo
_loggerFactory = loggerFactory;
}
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
@ -132,7 +131,7 @@ namespace Kyoo
{
AllowedOrigins = { new Uri(publicUrl).GetLeftPart(UriPartial.Authority) }
});
services.AddScoped<ILibraryRepository, LibraryRepository>();
services.AddScoped<ILibraryItemRepository, LibraryItemRepository>();

View File

@ -1,68 +1,46 @@
using Kyoo.Models;
using System;
using Kyoo.Models;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Kyoo.Controllers;
using Kyoo.Models.Watch;
using Microsoft.AspNetCore.Authorization;
namespace Kyoo.Api
{
[Route("[controller]")]
[Route("subtitle")]
[ApiController]
public class SubtitleController : ControllerBase
public class SubtitleApi : ControllerBase
{
private readonly ILibraryManager _libraryManager;
//private readonly ITranscoder _transcoder;
public SubtitleController(ILibraryManager libraryManager/*, ITranscoder transcoder*/)
public SubtitleApi(ILibraryManager libraryManager)
{
_libraryManager = libraryManager;
// _transcoder = transcoder;
}
//TODO Create a real route for movie's subtitles.
[HttpGet("{showSlug}-s{seasonNumber:int}e{episodeNumber:int}.{identifier}.{extension?}")]
[HttpGet("{slug}.{extension?}")]
[Authorize(Policy="Play")]
public async Task<IActionResult> GetSubtitle(string showSlug,
int seasonNumber,
int episodeNumber,
string identifier,
string extension)
public async Task<IActionResult> GetSubtitle(string slug, string extension)
{
string languageTag = identifier.Length >= 3 ? identifier.Substring(0, 3) : null;
bool forced = identifier.Length > 4 && identifier.Substring(4) == "forced";
Track subtitle = null;
if (languageTag != null)
subtitle = (await _libraryManager.GetEpisode(showSlug, seasonNumber, episodeNumber))?.Tracks
.FirstOrDefault(x => x.Type == StreamType.Subtitle && x.Language == languageTag && x.IsForced == forced);
if (subtitle == null)
Track subtitle;
try
{
string idString = identifier.IndexOf('-') != -1
? identifier.Substring(0, identifier.IndexOf('-'))
: identifier;
int.TryParse(idString, out int id);
subtitle = await _libraryManager.GetTrack(id);
subtitle = await _libraryManager.GetTrack(slug);
}
catch (ArgumentException ex)
{
return BadRequest(new {error = ex.Message});
}
if (subtitle == null)
return NotFound();
if (subtitle.Codec == "subrip" && extension == "vtt")
return new ConvertSubripToVtt(subtitle.Path);
string mime;
if (subtitle.Codec == "ass")
mime = "text/x-ssa";
else
mime = "application/x-subrip";
// TODO Should use appropriate mime type here
string mime = subtitle.Codec == "ass" ? "text/x-ssa" : "application/x-subrip";
return PhysicalFile(subtitle.Path, mime);
}
@ -129,21 +107,19 @@ namespace Kyoo.Api
await writer.WriteLineAsync("");
await writer.WriteLineAsync("");
using (StreamReader reader = new StreamReader(_path))
using StreamReader reader = new StreamReader(_path);
while ((line = await reader.ReadLineAsync()) != null)
{
while ((line = await reader.ReadLineAsync()) != null)
if (line == "")
{
if (line == "")
{
lines.Add("");
IEnumerable<string> processedBlock = ConvertBlock(lines);
foreach (string t in processedBlock)
await writer.WriteLineAsync(t);
lines.Clear();
}
else
lines.Add(line);
lines.Add("");
IEnumerable<string> processedBlock = ConvertBlock(lines);
foreach (string t in processedBlock)
await writer.WriteLineAsync(t);
lines.Clear();
}
else
lines.Add(line);
}
}

View File

@ -5,19 +5,21 @@ using Microsoft.Extensions.Configuration;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.StaticFiles;
namespace Kyoo.Api
{
[Route("[controller]")]
[Route("video")]
[ApiController]
public class VideoController : ControllerBase
public class VideoApi : ControllerBase
{
private readonly ILibraryManager _libraryManager;
private readonly ITranscoder _transcoder;
private readonly string _transmuxPath;
private readonly string _transcodePath;
private FileExtensionContentTypeProvider _provider;
public VideoController(ILibraryManager libraryManager, ITranscoder transcoder, IConfiguration config)
public VideoApi(ILibraryManager libraryManager, ITranscoder transcoder, IConfiguration config)
{
_libraryManager = libraryManager;
_transcoder = transcoder;
@ -25,101 +27,124 @@ namespace Kyoo.Api
_transcodePath = config.GetValue<string>("transcodeTempPath");
}
[HttpGet("{showSlug}-s{seasonNumber}e{episodeNumber}")]
[Authorize(Policy="Play")]
public async Task<IActionResult> Index(string showSlug, int seasonNumber, int episodeNumber)
private string _GetContentType(string path)
{
Episode episode = await _libraryManager.GetEpisode(showSlug, seasonNumber, episodeNumber);
if (_provider == null)
{
_provider = new FileExtensionContentTypeProvider();
_provider.Mappings[".mkv"] = "video/x-matroska";
}
if (_provider.TryGetContentType(path, out string contentType))
return contentType;
return "video/mp4";
}
[HttpGet("{showSlug}-s{seasonNumber}e{episodeNumber}")]
[HttpGet("direct/{showSlug}-s{seasonNumber}e{episodeNumber}")]
[Authorize(Policy="Play")]
public async Task<IActionResult> DirectEpisode(string showSlug, int seasonNumber, int episodeNumber)
{
if (seasonNumber < 0 || episodeNumber < 0)
return BadRequest(new {error = "Season number or episode number can not be negative."});
Episode episode = await _libraryManager.GetEpisode(showSlug, seasonNumber, episodeNumber);
if (episode != null && System.IO.File.Exists(episode.Path))
return PhysicalFile(episode.Path, "video/x-matroska", true);
return PhysicalFile(episode.Path, _GetContentType(episode.Path), true);
return NotFound();
}
[HttpGet("{movieSlug}")]
[HttpGet("direct/{movieSlug}")]
[Authorize(Policy="Play")]
public async Task<IActionResult> DirectMovie(string movieSlug)
{
Episode episode = await _libraryManager.GetMovieEpisode(movieSlug);
if (episode != null && System.IO.File.Exists(episode.Path))
return PhysicalFile(episode.Path, _GetContentType(episode.Path), true);
return NotFound();
}
[HttpGet("transmux/{showSlug}-s{seasonNumber}e{episodeNumber}")]
[Authorize(Policy="Play")]
public async Task<IActionResult> Transmux(string showSlug, int seasonNumber, int episodeNumber)
public async Task<IActionResult> TransmuxEpisode(string showSlug, int seasonNumber, int episodeNumber)
{
if (seasonNumber < 0 || episodeNumber < 0)
return BadRequest(new {error = "Season number or episode number can not be negative."});
Episode episode = await _libraryManager.GetEpisode(showSlug, seasonNumber, episodeNumber);
if (episode == null || !System.IO.File.Exists(episode.Path))
return NotFound();
string path = await _transcoder.Transmux(episode);
if (path == null)
return StatusCode(500);
return PhysicalFile(path, "application/x-mpegURL ", true);
}
[HttpGet("transmux/{movieSlug}")]
[Authorize(Policy="Play")]
public async Task<IActionResult> TransmuxMovie(string movieSlug)
{
Episode episode = await _libraryManager.GetMovieEpisode(movieSlug);
if (episode == null || !System.IO.File.Exists(episode.Path))
return NotFound();
string path = await _transcoder.Transmux(episode);
if (path != null)
return PhysicalFile(path, "application/x-mpegURL ", true);
return StatusCode(500);
}
[HttpGet("transmux/{episodeLink}/segment/{chunk}")]
public IActionResult GetTransmuxedChunk(string episodeLink, string chunk)
{
string path = Path.GetFullPath(Path.Combine(_transmuxPath, episodeLink));
path = Path.Combine(path, "segments" + Path.DirectorySeparatorChar + chunk);
return PhysicalFile(path, "video/MP2T");
if (path == null)
return StatusCode(500);
return PhysicalFile(path, "application/x-mpegURL ", true);
}
[HttpGet("transcode/{showSlug}-s{seasonNumber}e{episodeNumber}")]
[Authorize(Policy="Play")]
public async Task<IActionResult> Transcode(string showSlug, int seasonNumber, int episodeNumber)
public async Task<IActionResult> TranscodeEpisode(string showSlug, int seasonNumber, int episodeNumber)
{
if (seasonNumber < 0 || episodeNumber < 0)
return BadRequest(new {error = "Season number or episode number can not be negative."});
Episode episode = await _libraryManager.GetEpisode(showSlug, seasonNumber, episodeNumber);
if (episode == null || !System.IO.File.Exists(episode.Path))
return NotFound();
string path = await _transcoder.Transcode(episode);
if (path == null)
return StatusCode(500);
return PhysicalFile(path, "application/x-mpegURL ", true);
}
[HttpGet("transcode/{movieSlug}")]
[Authorize(Policy="Play")]
public async Task<IActionResult> TranscodeMovie(string movieSlug)
{
Episode episode = await _libraryManager.GetMovieEpisode(movieSlug);
if (episode == null || !System.IO.File.Exists(episode.Path))
return NotFound();
string path = await _transcoder.Transcode(episode);
if (path != null)
return PhysicalFile(path, "application/x-mpegURL ", true);
return StatusCode(500);
if (path == null)
return StatusCode(500);
return PhysicalFile(path, "application/x-mpegURL ", true);
}
[HttpGet("transcode/{episodeLink}/segment/{chunk}")]
public IActionResult GetTranscodedChunk(string episodeLink, string chunk)
[HttpGet("transmux/{episodeLink}/segment/{chunk}")]
[Authorize(Policy="Play")]
public IActionResult GetTransmuxedChunk(string episodeLink, string chunk)
{
string path = Path.GetFullPath(Path.Combine(_transcodePath, episodeLink));
path = Path.Combine(path, "segments" + Path.DirectorySeparatorChar + chunk);
string path = Path.GetFullPath(Path.Combine(_transmuxPath, episodeLink));
path = Path.Combine(path, "segments", chunk);
return PhysicalFile(path, "video/MP2T");
}
[HttpGet("{movieSlug}")]
[HttpGet("transcode/{episodeLink}/segment/{chunk}")]
[Authorize(Policy="Play")]
public async Task<IActionResult> Index(string movieSlug)
public IActionResult GetTranscodedChunk(string episodeLink, string chunk)
{
Episode episode = await _libraryManager.GetMovieEpisode(movieSlug);
if (episode != null && System.IO.File.Exists(episode.Path))
return PhysicalFile(episode.Path, "video/webm", true);
return NotFound();
}
[HttpGet("transmux/{movieSlug}")]
[Authorize(Policy="Play")]
public async Task<IActionResult> Transmux(string movieSlug)
{
Episode episode = await _libraryManager.GetMovieEpisode(movieSlug);
if (episode == null || !System.IO.File.Exists(episode.Path))
return NotFound();
string path = await _transcoder.Transmux(episode);
if (path != null)
return PhysicalFile(path, "application/x-mpegURL ", true);
return StatusCode(500);
}
[HttpGet("transcode/{movieSlug}")]
[Authorize(Policy="Play")]
public async Task<IActionResult> Transcode(string movieSlug)
{
Episode episode = await _libraryManager.GetMovieEpisode(movieSlug);
if (episode == null || !System.IO.File.Exists(episode.Path))
return NotFound();
string path = await _transcoder.Transcode(episode);
if (path != null)
return PhysicalFile(path, "application/x-mpegURL ", true);
return StatusCode(500);
string path = Path.GetFullPath(Path.Combine(_transcodePath, episodeLink));
path = Path.Combine(path, "segments", chunk);
return PhysicalFile(path, "video/MP2T");
}
}
}

@ -1 +1 @@
Subproject commit 59da0095a85914eabde9900973331a25a16088b3
Subproject commit d2d90e28cd697a20fdd2e1c28fdfc2038c7f1d7c