mirror of
				https://github.com/zoriya/Kyoo.git
				synced 2025-10-25 15:52:36 -04:00 
			
		
		
		
	Cleaning up the video & the subtitle api
This commit is contained in:
		
							parent
							
								
									c07494c9e8
								
							
						
					
					
						commit
						2dd0806517
					
				| @ -32,6 +32,7 @@ namespace Kyoo.Controllers | ||||
| 		Task<Episode> GetEpisode(int id); | ||||
| 		Task<Episode> GetEpisode(int showID, int seasonNumber, int episodeNumber); | ||||
| 		Task<Genre> GetGenre(int id); | ||||
| 		Task<Track> GetTrack(int id); | ||||
| 		Task<Studio> GetStudio(int id); | ||||
| 		Task<People> GetPeople(int id); | ||||
| 		 | ||||
| @ -42,7 +43,7 @@ namespace Kyoo.Controllers | ||||
| 		Task<Season> GetSeason(string showSlug, int seasonNumber); | ||||
| 		Task<Episode> GetEpisode(string showSlug, int seasonNumber, int episodeNumber); | ||||
| 		Task<Episode> GetMovieEpisode(string movieSlug); | ||||
| 		Task<Track> GetTrack(int id); | ||||
| 		Task<Track> GetTrack(string slug); | ||||
| 		Task<Genre> GetGenre(string slug); | ||||
| 		Task<Studio> GetStudio(string slug); | ||||
| 		Task<People> GetPeople(string slug); | ||||
|  | ||||
| @ -109,7 +109,12 @@ namespace Kyoo.Controllers | ||||
| 		{ | ||||
| 			return EpisodeRepository.Get(showID, seasonNumber, episodeNumber); | ||||
| 		} | ||||
| 		 | ||||
| 
 | ||||
| 		public Task<Track> GetTrack(string slug) | ||||
| 		{ | ||||
| 			return TrackRepository.Get(slug); | ||||
| 		} | ||||
| 
 | ||||
| 		public Task<Genre> GetGenre(int id) | ||||
| 		{ | ||||
| 			return GenreRepository.Get(id); | ||||
|  | ||||
| @ -93,7 +93,9 @@ namespace Kyoo.Models | ||||
| 
 | ||||
| 		public static string GetSlug(string showSlug, int seasonNumber, int episodeNumber) | ||||
| 		{ | ||||
| 			return showSlug + "-s" + seasonNumber + "e" + episodeNumber; | ||||
| 			if (seasonNumber == -1) | ||||
| 				return showSlug; | ||||
| 			return $"{showSlug}-s{seasonNumber}e{episodeNumber}"; | ||||
| 		} | ||||
| 
 | ||||
| 		public void OnMerge(object merged) | ||||
|  | ||||
| @ -39,18 +39,21 @@ namespace Kyoo.Controllers | ||||
| 		public override Task<Track> Get(string slug) | ||||
| 		{ | ||||
| 			Match match = Regex.Match(slug, | ||||
| 				@"(?<show>.*)-s(?<season>\d*)-e(?<episode>\d*).(?<language>.{0,3})(?<forced>-forced)?(\..*)?"); | ||||
| 				@"(?<show>.*)-s(?<season>\d+)e(?<episode>\d+)\.(?<language>.{0,3})(?<forced>-forced)?(\..*)?"); | ||||
| 
 | ||||
| 			if (!match.Success) | ||||
| 			{ | ||||
| 				if (int.TryParse(slug, out int id)) | ||||
| 					return Get(id); | ||||
| 				throw new ArgumentException("Invalid track slug. Format: {episodeSlug}.{language}[-forced][.{extension}]"); | ||||
| 				match = Regex.Match(slug, @"(?<show>.*)\.(?<language>.{0,3})(?<forced>-forced)?(\..*)?"); | ||||
| 				if (!match.Success) | ||||
| 					throw new ArgumentException("Invalid track slug. " + | ||||
| 					                            "Format: {episodeSlug}.{language}[-forced][.{extension}]"); | ||||
| 			} | ||||
| 
 | ||||
| 			string showSlug = match.Groups["show"].Value; | ||||
| 			int seasonNumber = int.Parse(match.Groups["season"].Value); | ||||
| 			int episodeNumber = int.Parse(match.Groups["episode"].Value); | ||||
| 			int seasonNumber = match.Groups["season"].Success ? int.Parse(match.Groups["season"].Value) : -1; | ||||
| 			int episodeNumber = match.Groups["episode"].Success ? int.Parse(match.Groups["episode"].Value) : -1; | ||||
| 			string language = match.Groups["language"].Value; | ||||
| 			bool forced = match.Groups["forced"].Success; | ||||
| 			return _database.Tracks.FirstOrDefaultAsync(x => x.Episode.Show.Slug == showSlug | ||||
| @ -59,6 +62,7 @@ namespace Kyoo.Controllers | ||||
| 			                                                 && x.Language == language | ||||
| 			                                                 && x.IsForced == forced); | ||||
| 		} | ||||
| 
 | ||||
| 		public Task<ICollection<Track>> Search(string query) | ||||
| 		{ | ||||
| 			throw new InvalidOperationException("Tracks do not support the search method."); | ||||
|  | ||||
| @ -79,7 +79,7 @@ namespace Kyoo.Controllers | ||||
| 
 | ||||
| 		public Task<string> Transcode(Episode episode) | ||||
| 		{ | ||||
| 			return null; // Not implemented yet. | ||||
| 			return Task.FromResult<string>(null); // Not implemented yet. | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @ -32,7 +32,6 @@ namespace Kyoo | ||||
| 			_loggerFactory = loggerFactory; | ||||
| 		} | ||||
| 
 | ||||
| 
 | ||||
| 		// This method gets called by the runtime. Use this method to add services to the container. | ||||
| 		public void ConfigureServices(IServiceCollection services) | ||||
| 		{ | ||||
| @ -132,7 +131,7 @@ namespace Kyoo | ||||
| 			{ | ||||
| 				AllowedOrigins = { new Uri(publicUrl).GetLeftPart(UriPartial.Authority) } | ||||
| 			}); | ||||
| 
 | ||||
| 			 | ||||
| 
 | ||||
| 			services.AddScoped<ILibraryRepository, LibraryRepository>(); | ||||
| 			services.AddScoped<ILibraryItemRepository, LibraryItemRepository>(); | ||||
|  | ||||
| @ -1,68 +1,46 @@ | ||||
| using Kyoo.Models; | ||||
| using System; | ||||
| using Kyoo.Models; | ||||
| using Microsoft.AspNetCore.Mvc; | ||||
| using System.Collections.Generic; | ||||
| using System.IO; | ||||
| using System.Linq; | ||||
| using System.Threading.Tasks; | ||||
| using Kyoo.Controllers; | ||||
| using Kyoo.Models.Watch; | ||||
| using Microsoft.AspNetCore.Authorization; | ||||
| 
 | ||||
| namespace Kyoo.Api | ||||
| { | ||||
| 	[Route("[controller]")]
 | ||||
| 	[Route("subtitle")] | ||||
| 	[ApiController] | ||||
| 	public class SubtitleController : ControllerBase | ||||
| 	public class SubtitleApi : ControllerBase | ||||
| 	{ | ||||
| 		private readonly ILibraryManager _libraryManager; | ||||
| 		//private readonly ITranscoder _transcoder; | ||||
| 
 | ||||
| 		public SubtitleController(ILibraryManager libraryManager/*, ITranscoder transcoder*/) | ||||
| 		public SubtitleApi(ILibraryManager libraryManager) | ||||
| 		{ | ||||
| 			_libraryManager = libraryManager; | ||||
| 		//	_transcoder = transcoder; | ||||
| 		} | ||||
| 		 | ||||
| 		//TODO Create a real route for movie's subtitles. | ||||
| 
 | ||||
| 		[HttpGet("{showSlug}-s{seasonNumber:int}e{episodeNumber:int}.{identifier}.{extension?}")] | ||||
| 		 | ||||
| 		[HttpGet("{slug}.{extension?}")] | ||||
| 		[Authorize(Policy="Play")] | ||||
| 		public async Task<IActionResult> GetSubtitle(string showSlug, | ||||
| 			int seasonNumber,  | ||||
| 			int episodeNumber,  | ||||
| 			string identifier, | ||||
| 			string extension) | ||||
| 		public async Task<IActionResult> GetSubtitle(string slug, string extension) | ||||
| 		{ | ||||
| 			string languageTag = identifier.Length >= 3 ? identifier.Substring(0, 3) : null; | ||||
| 			bool forced = identifier.Length > 4 && identifier.Substring(4) == "forced"; | ||||
| 			Track subtitle = null; | ||||
| 			 | ||||
| 			if (languageTag != null) | ||||
| 				subtitle = (await _libraryManager.GetEpisode(showSlug, seasonNumber, episodeNumber))?.Tracks | ||||
| 					.FirstOrDefault(x => x.Type == StreamType.Subtitle && x.Language == languageTag && x.IsForced == forced); | ||||
| 				 | ||||
| 			if (subtitle == null) | ||||
| 			Track subtitle; | ||||
| 			try | ||||
| 			{ | ||||
| 				string idString = identifier.IndexOf('-') != -1  | ||||
| 					? identifier.Substring(0, identifier.IndexOf('-'))  | ||||
| 					: identifier; | ||||
| 				int.TryParse(idString, out int id); | ||||
| 				subtitle = await _libraryManager.GetTrack(id); | ||||
| 				subtitle = await _libraryManager.GetTrack(slug); | ||||
| 			} | ||||
| 			 | ||||
| 			catch (ArgumentException ex) | ||||
| 			{ | ||||
| 				return BadRequest(new {error = ex.Message}); | ||||
| 			} | ||||
| 
 | ||||
| 			if (subtitle == null) | ||||
| 				return NotFound(); | ||||
| 			 | ||||
| 			if (subtitle.Codec == "subrip" && extension == "vtt") | ||||
| 				return new ConvertSubripToVtt(subtitle.Path); | ||||
| 
 | ||||
| 			string mime; | ||||
| 			if (subtitle.Codec == "ass") | ||||
| 				mime = "text/x-ssa"; | ||||
| 			else | ||||
| 				mime = "application/x-subrip"; | ||||
| 
 | ||||
| 			// TODO Should use appropriate mime type here | ||||
| 			string mime = subtitle.Codec == "ass" ? "text/x-ssa" : "application/x-subrip"; | ||||
| 			return PhysicalFile(subtitle.Path, mime); | ||||
| 		} | ||||
| 
 | ||||
| @ -129,21 +107,19 @@ namespace Kyoo.Api | ||||
| 				await writer.WriteLineAsync(""); | ||||
| 				await writer.WriteLineAsync(""); | ||||
| 
 | ||||
| 				using (StreamReader reader = new StreamReader(_path)) | ||||
| 				using StreamReader reader = new StreamReader(_path); | ||||
| 				while ((line = await reader.ReadLineAsync()) != null) | ||||
| 				{ | ||||
| 					while ((line = await reader.ReadLineAsync()) != null) | ||||
| 					if (line == "") | ||||
| 					{ | ||||
| 						if (line == "") | ||||
| 						{ | ||||
| 							lines.Add(""); | ||||
| 							IEnumerable<string> processedBlock = ConvertBlock(lines); | ||||
| 							foreach (string t in processedBlock) | ||||
| 								await writer.WriteLineAsync(t); | ||||
| 							lines.Clear(); | ||||
| 						} | ||||
| 						else | ||||
| 							lines.Add(line); | ||||
| 						lines.Add(""); | ||||
| 						IEnumerable<string> processedBlock = ConvertBlock(lines); | ||||
| 						foreach (string t in processedBlock) | ||||
| 							await writer.WriteLineAsync(t); | ||||
| 						lines.Clear(); | ||||
| 					} | ||||
| 					else | ||||
| 						lines.Add(line); | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| @ -5,19 +5,21 @@ using Microsoft.Extensions.Configuration; | ||||
| using System.IO; | ||||
| using System.Threading.Tasks; | ||||
| using Microsoft.AspNetCore.Authorization; | ||||
| using Microsoft.AspNetCore.StaticFiles; | ||||
| 
 | ||||
| namespace Kyoo.Api | ||||
| { | ||||
| 	[Route("[controller]")]
 | ||||
| 	[Route("video")] | ||||
| 	[ApiController] | ||||
| 	public class VideoController : ControllerBase | ||||
| 	public class VideoApi : ControllerBase | ||||
| 	{ | ||||
| 		private readonly ILibraryManager _libraryManager; | ||||
| 		private readonly ITranscoder _transcoder; | ||||
| 		private readonly string _transmuxPath; | ||||
| 		private readonly string _transcodePath; | ||||
| 		private FileExtensionContentTypeProvider _provider; | ||||
| 
 | ||||
| 		public VideoController(ILibraryManager libraryManager, ITranscoder transcoder, IConfiguration config) | ||||
| 		public VideoApi(ILibraryManager libraryManager, ITranscoder transcoder, IConfiguration config) | ||||
| 		{ | ||||
| 			_libraryManager = libraryManager; | ||||
| 			_transcoder = transcoder; | ||||
| @ -25,101 +27,124 @@ namespace Kyoo.Api | ||||
| 			_transcodePath = config.GetValue<string>("transcodeTempPath"); | ||||
| 		} | ||||
| 
 | ||||
| 		[HttpGet("{showSlug}-s{seasonNumber}e{episodeNumber}")] | ||||
| 		[Authorize(Policy="Play")] | ||||
| 		public async Task<IActionResult> Index(string showSlug, int seasonNumber, int episodeNumber) | ||||
| 		private string _GetContentType(string path) | ||||
| 		{ | ||||
| 			Episode episode = await _libraryManager.GetEpisode(showSlug, seasonNumber, episodeNumber); | ||||
| 			if (_provider == null) | ||||
| 			{ | ||||
| 				_provider = new FileExtensionContentTypeProvider(); | ||||
| 				_provider.Mappings[".mkv"] = "video/x-matroska"; | ||||
| 			} | ||||
| 
 | ||||
| 			if (_provider.TryGetContentType(path, out string contentType)) | ||||
| 				return contentType; | ||||
| 			return "video/mp4"; | ||||
| 		} | ||||
| 		 | ||||
| 
 | ||||
| 		[HttpGet("{showSlug}-s{seasonNumber}e{episodeNumber}")] | ||||
| 		[HttpGet("direct/{showSlug}-s{seasonNumber}e{episodeNumber}")] | ||||
| 		[Authorize(Policy="Play")] | ||||
| 		public async Task<IActionResult> DirectEpisode(string showSlug, int seasonNumber, int episodeNumber) | ||||
| 		{ | ||||
| 			if (seasonNumber < 0 || episodeNumber < 0) | ||||
| 				return BadRequest(new {error = "Season number or episode number can not be negative."}); | ||||
| 
 | ||||
| 			Episode episode = await _libraryManager.GetEpisode(showSlug, seasonNumber, episodeNumber); | ||||
| 			if (episode != null && System.IO.File.Exists(episode.Path)) | ||||
| 				return PhysicalFile(episode.Path, "video/x-matroska", true); | ||||
| 				return PhysicalFile(episode.Path, _GetContentType(episode.Path), true); | ||||
| 			return NotFound(); | ||||
| 		} | ||||
| 		 | ||||
| 		[HttpGet("{movieSlug}")] | ||||
| 		[HttpGet("direct/{movieSlug}")] | ||||
| 		[Authorize(Policy="Play")] | ||||
| 		public async Task<IActionResult> DirectMovie(string movieSlug) | ||||
| 		{ | ||||
| 			Episode episode = await _libraryManager.GetMovieEpisode(movieSlug); | ||||
| 
 | ||||
| 			if (episode != null && System.IO.File.Exists(episode.Path)) | ||||
| 				return PhysicalFile(episode.Path, _GetContentType(episode.Path), true); | ||||
| 			return NotFound(); | ||||
| 		} | ||||
| 		 | ||||
| 
 | ||||
| 		[HttpGet("transmux/{showSlug}-s{seasonNumber}e{episodeNumber}")] | ||||
| 		[Authorize(Policy="Play")] | ||||
| 		public async Task<IActionResult> Transmux(string showSlug, int seasonNumber, int episodeNumber) | ||||
| 		public async Task<IActionResult> TransmuxEpisode(string showSlug, int seasonNumber, int episodeNumber) | ||||
| 		{ | ||||
| 			if (seasonNumber < 0 || episodeNumber < 0) | ||||
| 				return BadRequest(new {error = "Season number or episode number can not be negative."}); | ||||
| 			 | ||||
| 			Episode episode = await _libraryManager.GetEpisode(showSlug, seasonNumber, episodeNumber); | ||||
| 			if (episode == null || !System.IO.File.Exists(episode.Path)) | ||||
| 				return NotFound(); | ||||
| 			string path = await _transcoder.Transmux(episode); | ||||
| 			if (path == null) | ||||
| 				return StatusCode(500); | ||||
| 			return PhysicalFile(path, "application/x-mpegURL ", true); | ||||
| 		} | ||||
| 		 | ||||
| 		[HttpGet("transmux/{movieSlug}")] | ||||
| 		[Authorize(Policy="Play")] | ||||
| 		public async Task<IActionResult> TransmuxMovie(string movieSlug) | ||||
| 		{ | ||||
| 			Episode episode = await _libraryManager.GetMovieEpisode(movieSlug); | ||||
| 
 | ||||
| 			if (episode == null || !System.IO.File.Exists(episode.Path)) | ||||
| 				return NotFound(); | ||||
| 			string path = await _transcoder.Transmux(episode); | ||||
| 			if (path != null) | ||||
| 				return PhysicalFile(path, "application/x-mpegURL ", true); | ||||
| 			return StatusCode(500); | ||||
| 		} | ||||
| 
 | ||||
| 		[HttpGet("transmux/{episodeLink}/segment/{chunk}")] | ||||
| 		public IActionResult GetTransmuxedChunk(string episodeLink, string chunk) | ||||
| 		{ | ||||
| 			string path = Path.GetFullPath(Path.Combine(_transmuxPath, episodeLink)); | ||||
| 			path = Path.Combine(path, "segments" + Path.DirectorySeparatorChar + chunk); | ||||
| 
 | ||||
| 			return PhysicalFile(path, "video/MP2T"); | ||||
| 			if (path == null) | ||||
| 				return StatusCode(500); | ||||
| 			return PhysicalFile(path, "application/x-mpegURL ", true); | ||||
| 		} | ||||
| 
 | ||||
| 		[HttpGet("transcode/{showSlug}-s{seasonNumber}e{episodeNumber}")] | ||||
| 		[Authorize(Policy="Play")] | ||||
| 		public async Task<IActionResult> Transcode(string showSlug, int seasonNumber, int episodeNumber) | ||||
| 		public async Task<IActionResult> TranscodeEpisode(string showSlug, int seasonNumber, int episodeNumber) | ||||
| 		{ | ||||
| 			if (seasonNumber < 0 || episodeNumber < 0) | ||||
| 				return BadRequest(new {error = "Season number or episode number can not be negative."}); | ||||
| 			 | ||||
| 			Episode episode = await _libraryManager.GetEpisode(showSlug, seasonNumber, episodeNumber); | ||||
| 			if (episode == null || !System.IO.File.Exists(episode.Path)) | ||||
| 				return NotFound(); | ||||
| 			string path = await _transcoder.Transcode(episode); | ||||
| 			if (path == null) | ||||
| 				return StatusCode(500); | ||||
| 			return PhysicalFile(path, "application/x-mpegURL ", true); | ||||
| 		} | ||||
| 		 | ||||
| 		[HttpGet("transcode/{movieSlug}")] | ||||
| 		[Authorize(Policy="Play")] | ||||
| 		public async Task<IActionResult> TranscodeMovie(string movieSlug) | ||||
| 		{ | ||||
| 			Episode episode = await _libraryManager.GetMovieEpisode(movieSlug); | ||||
| 
 | ||||
| 			if (episode == null || !System.IO.File.Exists(episode.Path)) | ||||
| 				return NotFound(); | ||||
| 			string path = await _transcoder.Transcode(episode); | ||||
| 			if (path != null) | ||||
| 				return PhysicalFile(path, "application/x-mpegURL ", true); | ||||
| 			return StatusCode(500); | ||||
| 			if (path == null) | ||||
| 				return StatusCode(500); | ||||
| 			return PhysicalFile(path, "application/x-mpegURL ", true); | ||||
| 		} | ||||
| 		 | ||||
| 		[HttpGet("transcode/{episodeLink}/segment/{chunk}")] | ||||
| 		public IActionResult GetTranscodedChunk(string episodeLink, string chunk) | ||||
| 		 | ||||
| 		[HttpGet("transmux/{episodeLink}/segment/{chunk}")] | ||||
| 		[Authorize(Policy="Play")] | ||||
| 		public IActionResult GetTransmuxedChunk(string episodeLink, string chunk) | ||||
| 		{ | ||||
| 			string path = Path.GetFullPath(Path.Combine(_transcodePath, episodeLink)); | ||||
| 			path = Path.Combine(path, "segments" + Path.DirectorySeparatorChar + chunk); | ||||
| 
 | ||||
| 			string path = Path.GetFullPath(Path.Combine(_transmuxPath, episodeLink)); | ||||
| 			path = Path.Combine(path, "segments", chunk); | ||||
| 			return PhysicalFile(path, "video/MP2T"); | ||||
| 		} | ||||
| 		 | ||||
| 		 | ||||
| 		[HttpGet("{movieSlug}")] | ||||
| 		[HttpGet("transcode/{episodeLink}/segment/{chunk}")] | ||||
| 		[Authorize(Policy="Play")] | ||||
| 		public async Task<IActionResult> Index(string movieSlug) | ||||
| 		public IActionResult GetTranscodedChunk(string episodeLink, string chunk) | ||||
| 		{ | ||||
| 			Episode episode = await _libraryManager.GetMovieEpisode(movieSlug); | ||||
| 
 | ||||
| 			if (episode != null && System.IO.File.Exists(episode.Path)) | ||||
| 				return PhysicalFile(episode.Path, "video/webm", true); | ||||
| 			return NotFound(); | ||||
| 		} | ||||
| 
 | ||||
| 		[HttpGet("transmux/{movieSlug}")] | ||||
| 		[Authorize(Policy="Play")] | ||||
| 		public async Task<IActionResult> Transmux(string movieSlug) | ||||
| 		{ | ||||
| 			Episode episode = await _libraryManager.GetMovieEpisode(movieSlug); | ||||
| 
 | ||||
| 			if (episode == null || !System.IO.File.Exists(episode.Path)) | ||||
| 				return NotFound(); | ||||
| 			string path = await _transcoder.Transmux(episode); | ||||
| 			if (path != null) | ||||
| 				return PhysicalFile(path, "application/x-mpegURL ", true); | ||||
| 			return StatusCode(500); | ||||
| 		} | ||||
| 
 | ||||
| 		[HttpGet("transcode/{movieSlug}")] | ||||
| 		[Authorize(Policy="Play")] | ||||
| 		public async Task<IActionResult> Transcode(string movieSlug) | ||||
| 		{ | ||||
| 			Episode episode = await _libraryManager.GetMovieEpisode(movieSlug); | ||||
| 
 | ||||
| 			if (episode == null || !System.IO.File.Exists(episode.Path)) | ||||
| 				return NotFound(); | ||||
| 			string path = await _transcoder.Transcode(episode); | ||||
| 			if (path != null) | ||||
| 				return PhysicalFile(path, "application/x-mpegURL ", true); | ||||
| 			return StatusCode(500); | ||||
| 			string path = Path.GetFullPath(Path.Combine(_transcodePath, episodeLink)); | ||||
| 			path = Path.Combine(path, "segments", chunk); | ||||
| 			return PhysicalFile(path, "video/MP2T"); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @ -1 +1 @@ | ||||
| Subproject commit 59da0095a85914eabde9900973331a25a16088b3 | ||||
| Subproject commit d2d90e28cd697a20fdd2e1c28fdfc2038c7f1d7c | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user