Adding external subtitles support

This commit is contained in:
Zoe Roux 2021-02-02 00:41:38 +01:00
parent e7267d6b51
commit 8aae1c9bd6
7 changed files with 88 additions and 26 deletions

View File

@ -82,6 +82,8 @@ namespace Kyoo.Models
string name = info?.EnglishName ?? language; string name = info?.EnglishName ?? language;
if (IsForced) if (IsForced)
name += " Forced"; name += " Forced";
if (IsExternal)
name += " (External)";
if (Title != null && Title.Length > 1) if (Title != null && Title.Length > 1)
name += " - " + Title; name += " - " + Title;
return name; return name;

View File

@ -116,9 +116,9 @@ namespace Kyoo.CommonApi
valueConst = Expression.Constant(value); valueConst = Expression.Constant(value);
} }
if (notEqual) return notEqual
return Expression.NotEqual(field, valueConst); ? Expression.NotEqual(field, valueConst)
return Expression.Equal(field, valueConst); : Expression.Equal(field, valueConst);
} }
private static Expression ContainsResourceExpression(MemberExpression xProperty, string value) private static Expression ContainsResourceExpression(MemberExpression xProperty, string value)

View File

@ -81,7 +81,11 @@ namespace Kyoo.Controllers
public override async Task<Track> Create(Track obj) public override async Task<Track> Create(Track obj)
{ {
if (obj.EpisodeID <= 0) if (obj.EpisodeID <= 0)
throw new InvalidOperationException($"Can't store a track not related to any episode (episodeID: {obj.EpisodeID})."); {
obj.EpisodeID = obj.Episode?.ID ?? -1;
if (obj.EpisodeID <= 0)
throw new InvalidOperationException($"Can't store a track not related to any episode (episodeID: {obj.EpisodeID}).");
}
await base.Create(obj); await base.Create(obj);
_database.Entry(obj).State = EntityState.Added; _database.Entry(obj).State = EntityState.Added;

View File

@ -31,10 +31,7 @@ namespace Kyoo.Controllers
if (dir == null) if (dir == null)
throw new ArgumentException("Invalid path."); throw new ArgumentException("Invalid path.");
return Task.Factory.StartNew(() => return Task.Factory.StartNew(() => TranscoderAPI.ExtractInfos(path, dir), TaskCreationOptions.LongRunning);
{
return TranscoderAPI.ExtractInfos(path, dir);
}, TaskCreationOptions.LongRunning);
} }
public async Task<string> Transmux(Episode episode) public async Task<string> Transmux(Episode episode)

View File

@ -50,7 +50,7 @@ namespace Kyoo.Controllers.TranscoderLink
} }
} }
else else
tracks = new Track[0]; tracks = Array.Empty<Track>();
if (ptr != IntPtr.Zero) if (ptr != IntPtr.Zero)
free(ptr); // free_streams is not necesarry since the Marshal free the unmanaged pointers. free(ptr); // free_streams is not necesarry since the Marshal free the unmanaged pointers.

View File

@ -33,7 +33,7 @@ namespace Kyoo.Controllers
{ {
using IServiceScope serviceScope = _serviceProvider.CreateScope(); using IServiceScope serviceScope = _serviceProvider.CreateScope();
await using ILibraryManager libraryManager = serviceScope.ServiceProvider.GetService<ILibraryManager>(); await using ILibraryManager libraryManager = serviceScope.ServiceProvider.GetService<ILibraryManager>();
return (await libraryManager.GetLibraries()).Select(x => x.Slug); return (await libraryManager!.GetLibraries()).Select(x => x.Slug);
} }
public int? Progress() public int? Progress()
@ -58,31 +58,33 @@ namespace Kyoo.Controllers
using IServiceScope serviceScope = _serviceProvider.CreateScope(); using IServiceScope serviceScope = _serviceProvider.CreateScope();
await using ILibraryManager libraryManager = serviceScope.ServiceProvider.GetService<ILibraryManager>(); await using ILibraryManager libraryManager = serviceScope.ServiceProvider.GetService<ILibraryManager>();
foreach (Show show in await libraryManager.GetShows()) foreach (Show show in await libraryManager!.GetShows())
if (!Directory.Exists(show.Path)) if (!Directory.Exists(show.Path))
await libraryManager.DeleteShow(show); await libraryManager.DeleteShow(show);
ICollection<Episode> episodes = await libraryManager.GetEpisodes(); ICollection<Episode> episodes = await libraryManager.GetEpisodes();
foreach (Episode episode in episodes)
if (!File.Exists(episode.Path))
await libraryManager.DeleteEpisode(episode);
ICollection<Track> tracks = await libraryManager.GetTracks();
foreach (Track track in tracks)
if (!File.Exists(track.Path))
await libraryManager.DeleteTrack(track);
ICollection<Library> libraries = argument == null ICollection<Library> libraries = argument == null
? await libraryManager.GetLibraries() ? await libraryManager.GetLibraries()
: new [] { await libraryManager.GetLibrary(argument)}; : new [] { await libraryManager.GetLibrary(argument)};
foreach (Episode episode in episodes)
{
if (!File.Exists(episode.Path))
await libraryManager.DeleteEpisode(episode);
}
// TODO replace this grotesque way to load the providers. // TODO replace this grotesque way to load the providers.
foreach (Library library in libraries) foreach (Library library in libraries)
library.Providers = library.Providers; library.Providers = library.Providers;
foreach (Library library in libraries) foreach (Library library in libraries)
await Scan(library, episodes, cancellationToken); await Scan(library, episodes, tracks, cancellationToken);
Console.WriteLine("Scan finished!"); Console.WriteLine("Scan finished!");
} }
private async Task Scan(Library library, IEnumerable<Episode> episodes, CancellationToken cancellationToken) private async Task Scan(Library library, IEnumerable<Episode> episodes, IEnumerable<Track> tracks, CancellationToken cancellationToken)
{ {
Console.WriteLine($"Scanning library {library.Name} at {string.Join(", ", library.Paths)}."); Console.WriteLine($"Scanning library {library.Name} at {string.Join(", ", library.Paths)}.");
foreach (string path in library.Paths) foreach (string path in library.Paths)
@ -130,9 +132,54 @@ namespace Kyoo.Controllers
foreach (string[] episodeTasks in tasks.BatchBy(_parallelTasks * 2)) foreach (string[] episodeTasks in tasks.BatchBy(_parallelTasks * 2))
await Task.WhenAll(episodeTasks await Task.WhenAll(episodeTasks
.Select(x => RegisterFile(x, x.Substring(path.Length), library, cancellationToken))); .Select(x => RegisterFile(x, x.Substring(path.Length), library, cancellationToken)));
await Task.WhenAll(files.Where(x => IsSubtitle(x) && tracks.All(y => y.Path != x))
.Select(x => RegisterExternalSubtitle(x, cancellationToken)));
} }
} }
private async Task RegisterExternalSubtitle(string path, CancellationToken token)
{
if (token.IsCancellationRequested || path.Split(Path.DirectorySeparatorChar).Contains("Subtitles"))
return;
using IServiceScope serviceScope = _serviceProvider.CreateScope();
await using ILibraryManager libraryManager = serviceScope.ServiceProvider.GetService<ILibraryManager>();
string patern = _config.GetValue<string>("subtitleRegex");
Regex regex = new(patern, RegexOptions.IgnoreCase);
Match match = regex.Match(path);
if (!match.Success)
{
await Console.Error.WriteLineAsync($"The subtitle at {path} does not match the subtitle's regex.");
return;
}
string episodePath = match.Groups["Episode"].Value;
Episode episode = await libraryManager!.GetEpisode(x => x.Path.StartsWith(episodePath));
if (episode == null)
{
await Console.Error.WriteLineAsync($"No episode found for subtitle at: ${path}.");
return;
}
Track track = new(StreamType.Subtitle,
null,
match.Groups["Language"].Value,
match.Groups["Default"].Value.Length > 0,
match.Groups["Forced"].Value.Length > 0,
SubtitleExtensions[Path.GetExtension(path)],
true,
path)
{
Episode = episode
};
await libraryManager.RegisterTrack(track);
Console.WriteLine($"Registering subtitle at: {path}.");
}
private async Task RegisterFile(string path, string relativePath, Library library, CancellationToken token) private async Task RegisterFile(string path, string relativePath, Library library, CancellationToken token)
{ {
if (token.IsCancellationRequested) if (token.IsCancellationRequested)
@ -144,7 +191,7 @@ namespace Kyoo.Controllers
await using ILibraryManager libraryManager = serviceScope.ServiceProvider.GetService<ILibraryManager>(); await using ILibraryManager libraryManager = serviceScope.ServiceProvider.GetService<ILibraryManager>();
string patern = _config.GetValue<string>("regex"); string patern = _config.GetValue<string>("regex");
Regex regex = new Regex(patern, RegexOptions.IgnoreCase); Regex regex = new(patern, RegexOptions.IgnoreCase);
Match match = regex.Match(relativePath); Match match = regex.Match(relativePath);
if (!match.Success) if (!match.Success)
@ -154,7 +201,7 @@ namespace Kyoo.Controllers
} }
string showPath = Path.GetDirectoryName(path); string showPath = Path.GetDirectoryName(path);
string collectionName = match.Groups["Collection"]?.Value; string collectionName = match.Groups["Collection"].Value;
string showName = match.Groups["Show"].Value; string showName = match.Groups["Show"].Value;
int seasonNumber = int.TryParse(match.Groups["Season"].Value, out int tmp) ? tmp : -1; int seasonNumber = int.TryParse(match.Groups["Season"].Value, out int tmp) ? tmp : -1;
int episodeNumber = int.TryParse(match.Groups["Episode"].Value, out tmp) ? tmp : -1; int episodeNumber = int.TryParse(match.Groups["Episode"].Value, out tmp) ? tmp : -1;
@ -164,7 +211,7 @@ namespace Kyoo.Controllers
bool isMovie = seasonNumber == -1 && episodeNumber == -1 && absoluteNumber == -1; bool isMovie = seasonNumber == -1 && episodeNumber == -1 && absoluteNumber == -1;
Show show = await GetShow(libraryManager, showName, showPath, isMovie, library); Show show = await GetShow(libraryManager, showName, showPath, isMovie, library);
if (isMovie) if (isMovie)
await libraryManager.RegisterEpisode(await GetMovie(show, path)); await libraryManager!.RegisterEpisode(await GetMovie(show, path));
else else
{ {
Season season = await GetSeason(libraryManager, show, seasonNumber, library); Season season = await GetSeason(libraryManager, show, seasonNumber, library);
@ -175,7 +222,7 @@ namespace Kyoo.Controllers
absoluteNumber, absoluteNumber,
path, path,
library); library);
await libraryManager.RegisterEpisode(episode); await libraryManager!.RegisterEpisode(episode);
} }
await libraryManager.AddShowLink(show, library, collection); await libraryManager.AddShowLink(show, library, collection);
@ -295,7 +342,7 @@ namespace Kyoo.Controllers
private async Task<Episode> GetMovie(Show show, string episodePath) private async Task<Episode> GetMovie(Show show, string episodePath)
{ {
Episode episode = new Episode Episode episode = new()
{ {
Title = show.Title, Title = show.Title,
Path = episodePath, Path = episodePath,
@ -346,5 +393,16 @@ namespace Kyoo.Controllers
{ {
return VideoExtensions.Contains(Path.GetExtension(filePath)); return VideoExtensions.Contains(Path.GetExtension(filePath));
} }
private static readonly Dictionary<string, string> SubtitleExtensions = new()
{
{".ass", "ass"},
{".str", "subrip"}
};
private static bool IsSubtitle(string filePath)
{
return SubtitleExtensions.ContainsKey(Path.GetExtension(filePath));
}
} }
} }

View File

@ -33,5 +33,6 @@
"plugins": "plugins/", "plugins": "plugins/",
"defaultPermissions": "read,play,write,admin", "defaultPermissions": "read,play,write,admin",
"newUserPermissions": "read,play,write,admin", "newUserPermissions": "read,play,write,admin",
"regex": "(?:\\/(?<Collection>.*?))?\\/(?<Show>.*?)(?: \\(\\d+\\))?\\/\\k<Show>(?: \\(\\d+\\))?(?:(?: S(?<Season>\\d+)E(?<Episode>\\d+))| (?<Absolute>\\d+))?.*$" "regex": "(?:\\/(?<Collection>.*?))?\\/(?<Show>.*?)(?: \\(\\d+\\))?\\/\\k<Show>(?: \\(\\d+\\))?(?:(?: S(?<Season>\\d+)E(?<Episode>\\d+))| (?<Absolute>\\d+))?.*$",
"subtitleRegex": "^(?<Episode>.*)\\.(?<Language>\\w{1,3})\\.(?<Default>default\\.)?(?<Forced>forced\\.)?.*$"
} }