mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-07-09 03:04:20 -04:00
Adding external subtitles support
This commit is contained in:
parent
e7267d6b51
commit
8aae1c9bd6
@ -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;
|
||||||
|
@ -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)
|
||||||
|
@ -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;
|
||||||
|
@ -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)
|
||||||
|
@ -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.
|
||||||
|
@ -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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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\\.)?.*$"
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user