diff --git a/MediaBrowser.Api/Library/SubtitleService.cs b/MediaBrowser.Api/Library/SubtitleService.cs index 12e3ef1388..7f283b715e 100644 --- a/MediaBrowser.Api/Library/SubtitleService.cs +++ b/MediaBrowser.Api/Library/SubtitleService.cs @@ -34,6 +34,9 @@ namespace MediaBrowser.Api.Library [ApiMember(Name = "Format", Description = "Format", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] public string Format { get; set; } + + [ApiMember(Name = "StartPositionTicks", Description = "StartPositionTicks", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public long StartPositionTicks { get; set; } } [Route("/Videos/{Id}/Subtitles/{Index}", "DELETE", Summary = "Deletes an external subtitle file")] @@ -127,10 +130,12 @@ namespace MediaBrowser.Api.Library private async Task GetSubtitles(GetSubtitle request) { - var stream = await _subtitleEncoder.GetSubtitles(request.Id, request.MediaSourceId, request.Index, request.Format, - CancellationToken.None); - - return stream; + return await _subtitleEncoder.GetSubtitles(request.Id, + request.MediaSourceId, + request.Index, + request.Format, + request.StartPositionTicks, + CancellationToken.None).ConfigureAwait(false); } public void Delete(DeleteSubtitle request) diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index 1d83009682..1460ceb604 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -65,30 +65,24 @@ namespace MediaBrowser.Api.Playback /// /// The media encoder. protected IMediaEncoder MediaEncoder { get; private set; } - protected IEncodingManager EncodingManager { get; private set; } - protected IDtoService DtoService { get; private set; } protected IFileSystem FileSystem { get; private set; } - protected IItemRepository ItemRepository { get; private set; } protected ILiveTvManager LiveTvManager { get; private set; } protected IDlnaManager DlnaManager { get; private set; } protected IChannelManager ChannelManager { get; private set; } - protected IHttpClient HttpClient { get; private set; } + protected ISubtitleEncoder SubtitleEncoder { get; private set; } /// /// Initializes a new instance of the class. /// - protected BaseStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager, IChannelManager channelManager, IHttpClient httpClient) + protected BaseStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder) { - HttpClient = httpClient; + SubtitleEncoder = subtitleEncoder; ChannelManager = channelManager; DlnaManager = dlnaManager; - EncodingManager = encodingManager; LiveTvManager = liveTvManager; - ItemRepository = itemRepository; FileSystem = fileSystem; - DtoService = dtoService; ServerConfigurationManager = serverConfig; UserManager = userManager; LibraryManager = libraryManager; @@ -587,7 +581,7 @@ namespace MediaBrowser.Api.Playback if (!string.IsNullOrEmpty(state.SubtitleStream.Language)) { - var charenc = MediaEncoder.GetSubtitleLanguageEncodingParam(subtitlePath, state.SubtitleStream.Language); + var charenc = SubtitleEncoder.GetSubtitleFileCharacterSet(subtitlePath, state.SubtitleStream.Language); if (!string.IsNullOrEmpty(charenc)) { @@ -1374,6 +1368,12 @@ namespace MediaBrowser.Api.Playback var path = recording.RecordingInfo.Path; var mediaUrl = recording.RecordingInfo.Url; + var source = string.IsNullOrEmpty(request.MediaSourceId) + ? recording.GetMediaSources(false).First() + : recording.GetMediaSources(false).First(i => string.Equals(i.Id, request.MediaSourceId)); + + mediaStreams = source.MediaStreams; + if (string.IsNullOrWhiteSpace(path) && string.IsNullOrWhiteSpace(mediaUrl)) { var streamInfo = await LiveTvManager.GetRecordingStream(request.Id, cancellationToken).ConfigureAwait(false); @@ -1453,51 +1453,45 @@ namespace MediaBrowser.Api.Playback } else { - var mediaSource = string.IsNullOrWhiteSpace(request.MediaSourceId) - ? item - : LibraryManager.GetItemById(request.MediaSourceId); + var hasMediaSources = (IHasMediaSources)item; + var mediaSource = string.IsNullOrEmpty(request.MediaSourceId) + ? hasMediaSources.GetMediaSources(false).First() + : hasMediaSources.GetMediaSources(false).First(i => string.Equals(i.Id, request.MediaSourceId)); + + mediaStreams = mediaSource.MediaStreams; state.MediaPath = mediaSource.Path; state.IsRemote = mediaSource.LocationType == LocationType.Remote; + state.InputContainer = mediaSource.Container; - var video = mediaSource as Video; - - if (video != null) + if (item is Video) { state.IsInputVideo = true; - state.VideoType = video.VideoType; - state.IsoType = video.IsoType; - state.PlayableStreamFileNames = video.PlayableStreamFileNames == null - ? new List() - : video.PlayableStreamFileNames.ToList(); - - state.DeInterlace = string.Equals(video.Container, "wtv", StringComparison.OrdinalIgnoreCase); - - if (video.Timestamp.HasValue) + if (mediaSource.VideoType.HasValue) { - state.InputTimestamp = video.Timestamp.Value; + state.VideoType = mediaSource.VideoType.Value; } - state.InputContainer = video.Container; - } + state.IsoType = mediaSource.IsoType; - var audio = mediaSource as Audio; - if (audio != null) - { - state.InputContainer = audio.Container; + //state.PlayableStreamFileNames = mediaSource.PlayableStreamFileNames.ToList(); + + if (mediaSource.Timestamp.HasValue) + { + state.InputTimestamp = mediaSource.Timestamp.Value; + } } state.RunTimeTicks = mediaSource.RunTimeTicks; } - var videoRequest = request as VideoStreamRequest; - - mediaStreams = mediaStreams ?? ItemRepository.GetMediaStreams(new MediaStreamQuery + if (string.Equals(state.InputContainer, "wtv", StringComparison.OrdinalIgnoreCase)) { - ItemId = new Guid(string.IsNullOrWhiteSpace(request.MediaSourceId) ? request.Id : request.MediaSourceId) + state.DeInterlace = true; + } - }).ToList(); + var videoRequest = request as VideoStreamRequest; AttachMediaStreamInfo(state, mediaStreams, videoRequest, url); diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs index 7258a01741..c4e1fa5de2 100644 --- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs @@ -25,7 +25,7 @@ namespace MediaBrowser.Api.Playback.Hls /// public abstract class BaseHlsService : BaseStreamingService { - protected BaseHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager, IChannelManager channelManager, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, dlnaManager, channelManager, httpClient) + protected BaseHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder) { } diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs index 92d6161f5a..bd8201c418 100644 --- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs @@ -62,7 +62,7 @@ namespace MediaBrowser.Api.Playback.Hls public class DynamicHlsService : BaseHlsService { - public DynamicHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager, IChannelManager channelManager, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, dlnaManager, channelManager, httpClient) + public DynamicHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder) { } diff --git a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs index 346b379609..8d0a4d14bf 100644 --- a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs @@ -56,7 +56,7 @@ namespace MediaBrowser.Api.Playback.Hls /// public class VideoHlsService : BaseHlsService { - public VideoHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager, IChannelManager channelManager, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, dlnaManager, channelManager, httpClient) + public VideoHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder) { } diff --git a/MediaBrowser.Api/Playback/Progressive/AudioService.cs b/MediaBrowser.Api/Playback/Progressive/AudioService.cs index 44065dfe89..4d21e009f1 100644 --- a/MediaBrowser.Api/Playback/Progressive/AudioService.cs +++ b/MediaBrowser.Api/Playback/Progressive/AudioService.cs @@ -44,7 +44,7 @@ namespace MediaBrowser.Api.Playback.Progressive /// public class AudioService : BaseProgressiveStreamingService { - public AudioService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager, IChannelManager channelManager, IHttpClient httpClient, IImageProcessor imageProcessor) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, dlnaManager, channelManager, httpClient, imageProcessor) + public AudioService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder, imageProcessor, httpClient) { } diff --git a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs index 4993dd3ce2..1e71c7c0d7 100644 --- a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs +++ b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs @@ -4,11 +4,9 @@ using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Drawing; -using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.IO; using ServiceStack.Web; using System; @@ -25,10 +23,12 @@ namespace MediaBrowser.Api.Playback.Progressive public abstract class BaseProgressiveStreamingService : BaseStreamingService { protected readonly IImageProcessor ImageProcessor; + protected readonly IHttpClient HttpClient; - protected BaseProgressiveStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager, IChannelManager channelManager, IHttpClient httpClient, IImageProcessor imageProcessor) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, dlnaManager, channelManager, httpClient) + protected BaseProgressiveStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder) { ImageProcessor = imageProcessor; + HttpClient = httpClient; } /// diff --git a/MediaBrowser.Api/Playback/Progressive/VideoService.cs b/MediaBrowser.Api/Playback/Progressive/VideoService.cs index 4b9d0fbb5c..8f5ebcf369 100644 --- a/MediaBrowser.Api/Playback/Progressive/VideoService.cs +++ b/MediaBrowser.Api/Playback/Progressive/VideoService.cs @@ -61,7 +61,7 @@ namespace MediaBrowser.Api.Playback.Progressive /// public class VideoService : BaseProgressiveStreamingService { - public VideoService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager, IChannelManager channelManager, IHttpClient httpClient, IImageProcessor imageProcessor) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, dlnaManager, channelManager, httpClient, imageProcessor) + public VideoService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder, imageProcessor, httpClient) { } diff --git a/MediaBrowser.Controller/Entities/Audio/Audio.cs b/MediaBrowser.Controller/Entities/Audio/Audio.cs index 43de1f5b0d..e14563ea46 100644 --- a/MediaBrowser.Controller/Entities/Audio/Audio.cs +++ b/MediaBrowser.Controller/Entities/Audio/Audio.cs @@ -7,7 +7,6 @@ using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; -using System.Threading; namespace MediaBrowser.Controller.Entities.Audio { @@ -15,7 +14,6 @@ namespace MediaBrowser.Controller.Entities.Audio /// Class Audio /// public class Audio : BaseItem, - IHasMediaStreams, IHasAlbumArtist, IHasArtist, IHasMusicGenres, diff --git a/MediaBrowser.Controller/Entities/IHasMediaStreams.cs b/MediaBrowser.Controller/Entities/IHasMediaStreams.cs deleted file mode 100644 index b700ef628a..0000000000 --- a/MediaBrowser.Controller/Entities/IHasMediaStreams.cs +++ /dev/null @@ -1,10 +0,0 @@ - -namespace MediaBrowser.Controller.Entities -{ - /// - /// This is essentially a marker interface - /// - public interface IHasMediaStreams - { - } -} diff --git a/MediaBrowser.Controller/Entities/Video.cs b/MediaBrowser.Controller/Entities/Video.cs index 3bc08506ef..ef12d46d63 100644 --- a/MediaBrowser.Controller/Entities/Video.cs +++ b/MediaBrowser.Controller/Entities/Video.cs @@ -20,7 +20,6 @@ namespace MediaBrowser.Controller.Entities /// Class Video /// public class Video : BaseItem, - IHasMediaStreams, IHasAspectRatio, IHasTags, ISupportsPlaceHolders, diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvRecording.cs b/MediaBrowser.Controller/LiveTv/ILiveTvRecording.cs index 700fe1f413..23610351e7 100644 --- a/MediaBrowser.Controller/LiveTv/ILiveTvRecording.cs +++ b/MediaBrowser.Controller/LiveTv/ILiveTvRecording.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; namespace MediaBrowser.Controller.LiveTv { - public interface ILiveTvRecording : IHasImages, IHasMediaStreams + public interface ILiveTvRecording : IHasImages, IHasMediaSources { string ServiceName { get; set; } diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 83155fe8cd..7bc82f76dc 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -128,7 +128,6 @@ - diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs index 9895d9a60b..0de119ae54 100644 --- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs @@ -41,26 +41,6 @@ namespace MediaBrowser.Controller.MediaEncoding /// The cancellation token. /// Task{Stream}. Task ExtractVideoImage(string[] inputFiles, InputType type, Video3DFormat? threedFormat, TimeSpan? offset, CancellationToken cancellationToken); - - /// - /// Extracts the text subtitle. - /// - /// The input files. - /// The type. - /// Index of the subtitle stream. - /// if set to true, copy stream instead of converting. - /// The output path. - /// The cancellation token. - /// Task. - Task ExtractTextSubtitle(string[] inputFiles, InputType type, int subtitleStreamIndex, bool copySubtitleStream, string outputPath, CancellationToken cancellationToken); - - /// - /// Gets the subtitle language encoding parameter. - /// - /// The path. - /// The language. - /// System.String. - string GetSubtitleLanguageEncodingParam(string path, string language); /// /// Gets the media info. diff --git a/MediaBrowser.Controller/MediaEncoding/ISubtitleEncoder.cs b/MediaBrowser.Controller/MediaEncoding/ISubtitleEncoder.cs index 8f85895f0c..6e9bcef2ea 100644 --- a/MediaBrowser.Controller/MediaEncoding/ISubtitleEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/ISubtitleEncoder.cs @@ -6,16 +6,46 @@ namespace MediaBrowser.Controller.MediaEncoding { public interface ISubtitleEncoder { + /// + /// Converts the subtitles. + /// + /// The stream. + /// The input format. + /// The output format. + /// The start time ticks. + /// The cancellation token. + /// Task{Stream}. Task ConvertSubtitles( - Stream stream, - string inputFormat, + Stream stream, + string inputFormat, string outputFormat, + long startTimeTicks, CancellationToken cancellationToken); - Task GetSubtitles(string itemId, + /// + /// Gets the subtitles. + /// + /// The item identifier. + /// The media source identifier. + /// Index of the subtitle stream. + /// The output format. + /// The start time ticks. + /// The cancellation token. + /// Task{Stream}. + Task GetSubtitles(string itemId, string mediaSourceId, int subtitleStreamIndex, string outputFormat, + long startTimeTicks, CancellationToken cancellationToken); + + /// + /// Gets the subtitle language encoding parameter. + /// + /// The path. + /// The language. + /// System.String. + string GetSubtitleFileCharacterSet(string path, string language); + } } diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index c14e7d476d..d532d100f9 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -285,289 +285,6 @@ namespace MediaBrowser.MediaEncoding.Encoder return string.Empty; } - /// - /// Gets the subtitle language encoding param. - /// - /// The path. - /// The language. - /// System.String. - public string GetSubtitleLanguageEncodingParam(string path, string language) - { - if (GetFileEncoding(path).Equals(Encoding.UTF8)) - { - return string.Empty; - } - - switch (language.ToLower()) - { - case "pol": - case "cze": - case "ces": - case "slo": - case "slk": - case "hun": - case "slv": - case "srp": - case "hrv": - case "rum": - case "ron": - case "rup": - case "alb": - case "sqi": - return "windows-1250"; - case "ara": - return "windows-1256"; - case "heb": - return "windows-1255"; - case "grc": - case "gre": - return "windows-1253"; - case "crh": - case "ota": - case "tur": - return "windows-1254"; - case "rus": - return "windows-1251"; - case "vie": - return "windows-1258"; - case "kor": - return "cp949"; - default: - return "windows-1252"; - } - } - - private static Encoding GetFileEncoding(string srcFile) - { - // *** Detect byte order mark if any - otherwise assume default - var buffer = new byte[5]; - - using (var file = new FileStream(srcFile, FileMode.Open)) - { - file.Read(buffer, 0, 5); - } - - if (buffer[0] == 0xef && buffer[1] == 0xbb && buffer[2] == 0xbf) - return Encoding.UTF8; - if (buffer[0] == 0xfe && buffer[1] == 0xff) - return Encoding.Unicode; - if (buffer[0] == 0 && buffer[1] == 0 && buffer[2] == 0xfe && buffer[3] == 0xff) - return Encoding.UTF32; - if (buffer[0] == 0x2b && buffer[1] == 0x2f && buffer[2] == 0x76) - return Encoding.UTF7; - - // It's ok - anything aside from utf is ok since that's what we're looking for - return Encoding.Default; - } - - /// - /// Extracts the text subtitle. - /// - /// The input files. - /// The type. - /// Index of the subtitle stream. - /// if set to true, copy stream instead of converting. - /// The output path. - /// The cancellation token. - /// Task. - /// Must use inputPath list overload - public async Task ExtractTextSubtitle(string[] inputFiles, InputType type, int subtitleStreamIndex, - bool copySubtitleStream, string outputPath, CancellationToken cancellationToken) - { - var semaphore = GetLock(outputPath); - - await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); - - try - { - if (!File.Exists(outputPath)) - { - await - ExtractTextSubtitleInternal(GetInputArgument(inputFiles, type), subtitleStreamIndex, - copySubtitleStream, outputPath, cancellationToken).ConfigureAwait(false); - } - } - finally - { - semaphore.Release(); - } - } - - /// - /// Extracts the text subtitle. - /// - /// The input path. - /// Index of the subtitle stream. - /// if set to true, copy stream instead of converting. - /// The output path. - /// The cancellation token. - /// Task. - /// inputPath - /// or - /// outputPath - /// or - /// cancellationToken - /// - private async Task ExtractTextSubtitleInternal(string inputPath, int subtitleStreamIndex, - bool copySubtitleStream, string outputPath, CancellationToken cancellationToken) - { - if (string.IsNullOrEmpty(inputPath)) - { - throw new ArgumentNullException("inputPath"); - } - - if (string.IsNullOrEmpty(outputPath)) - { - throw new ArgumentNullException("outputPath"); - } - - string processArgs = string.Format("-i {0} -map 0:{1} -an -vn -c:s ass \"{2}\"", inputPath, - subtitleStreamIndex, outputPath); - - if (copySubtitleStream) - { - processArgs = string.Format("-i {0} -map 0:{1} -an -vn -c:s copy \"{2}\"", inputPath, - subtitleStreamIndex, outputPath); - } - - var process = new Process - { - StartInfo = new ProcessStartInfo - { - CreateNoWindow = true, - UseShellExecute = false, - - RedirectStandardOutput = false, - RedirectStandardError = true, - - FileName = FFMpegPath, - Arguments = processArgs, - WindowStyle = ProcessWindowStyle.Hidden, - ErrorDialog = false - } - }; - - _logger.Debug("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments); - - var logFilePath = Path.Combine(_appPaths.LogDirectoryPath, "ffmpeg-sub-extract-" + Guid.NewGuid() + ".txt"); - Directory.CreateDirectory(Path.GetDirectoryName(logFilePath)); - - var logFileStream = _fileSystem.GetFileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, - true); - - try - { - process.Start(); - } - catch (Exception ex) - { - logFileStream.Dispose(); - - _logger.ErrorException("Error starting ffmpeg", ex); - - throw; - } - - process.StandardError.BaseStream.CopyToAsync(logFileStream); - - var ranToCompletion = process.WaitForExit(60000); - - if (!ranToCompletion) - { - try - { - _logger.Info("Killing ffmpeg subtitle extraction process"); - - process.Kill(); - - process.WaitForExit(1000); - } - catch (Exception ex) - { - _logger.ErrorException("Error killing subtitle extraction process", ex); - } - finally - { - logFileStream.Dispose(); - } - } - - var exitCode = ranToCompletion ? process.ExitCode : -1; - - process.Dispose(); - - var failed = false; - - if (exitCode == -1) - { - failed = true; - - if (File.Exists(outputPath)) - { - try - { - _logger.Info("Deleting extracted subtitle due to failure: ", outputPath); - File.Delete(outputPath); - } - catch (IOException ex) - { - _logger.ErrorException("Error deleting extracted subtitle {0}", ex, outputPath); - } - } - } - else if (!File.Exists(outputPath)) - { - failed = true; - } - - if (failed) - { - var msg = string.Format("ffmpeg subtitle extraction failed for {0} to {1}", inputPath, outputPath); - - _logger.Error(msg); - - throw new ApplicationException(msg); - } - else - { - var msg = string.Format("ffmpeg subtitle extraction completed for {0} to {1}", inputPath, outputPath); - - _logger.Info(msg); - } - - await SetAssFont(outputPath).ConfigureAwait(false); - } - - /// - /// Sets the ass font. - /// - /// The file. - /// Task. - private async Task SetAssFont(string file) - { - _logger.Info("Setting ass font within {0}", file); - - string text; - Encoding encoding; - - using (var reader = new StreamReader(file, detectEncodingFromByteOrderMarks: true)) - { - encoding = reader.CurrentEncoding; - - text = await reader.ReadToEndAsync().ConfigureAwait(false); - } - - var newText = text.Replace(",Arial,", ",Arial Unicode MS,"); - - if (!string.Equals(text, newText)) - { - using (var writer = new StreamWriter(file, false, encoding)) - { - writer.Write(newText); - } - } - } - public Task ExtractAudioImage(string path, CancellationToken cancellationToken) { return ExtractImage(new[] { path }, InputType.File, true, null, null, cancellationToken); diff --git a/MediaBrowser.MediaEncoding/Subtitles/SrtParser.cs b/MediaBrowser.MediaEncoding/Subtitles/SrtParser.cs index 80fd0d6022..0f1f17bc03 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/SrtParser.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/SrtParser.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Globalization; using System.IO; +using System.Text; using System.Text.RegularExpressions; using System.Threading; diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs index 7b783711a9..461f95174a 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs @@ -47,13 +47,16 @@ namespace MediaBrowser.MediaEncoding.Subtitles public async Task ConvertSubtitles(Stream stream, string inputFormat, string outputFormat, + long startTimeTicks, CancellationToken cancellationToken) { var ms = new MemoryStream(); try { - if (string.Equals(inputFormat, outputFormat, StringComparison.OrdinalIgnoreCase)) + // Return the original without any conversions, if possible + if (startTimeTicks == 0 && + string.Equals(inputFormat, outputFormat, StringComparison.OrdinalIgnoreCase)) { await stream.CopyToAsync(ms, 81920, cancellationToken).ConfigureAwait(false); } @@ -61,6 +64,8 @@ namespace MediaBrowser.MediaEncoding.Subtitles { var trackInfo = await GetTrackInfo(stream, inputFormat, cancellationToken).ConfigureAwait(false); + UpdateStartingPosition(trackInfo, startTimeTicks); + var writer = GetWriter(outputFormat); writer.Write(trackInfo, ms, cancellationToken); @@ -76,10 +81,26 @@ namespace MediaBrowser.MediaEncoding.Subtitles return ms; } + private void UpdateStartingPosition(SubtitleTrackInfo track, long startPositionTicks) + { + if (startPositionTicks == 0) return; + + foreach (var trackEvent in track.TrackEvents) + { + trackEvent.EndPositionTicks -= startPositionTicks; + trackEvent.StartPositionTicks -= startPositionTicks; + } + + track.TrackEvents = track.TrackEvents + .SkipWhile(i => i.StartPositionTicks < 0 || i.EndPositionTicks < 0) + .ToList(); + } + public async Task GetSubtitles(string itemId, string mediaSourceId, int subtitleStreamIndex, string outputFormat, + long startTimeTicks, CancellationToken cancellationToken) { var subtitle = await GetSubtitleStream(itemId, mediaSourceId, subtitleStreamIndex, cancellationToken) @@ -89,7 +110,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles { var inputFormat = subtitle.Item2; - return await ConvertSubtitles(stream, inputFormat, outputFormat, cancellationToken).ConfigureAwait(false); + return await ConvertSubtitles(stream, inputFormat, outputFormat, startTimeTicks, cancellationToken).ConfigureAwait(false); } } @@ -127,11 +148,36 @@ namespace MediaBrowser.MediaEncoding.Subtitles var fileInfo = await GetReadableFile(mediaSource.Path, inputFiles, inputType, subtitleStream, cancellationToken).ConfigureAwait(false); - var stream = File.OpenRead(fileInfo.Item1); + var stream = await GetSubtitleStream(fileInfo.Item1, subtitleStream.Language).ConfigureAwait(false); return new Tuple(stream, fileInfo.Item2); } + private async Task GetSubtitleStream(string path, string language) + { + if (!string.IsNullOrEmpty(language)) + { + var charset = GetSubtitleFileCharacterSet(path, language); + + if (!string.IsNullOrEmpty(charset)) + { + using (var fs = _fileSystem.GetFileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true)) + { + using (var reader = new StreamReader(fs, Encoding.GetEncoding(charset))) + { + var text = await reader.ReadToEndAsync().ConfigureAwait(false); + + var bytes = Encoding.UTF8.GetBytes(text); + + return new MemoryStream(bytes); + } + } + } + } + + return File.OpenRead(path); + } + private async Task> GetReadableFile(string mediaPath, string[] inputFiles, InputType type, @@ -282,10 +328,11 @@ namespace MediaBrowser.MediaEncoding.Subtitles throw new ArgumentNullException("outputPath"); } + Directory.CreateDirectory(Path.GetDirectoryName(outputPath)); var encodingParam = string.IsNullOrEmpty(language) ? string.Empty - : _mediaEncoder.GetSubtitleLanguageEncodingParam(inputPath, language); + : GetSubtitleFileCharacterSet(inputPath, language); if (!string.IsNullOrEmpty(encodingParam)) { @@ -456,7 +503,9 @@ namespace MediaBrowser.MediaEncoding.Subtitles throw new ArgumentNullException("outputPath"); } - string processArgs = string.Format("-i {0} -map 0:{1} -an -vn -c:s ass \"{2}\"", inputPath, + Directory.CreateDirectory(Path.GetDirectoryName(outputPath)); + + var processArgs = string.Format("-i {0} -map 0:{1} -an -vn -c:s ass \"{2}\"", inputPath, subtitleStreamIndex, outputPath); if (copySubtitleStream) @@ -615,5 +664,80 @@ namespace MediaBrowser.MediaEncoding.Subtitles return Path.Combine(SubtitleCachePath, prefix, filename); } + + /// + /// Gets the subtitle language encoding param. + /// + /// The path. + /// The language. + /// System.String. + public string GetSubtitleFileCharacterSet(string path, string language) + { + if (GetFileEncoding(path).Equals(Encoding.UTF8)) + { + return string.Empty; + } + + switch (language.ToLower()) + { + case "pol": + case "cze": + case "ces": + case "slo": + case "slk": + case "hun": + case "slv": + case "srp": + case "hrv": + case "rum": + case "ron": + case "rup": + case "alb": + case "sqi": + return "windows-1250"; + case "ara": + return "windows-1256"; + case "heb": + return "windows-1255"; + case "grc": + case "gre": + return "windows-1253"; + case "crh": + case "ota": + case "tur": + return "windows-1254"; + case "rus": + return "windows-1251"; + case "vie": + return "windows-1258"; + case "kor": + return "cp949"; + default: + return "windows-1252"; + } + } + + private static Encoding GetFileEncoding(string srcFile) + { + // *** Detect byte order mark if any - otherwise assume default + var buffer = new byte[5]; + + using (var file = new FileStream(srcFile, FileMode.Open)) + { + file.Read(buffer, 0, 5); + } + + if (buffer[0] == 0xef && buffer[1] == 0xbb && buffer[2] == 0xbf) + return Encoding.UTF8; + if (buffer[0] == 0xfe && buffer[1] == 0xff) + return Encoding.Unicode; + if (buffer[0] == 0 && buffer[1] == 0 && buffer[2] == 0xfe && buffer[3] == 0xff) + return Encoding.UTF32; + if (buffer[0] == 0x2b && buffer[1] == 0x2f && buffer[2] == 0x76) + return Encoding.UTF7; + + // It's ok - anything aside from utf is ok since that's what we're looking for + return Encoding.Default; + } } } diff --git a/MediaBrowser.Model/Entities/MediaStream.cs b/MediaBrowser.Model/Entities/MediaStream.cs index 6ae620908c..4de183bf9b 100644 --- a/MediaBrowser.Model/Entities/MediaStream.cs +++ b/MediaBrowser.Model/Entities/MediaStream.cs @@ -130,7 +130,6 @@ namespace MediaBrowser.Model.Entities /// true if this instance is external; otherwise, false. public bool IsExternal { get; set; } - [IgnoreDataMember] public bool IsGraphicalSubtitleStream { get diff --git a/MediaBrowser.Server.Implementations/Dto/DtoService.cs b/MediaBrowser.Server.Implementations/Dto/DtoService.cs index e19a6c7d67..a43c51282d 100644 --- a/MediaBrowser.Server.Implementations/Dto/DtoService.cs +++ b/MediaBrowser.Server.Implementations/Dto/DtoService.cs @@ -1,5 +1,4 @@ -using System.Threading; -using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.Extensions; using MediaBrowser.Common.IO; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Configuration; @@ -30,7 +29,6 @@ namespace MediaBrowser.Server.Implementations.Dto { private readonly ILogger _logger; private readonly ILibraryManager _libraryManager; - private readonly IUserManager _userManager; private readonly IUserDataManager _userDataRepository; private readonly IItemRepository _itemRepo; @@ -41,11 +39,10 @@ namespace MediaBrowser.Server.Implementations.Dto private readonly Func _channelManagerFactory; - public DtoService(ILogger logger, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataRepository, IItemRepository itemRepo, IImageProcessor imageProcessor, IServerConfigurationManager config, IFileSystem fileSystem, IProviderManager providerManager, Func channelManagerFactory) + public DtoService(ILogger logger, ILibraryManager libraryManager, IUserDataManager userDataRepository, IItemRepository itemRepo, IImageProcessor imageProcessor, IServerConfigurationManager config, IFileSystem fileSystem, IProviderManager providerManager, Func channelManagerFactory) { _logger = logger; _libraryManager = libraryManager; - _userManager = userManager; _userDataRepository = userDataRepository; _itemRepo = itemRepo; _imageProcessor = imageProcessor; @@ -993,9 +990,9 @@ namespace MediaBrowser.Server.Implementations.Dto if (fields.Contains(ItemFields.MediaStreams)) { // Add VideoInfo - var iHasMediaStreams = item as IHasMediaStreams; + var iHasMediaSources = item as IHasMediaSources; - if (iHasMediaStreams != null) + if (iHasMediaSources != null) { List mediaStreams; @@ -1007,11 +1004,7 @@ namespace MediaBrowser.Server.Implementations.Dto } else { - mediaStreams = _itemRepo.GetMediaStreams(new MediaStreamQuery - { - ItemId = item.Id - - }).ToList(); + mediaStreams = iHasMediaSources.GetMediaSources(true).First().MediaStreams; } dto.MediaStreams = mediaStreams; diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs index 183900171e..675e670419 100644 --- a/MediaBrowser.ServerApplication/ApplicationHost.cs +++ b/MediaBrowser.ServerApplication/ApplicationHost.cs @@ -498,7 +498,7 @@ namespace MediaBrowser.ServerApplication ImageProcessor = new ImageProcessor(LogManager.GetLogger("ImageProcessor"), ServerConfigurationManager.ApplicationPaths, FileSystemManager, JsonSerializer, MediaEncoder); RegisterSingleInstance(ImageProcessor); - DtoService = new DtoService(Logger, LibraryManager, UserManager, UserDataManager, ItemRepository, ImageProcessor, ServerConfigurationManager, FileSystemManager, ProviderManager, () => ChannelManager); + DtoService = new DtoService(Logger, LibraryManager, UserDataManager, ItemRepository, ImageProcessor, ServerConfigurationManager, FileSystemManager, ProviderManager, () => ChannelManager); RegisterSingleInstance(DtoService); SessionManager = new SessionManager(UserDataManager, ServerConfigurationManager, Logger, UserRepository, LibraryManager, UserManager, musicManager, DtoService, ImageProcessor, ItemRepository, JsonSerializer, this, HttpClient); diff --git a/MediaBrowser.ServerApplication/LibraryViewer.cs b/MediaBrowser.ServerApplication/LibraryViewer.cs index 8e90919067..26cf243daf 100644 --- a/MediaBrowser.ServerApplication/LibraryViewer.cs +++ b/MediaBrowser.ServerApplication/LibraryViewer.cs @@ -46,18 +46,12 @@ namespace MediaBrowser.ServerApplication var json = FormatJson(_jsonSerializer.SerializeToString(item)); - if (item is IHasMediaStreams) + var hasMediaSources = item as IHasMediaSources; + if (hasMediaSources != null) { - var mediaStreams = _itemRepository.GetMediaStreams(new MediaStreamQuery - { - ItemId = item.Id + var sources = hasMediaSources.GetMediaSources(false).ToList(); - }).ToList(); - - if (mediaStreams.Count > 0) - { - json += "\n\nMedia Streams:\n\n" + FormatJson(_jsonSerializer.SerializeToString(mediaStreams)); - } + json += "\n\nMedia Sources:\n\n" + FormatJson(_jsonSerializer.SerializeToString(sources)); } txtJson.Text = json;