mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-05-24 02:02:36 -04:00
Adding fonts & chapters to the api
This commit is contained in:
parent
72417b873d
commit
201f059349
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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)]
|
||||
|
@ -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];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 =
|
||||
|
@ -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
|
Loading…
x
Reference in New Issue
Block a user