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
|
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);
|
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);
|
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,
|
Unknow = 0,
|
||||||
Video = 1,
|
Video = 1,
|
||||||
Audio = 2,
|
Audio = 2,
|
||||||
Subtitle = 3
|
Subtitle = 3,
|
||||||
|
Font = 4
|
||||||
}
|
}
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
|
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
|
||||||
|
@ -1,12 +1,29 @@
|
|||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Kyoo.Controllers;
|
using Kyoo.Controllers;
|
||||||
using Kyoo.Models.Watch;
|
using Kyoo.Models.Watch;
|
||||||
|
using PathIO = System.IO.Path;
|
||||||
|
|
||||||
namespace Kyoo.Models
|
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
|
public class WatchItem
|
||||||
{
|
{
|
||||||
[JsonIgnore] public readonly int EpisodeID = -1;
|
[JsonIgnore] public readonly int EpisodeID = -1;
|
||||||
@ -27,10 +44,11 @@ namespace Kyoo.Models
|
|||||||
public Track Video;
|
public Track Video;
|
||||||
public IEnumerable<Track> Audios;
|
public IEnumerable<Track> Audios;
|
||||||
public IEnumerable<Track> Subtitles;
|
public IEnumerable<Track> Subtitles;
|
||||||
|
public IEnumerable<Chapter> Chapters;
|
||||||
|
|
||||||
public WatchItem() { }
|
public WatchItem() { }
|
||||||
|
|
||||||
public WatchItem(int episodeID,
|
private WatchItem(int episodeID,
|
||||||
string showTitle,
|
string showTitle,
|
||||||
string showSlug,
|
string showSlug,
|
||||||
int seasonNumber,
|
int seasonNumber,
|
||||||
@ -52,7 +70,7 @@ namespace Kyoo.Models
|
|||||||
Slug = Episode.GetSlug(ShowSlug, seasonNumber, episodeNumber);
|
Slug = Episode.GetSlug(ShowSlug, seasonNumber, episodeNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
public WatchItem(int episodeID,
|
private WatchItem(int episodeID,
|
||||||
string showTitle,
|
string showTitle,
|
||||||
string showSlug,
|
string showSlug,
|
||||||
int seasonNumber,
|
int seasonNumber,
|
||||||
@ -107,8 +125,35 @@ namespace Kyoo.Models
|
|||||||
{
|
{
|
||||||
IsMovie = show.IsMovie,
|
IsMovie = show.IsMovie,
|
||||||
PreviousEpisode = previous,
|
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
|
public class CrudApi<T> : ControllerBase where T : class, IResource
|
||||||
{
|
{
|
||||||
private readonly IRepository<T> _repository;
|
private readonly IRepository<T> _repository;
|
||||||
private readonly string _baseURL;
|
protected readonly string BaseURL;
|
||||||
|
|
||||||
public CrudApi(IRepository<T> repository, IConfiguration configuration)
|
public CrudApi(IRepository<T> repository, IConfiguration configuration)
|
||||||
{
|
{
|
||||||
_repository = repository;
|
_repository = repository;
|
||||||
_baseURL = configuration.GetValue<string>("public_url").TrimEnd('/');
|
BaseURL = configuration.GetValue<string>("public_url").TrimEnd('/');
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("{id:int}")]
|
[HttpGet("{id:int}")]
|
||||||
@ -90,7 +90,7 @@ namespace Kyoo.CommonApi
|
|||||||
where TResult : IResource
|
where TResult : IResource
|
||||||
{
|
{
|
||||||
return new Page<TResult>(resources,
|
return new Page<TResult>(resources,
|
||||||
_baseURL + Request.Path,
|
BaseURL + Request.Path,
|
||||||
Request.Query.ToDictionary(x => x.Key, x => x.Value.ToString(), StringComparer.InvariantCultureIgnoreCase),
|
Request.Query.ToDictionary(x => x.Key, x => x.Value.ToString(), StringComparer.InvariantCultureIgnoreCase),
|
||||||
limit);
|
limit);
|
||||||
}
|
}
|
||||||
|
@ -25,27 +25,15 @@ namespace Kyoo.Controllers
|
|||||||
throw new BadTranscoderException();
|
throw new BadTranscoderException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<Track[]> GetTrackInfo(string path)
|
public Task<Track[]> ExtractInfos(string path)
|
||||||
{
|
|
||||||
return Task.Factory.StartNew(() =>
|
|
||||||
{
|
|
||||||
TranscoderAPI.GetTrackInfo(path, out Track[] tracks);
|
|
||||||
return tracks;
|
|
||||||
}, TaskCreationOptions.LongRunning);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<Track[]> ExtractSubtitles(string path)
|
|
||||||
{
|
{
|
||||||
string dir = Path.GetDirectoryName(path);
|
string dir = Path.GetDirectoryName(path);
|
||||||
if (dir == null)
|
if (dir == null)
|
||||||
throw new ArgumentException("Invalid path.");
|
throw new ArgumentException("Invalid path.");
|
||||||
|
|
||||||
string output = Path.Combine(dir, "Subtitles");
|
|
||||||
Directory.CreateDirectory(output);
|
|
||||||
return Task.Factory.StartNew(() =>
|
return Task.Factory.StartNew(() =>
|
||||||
{
|
{
|
||||||
TranscoderAPI.ExtractSubtitles(path, output, out Track[] tracks);
|
return TranscoderAPI.ExtractInfos(path, dir);
|
||||||
return tracks;
|
|
||||||
}, TaskCreationOptions.LongRunning);
|
}, TaskCreationOptions.LongRunning);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,23 +20,18 @@ namespace Kyoo.Controllers.TranscoderLink
|
|||||||
public static extern int transcode(string path, string out_path, out float playableDuration);
|
public static extern int transcode(string path, string out_path, out float playableDuration);
|
||||||
|
|
||||||
[DllImport(TranscoderPath, CallingConvention = CallingConvention.Cdecl)]
|
[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)]
|
[DllImport(TranscoderPath, CallingConvention = CallingConvention.Cdecl)]
|
||||||
private static extern IntPtr extract_subtitles(string path, string out_path, out int array_length, out int track_count);
|
private static extern void free(IntPtr stream_ptr);
|
||||||
|
|
||||||
// [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);
|
|
||||||
|
|
||||||
|
|
||||||
public static void GetTrackInfo(string path, out Track[] tracks)
|
public static Track[] ExtractInfos(string path, string outPath)
|
||||||
{
|
{
|
||||||
int size = Marshal.SizeOf<Stream>();
|
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;
|
IntPtr streamsPtr = ptr;
|
||||||
|
Track[] tracks;
|
||||||
|
|
||||||
if (trackCount > 0 && ptr != IntPtr.Zero)
|
if (trackCount > 0 && ptr != IntPtr.Zero)
|
||||||
{
|
{
|
||||||
@ -46,7 +41,7 @@ namespace Kyoo.Controllers.TranscoderLink
|
|||||||
for (int i = 0; i < arrayLength; i++)
|
for (int i = 0; i < arrayLength; i++)
|
||||||
{
|
{
|
||||||
Stream stream = Marshal.PtrToStructure<Stream>(streamsPtr);
|
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);
|
tracks[j] = new Track(stream);
|
||||||
j++;
|
j++;
|
||||||
@ -57,35 +52,9 @@ namespace Kyoo.Controllers.TranscoderLink
|
|||||||
else
|
else
|
||||||
tracks = new Track[0];
|
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;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,6 @@ using System.Text.RegularExpressions;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Kyoo.Models.Exceptions;
|
using Kyoo.Models.Exceptions;
|
||||||
using Kyoo.Models.Watch;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
namespace Kyoo.Controllers
|
namespace Kyoo.Controllers
|
||||||
@ -307,52 +306,8 @@ namespace Kyoo.Controllers
|
|||||||
|
|
||||||
private async Task<IEnumerable<Track>> GetTracks(Episode episode)
|
private async Task<IEnumerable<Track>> GetTracks(Episode episode)
|
||||||
{
|
{
|
||||||
IEnumerable<Track> tracks = await _transcoder.GetTrackInfo(episode.Path);
|
episode.Tracks = await _transcoder.ExtractInfos(episode.Path);
|
||||||
List<Track> epTracks = tracks.Where(x => x.Type != StreamType.Subtitle)
|
return episode.Tracks;
|
||||||
.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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static readonly string[] VideoExtensions =
|
private static readonly string[] VideoExtensions =
|
||||||
|
@ -2,12 +2,14 @@
|
|||||||
using Kyoo.Models;
|
using Kyoo.Models;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Kyoo.CommonApi;
|
using Kyoo.CommonApi;
|
||||||
using Kyoo.Controllers;
|
using Kyoo.Controllers;
|
||||||
using Kyoo.Models.Exceptions;
|
using Kyoo.Models.Exceptions;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.StaticFiles;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
|
|
||||||
namespace Kyoo.Api
|
namespace Kyoo.Api
|
||||||
@ -18,6 +20,7 @@ namespace Kyoo.Api
|
|||||||
public class ShowApi : CrudApi<Show>
|
public class ShowApi : CrudApi<Show>
|
||||||
{
|
{
|
||||||
private readonly ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager;
|
||||||
|
private FileExtensionContentTypeProvider _provider;
|
||||||
|
|
||||||
public ShowApi(ILibraryManager libraryManager, IConfiguration configuration)
|
public ShowApi(ILibraryManager libraryManager, IConfiguration configuration)
|
||||||
: base(libraryManager.ShowRepository, configuration)
|
: base(libraryManager.ShowRepository, configuration)
|
||||||
@ -410,5 +413,35 @@ namespace Kyoo.Api
|
|||||||
return BadRequest(new {Error = ex.Message});
|
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