mirror of
				https://github.com/zoriya/Kyoo.git
				synced 2025-11-03 19:17:16 -05: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)
 | 
				
			||||||
 | 
				
			|||||||
@ -80,8 +80,12 @@ namespace Kyoo.Controllers
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		public override async Task<Track> Create(Track obj)
 | 
							public override async Task<Track> Create(Track obj)
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
 | 
								if (obj.EpisodeID <= 0)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									obj.EpisodeID = obj.Episode?.ID ?? -1;
 | 
				
			||||||
				if (obj.EpisodeID <= 0)
 | 
									if (obj.EpisodeID <= 0)
 | 
				
			||||||
					throw new InvalidOperationException($"Can't store a track not related to any episode (episodeID: {obj.EpisodeID}).");
 | 
										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