Adding fonts & chapters to the api

This commit is contained in:
Zoe Roux 2020-10-31 02:02:41 +01:00
parent 72417b873d
commit 201f059349
9 changed files with 106 additions and 123 deletions

View File

@ -6,16 +6,8 @@ namespace Kyoo.Controllers
{
public interface ITranscoder
{
// Should transcode to a mp4 container (same video/audio format if possible, no subtitles).
Task<Track[]> ExtractInfos(string path);
Task<string> Transmux(Episode episode);
// Should transcode to a mp4 container with a h264 video format and a AAC audio format, no subtitles.
Task<string> Transcode(Episode episode);
// Get video and audio tracks infos (codec, name, language...)
Task<Track[]> GetTrackInfo(string path);
// Extract all subtitles of a video and save them in the subtitles sub-folder.
Task<Track[]> ExtractSubtitles(string path);
}
}

View File

@ -13,7 +13,8 @@ namespace Kyoo.Models
Unknow = 0,
Video = 1,
Audio = 2,
Subtitle = 3
Subtitle = 3,
Font = 4
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]

View File

@ -1,12 +1,29 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Kyoo.Controllers;
using Kyoo.Models.Watch;
using PathIO = System.IO.Path;
namespace Kyoo.Models
{
public class Chapter
{
public long StartTime;
public long EndTime;
public string Name;
public Chapter(long startTime, long endTime, string name)
{
StartTime = startTime;
EndTime = endTime;
Name = name;
}
}
public class WatchItem
{
[JsonIgnore] public readonly int EpisodeID = -1;
@ -27,10 +44,11 @@ namespace Kyoo.Models
public Track Video;
public IEnumerable<Track> Audios;
public IEnumerable<Track> Subtitles;
public IEnumerable<Chapter> Chapters;
public WatchItem() { }
public WatchItem(int episodeID,
private WatchItem(int episodeID,
string showTitle,
string showSlug,
int seasonNumber,
@ -52,7 +70,7 @@ namespace Kyoo.Models
Slug = Episode.GetSlug(ShowSlug, seasonNumber, episodeNumber);
}
public WatchItem(int episodeID,
private WatchItem(int episodeID,
string showTitle,
string showSlug,
int seasonNumber,
@ -107,8 +125,35 @@ namespace Kyoo.Models
{
IsMovie = show.IsMovie,
PreviousEpisode = previous,
NextEpisode = next
NextEpisode = next,
Chapters = await GetChapters(ep.Path)
};
}
private static async Task<IEnumerable<Chapter>> GetChapters(string episodePath)
{
string path = PathIO.Combine(
PathIO.GetDirectoryName(episodePath)!,
"Chapters",
PathIO.GetFileNameWithoutExtension(episodePath) + ".txt"
);
if (!File.Exists(path))
return new Chapter[0];
try
{
return (await File.ReadAllLinesAsync(path))
.Select(x =>
{
string[] values = x.Split(' ');
return new Chapter(long.Parse(values[0]), long.Parse(values[1]), values[2]);
})
.ToArray();
}
catch
{
await Console.Error.WriteLineAsync($"Invalid chapter file at {path}");
return new Chapter[0];
}
}
}
}

View File

@ -15,12 +15,12 @@ namespace Kyoo.CommonApi
public class CrudApi<T> : ControllerBase where T : class, IResource
{
private readonly IRepository<T> _repository;
private readonly string _baseURL;
protected readonly string BaseURL;
public CrudApi(IRepository<T> repository, IConfiguration configuration)
{
_repository = repository;
_baseURL = configuration.GetValue<string>("public_url").TrimEnd('/');
BaseURL = configuration.GetValue<string>("public_url").TrimEnd('/');
}
[HttpGet("{id:int}")]
@ -90,7 +90,7 @@ namespace Kyoo.CommonApi
where TResult : IResource
{
return new Page<TResult>(resources,
_baseURL + Request.Path,
BaseURL + Request.Path,
Request.Query.ToDictionary(x => x.Key, x => x.Value.ToString(), StringComparer.InvariantCultureIgnoreCase),
limit);
}

View File

@ -25,27 +25,15 @@ namespace Kyoo.Controllers
throw new BadTranscoderException();
}
public Task<Track[]> GetTrackInfo(string path)
{
return Task.Factory.StartNew(() =>
{
TranscoderAPI.GetTrackInfo(path, out Track[] tracks);
return tracks;
}, TaskCreationOptions.LongRunning);
}
public Task<Track[]> ExtractSubtitles(string path)
public Task<Track[]> ExtractInfos(string path)
{
string dir = Path.GetDirectoryName(path);
if (dir == null)
throw new ArgumentException("Invalid path.");
string output = Path.Combine(dir, "Subtitles");
Directory.CreateDirectory(output);
return Task.Factory.StartNew(() =>
{
TranscoderAPI.ExtractSubtitles(path, output, out Track[] tracks);
return tracks;
return TranscoderAPI.ExtractInfos(path, dir);
}, TaskCreationOptions.LongRunning);
}

View File

@ -20,23 +20,18 @@ namespace Kyoo.Controllers.TranscoderLink
public static extern int transcode(string path, string out_path, out float playableDuration);
[DllImport(TranscoderPath, CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr get_track_info(string path, out int array_length, out int track_count);
private static extern IntPtr extract_infos(string path, string outpath, out int length, out int track_count);
[DllImport(TranscoderPath, CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr extract_subtitles(string path, string out_path, out int array_length, out int track_count);
// [DllImport(TranscoderPath, CallingConvention = CallingConvention.Cdecl)]
// private static extern void free_streams(IntPtr stream_ptr);
[DllImport(TranscoderPath, CallingConvention = CallingConvention.Cdecl)]
private static extern void free(IntPtr ptr);
private static extern void free(IntPtr stream_ptr);
public static void GetTrackInfo(string path, out Track[] tracks)
public static Track[] ExtractInfos(string path, string outPath)
{
int size = Marshal.SizeOf<Stream>();
IntPtr ptr = get_track_info(path, out int arrayLength, out int trackCount);
IntPtr ptr = extract_infos(path, outPath, out int arrayLength, out int trackCount);
IntPtr streamsPtr = ptr;
Track[] tracks;
if (trackCount > 0 && ptr != IntPtr.Zero)
{
@ -46,7 +41,7 @@ namespace Kyoo.Controllers.TranscoderLink
for (int i = 0; i < arrayLength; i++)
{
Stream stream = Marshal.PtrToStructure<Stream>(streamsPtr);
if (stream!.Type != StreamType.Unknow)
if (stream!.Type != StreamType.Unknow && stream.Type != StreamType.Font)
{
tracks[j] = new Track(stream);
j++;
@ -57,35 +52,9 @@ namespace Kyoo.Controllers.TranscoderLink
else
tracks = new Track[0];
free(ptr);
}
public static void ExtractSubtitles(string path, string outPath, out Track[] tracks)
{
int size = Marshal.SizeOf<Stream>();
IntPtr ptr = extract_subtitles(path, outPath, out int arrayLength, out int trackCount);
IntPtr streamsPtr = ptr;
if (trackCount > 0 && ptr != IntPtr.Zero)
{
tracks = new Track[trackCount];
int j = 0;
for (int i = 0; i < arrayLength; i++)
{
Stream stream = Marshal.PtrToStructure<Stream>(streamsPtr);
if (stream!.Type != StreamType.Unknow)
{
tracks[j] = new Track(stream);
j++;
}
streamsPtr += size;
}
}
else
tracks = new Track[0];
free(ptr);
if (ptr != IntPtr.Zero)
free(ptr); // free_streams is not necesarry since the Marshal free the unmanaged pointers.
return tracks;
}
}
}

View File

@ -8,7 +8,6 @@ using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Kyoo.Models.Exceptions;
using Kyoo.Models.Watch;
using Microsoft.Extensions.DependencyInjection;
namespace Kyoo.Controllers
@ -307,52 +306,8 @@ namespace Kyoo.Controllers
private async Task<IEnumerable<Track>> GetTracks(Episode episode)
{
IEnumerable<Track> tracks = await _transcoder.GetTrackInfo(episode.Path);
List<Track> epTracks = tracks.Where(x => x.Type != StreamType.Subtitle)
.Concat(GetExtractedSubtitles(episode))
.ToList();
if (epTracks.Count(x => !x.IsExternal) < tracks.Count())
epTracks.AddRange(await _transcoder.ExtractSubtitles(episode.Path));
episode.Tracks = epTracks;
return epTracks;
}
private static IEnumerable<Track> GetExtractedSubtitles(Episode episode)
{
List<Track> tracks = new List<Track>();
if (episode.Path == null)
return tracks;
string path = Path.Combine(Path.GetDirectoryName(episode.Path)!, "Subtitles");
if (!Directory.Exists(path))
return tracks;
foreach (string sub in Directory.EnumerateFiles(path, "", SearchOption.AllDirectories))
{
string episodeLink = Path.GetFileNameWithoutExtension(episode.Path);
string subName = Path.GetFileName(sub);
if (episodeLink == null
|| subName?.Contains(episodeLink) == false
|| subName.Length < episodeLink.Length + 5)
continue;
string language = subName.Substring(episodeLink.Length + 1, 3);
bool isDefault = sub.Contains("default");
bool isForced = sub.Contains("forced");
Track track = new Track(StreamType.Subtitle, null, language, isDefault, isForced, null, false, sub)
{
EpisodeID = episode.ID,
Codec = Path.GetExtension(sub) switch
{
".ass" => "ass",
".srt" => "subrip",
_ => null
}
};
tracks.Add(track);
}
return tracks;
episode.Tracks = await _transcoder.ExtractInfos(episode.Path);
return episode.Tracks;
}
private static readonly string[] VideoExtensions =

View File

@ -2,12 +2,14 @@
using Kyoo.Models;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Kyoo.CommonApi;
using Kyoo.Controllers;
using Kyoo.Models.Exceptions;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.StaticFiles;
using Microsoft.Extensions.Configuration;
namespace Kyoo.Api
@ -18,6 +20,7 @@ namespace Kyoo.Api
public class ShowApi : CrudApi<Show>
{
private readonly ILibraryManager _libraryManager;
private FileExtensionContentTypeProvider _provider;
public ShowApi(ILibraryManager libraryManager, IConfiguration configuration)
: base(libraryManager.ShowRepository, configuration)
@ -410,5 +413,35 @@ namespace Kyoo.Api
return BadRequest(new {Error = ex.Message});
}
}
[HttpGet("{slug}/font")]
[HttpGet("{slug}/fonts")]
[Authorize(Policy = "Read")]
public async Task<ActionResult<Dictionary<string, string>>> GetFonts(string slug)
{
string path = (await _libraryManager.GetShow(slug))?.Path;
if (path == null)
return NotFound();
return Directory.GetFiles(path)
.ToDictionary(Path.GetFileNameWithoutExtension, x => $"{BaseURL}/shows/{slug}/fonts/{x}");
}
[HttpGet("{showSlug}/font/{slug}")]
[HttpGet("{showSlug}/fonts/{slug}")]
[Authorize(Policy = "Read")]
public async Task<ActionResult<Dictionary<string, string>>> GetFont(string showSlug, string slug)
{
string path = (await _libraryManager.GetShow(showSlug))?.Path;
if (path == null)
return NotFound();
string fontPath = Path.Combine(path, "Subtitles", "fonts", slug);
if (!System.IO.File.Exists(fontPath))
return NotFound();
if (_provider == null)
_provider = new FileExtensionContentTypeProvider();
_provider.TryGetContentType(path, out string contentType);
return PhysicalFile(fontPath, contentType ?? "application/x-font-ttf");
}
}
}

@ -1 +1 @@
Subproject commit d2d90e28cd697a20fdd2e1c28fdfc2038c7f1d7c
Subproject commit 00b52325c5bda97f03fcb8e33caf9fef239c6c2c