diff options
Diffstat (limited to 'MediaBrowser.MediaEncoding')
31 files changed, 0 insertions, 6731 deletions
diff --git a/MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs b/MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs deleted file mode 100644 index 219b1f3c5..000000000 --- a/MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs +++ /dev/null @@ -1,201 +0,0 @@ -using BDInfo; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.MediaInfo; -using System; -using System.Collections.Generic; -using System.Linq; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Text; - -namespace MediaBrowser.MediaEncoding.BdInfo -{ - /// <summary> - /// Class BdInfoExaminer - /// </summary> - public class BdInfoExaminer : IBlurayExaminer - { - private readonly IFileSystem _fileSystem; - private readonly ITextEncoding _textEncoding; - - public BdInfoExaminer(IFileSystem fileSystem, ITextEncoding textEncoding) - { - _fileSystem = fileSystem; - _textEncoding = textEncoding; - } - - /// <summary> - /// Gets the disc info. - /// </summary> - /// <param name="path">The path.</param> - /// <returns>BlurayDiscInfo.</returns> - public BlurayDiscInfo GetDiscInfo(string path) - { - if (string.IsNullOrWhiteSpace(path)) - { - throw new ArgumentNullException("path"); - } - - var bdrom = new BDROM(path, _fileSystem, _textEncoding); - - bdrom.Scan(); - - // Get the longest playlist - var playlist = bdrom.PlaylistFiles.Values.OrderByDescending(p => p.TotalLength).FirstOrDefault(p => p.IsValid); - - var outputStream = new BlurayDiscInfo - { - MediaStreams = new List<MediaStream>() - }; - - if (playlist == null) - { - return outputStream; - } - - outputStream.Chapters = playlist.Chapters; - - outputStream.RunTimeTicks = TimeSpan.FromSeconds(playlist.TotalLength).Ticks; - - var mediaStreams = new List<MediaStream>(); - - foreach (var stream in playlist.SortedStreams) - { - var videoStream = stream as TSVideoStream; - - if (videoStream != null) - { - AddVideoStream(mediaStreams, videoStream); - continue; - } - - var audioStream = stream as TSAudioStream; - - if (audioStream != null) - { - AddAudioStream(mediaStreams, audioStream); - continue; - } - - var textStream = stream as TSTextStream; - - if (textStream != null) - { - AddSubtitleStream(mediaStreams, textStream); - continue; - } - - var graphicsStream = stream as TSGraphicsStream; - - if (graphicsStream != null) - { - AddSubtitleStream(mediaStreams, graphicsStream); - } - } - - outputStream.MediaStreams = mediaStreams; - - outputStream.PlaylistName = playlist.Name; - - if (playlist.StreamClips != null && playlist.StreamClips.Any()) - { - // Get the files in the playlist - outputStream.Files = playlist.StreamClips.Select(i => i.StreamFile.Name).ToList(); - } - - return outputStream; - } - - /// <summary> - /// Adds the video stream. - /// </summary> - /// <param name="streams">The streams.</param> - /// <param name="videoStream">The video stream.</param> - private void AddVideoStream(List<MediaStream> streams, TSVideoStream videoStream) - { - var mediaStream = new MediaStream - { - BitRate = Convert.ToInt32(videoStream.BitRate), - Width = videoStream.Width, - Height = videoStream.Height, - Codec = videoStream.CodecShortName, - IsInterlaced = videoStream.IsInterlaced, - Type = MediaStreamType.Video, - Index = streams.Count - }; - - if (videoStream.FrameRateDenominator > 0) - { - float frameRateEnumerator = videoStream.FrameRateEnumerator; - float frameRateDenominator = videoStream.FrameRateDenominator; - - mediaStream.AverageFrameRate = mediaStream.RealFrameRate = frameRateEnumerator / frameRateDenominator; - } - - streams.Add(mediaStream); - } - - /// <summary> - /// Adds the audio stream. - /// </summary> - /// <param name="streams">The streams.</param> - /// <param name="audioStream">The audio stream.</param> - private void AddAudioStream(List<MediaStream> streams, TSAudioStream audioStream) - { - var stream = new MediaStream - { - Codec = audioStream.CodecShortName, - Language = audioStream.LanguageCode, - Channels = audioStream.ChannelCount, - SampleRate = audioStream.SampleRate, - Type = MediaStreamType.Audio, - Index = streams.Count - }; - - var bitrate = Convert.ToInt32(audioStream.BitRate); - - if (bitrate > 0) - { - stream.BitRate = bitrate; - } - - if (audioStream.LFE > 0) - { - stream.Channels = audioStream.ChannelCount + 1; - } - - streams.Add(stream); - } - - /// <summary> - /// Adds the subtitle stream. - /// </summary> - /// <param name="streams">The streams.</param> - /// <param name="textStream">The text stream.</param> - private void AddSubtitleStream(List<MediaStream> streams, TSTextStream textStream) - { - streams.Add(new MediaStream - { - Language = textStream.LanguageCode, - Codec = textStream.CodecShortName, - Type = MediaStreamType.Subtitle, - Index = streams.Count - }); - } - - /// <summary> - /// Adds the subtitle stream. - /// </summary> - /// <param name="streams">The streams.</param> - /// <param name="textStream">The text stream.</param> - private void AddSubtitleStream(List<MediaStream> streams, TSGraphicsStream textStream) - { - streams.Add(new MediaStream - { - Language = textStream.LanguageCode, - Codec = textStream.CodecShortName, - Type = MediaStreamType.Subtitle, - Index = streams.Count - }); - } - } -} diff --git a/MediaBrowser.MediaEncoding/Configuration/EncodingConfigurationFactory.cs b/MediaBrowser.MediaEncoding/Configuration/EncodingConfigurationFactory.cs deleted file mode 100644 index 16c67655a..000000000 --- a/MediaBrowser.MediaEncoding/Configuration/EncodingConfigurationFactory.cs +++ /dev/null @@ -1,58 +0,0 @@ -using MediaBrowser.Common.Configuration; -using MediaBrowser.Model.Configuration; -using System.Collections.Generic; -using System.IO; - -using MediaBrowser.Controller.IO; -using MediaBrowser.Model.IO; - -namespace MediaBrowser.MediaEncoding.Configuration -{ - public class EncodingConfigurationFactory : IConfigurationFactory - { - private readonly IFileSystem _fileSystem; - - public EncodingConfigurationFactory(IFileSystem fileSystem) - { - _fileSystem = fileSystem; - } - - public IEnumerable<ConfigurationStore> GetConfigurations() - { - return new[] - { - new EncodingConfigurationStore(_fileSystem) - }; - } - } - - public class EncodingConfigurationStore : ConfigurationStore, IValidatingConfiguration - { - private readonly IFileSystem _fileSystem; - - public EncodingConfigurationStore(IFileSystem fileSystem) - { - ConfigurationType = typeof(EncodingOptions); - Key = "encoding"; - _fileSystem = fileSystem; - } - - public void Validate(object oldConfig, object newConfig) - { - var oldEncodingConfig = (EncodingOptions)oldConfig; - var newEncodingConfig = (EncodingOptions)newConfig; - - var newPath = newEncodingConfig.TranscodingTempPath; - - if (!string.IsNullOrWhiteSpace(newPath) - && !string.Equals(oldEncodingConfig.TranscodingTempPath ?? string.Empty, newPath)) - { - // Validate - if (!_fileSystem.DirectoryExists(newPath)) - { - throw new FileNotFoundException(string.Format("{0} does not exist.", newPath)); - } - } - } - } -} diff --git a/MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs deleted file mode 100644 index 566e7946d..000000000 --- a/MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs +++ /dev/null @@ -1,62 +0,0 @@ -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Controller.Session; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Logging; -using System; -using MediaBrowser.Model.Diagnostics; - -namespace MediaBrowser.MediaEncoding.Encoder -{ - public class AudioEncoder : BaseEncoder - { - public AudioEncoder(MediaEncoder mediaEncoder, ILogger logger, IServerConfigurationManager configurationManager, IFileSystem fileSystem, IIsoManager isoManager, ILibraryManager libraryManager, ISessionManager sessionManager, ISubtitleEncoder subtitleEncoder, IMediaSourceManager mediaSourceManager, IProcessFactory processFactory) : base(mediaEncoder, logger, configurationManager, fileSystem, isoManager, libraryManager, sessionManager, subtitleEncoder, mediaSourceManager, processFactory) - { - } - - protected override string GetCommandLineArguments(EncodingJob state) - { - var encodingOptions = GetEncodingOptions(); - - return EncodingHelper.GetProgressiveAudioFullCommandLine(state, encodingOptions, state.OutputFilePath); - } - - protected override string GetOutputFileExtension(EncodingJob state) - { - var ext = base.GetOutputFileExtension(state); - - if (!string.IsNullOrEmpty(ext)) - { - return ext; - } - - var audioCodec = state.Options.AudioCodec; - - if (string.Equals("aac", audioCodec, StringComparison.OrdinalIgnoreCase)) - { - return ".aac"; - } - if (string.Equals("mp3", audioCodec, StringComparison.OrdinalIgnoreCase)) - { - return ".mp3"; - } - if (string.Equals("vorbis", audioCodec, StringComparison.OrdinalIgnoreCase)) - { - return ".ogg"; - } - if (string.Equals("wma", audioCodec, StringComparison.OrdinalIgnoreCase)) - { - return ".wma"; - } - - return null; - } - - protected override bool IsVideoEncoder - { - get { return false; } - } - - } -} diff --git a/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs deleted file mode 100644 index 3672e4e84..000000000 --- a/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs +++ /dev/null @@ -1,375 +0,0 @@ -using MediaBrowser.Common.Configuration; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Controller.Session; -using MediaBrowser.Model.Configuration; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.MediaInfo; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Model.Diagnostics; -using MediaBrowser.Model.Dlna; - -namespace MediaBrowser.MediaEncoding.Encoder -{ - public abstract class BaseEncoder - { - protected readonly MediaEncoder MediaEncoder; - protected readonly ILogger Logger; - protected readonly IServerConfigurationManager ConfigurationManager; - protected readonly IFileSystem FileSystem; - protected readonly IIsoManager IsoManager; - protected readonly ILibraryManager LibraryManager; - protected readonly ISessionManager SessionManager; - protected readonly ISubtitleEncoder SubtitleEncoder; - protected readonly IMediaSourceManager MediaSourceManager; - protected IProcessFactory ProcessFactory; - - protected readonly CultureInfo UsCulture = new CultureInfo("en-US"); - - protected EncodingHelper EncodingHelper; - - protected BaseEncoder(MediaEncoder mediaEncoder, - ILogger logger, - IServerConfigurationManager configurationManager, - IFileSystem fileSystem, - IIsoManager isoManager, - ILibraryManager libraryManager, - ISessionManager sessionManager, - ISubtitleEncoder subtitleEncoder, - IMediaSourceManager mediaSourceManager, IProcessFactory processFactory) - { - MediaEncoder = mediaEncoder; - Logger = logger; - ConfigurationManager = configurationManager; - FileSystem = fileSystem; - IsoManager = isoManager; - LibraryManager = libraryManager; - SessionManager = sessionManager; - SubtitleEncoder = subtitleEncoder; - MediaSourceManager = mediaSourceManager; - ProcessFactory = processFactory; - - EncodingHelper = new EncodingHelper(MediaEncoder, ConfigurationManager, FileSystem, SubtitleEncoder); - } - - public async Task<EncodingJob> Start(EncodingJobOptions options, - IProgress<double> progress, - CancellationToken cancellationToken) - { - var encodingJob = await new EncodingJobFactory(Logger, LibraryManager, MediaSourceManager, ConfigurationManager, MediaEncoder) - .CreateJob(options, EncodingHelper, IsVideoEncoder, progress, cancellationToken).ConfigureAwait(false); - - encodingJob.OutputFilePath = GetOutputFilePath(encodingJob); - FileSystem.CreateDirectory(FileSystem.GetDirectoryName(encodingJob.OutputFilePath)); - - encodingJob.ReadInputAtNativeFramerate = options.ReadInputAtNativeFramerate; - - await AcquireResources(encodingJob, cancellationToken).ConfigureAwait(false); - - var commandLineArgs = GetCommandLineArguments(encodingJob); - - var process = ProcessFactory.Create(new ProcessOptions - { - CreateNoWindow = true, - UseShellExecute = false, - - // Must consume both stdout and stderr or deadlocks may occur - //RedirectStandardOutput = true, - RedirectStandardError = true, - RedirectStandardInput = true, - - FileName = MediaEncoder.EncoderPath, - Arguments = commandLineArgs, - - IsHidden = true, - ErrorDialog = false, - EnableRaisingEvents = true - }); - - var workingDirectory = GetWorkingDirectory(options); - if (!string.IsNullOrWhiteSpace(workingDirectory)) - { - process.StartInfo.WorkingDirectory = workingDirectory; - } - - OnTranscodeBeginning(encodingJob); - - var commandLineLogMessage = process.StartInfo.FileName + " " + process.StartInfo.Arguments; - Logger.Info(commandLineLogMessage); - - var logFilePath = Path.Combine(ConfigurationManager.CommonApplicationPaths.LogDirectoryPath, "transcode-" + Guid.NewGuid() + ".txt"); - FileSystem.CreateDirectory(FileSystem.GetDirectoryName(logFilePath)); - - // FFMpeg writes debug/error info to stderr. This is useful when debugging so let's put it in the log directory. - encodingJob.LogFileStream = FileSystem.GetFileStream(logFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true); - - var commandLineLogMessageBytes = Encoding.UTF8.GetBytes(commandLineLogMessage + Environment.NewLine + Environment.NewLine); - await encodingJob.LogFileStream.WriteAsync(commandLineLogMessageBytes, 0, commandLineLogMessageBytes.Length, cancellationToken).ConfigureAwait(false); - - process.Exited += (sender, args) => OnFfMpegProcessExited(process, encodingJob); - - try - { - process.Start(); - } - catch (Exception ex) - { - Logger.ErrorException("Error starting ffmpeg", ex); - - OnTranscodeFailedToStart(encodingJob.OutputFilePath, encodingJob); - - throw; - } - - cancellationToken.Register(() => Cancel(process, encodingJob)); - - // MUST read both stdout and stderr asynchronously or a deadlock may occurr - //process.BeginOutputReadLine(); - - // Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback - new JobLogger(Logger).StartStreamingLog(encodingJob, process.StandardError.BaseStream, encodingJob.LogFileStream); - - // Wait for the file to exist before proceeeding - while (!FileSystem.FileExists(encodingJob.OutputFilePath) && !encodingJob.HasExited) - { - await Task.Delay(100, cancellationToken).ConfigureAwait(false); - } - - return encodingJob; - } - - private void Cancel(IProcess process, EncodingJob job) - { - Logger.Info("Killing ffmpeg process for {0}", job.OutputFilePath); - - //process.Kill(); - process.StandardInput.WriteLine("q"); - - job.IsCancelled = true; - } - - /// <summary> - /// Processes the exited. - /// </summary> - /// <param name="process">The process.</param> - /// <param name="job">The job.</param> - private void OnFfMpegProcessExited(IProcess process, EncodingJob job) - { - job.HasExited = true; - - Logger.Debug("Disposing stream resources"); - job.Dispose(); - - var isSuccesful = false; - - try - { - var exitCode = process.ExitCode; - Logger.Info("FFMpeg exited with code {0}", exitCode); - - isSuccesful = exitCode == 0; - } - catch - { - Logger.Error("FFMpeg exited with an error."); - } - - if (isSuccesful && !job.IsCancelled) - { - job.TaskCompletionSource.TrySetResult(true); - } - else if (job.IsCancelled) - { - try - { - DeleteFiles(job); - } - catch - { - } - try - { - job.TaskCompletionSource.TrySetException(new OperationCanceledException()); - } - catch - { - } - } - else - { - try - { - DeleteFiles(job); - } - catch - { - } - try - { - job.TaskCompletionSource.TrySetException(new Exception("Encoding failed")); - } - catch - { - } - } - - // This causes on exited to be called twice: - //try - //{ - // // Dispose the process - // process.Dispose(); - //} - //catch (Exception ex) - //{ - // Logger.ErrorException("Error disposing ffmpeg.", ex); - //} - } - - protected virtual void DeleteFiles(EncodingJob job) - { - FileSystem.DeleteFile(job.OutputFilePath); - } - - private void OnTranscodeBeginning(EncodingJob job) - { - job.ReportTranscodingProgress(null, null, null, null, null); - } - - private void OnTranscodeFailedToStart(string path, EncodingJob job) - { - if (!string.IsNullOrWhiteSpace(job.Options.DeviceId)) - { - SessionManager.ClearTranscodingInfo(job.Options.DeviceId); - } - } - - protected abstract bool IsVideoEncoder { get; } - - protected virtual string GetWorkingDirectory(EncodingJobOptions options) - { - return null; - } - - protected EncodingOptions GetEncodingOptions() - { - return ConfigurationManager.GetConfiguration<EncodingOptions>("encoding"); - } - - protected abstract string GetCommandLineArguments(EncodingJob job); - - private string GetOutputFilePath(EncodingJob state) - { - var folder = string.IsNullOrWhiteSpace(state.Options.OutputDirectory) ? - ConfigurationManager.ApplicationPaths.TranscodingTempPath : - state.Options.OutputDirectory; - - var outputFileExtension = GetOutputFileExtension(state); - - var filename = state.Id + (outputFileExtension ?? string.Empty).ToLower(); - return Path.Combine(folder, filename); - } - - protected virtual string GetOutputFileExtension(EncodingJob state) - { - if (!string.IsNullOrWhiteSpace(state.Options.OutputContainer)) - { - return "." + state.Options.OutputContainer; - } - - return null; - } - - /// <summary> - /// Gets the name of the output video codec - /// </summary> - /// <param name="state">The state.</param> - /// <returns>System.String.</returns> - protected string GetVideoDecoder(EncodingJob state) - { - if (string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) - { - return null; - } - - // Only use alternative encoders for video files. - // When using concat with folder rips, if the mfx session fails to initialize, ffmpeg will be stuck retrying and will not exit gracefully - // Since transcoding of folder rips is expiremental anyway, it's not worth adding additional variables such as this. - if (state.VideoType != VideoType.VideoFile) - { - return null; - } - - if (state.VideoStream != null && !string.IsNullOrWhiteSpace(state.VideoStream.Codec)) - { - if (string.Equals(GetEncodingOptions().HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)) - { - switch (state.MediaSource.VideoStream.Codec.ToLower()) - { - case "avc": - case "h264": - if (MediaEncoder.SupportsDecoder("h264_qsv")) - { - // Seeing stalls and failures with decoding. Not worth it compared to encoding. - return "-c:v h264_qsv "; - } - break; - case "mpeg2video": - if (MediaEncoder.SupportsDecoder("mpeg2_qsv")) - { - return "-c:v mpeg2_qsv "; - } - break; - case "vc1": - if (MediaEncoder.SupportsDecoder("vc1_qsv")) - { - return "-c:v vc1_qsv "; - } - break; - } - } - } - - // leave blank so ffmpeg will decide - return null; - } - - private async Task AcquireResources(EncodingJob state, CancellationToken cancellationToken) - { - if (state.VideoType == VideoType.Iso && state.IsoType.HasValue && IsoManager.CanMount(state.MediaPath)) - { - state.IsoMount = await IsoManager.Mount(state.MediaPath, cancellationToken).ConfigureAwait(false); - } - - if (state.MediaSource.RequiresOpening && string.IsNullOrWhiteSpace(state.Options.LiveStreamId)) - { - var liveStreamResponse = await MediaSourceManager.OpenLiveStream(new LiveStreamRequest - { - OpenToken = state.MediaSource.OpenToken - - }, cancellationToken).ConfigureAwait(false); - - EncodingHelper.AttachMediaSourceInfo(state, liveStreamResponse.MediaSource, null); - - if (state.IsVideoRequest) - { - EncodingHelper.TryStreamCopy(state); - } - } - - if (state.MediaSource.BufferMs.HasValue) - { - await Task.Delay(state.MediaSource.BufferMs.Value, cancellationToken).ConfigureAwait(false); - } - } - } -} diff --git a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs deleted file mode 100644 index 59f3576ec..000000000 --- a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs +++ /dev/null @@ -1,219 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Globalization; -using MediaBrowser.Model.Diagnostics; -using MediaBrowser.Model.Logging; - -namespace MediaBrowser.MediaEncoding.Encoder -{ - public class EncoderValidator - { - private readonly ILogger _logger; - private readonly IProcessFactory _processFactory; - - public EncoderValidator(ILogger logger, IProcessFactory processFactory) - { - _logger = logger; - _processFactory = processFactory; - } - - public Tuple<List<string>, List<string>> Validate(string encoderPath) - { - _logger.Info("Validating media encoder at {0}", encoderPath); - - var decoders = GetDecoders(encoderPath); - var encoders = GetEncoders(encoderPath); - - _logger.Info("Encoder validation complete"); - - return new Tuple<List<string>, List<string>>(decoders, encoders); - } - - public bool ValidateVersion(string encoderAppPath, bool logOutput) - { - string output = string.Empty; - try - { - output = GetProcessOutput(encoderAppPath, "-version"); - } - catch (Exception ex) - { - if (logOutput) - { - _logger.ErrorException("Error validating encoder", ex); - } - } - - if (string.IsNullOrWhiteSpace(output)) - { - return false; - } - - _logger.Info("ffmpeg info: {0}", output); - - if (output.IndexOf("Libav developers", StringComparison.OrdinalIgnoreCase) != -1) - { - return false; - } - - output = " " + output + " "; - - for (var i = 2013; i <= 2015; i++) - { - var yearString = i.ToString(CultureInfo.InvariantCulture); - if (output.IndexOf(" " + yearString + " ", StringComparison.OrdinalIgnoreCase) != -1) - { - return false; - } - } - - return true; - } - - private List<string> GetDecoders(string encoderAppPath) - { - string output = string.Empty; - try - { - output = GetProcessOutput(encoderAppPath, "-decoders"); - } - catch (Exception ) - { - //_logger.ErrorException("Error detecting available decoders", ex); - } - - var found = new List<string>(); - var required = new[] - { - "mpeg2video", - "h264_qsv", - "hevc_qsv", - "mpeg2_qsv", - "vc1_qsv", - "h264_cuvid", - "hevc_cuvid", - "dts", - "ac3", - "aac", - "mp3", - "h264", - "hevc" - }; - - foreach (var codec in required) - { - var srch = " " + codec + " "; - - if (output.IndexOf(srch, StringComparison.OrdinalIgnoreCase) != -1) - { - _logger.Info("Decoder available: " + codec); - found.Add(codec); - } - } - - return found; - } - - private List<string> GetEncoders(string encoderAppPath) - { - string output = null; - try - { - output = GetProcessOutput(encoderAppPath, "-encoders"); - } - catch - { - } - - var found = new List<string>(); - var required = new[] - { - "libx264", - "libx265", - "mpeg4", - "msmpeg4", - "libvpx", - "libvpx-vp9", - "aac", - "libmp3lame", - "libopus", - "libvorbis", - "srt", - "h264_nvenc", - "hevc_nvenc", - "h264_qsv", - "hevc_qsv", - "h264_omx", - "hevc_omx", - "h264_vaapi", - "hevc_vaapi", - "ac3" - }; - - output = output ?? string.Empty; - - var index = 0; - - foreach (var codec in required) - { - var srch = " " + codec + " "; - - if (output.IndexOf(srch, StringComparison.OrdinalIgnoreCase) != -1) - { - if (index < required.Length - 1) - { - _logger.Info("Encoder available: " + codec); - } - - found.Add(codec); - } - index++; - } - - return found; - } - - private string GetProcessOutput(string path, string arguments) - { - var process = _processFactory.Create(new ProcessOptions - { - CreateNoWindow = true, - UseShellExecute = false, - FileName = path, - Arguments = arguments, - IsHidden = true, - ErrorDialog = false, - RedirectStandardOutput = true - }); - - _logger.Info("Running {0} {1}", path, arguments); - - using (process) - { - process.Start(); - - try - { - return process.StandardOutput.ReadToEnd(); - } - catch - { - _logger.Info("Killing process {0} {1}", path, arguments); - - // Hate having to do this - try - { - process.Kill(); - } - catch (Exception ex1) - { - _logger.ErrorException("Error killing process", ex1); - } - - throw; - } - } - } - } -} diff --git a/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs b/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs deleted file mode 100644 index d53701feb..000000000 --- a/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs +++ /dev/null @@ -1,197 +0,0 @@ -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Model.Dlna; -using MediaBrowser.Model.Drawing; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.MediaInfo; -using MediaBrowser.Model.Net; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace MediaBrowser.MediaEncoding.Encoder -{ - public class EncodingJob : EncodingJobInfo, IDisposable - { - public bool HasExited { get; internal set; } - public bool IsCancelled { get; internal set; } - - public Stream LogFileStream { get; set; } - public IProgress<double> Progress { get; set; } - public TaskCompletionSource<bool> TaskCompletionSource; - - public EncodingJobOptions Options - { - get { return (EncodingJobOptions) BaseRequest; } - set { BaseRequest = value; } - } - - public string Id { get; set; } - - public string MimeType { get; set; } - public bool EstimateContentLength { get; set; } - public TranscodeSeekInfo TranscodeSeekInfo { get; set; } - public long? EncodingDurationTicks { get; set; } - - public string ItemType { get; set; } - - public string GetMimeType(string outputPath) - { - if (!string.IsNullOrEmpty(MimeType)) - { - return MimeType; - } - - return MimeTypes.GetMimeType(outputPath); - } - - private readonly ILogger _logger; - private readonly IMediaSourceManager _mediaSourceManager; - - public EncodingJob(ILogger logger, IMediaSourceManager mediaSourceManager) : - base(logger, TranscodingJobType.Progressive) - { - _logger = logger; - _mediaSourceManager = mediaSourceManager; - Id = Guid.NewGuid().ToString("N"); - - _logger = logger; - TaskCompletionSource = new TaskCompletionSource<bool>(); - } - - public void Dispose() - { - DisposeLiveStream(); - DisposeLogStream(); - DisposeIsoMount(); - } - - private void DisposeLogStream() - { - if (LogFileStream != null) - { - try - { - LogFileStream.Dispose(); - } - catch (Exception ex) - { - _logger.ErrorException("Error disposing log stream", ex); - } - - LogFileStream = null; - } - } - - private async void DisposeLiveStream() - { - if (MediaSource.RequiresClosing && string.IsNullOrWhiteSpace(Options.LiveStreamId) && !string.IsNullOrWhiteSpace(MediaSource.LiveStreamId)) - { - try - { - await _mediaSourceManager.CloseLiveStream(MediaSource.LiveStreamId).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.ErrorException("Error closing media source", ex); - } - } - } - - public string OutputFilePath { get; set; } - - public string ActualOutputVideoCodec - { - get - { - var codec = OutputVideoCodec; - - if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase)) - { - var stream = VideoStream; - - if (stream != null) - { - return stream.Codec; - } - - return null; - } - - return codec; - } - } - - public string ActualOutputAudioCodec - { - get - { - var codec = OutputAudioCodec; - - if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase)) - { - var stream = AudioStream; - - if (stream != null) - { - return stream.Codec; - } - - return null; - } - - return codec; - } - } - - public override void ReportTranscodingProgress(TimeSpan? transcodingPosition, float? framerate, double? percentComplete, long? bytesTranscoded, int? bitRate) - { - var ticks = transcodingPosition.HasValue ? transcodingPosition.Value.Ticks : (long?)null; - - // job.Framerate = framerate; - - if (!percentComplete.HasValue && ticks.HasValue && RunTimeTicks.HasValue) - { - var pct = ticks.Value / RunTimeTicks.Value; - percentComplete = pct * 100; - } - - if (percentComplete.HasValue) - { - Progress.Report(percentComplete.Value); - } - - // job.TranscodingPositionTicks = ticks; - // job.BytesTranscoded = bytesTranscoded; - - var deviceId = Options.DeviceId; - - if (!string.IsNullOrWhiteSpace(deviceId)) - { - var audioCodec = ActualOutputVideoCodec; - var videoCodec = ActualOutputVideoCodec; - - // SessionManager.ReportTranscodingInfo(deviceId, new TranscodingInfo - // { - // Bitrate = job.TotalOutputBitrate, - // AudioCodec = audioCodec, - // VideoCodec = videoCodec, - // Container = job.Options.OutputContainer, - // Framerate = framerate, - // CompletionPercentage = percentComplete, - // Width = job.OutputWidth, - // Height = job.OutputHeight, - // AudioChannels = job.OutputAudioChannels, - // IsAudioDirect = string.Equals(job.OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase), - // IsVideoDirect = string.Equals(job.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase) - // }); - } - } - } -} diff --git a/MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs b/MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs deleted file mode 100644 index 50d3d254a..000000000 --- a/MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs +++ /dev/null @@ -1,305 +0,0 @@ -using MediaBrowser.Common.Configuration; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Model.Configuration; -using MediaBrowser.Model.Dlna; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.MediaInfo; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace MediaBrowser.MediaEncoding.Encoder -{ - public class EncodingJobFactory - { - private readonly ILogger _logger; - private readonly ILibraryManager _libraryManager; - private readonly IMediaSourceManager _mediaSourceManager; - private readonly IConfigurationManager _config; - private readonly IMediaEncoder _mediaEncoder; - - protected static readonly CultureInfo UsCulture = new CultureInfo("en-US"); - - public EncodingJobFactory(ILogger logger, ILibraryManager libraryManager, IMediaSourceManager mediaSourceManager, IConfigurationManager config, IMediaEncoder mediaEncoder) - { - _logger = logger; - _libraryManager = libraryManager; - _mediaSourceManager = mediaSourceManager; - _config = config; - _mediaEncoder = mediaEncoder; - } - - public async Task<EncodingJob> CreateJob(EncodingJobOptions options, EncodingHelper encodingHelper, bool isVideoRequest, IProgress<double> progress, CancellationToken cancellationToken) - { - var request = options; - - if (string.IsNullOrEmpty(request.AudioCodec)) - { - request.AudioCodec = InferAudioCodec(request.OutputContainer); - } - - var state = new EncodingJob(_logger, _mediaSourceManager) - { - Options = options, - IsVideoRequest = isVideoRequest, - Progress = progress - }; - - if (!string.IsNullOrWhiteSpace(request.VideoCodec)) - { - state.SupportedVideoCodecs = request.VideoCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToList(); - request.VideoCodec = state.SupportedVideoCodecs.FirstOrDefault(); - } - - if (!string.IsNullOrWhiteSpace(request.AudioCodec)) - { - state.SupportedAudioCodecs = request.AudioCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToList(); - request.AudioCodec = state.SupportedAudioCodecs.FirstOrDefault(); - } - - if (!string.IsNullOrWhiteSpace(request.SubtitleCodec)) - { - state.SupportedSubtitleCodecs = request.SubtitleCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToList(); - request.SubtitleCodec = state.SupportedSubtitleCodecs.FirstOrDefault(i => _mediaEncoder.CanEncodeToSubtitleCodec(i)) - ?? state.SupportedSubtitleCodecs.FirstOrDefault(); - } - - var item = _libraryManager.GetItemById(request.ItemId); - state.ItemType = item.GetType().Name; - - state.IsInputVideo = string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase); - - var primaryImage = item.GetImageInfo(ImageType.Primary, 0) ?? - item.Parents.Select(i => i.GetImageInfo(ImageType.Primary, 0)).FirstOrDefault(i => i != null); - - if (primaryImage != null) - { - state.AlbumCoverPath = primaryImage.Path; - } - - var mediaSources = await _mediaSourceManager.GetPlayackMediaSources(request.ItemId, null, false, new[] { MediaType.Audio, MediaType.Video }, cancellationToken).ConfigureAwait(false); - - var mediaSource = string.IsNullOrEmpty(request.MediaSourceId) - ? mediaSources.First() - : mediaSources.First(i => string.Equals(i.Id, request.MediaSourceId)); - - var videoRequest = state.Options; - - encodingHelper.AttachMediaSourceInfo(state, mediaSource, null); - - //var container = Path.GetExtension(state.RequestedUrl); - - //if (string.IsNullOrEmpty(container)) - //{ - // container = request.Static ? - // state.InputContainer : - // (Path.GetExtension(GetOutputFilePath(state)) ?? string.Empty).TrimStart('.'); - //} - - //state.OutputContainer = (container ?? string.Empty).TrimStart('.'); - - state.OutputAudioBitrate = encodingHelper.GetAudioBitrateParam(state.Options, state.AudioStream); - - state.OutputAudioCodec = state.Options.AudioCodec; - - state.OutputAudioChannels = encodingHelper.GetNumAudioChannelsParam(state.Options, state.AudioStream, state.OutputAudioCodec); - - if (videoRequest != null) - { - state.OutputVideoCodec = state.Options.VideoCodec; - state.OutputVideoBitrate = encodingHelper.GetVideoBitrateParamValue(state.Options, state.VideoStream, state.OutputVideoCodec); - - if (state.OutputVideoBitrate.HasValue) - { - var resolution = ResolutionNormalizer.Normalize( - state.VideoStream == null ? (int?)null : state.VideoStream.BitRate, - state.OutputVideoBitrate.Value, - state.VideoStream == null ? null : state.VideoStream.Codec, - state.OutputVideoCodec, - videoRequest.MaxWidth, - videoRequest.MaxHeight); - - videoRequest.MaxWidth = resolution.MaxWidth; - videoRequest.MaxHeight = resolution.MaxHeight; - } - } - - ApplyDeviceProfileSettings(state); - - if (videoRequest != null) - { - encodingHelper.TryStreamCopy(state); - } - - //state.OutputFilePath = GetOutputFilePath(state); - - return state; - } - - protected EncodingOptions GetEncodingOptions() - { - return _config.GetConfiguration<EncodingOptions>("encoding"); - } - - /// <summary> - /// Infers the video codec. - /// </summary> - /// <param name="container">The container.</param> - /// <returns>System.Nullable{VideoCodecs}.</returns> - private static string InferVideoCodec(string container) - { - var ext = "." + (container ?? string.Empty); - - if (string.Equals(ext, ".asf", StringComparison.OrdinalIgnoreCase)) - { - return "wmv"; - } - if (string.Equals(ext, ".webm", StringComparison.OrdinalIgnoreCase)) - { - return "vpx"; - } - if (string.Equals(ext, ".ogg", StringComparison.OrdinalIgnoreCase) || string.Equals(ext, ".ogv", StringComparison.OrdinalIgnoreCase)) - { - return "theora"; - } - if (string.Equals(ext, ".m3u8", StringComparison.OrdinalIgnoreCase) || string.Equals(ext, ".ts", StringComparison.OrdinalIgnoreCase)) - { - return "h264"; - } - - return "copy"; - } - - private string InferAudioCodec(string container) - { - var ext = "." + (container ?? string.Empty); - - if (string.Equals(ext, ".mp3", StringComparison.OrdinalIgnoreCase)) - { - return "mp3"; - } - if (string.Equals(ext, ".aac", StringComparison.OrdinalIgnoreCase)) - { - return "aac"; - } - if (string.Equals(ext, ".wma", StringComparison.OrdinalIgnoreCase)) - { - return "wma"; - } - if (string.Equals(ext, ".ogg", StringComparison.OrdinalIgnoreCase)) - { - return "vorbis"; - } - if (string.Equals(ext, ".oga", StringComparison.OrdinalIgnoreCase)) - { - return "vorbis"; - } - if (string.Equals(ext, ".ogv", StringComparison.OrdinalIgnoreCase)) - { - return "vorbis"; - } - if (string.Equals(ext, ".webm", StringComparison.OrdinalIgnoreCase)) - { - return "vorbis"; - } - if (string.Equals(ext, ".webma", StringComparison.OrdinalIgnoreCase)) - { - return "vorbis"; - } - - return "copy"; - } - - /// <summary> - /// Determines whether the specified stream is H264. - /// </summary> - /// <param name="stream">The stream.</param> - /// <returns><c>true</c> if the specified stream is H264; otherwise, <c>false</c>.</returns> - protected bool IsH264(MediaStream stream) - { - var codec = stream.Codec ?? string.Empty; - - return codec.IndexOf("264", StringComparison.OrdinalIgnoreCase) != -1 || - codec.IndexOf("avc", StringComparison.OrdinalIgnoreCase) != -1; - } - - private static int GetVideoProfileScore(string profile) - { - var list = new List<string> - { - "Constrained Baseline", - "Baseline", - "Extended", - "Main", - "High", - "Progressive High", - "Constrained High" - }; - - return Array.FindIndex(list.ToArray(), t => string.Equals(t, profile, StringComparison.OrdinalIgnoreCase)); - } - - private void ApplyDeviceProfileSettings(EncodingJob state) - { - var profile = state.Options.DeviceProfile; - - if (profile == null) - { - // Don't use settings from the default profile. - // Only use a specific profile if it was requested. - return; - } - - var audioCodec = state.ActualOutputAudioCodec; - - var videoCodec = state.ActualOutputVideoCodec; - var outputContainer = state.Options.OutputContainer; - - var mediaProfile = state.IsVideoRequest ? - profile.GetAudioMediaProfile(outputContainer, audioCodec, state.OutputAudioChannels, state.OutputAudioBitrate, state.OutputAudioSampleRate, state.OutputAudioBitDepth) : - profile.GetVideoMediaProfile(outputContainer, - audioCodec, - videoCodec, - state.OutputWidth, - state.OutputHeight, - state.TargetVideoBitDepth, - state.OutputVideoBitrate, - state.TargetVideoProfile, - state.TargetVideoLevel, - state.TargetFramerate, - state.TargetPacketLength, - state.TargetTimestamp, - state.IsTargetAnamorphic, - state.IsTargetInterlaced, - state.TargetRefFrames, - state.TargetVideoStreamCount, - state.TargetAudioStreamCount, - state.TargetVideoCodecTag, - state.IsTargetAVC); - - if (mediaProfile != null) - { - state.MimeType = mediaProfile.MimeType; - } - - var transcodingProfile = state.IsVideoRequest ? - profile.GetAudioTranscodingProfile(outputContainer, audioCodec) : - profile.GetVideoTranscodingProfile(outputContainer, audioCodec, videoCodec); - - if (transcodingProfile != null) - { - state.EstimateContentLength = transcodingProfile.EstimateContentLength; - state.EnableMpegtsM2TsMode = transcodingProfile.EnableMpegtsM2TsMode; - state.TranscodeSeekInfo = transcodingProfile.TranscodeSeekInfo; - - state.Options.CopyTimestamps = transcodingProfile.CopyTimestamps; - } - } - } -} diff --git a/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs b/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs deleted file mode 100644 index dc3cb5f5e..000000000 --- a/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs +++ /dev/null @@ -1,70 +0,0 @@ -using MediaBrowser.Model.MediaInfo; -using System.Collections.Generic; -using System.Linq; - -namespace MediaBrowser.MediaEncoding.Encoder -{ - public static class EncodingUtils - { - public static string GetInputArgument(List<string> inputFiles, MediaProtocol protocol) - { - if (protocol != MediaProtocol.File) - { - var url = inputFiles.First(); - - return string.Format("\"{0}\"", url); - } - - return GetConcatInputArgument(inputFiles); - } - - /// <summary> - /// Gets the concat input argument. - /// </summary> - /// <param name="inputFiles">The input files.</param> - /// <returns>System.String.</returns> - private static string GetConcatInputArgument(IReadOnlyList<string> inputFiles) - { - // Get all streams - // If there's more than one we'll need to use the concat command - if (inputFiles.Count > 1) - { - var files = string.Join("|", inputFiles.Select(NormalizePath).ToArray()); - - return string.Format("concat:\"{0}\"", files); - } - - // Determine the input path for video files - return GetFileInputArgument(inputFiles[0]); - } - - /// <summary> - /// Gets the file input argument. - /// </summary> - /// <param name="path">The path.</param> - /// <returns>System.String.</returns> - private static string GetFileInputArgument(string path) - { - if (path.IndexOf("://") != -1) - { - return string.Format("\"{0}\"", path); - } - - // Quotes are valid path characters in linux and they need to be escaped here with a leading \ - path = NormalizePath(path); - - return string.Format("file:\"{0}\"", path); - } - - /// <summary> - /// Normalizes the path. - /// </summary> - /// <param name="path">The path.</param> - /// <returns>System.String.</returns> - private static string NormalizePath(string path) - { - // Quotes are valid path characters in linux and they need to be escaped here with a leading \ - return path.Replace("\"", "\\\""); - } - } -} diff --git a/MediaBrowser.MediaEncoding/Encoder/FontConfigLoader.cs b/MediaBrowser.MediaEncoding/Encoder/FontConfigLoader.cs deleted file mode 100644 index e21292cbd..000000000 --- a/MediaBrowser.MediaEncoding/Encoder/FontConfigLoader.cs +++ /dev/null @@ -1,182 +0,0 @@ -using System; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using MediaBrowser.Model.IO; -using MediaBrowser.Common.Configuration; - -using MediaBrowser.Common.Net; -using MediaBrowser.Common.Progress; -using MediaBrowser.Controller.IO; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Net; - -namespace MediaBrowser.MediaEncoding.Encoder -{ - public class FontConfigLoader - { - private readonly IHttpClient _httpClient; - private readonly IApplicationPaths _appPaths; - private readonly ILogger _logger; - private readonly IZipClient _zipClient; - private readonly IFileSystem _fileSystem; - - private readonly string[] _fontUrls = - { - "https://github.com/MediaBrowser/MediaBrowser.Resources/raw/master/ffmpeg/ARIALUNI.7z" - }; - - public FontConfigLoader(IHttpClient httpClient, IApplicationPaths appPaths, ILogger logger, IZipClient zipClient, IFileSystem fileSystem) - { - _httpClient = httpClient; - _appPaths = appPaths; - _logger = logger; - _zipClient = zipClient; - _fileSystem = fileSystem; - } - - /// <summary> - /// Extracts the fonts. - /// </summary> - /// <param name="targetPath">The target path.</param> - /// <returns>Task.</returns> - public async Task DownloadFonts(string targetPath) - { - try - { - var fontsDirectory = Path.Combine(targetPath, "fonts"); - - _fileSystem.CreateDirectory(fontsDirectory); - - const string fontFilename = "ARIALUNI.TTF"; - - var fontFile = Path.Combine(fontsDirectory, fontFilename); - - if (_fileSystem.FileExists(fontFile)) - { - await WriteFontConfigFile(fontsDirectory).ConfigureAwait(false); - } - else - { - // Kick this off, but no need to wait on it - var task = Task.Run(async () => - { - await DownloadFontFile(fontsDirectory, fontFilename, new SimpleProgress<double>()).ConfigureAwait(false); - - await WriteFontConfigFile(fontsDirectory).ConfigureAwait(false); - }); - } - } - catch (HttpException ex) - { - // Don't let the server crash because of this - _logger.ErrorException("Error downloading ffmpeg font files", ex); - } - catch (Exception ex) - { - // Don't let the server crash because of this - _logger.ErrorException("Error writing ffmpeg font files", ex); - } - } - - /// <summary> - /// Downloads the font file. - /// </summary> - /// <param name="fontsDirectory">The fonts directory.</param> - /// <param name="fontFilename">The font filename.</param> - /// <returns>Task.</returns> - private async Task DownloadFontFile(string fontsDirectory, string fontFilename, IProgress<double> progress) - { - var existingFile = _fileSystem - .GetFilePaths(_appPaths.ProgramDataPath, true) - .FirstOrDefault(i => string.Equals(fontFilename, Path.GetFileName(i), StringComparison.OrdinalIgnoreCase)); - - if (existingFile != null) - { - try - { - _fileSystem.CopyFile(existingFile, Path.Combine(fontsDirectory, fontFilename), true); - return; - } - catch (IOException ex) - { - // Log this, but don't let it fail the operation - _logger.ErrorException("Error copying file", ex); - } - } - - string tempFile = null; - - foreach (var url in _fontUrls) - { - progress.Report(0); - - try - { - tempFile = await _httpClient.GetTempFile(new HttpRequestOptions - { - Url = url, - Progress = progress - - }).ConfigureAwait(false); - - break; - } - catch (Exception ex) - { - // The core can function without the font file, so handle this - _logger.ErrorException("Failed to download ffmpeg font file from {0}", ex, url); - } - } - - if (string.IsNullOrEmpty(tempFile)) - { - return; - } - - Extract7zArchive(tempFile, fontsDirectory); - - try - { - _fileSystem.DeleteFile(tempFile); - } - catch (IOException ex) - { - // Log this, but don't let it fail the operation - _logger.ErrorException("Error deleting temp file {0}", ex, tempFile); - } - } - private void Extract7zArchive(string archivePath, string targetPath) - { - _logger.Info("Extracting {0} to {1}", archivePath, targetPath); - - _zipClient.ExtractAllFrom7z(archivePath, targetPath, true); - } - - /// <summary> - /// Writes the font config file. - /// </summary> - /// <param name="fontsDirectory">The fonts directory.</param> - /// <returns>Task.</returns> - private async Task WriteFontConfigFile(string fontsDirectory) - { - const string fontConfigFilename = "fonts.conf"; - var fontConfigFile = Path.Combine(fontsDirectory, fontConfigFilename); - - if (!_fileSystem.FileExists(fontConfigFile)) - { - var contents = string.Format("<?xml version=\"1.0\"?><fontconfig><dir>{0}</dir><alias><family>Arial</family><prefer>Arial Unicode MS</prefer></alias></fontconfig>", fontsDirectory); - - var bytes = Encoding.UTF8.GetBytes(contents); - - using (var fileStream = _fileSystem.GetFileStream(fontConfigFile, FileOpenMode.Create, FileAccessMode.Write, - FileShareMode.Read, true)) - { - await fileStream.WriteAsync(bytes, 0, bytes.Length); - } - } - } - } -} diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs deleted file mode 100644 index f416ea417..000000000 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ /dev/null @@ -1,1089 +0,0 @@ -using MediaBrowser.Controller.Channels; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.LiveTv; -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Controller.Session; -using MediaBrowser.MediaEncoding.Probing; -using MediaBrowser.Model.Dlna; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.MediaInfo; -using MediaBrowser.Model.Serialization; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Model.Configuration; -using MediaBrowser.Common.Configuration; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Net; -using MediaBrowser.Model.Diagnostics; -using MediaBrowser.Model.System; - -namespace MediaBrowser.MediaEncoding.Encoder -{ - /// <summary> - /// Class MediaEncoder - /// </summary> - public class MediaEncoder : IMediaEncoder, IDisposable - { - /// <summary> - /// The _logger - /// </summary> - private readonly ILogger _logger; - - /// <summary> - /// Gets the json serializer. - /// </summary> - /// <value>The json serializer.</value> - private readonly IJsonSerializer _jsonSerializer; - - /// <summary> - /// The _thumbnail resource pool - /// </summary> - private readonly SemaphoreSlim _thumbnailResourcePool = new SemaphoreSlim(1, 1); - - public string FFMpegPath { get; private set; } - - public string FFProbePath { get; private set; } - - protected readonly IServerConfigurationManager ConfigurationManager; - protected readonly IFileSystem FileSystem; - protected readonly ILiveTvManager LiveTvManager; - protected readonly IIsoManager IsoManager; - protected readonly ILibraryManager LibraryManager; - protected readonly IChannelManager ChannelManager; - protected readonly ISessionManager SessionManager; - protected readonly Func<ISubtitleEncoder> SubtitleEncoder; - protected readonly Func<IMediaSourceManager> MediaSourceManager; - private readonly IHttpClient _httpClient; - private readonly IZipClient _zipClient; - private readonly IProcessFactory _processFactory; - private readonly IMemoryStreamFactory _memoryStreamProvider; - - private readonly List<ProcessWrapper> _runningProcesses = new List<ProcessWrapper>(); - private readonly bool _hasExternalEncoder; - private readonly string _originalFFMpegPath; - private readonly string _originalFFProbePath; - private readonly int DefaultImageExtractionTimeoutMs; - private readonly bool EnableEncoderFontFile; - - private readonly IEnvironmentInfo _environmentInfo; - - public MediaEncoder(ILogger logger, IJsonSerializer jsonSerializer, string ffMpegPath, string ffProbePath, bool hasExternalEncoder, IServerConfigurationManager configurationManager, IFileSystem fileSystem, ILiveTvManager liveTvManager, IIsoManager isoManager, ILibraryManager libraryManager, IChannelManager channelManager, ISessionManager sessionManager, Func<ISubtitleEncoder> subtitleEncoder, Func<IMediaSourceManager> mediaSourceManager, IHttpClient httpClient, IZipClient zipClient, IMemoryStreamFactory memoryStreamProvider, IProcessFactory processFactory, - int defaultImageExtractionTimeoutMs, - bool enableEncoderFontFile, IEnvironmentInfo environmentInfo) - { - _logger = logger; - _jsonSerializer = jsonSerializer; - ConfigurationManager = configurationManager; - FileSystem = fileSystem; - LiveTvManager = liveTvManager; - IsoManager = isoManager; - LibraryManager = libraryManager; - ChannelManager = channelManager; - SessionManager = sessionManager; - SubtitleEncoder = subtitleEncoder; - MediaSourceManager = mediaSourceManager; - _httpClient = httpClient; - _zipClient = zipClient; - _memoryStreamProvider = memoryStreamProvider; - _processFactory = processFactory; - DefaultImageExtractionTimeoutMs = defaultImageExtractionTimeoutMs; - EnableEncoderFontFile = enableEncoderFontFile; - _environmentInfo = environmentInfo; - FFProbePath = ffProbePath; - FFMpegPath = ffMpegPath; - _originalFFProbePath = ffProbePath; - _originalFFMpegPath = ffMpegPath; - - _hasExternalEncoder = hasExternalEncoder; - - SetEnvironmentVariable(); - } - - private readonly object _logLock = new object(); - public void SetLogFilename(string name) - { - lock (_logLock) - { - try - { - _environmentInfo.SetProcessEnvironmentVariable("FFREPORT", "file=" + name + ":level=32"); - } - catch (Exception ex) - { - _logger.ErrorException("Error setting FFREPORT environment variable", ex); - } - } - } - - public void ClearLogFilename() - { - lock (_logLock) - { - try - { - _environmentInfo.SetProcessEnvironmentVariable("FFREPORT", null); - } - catch (Exception ex) - { - //_logger.ErrorException("Error setting FFREPORT environment variable", ex); - } - } - } - - private void SetEnvironmentVariable() - { - try - { - //_environmentInfo.SetProcessEnvironmentVariable("FFREPORT", "file=program-YYYYMMDD-HHMMSS.txt:level=32"); - } - catch (Exception ex) - { - _logger.ErrorException("Error setting FFREPORT environment variable", ex); - } - try - { - //_environmentInfo.SetUserEnvironmentVariable("FFREPORT", "file=program-YYYYMMDD-HHMMSS.txt:level=32"); - } - catch (Exception ex) - { - _logger.ErrorException("Error setting FFREPORT environment variable", ex); - } - } - - public string EncoderLocationType - { - get - { - if (_hasExternalEncoder) - { - return "External"; - } - - if (string.IsNullOrWhiteSpace(FFMpegPath)) - { - return null; - } - - if (IsSystemInstalledPath(FFMpegPath)) - { - return "System"; - } - - return "Custom"; - } - } - - private bool IsSystemInstalledPath(string path) - { - if (path.IndexOf("/", StringComparison.Ordinal) == -1 && path.IndexOf("\\", StringComparison.Ordinal) == -1) - { - return true; - } - - return false; - } - - public async Task Init() - { - InitPaths(); - - if (!string.IsNullOrWhiteSpace(FFMpegPath)) - { - var result = new EncoderValidator(_logger, _processFactory).Validate(FFMpegPath); - - SetAvailableDecoders(result.Item1); - SetAvailableEncoders(result.Item2); - - if (EnableEncoderFontFile) - { - var directory = FileSystem.GetDirectoryName(FFMpegPath); - - if (!string.IsNullOrWhiteSpace(directory) && FileSystem.ContainsSubPath(ConfigurationManager.ApplicationPaths.ProgramDataPath, directory)) - { - await new FontConfigLoader(_httpClient, ConfigurationManager.ApplicationPaths, _logger, _zipClient, FileSystem).DownloadFonts(directory).ConfigureAwait(false); - } - } - } - } - - private void InitPaths() - { - ConfigureEncoderPaths(); - - if (_hasExternalEncoder) - { - LogPaths(); - return; - } - - // If the path was passed in, save it into config now. - var encodingOptions = GetEncodingOptions(); - var appPath = encodingOptions.EncoderAppPath; - - var valueToSave = FFMpegPath; - - if (!string.IsNullOrWhiteSpace(valueToSave)) - { - // if using system variable, don't save this. - if (IsSystemInstalledPath(valueToSave) || _hasExternalEncoder) - { - valueToSave = null; - } - } - - if (!string.Equals(valueToSave, appPath, StringComparison.Ordinal)) - { - encodingOptions.EncoderAppPath = valueToSave; - ConfigurationManager.SaveConfiguration("encoding", encodingOptions); - } - } - - public void UpdateEncoderPath(string path, string pathType) - { - if (_hasExternalEncoder) - { - return; - } - - _logger.Info("Attempting to update encoder path to {0}. pathType: {1}", path ?? string.Empty, pathType ?? string.Empty); - - Tuple<string, string> newPaths; - - if (string.Equals(pathType, "system", StringComparison.OrdinalIgnoreCase)) - { - path = "ffmpeg"; - - newPaths = TestForInstalledVersions(); - } - else if (string.Equals(pathType, "custom", StringComparison.OrdinalIgnoreCase)) - { - if (string.IsNullOrWhiteSpace(path)) - { - throw new ArgumentNullException("path"); - } - - if (!FileSystem.FileExists(path) && !FileSystem.DirectoryExists(path)) - { - throw new ResourceNotFoundException(); - } - newPaths = GetEncoderPaths(path); - } - else - { - throw new ArgumentException("Unexpected pathType value"); - } - - if (string.IsNullOrWhiteSpace(newPaths.Item1)) - { - throw new ResourceNotFoundException("ffmpeg not found"); - } - if (string.IsNullOrWhiteSpace(newPaths.Item2)) - { - throw new ResourceNotFoundException("ffprobe not found"); - } - - path = newPaths.Item1; - - if (!ValidateVersion(path, true)) - { - throw new ResourceNotFoundException("ffmpeg version 3.0 or greater is required."); - } - - var config = GetEncodingOptions(); - config.EncoderAppPath = path; - ConfigurationManager.SaveConfiguration("encoding", config); - - Init(); - } - - private bool ValidateVersion(string path, bool logOutput) - { - return new EncoderValidator(_logger, _processFactory).ValidateVersion(path, logOutput); - } - - private void ConfigureEncoderPaths() - { - if (_hasExternalEncoder) - { - return; - } - - var appPath = GetEncodingOptions().EncoderAppPath; - - if (string.IsNullOrWhiteSpace(appPath)) - { - appPath = Path.Combine(ConfigurationManager.ApplicationPaths.ProgramDataPath, "ffmpeg"); - } - - var newPaths = GetEncoderPaths(appPath); - if (string.IsNullOrWhiteSpace(newPaths.Item1) || string.IsNullOrWhiteSpace(newPaths.Item2) || IsSystemInstalledPath(appPath)) - { - newPaths = TestForInstalledVersions(); - } - - if (!string.IsNullOrWhiteSpace(newPaths.Item1) && !string.IsNullOrWhiteSpace(newPaths.Item2)) - { - FFMpegPath = newPaths.Item1; - FFProbePath = newPaths.Item2; - } - - LogPaths(); - } - - private Tuple<string, string> GetEncoderPaths(string configuredPath) - { - var appPath = configuredPath; - - if (!string.IsNullOrWhiteSpace(appPath)) - { - if (FileSystem.DirectoryExists(appPath)) - { - return GetPathsFromDirectory(appPath); - } - - if (FileSystem.FileExists(appPath)) - { - return new Tuple<string, string>(appPath, GetProbePathFromEncoderPath(appPath)); - } - } - - return new Tuple<string, string>(null, null); - } - - private Tuple<string, string> TestForInstalledVersions() - { - string encoderPath = null; - string probePath = null; - - if (_hasExternalEncoder && ValidateVersion(_originalFFMpegPath, true)) - { - encoderPath = _originalFFMpegPath; - probePath = _originalFFProbePath; - } - - if (string.IsNullOrWhiteSpace(encoderPath)) - { - if (ValidateVersion("ffmpeg", true) && ValidateVersion("ffprobe", false)) - { - encoderPath = "ffmpeg"; - probePath = "ffprobe"; - } - } - - return new Tuple<string, string>(encoderPath, probePath); - } - - private Tuple<string, string> GetPathsFromDirectory(string path) - { - // Since we can't predict the file extension, first try directly within the folder - // If that doesn't pan out, then do a recursive search - var files = FileSystem.GetFilePaths(path); - - var excludeExtensions = new[] { ".c" }; - - var ffmpegPath = files.FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffmpeg", StringComparison.OrdinalIgnoreCase) && !excludeExtensions.Contains(Path.GetExtension(i) ?? string.Empty)); - var ffprobePath = files.FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffprobe", StringComparison.OrdinalIgnoreCase) && !excludeExtensions.Contains(Path.GetExtension(i) ?? string.Empty)); - - if (string.IsNullOrWhiteSpace(ffmpegPath) || !FileSystem.FileExists(ffmpegPath)) - { - files = FileSystem.GetFilePaths(path, true); - - ffmpegPath = files.FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffmpeg", StringComparison.OrdinalIgnoreCase) && !excludeExtensions.Contains(Path.GetExtension(i) ?? string.Empty)); - - if (!string.IsNullOrWhiteSpace(ffmpegPath)) - { - ffprobePath = GetProbePathFromEncoderPath(ffmpegPath); - } - } - - return new Tuple<string, string>(ffmpegPath, ffprobePath); - } - - private string GetProbePathFromEncoderPath(string appPath) - { - return FileSystem.GetFilePaths(FileSystem.GetDirectoryName(appPath)) - .FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffprobe", StringComparison.OrdinalIgnoreCase)); - } - - private void LogPaths() - { - _logger.Info("FFMpeg: {0}", FFMpegPath ?? "not found"); - _logger.Info("FFProbe: {0}", FFProbePath ?? "not found"); - } - - private EncodingOptions GetEncodingOptions() - { - return ConfigurationManager.GetConfiguration<EncodingOptions>("encoding"); - } - - private List<string> _encoders = new List<string>(); - public void SetAvailableEncoders(List<string> list) - { - _encoders = list.ToList(); - //_logger.Info("Supported encoders: {0}", string.Join(",", list.ToArray())); - } - - private List<string> _decoders = new List<string>(); - public void SetAvailableDecoders(List<string> list) - { - _decoders = list.ToList(); - //_logger.Info("Supported decoders: {0}", string.Join(",", list.ToArray())); - } - - public bool SupportsEncoder(string encoder) - { - return _encoders.Contains(encoder, StringComparer.OrdinalIgnoreCase); - } - - public bool SupportsDecoder(string decoder) - { - return _decoders.Contains(decoder, StringComparer.OrdinalIgnoreCase); - } - - public bool CanEncodeToAudioCodec(string codec) - { - if (string.Equals(codec, "opus", StringComparison.OrdinalIgnoreCase)) - { - codec = "libopus"; - } - else if (string.Equals(codec, "mp3", StringComparison.OrdinalIgnoreCase)) - { - codec = "libmp3lame"; - } - - return SupportsEncoder(codec); - } - - public bool CanEncodeToSubtitleCodec(string codec) - { - // TODO - return true; - } - - /// <summary> - /// Gets the encoder path. - /// </summary> - /// <value>The encoder path.</value> - public string EncoderPath - { - get { return FFMpegPath; } - } - - /// <summary> - /// Gets the media info. - /// </summary> - /// <param name="request">The request.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> - public Task<MediaInfo> GetMediaInfo(MediaInfoRequest request, CancellationToken cancellationToken) - { - var extractChapters = request.MediaType == DlnaProfileType.Video && request.ExtractChapters; - - var inputFiles = MediaEncoderHelpers.GetInputArgument(FileSystem, request.InputPath, request.Protocol, request.MountedIso, request.PlayableStreamFileNames); - - var probeSize = EncodingHelper.GetProbeSizeArgument(inputFiles.Length); - string analyzeDuration; - - if (request.AnalyzeDurationMs > 0) - { - analyzeDuration = "-analyzeduration " + - (request.AnalyzeDurationMs * 1000).ToString(CultureInfo.InvariantCulture); - } - else - { - analyzeDuration = EncodingHelper.GetAnalyzeDurationArgument(inputFiles.Length); - } - - probeSize = probeSize + " " + analyzeDuration; - probeSize = probeSize.Trim(); - - var forceEnableLogging = request.Protocol != MediaProtocol.File; - - return GetMediaInfoInternal(GetInputArgument(inputFiles, request.Protocol), request.InputPath, request.Protocol, extractChapters, - probeSize, request.MediaType == DlnaProfileType.Audio, request.VideoType, forceEnableLogging, cancellationToken); - } - - /// <summary> - /// Gets the input argument. - /// </summary> - /// <param name="inputFiles">The input files.</param> - /// <param name="protocol">The protocol.</param> - /// <returns>System.String.</returns> - /// <exception cref="System.ArgumentException">Unrecognized InputType</exception> - public string GetInputArgument(string[] inputFiles, MediaProtocol protocol) - { - return EncodingUtils.GetInputArgument(inputFiles.ToList(), protocol); - } - - /// <summary> - /// Gets the media info internal. - /// </summary> - /// <returns>Task{MediaInfoResult}.</returns> - private async Task<MediaInfo> GetMediaInfoInternal(string inputPath, - string primaryPath, - MediaProtocol protocol, - bool extractChapters, - string probeSizeArgument, - bool isAudio, - VideoType videoType, - bool forceEnableLogging, - CancellationToken cancellationToken) - { - var args = extractChapters - ? "{0} -i {1} -threads 0 -v info -print_format json -show_streams -show_chapters -show_format" - : "{0} -i {1} -threads 0 -v info -print_format json -show_streams -show_format"; - - var process = _processFactory.Create(new ProcessOptions - { - CreateNoWindow = true, - UseShellExecute = false, - - // Must consume both or ffmpeg may hang due to deadlocks. See comments below. - RedirectStandardOutput = true, - FileName = FFProbePath, - Arguments = string.Format(args, probeSizeArgument, inputPath).Trim(), - - IsHidden = true, - ErrorDialog = false, - EnableRaisingEvents = true - }); - - if (forceEnableLogging) - { - _logger.Info("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments); - } - else - { - _logger.Debug("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments); - } - - using (var processWrapper = new ProcessWrapper(process, this, _logger)) - { - StartProcess(processWrapper); - - try - { - //process.BeginErrorReadLine(); - - var result = _jsonSerializer.DeserializeFromStream<InternalMediaInfoResult>(process.StandardOutput.BaseStream); - - if (result == null || (result.streams == null && result.format == null)) - { - throw new Exception("ffprobe failed - streams and format are both null."); - } - - if (result.streams != null) - { - // Normalize aspect ratio if invalid - foreach (var stream in result.streams) - { - if (string.Equals(stream.display_aspect_ratio, "0:1", StringComparison.OrdinalIgnoreCase)) - { - stream.display_aspect_ratio = string.Empty; - } - if (string.Equals(stream.sample_aspect_ratio, "0:1", StringComparison.OrdinalIgnoreCase)) - { - stream.sample_aspect_ratio = string.Empty; - } - } - } - - return new ProbeResultNormalizer(_logger, FileSystem, _memoryStreamProvider).GetMediaInfo(result, videoType, isAudio, primaryPath, protocol); - } - catch - { - StopProcess(processWrapper, 100); - - throw; - } - } - } - - /// <summary> - /// The us culture - /// </summary> - protected readonly CultureInfo UsCulture = new CultureInfo("en-US"); - - public Task<string> ExtractAudioImage(string path, int? imageStreamIndex, CancellationToken cancellationToken) - { - return ExtractImage(new[] { path }, null, imageStreamIndex, MediaProtocol.File, true, null, null, cancellationToken); - } - - public Task<string> ExtractVideoImage(string[] inputFiles, string container, MediaProtocol protocol, Video3DFormat? threedFormat, TimeSpan? offset, CancellationToken cancellationToken) - { - return ExtractImage(inputFiles, container, null, protocol, false, threedFormat, offset, cancellationToken); - } - - public Task<string> ExtractVideoImage(string[] inputFiles, string container, MediaProtocol protocol, int? imageStreamIndex, CancellationToken cancellationToken) - { - return ExtractImage(inputFiles, container, imageStreamIndex, protocol, false, null, null, cancellationToken); - } - - private async Task<string> ExtractImage(string[] inputFiles, string container, int? imageStreamIndex, MediaProtocol protocol, bool isAudio, - Video3DFormat? threedFormat, TimeSpan? offset, CancellationToken cancellationToken) - { - var inputArgument = GetInputArgument(inputFiles, protocol); - - if (isAudio) - { - if (imageStreamIndex.HasValue && imageStreamIndex.Value > 0) - { - // It seems for audio files we need to subtract 1 (for the audio stream??) - imageStreamIndex = imageStreamIndex.Value - 1; - } - } - else - { - try - { - return await ExtractImageInternal(inputArgument, container, imageStreamIndex, protocol, threedFormat, offset, true, cancellationToken).ConfigureAwait(false); - } - catch (ArgumentException) - { - throw; - } - catch - { - _logger.Error("I-frame image extraction failed, will attempt standard way. Input: {0}", inputArgument); - } - } - - return await ExtractImageInternal(inputArgument, container, imageStreamIndex, protocol, threedFormat, offset, false, cancellationToken).ConfigureAwait(false); - } - - private async Task<string> ExtractImageInternal(string inputPath, string container, int? imageStreamIndex, MediaProtocol protocol, Video3DFormat? threedFormat, TimeSpan? offset, bool useIFrame, CancellationToken cancellationToken) - { - if (string.IsNullOrEmpty(inputPath)) - { - throw new ArgumentNullException("inputPath"); - } - - var tempExtractPath = Path.Combine(ConfigurationManager.ApplicationPaths.TempDirectory, Guid.NewGuid() + ".jpg"); - FileSystem.CreateDirectory(FileSystem.GetDirectoryName(tempExtractPath)); - - // apply some filters to thumbnail extracted below (below) crop any black lines that we made and get the correct ar then scale to width 600. - // This filter chain may have adverse effects on recorded tv thumbnails if ar changes during presentation ex. commercials @ diff ar - var vf = "scale=600:trunc(600/dar/2)*2"; - - if (threedFormat.HasValue) - { - switch (threedFormat.Value) - { - case Video3DFormat.HalfSideBySide: - vf = "crop=iw/2:ih:0:0,scale=(iw*2):ih,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1,scale=600:trunc(600/dar/2)*2"; - // hsbs crop width in half,scale to correct size, set the display aspect,crop out any black bars we may have made the scale width to 600. Work out the correct height based on the display aspect it will maintain the aspect where -1 in this case (3d) may not. - break; - case Video3DFormat.FullSideBySide: - vf = "crop=iw/2:ih:0:0,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1,scale=600:trunc(600/dar/2)*2"; - //fsbs crop width in half,set the display aspect,crop out any black bars we may have made the scale width to 600. - break; - case Video3DFormat.HalfTopAndBottom: - vf = "crop=iw:ih/2:0:0,scale=(iw*2):ih),setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1,scale=600:trunc(600/dar/2)*2"; - //htab crop heigh in half,scale to correct size, set the display aspect,crop out any black bars we may have made the scale width to 600 - break; - case Video3DFormat.FullTopAndBottom: - vf = "crop=iw:ih/2:0:0,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1,scale=600:trunc(600/dar/2)*2"; - // ftab crop heigt in half, set the display aspect,crop out any black bars we may have made the scale width to 600 - break; - default: - break; - } - } - - var mapArg = imageStreamIndex.HasValue ? (" -map 0:v:" + imageStreamIndex.Value.ToString(CultureInfo.InvariantCulture)) : string.Empty; - - var enableThumbnail = !new List<string> { "wtv" }.Contains(container ?? string.Empty, StringComparer.OrdinalIgnoreCase); - // Use ffmpeg to sample 100 (we can drop this if required using thumbnail=50 for 50 frames) frames and pick the best thumbnail. Have a fall back just in case. - var thumbnail = enableThumbnail ? ",thumbnail=24" : string.Empty; - - var args = useIFrame ? string.Format("-i {0}{3} -threads 0 -v quiet -vframes 1 -vf \"{2}{4}\" -f image2 \"{1}\"", inputPath, tempExtractPath, vf, mapArg, thumbnail) : - string.Format("-i {0}{3} -threads 0 -v quiet -vframes 1 -vf \"{2}\" -f image2 \"{1}\"", inputPath, tempExtractPath, vf, mapArg); - - var probeSizeArgument = EncodingHelper.GetProbeSizeArgument(1); - var analyzeDurationArgument = EncodingHelper.GetAnalyzeDurationArgument(1); - - if (!string.IsNullOrWhiteSpace(probeSizeArgument)) - { - args = probeSizeArgument + " " + args; - } - - if (!string.IsNullOrWhiteSpace(analyzeDurationArgument)) - { - args = analyzeDurationArgument + " " + args; - } - - if (offset.HasValue) - { - args = string.Format("-ss {0} ", GetTimeParameter(offset.Value)) + args; - } - - var process = _processFactory.Create(new ProcessOptions - { - CreateNoWindow = true, - UseShellExecute = false, - FileName = FFMpegPath, - Arguments = args, - IsHidden = true, - ErrorDialog = false - }); - - _logger.Debug("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments); - - using (var processWrapper = new ProcessWrapper(process, this, _logger)) - { - bool ranToCompletion; - - StartProcess(processWrapper); - - var timeoutMs = ConfigurationManager.Configuration.ImageExtractionTimeoutMs; - if (timeoutMs <= 0) - { - timeoutMs = DefaultImageExtractionTimeoutMs; - } - - ranToCompletion = await process.WaitForExitAsync(timeoutMs).ConfigureAwait(false); - - if (!ranToCompletion) - { - StopProcess(processWrapper, 1000); - } - - var exitCode = ranToCompletion ? processWrapper.ExitCode ?? 0 : -1; - var file = FileSystem.GetFileInfo(tempExtractPath); - - if (exitCode == -1 || !file.Exists || file.Length == 0) - { - var msg = string.Format("ffmpeg image extraction failed for {0}", inputPath); - - _logger.Error(msg); - - throw new Exception(msg); - } - - return tempExtractPath; - } - } - - public string GetTimeParameter(long ticks) - { - var time = TimeSpan.FromTicks(ticks); - - return GetTimeParameter(time); - } - - public string GetTimeParameter(TimeSpan time) - { - return time.ToString(@"hh\:mm\:ss\.fff", UsCulture); - } - - public async Task ExtractVideoImagesOnInterval(string[] inputFiles, - MediaProtocol protocol, - Video3DFormat? threedFormat, - TimeSpan interval, - string targetDirectory, - string filenamePrefix, - int? maxWidth, - CancellationToken cancellationToken) - { - var resourcePool = _thumbnailResourcePool; - - var inputArgument = GetInputArgument(inputFiles, protocol); - - var vf = "fps=fps=1/" + interval.TotalSeconds.ToString(UsCulture); - - if (maxWidth.HasValue) - { - var maxWidthParam = maxWidth.Value.ToString(UsCulture); - - vf += string.Format(",scale=min(iw\\,{0}):trunc(ow/dar/2)*2", maxWidthParam); - } - - FileSystem.CreateDirectory(targetDirectory); - var outputPath = Path.Combine(targetDirectory, filenamePrefix + "%05d.jpg"); - - var args = string.Format("-i {0} -threads 0 -v quiet -vf \"{2}\" -f image2 \"{1}\"", inputArgument, outputPath, vf); - - var probeSizeArgument = EncodingHelper.GetProbeSizeArgument(1); - var analyzeDurationArgument = EncodingHelper.GetAnalyzeDurationArgument(1); - - if (!string.IsNullOrWhiteSpace(probeSizeArgument)) - { - args = probeSizeArgument + " " + args; - } - - if (!string.IsNullOrWhiteSpace(analyzeDurationArgument)) - { - args = analyzeDurationArgument + " " + args; - } - - var process = _processFactory.Create(new ProcessOptions - { - CreateNoWindow = true, - UseShellExecute = false, - FileName = FFMpegPath, - Arguments = args, - IsHidden = true, - ErrorDialog = false - }); - - _logger.Info(process.StartInfo.FileName + " " + process.StartInfo.Arguments); - - await resourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); - - bool ranToCompletion = false; - - using (var processWrapper = new ProcessWrapper(process, this, _logger)) - { - try - { - StartProcess(processWrapper); - - // Need to give ffmpeg enough time to make all the thumbnails, which could be a while, - // but we still need to detect if the process hangs. - // Making the assumption that as long as new jpegs are showing up, everything is good. - - bool isResponsive = true; - int lastCount = 0; - - while (isResponsive) - { - if (await process.WaitForExitAsync(30000).ConfigureAwait(false)) - { - ranToCompletion = true; - break; - } - - cancellationToken.ThrowIfCancellationRequested(); - - var jpegCount = FileSystem.GetFilePaths(targetDirectory) - .Count(i => string.Equals(Path.GetExtension(i), ".jpg", StringComparison.OrdinalIgnoreCase)); - - isResponsive = (jpegCount > lastCount); - lastCount = jpegCount; - } - - if (!ranToCompletion) - { - StopProcess(processWrapper, 1000); - } - } - finally - { - resourcePool.Release(); - } - - var exitCode = ranToCompletion ? processWrapper.ExitCode ?? 0 : -1; - - if (exitCode == -1) - { - var msg = string.Format("ffmpeg image extraction failed for {0}", inputArgument); - - _logger.Error(msg); - - throw new Exception(msg); - } - } - } - - public async Task<string> EncodeAudio(EncodingJobOptions options, - IProgress<double> progress, - CancellationToken cancellationToken) - { - var job = await new AudioEncoder(this, - _logger, - ConfigurationManager, - FileSystem, - IsoManager, - LibraryManager, - SessionManager, - SubtitleEncoder(), - MediaSourceManager(), - _processFactory) - .Start(options, progress, cancellationToken).ConfigureAwait(false); - - await job.TaskCompletionSource.Task.ConfigureAwait(false); - - return job.OutputFilePath; - } - - public async Task<string> EncodeVideo(EncodingJobOptions options, - IProgress<double> progress, - CancellationToken cancellationToken) - { - var job = await new VideoEncoder(this, - _logger, - ConfigurationManager, - FileSystem, - IsoManager, - LibraryManager, - SessionManager, - SubtitleEncoder(), - MediaSourceManager(), - _processFactory) - .Start(options, progress, cancellationToken).ConfigureAwait(false); - - await job.TaskCompletionSource.Task.ConfigureAwait(false); - - return job.OutputFilePath; - } - - private void StartProcess(ProcessWrapper process) - { - process.Process.Start(); - - lock (_runningProcesses) - { - _runningProcesses.Add(process); - } - } - private void StopProcess(ProcessWrapper process, int waitTimeMs) - { - try - { - if (process.Process.WaitForExit(waitTimeMs)) - { - return; - } - } - catch (Exception ex) - { - _logger.Error("Error in WaitForExit", ex); - } - - try - { - _logger.Info("Killing ffmpeg process"); - - process.Process.Kill(); - } - catch (Exception ex) - { - _logger.ErrorException("Error killing process", ex); - } - } - - private void StopProcesses() - { - List<ProcessWrapper> proceses; - lock (_runningProcesses) - { - proceses = _runningProcesses.ToList(); - _runningProcesses.Clear(); - } - - foreach (var process in proceses) - { - if (!process.HasExited) - { - StopProcess(process, 500); - } - } - } - - public string EscapeSubtitleFilterPath(string path) - { - // https://ffmpeg.org/ffmpeg-filters.html#Notes-on-filtergraph-escaping - // We need to double escape - - return path.Replace('\\', '/').Replace(":", "\\:").Replace("'", "'\\\\\\''"); - } - - /// <summary> - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// </summary> - public void Dispose() - { - Dispose(true); - } - - /// <summary> - /// Releases unmanaged and - optionally - managed resources. - /// </summary> - /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> - protected virtual void Dispose(bool dispose) - { - if (dispose) - { - StopProcesses(); - } - } - - private class ProcessWrapper : IDisposable - { - public readonly IProcess Process; - public bool HasExited; - public int? ExitCode; - private readonly MediaEncoder _mediaEncoder; - private readonly ILogger _logger; - - public ProcessWrapper(IProcess process, MediaEncoder mediaEncoder, ILogger logger) - { - Process = process; - _mediaEncoder = mediaEncoder; - _logger = logger; - Process.Exited += Process_Exited; - } - - void Process_Exited(object sender, EventArgs e) - { - var process = (IProcess)sender; - - HasExited = true; - - try - { - ExitCode = process.ExitCode; - } - catch - { - } - - DisposeProcess(process); - } - - private void DisposeProcess(IProcess process) - { - lock (_mediaEncoder._runningProcesses) - { - _mediaEncoder._runningProcesses.Remove(this); - } - - try - { - process.Dispose(); - } - catch - { - } - } - - private bool _disposed; - private readonly object _syncLock = new object(); - public void Dispose() - { - lock (_syncLock) - { - if (!_disposed) - { - if (Process != null) - { - Process.Exited -= Process_Exited; - DisposeProcess(Process); - } - } - - _disposed = true; - } - } - } - } -}
\ No newline at end of file diff --git a/MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs deleted file mode 100644 index 96c126923..000000000 --- a/MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs +++ /dev/null @@ -1,66 +0,0 @@ -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Controller.Session; -using MediaBrowser.Model.Dlna; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Logging; -using System; -using System.IO; -using System.Threading.Tasks; -using MediaBrowser.Model.Diagnostics; - -namespace MediaBrowser.MediaEncoding.Encoder -{ - public class VideoEncoder : BaseEncoder - { - public VideoEncoder(MediaEncoder mediaEncoder, ILogger logger, IServerConfigurationManager configurationManager, IFileSystem fileSystem, IIsoManager isoManager, ILibraryManager libraryManager, ISessionManager sessionManager, ISubtitleEncoder subtitleEncoder, IMediaSourceManager mediaSourceManager, IProcessFactory processFactory) : base(mediaEncoder, logger, configurationManager, fileSystem, isoManager, libraryManager, sessionManager, subtitleEncoder, mediaSourceManager, processFactory) - { - } - - protected override string GetCommandLineArguments(EncodingJob state) - { - // Get the output codec name - var encodingOptions = GetEncodingOptions(); - - return EncodingHelper.GetProgressiveVideoFullCommandLine(state, encodingOptions, state.OutputFilePath, "superfast"); - } - - protected override string GetOutputFileExtension(EncodingJob state) - { - var ext = base.GetOutputFileExtension(state); - - if (!string.IsNullOrEmpty(ext)) - { - return ext; - } - - var videoCodec = state.Options.VideoCodec; - - if (string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase)) - { - return ".ts"; - } - if (string.Equals(videoCodec, "theora", StringComparison.OrdinalIgnoreCase)) - { - return ".ogv"; - } - if (string.Equals(videoCodec, "vpx", StringComparison.OrdinalIgnoreCase)) - { - return ".webm"; - } - if (string.Equals(videoCodec, "wmv", StringComparison.OrdinalIgnoreCase)) - { - return ".asf"; - } - - return null; - } - - protected override bool IsVideoEncoder - { - get { return true; } - } - - } -}
\ No newline at end of file diff --git a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj deleted file mode 100644 index 142e1c627..000000000 --- a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj +++ /dev/null @@ -1,100 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> - <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> - <PropertyGroup> - <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> - <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> - <ProjectGuid>{0BD82FA6-EB8A-4452-8AF5-74F9C3849451}</ProjectGuid> - <OutputType>Library</OutputType> - <AppDesignerFolder>Properties</AppDesignerFolder> - <RootNamespace>MediaBrowser.MediaEncoding</RootNamespace> - <AssemblyName>MediaBrowser.MediaEncoding</AssemblyName> - <FileAlignment>512</FileAlignment> - <ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> - <TargetFrameworkProfile>Profile7</TargetFrameworkProfile> - <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> - </PropertyGroup> - <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> - <DebugSymbols>true</DebugSymbols> - <DebugType>full</DebugType> - <Optimize>false</Optimize> - <OutputPath>bin\Debug\</OutputPath> - <DefineConstants>DEBUG;TRACE</DefineConstants> - <ErrorReport>prompt</ErrorReport> - <WarningLevel>4</WarningLevel> - </PropertyGroup> - <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> - <DebugType>none</DebugType> - <Optimize>true</Optimize> - <OutputPath>bin\Release\</OutputPath> - <DefineConstants>TRACE</DefineConstants> - <ErrorReport>prompt</ErrorReport> - <WarningLevel>4</WarningLevel> - </PropertyGroup> - <ItemGroup> - <Compile Include="..\SharedVersion.cs"> - <Link>Properties\SharedVersion.cs</Link> - </Compile> - <Compile Include="BdInfo\BdInfoExaminer.cs" /> - <Compile Include="Configuration\EncodingConfigurationFactory.cs" /> - <Compile Include="Encoder\AudioEncoder.cs" /> - <Compile Include="Encoder\BaseEncoder.cs" /> - <Compile Include="Encoder\EncodingJob.cs" /> - <Compile Include="Encoder\EncodingJobFactory.cs" /> - <Compile Include="Encoder\EncodingUtils.cs" /> - <Compile Include="Encoder\EncoderValidator.cs" /> - <Compile Include="Encoder\FontConfigLoader.cs" /> - <Compile Include="Encoder\MediaEncoder.cs" /> - <Compile Include="Encoder\VideoEncoder.cs" /> - <Compile Include="Probing\FFProbeHelpers.cs" /> - <Compile Include="Probing\InternalMediaInfoResult.cs" /> - <Compile Include="Probing\ProbeResultNormalizer.cs" /> - <Compile Include="Properties\AssemblyInfo.cs" /> - <Compile Include="Subtitles\ConfigurationExtension.cs" /> - <Compile Include="Subtitles\ISubtitleParser.cs" /> - <Compile Include="Subtitles\ISubtitleWriter.cs" /> - <Compile Include="Subtitles\JsonWriter.cs" /> - <Compile Include="Subtitles\OpenSubtitleDownloader.cs" /> - <Compile Include="Subtitles\ParserValues.cs" /> - <Compile Include="Subtitles\SrtParser.cs" /> - <Compile Include="Subtitles\SrtWriter.cs" /> - <Compile Include="Subtitles\AssParser.cs" /> - <Compile Include="Subtitles\SsaParser.cs" /> - <Compile Include="Subtitles\SubtitleEncoder.cs" /> - <Compile Include="Subtitles\TtmlWriter.cs" /> - <Compile Include="Subtitles\VttWriter.cs" /> - </ItemGroup> - <ItemGroup> - <ProjectReference Include="..\BDInfo\BDInfo.csproj"> - <Project>{88ae38df-19d7-406f-a6a9-09527719a21e}</Project> - <Name>BDInfo</Name> - </ProjectReference> - <ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj"> - <Project>{9142eefa-7570-41e1-bfcc-468bb571af2f}</Project> - <Name>MediaBrowser.Common</Name> - </ProjectReference> - <ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj"> - <Project>{17e1f4e6-8abd-4fe5-9ecf-43d4b6087ba2}</Project> - <Name>MediaBrowser.Controller</Name> - </ProjectReference> - <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj"> - <Project>{7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b}</Project> - <Name>MediaBrowser.Model</Name> - </ProjectReference> - <ProjectReference Include="..\OpenSubtitlesHandler\OpenSubtitlesHandler.csproj"> - <Project>{4a4402d4-e910-443b-b8fc-2c18286a2ca0}</Project> - <Name>OpenSubtitlesHandler</Name> - </ProjectReference> - </ItemGroup> - <ItemGroup> - <None Include="packages.config" /> - </ItemGroup> - <Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" /> - <!-- To modify your build process, add your task inside one of the targets below and uncomment it. - Other similar extension points exist, see Microsoft.Common.targets. - <Target Name="BeforeBuild"> - </Target> - <Target Name="AfterBuild"> - </Target> - --> -</Project>
\ No newline at end of file diff --git a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.nuget.targets b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.nuget.targets deleted file mode 100644 index e69ce0e64..000000000 --- a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.nuget.targets +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="utf-8" standalone="no"?> -<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> - <Target Name="EmitMSBuildWarning" BeforeTargets="Build"> - <Warning Text="Packages containing MSBuild targets and props files cannot be fully installed in projects targeting multiple frameworks. The MSBuild targets and props files have been ignored." /> - </Target> -</Project>
\ No newline at end of file diff --git a/MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs b/MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs deleted file mode 100644 index 396c85e21..000000000 --- a/MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs +++ /dev/null @@ -1,117 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace MediaBrowser.MediaEncoding.Probing -{ - public static class FFProbeHelpers - { - /// <summary> - /// Normalizes the FF probe result. - /// </summary> - /// <param name="result">The result.</param> - public static void NormalizeFFProbeResult(InternalMediaInfoResult result) - { - if (result == null) - { - throw new ArgumentNullException("result"); - } - - if (result.format != null && result.format.tags != null) - { - result.format.tags = ConvertDictionaryToCaseInSensitive(result.format.tags); - } - - if (result.streams != null) - { - // Convert all dictionaries to case insensitive - foreach (var stream in result.streams) - { - if (stream.tags != null) - { - stream.tags = ConvertDictionaryToCaseInSensitive(stream.tags); - } - - if (stream.disposition != null) - { - stream.disposition = ConvertDictionaryToCaseInSensitive(stream.disposition); - } - } - } - } - - /// <summary> - /// Gets a string from an FFProbeResult tags dictionary - /// </summary> - /// <param name="tags">The tags.</param> - /// <param name="key">The key.</param> - /// <returns>System.String.</returns> - public static string GetDictionaryValue(Dictionary<string, string> tags, string key) - { - if (tags == null) - { - return null; - } - - string val; - - tags.TryGetValue(key, out val); - return val; - } - - /// <summary> - /// Gets an int from an FFProbeResult tags dictionary - /// </summary> - /// <param name="tags">The tags.</param> - /// <param name="key">The key.</param> - /// <returns>System.Nullable{System.Int32}.</returns> - public static int? GetDictionaryNumericValue(Dictionary<string, string> tags, string key) - { - var val = GetDictionaryValue(tags, key); - - if (!string.IsNullOrEmpty(val)) - { - int i; - - if (int.TryParse(val, out i)) - { - return i; - } - } - - return null; - } - - /// <summary> - /// Gets a DateTime from an FFProbeResult tags dictionary - /// </summary> - /// <param name="tags">The tags.</param> - /// <param name="key">The key.</param> - /// <returns>System.Nullable{DateTime}.</returns> - public static DateTime? GetDictionaryDateTime(Dictionary<string, string> tags, string key) - { - var val = GetDictionaryValue(tags, key); - - if (!string.IsNullOrEmpty(val)) - { - DateTime i; - - if (DateTime.TryParse(val, out i)) - { - return i.ToUniversalTime(); - } - } - - return null; - } - - /// <summary> - /// Converts a dictionary to case insensitive - /// </summary> - /// <param name="dict">The dict.</param> - /// <returns>Dictionary{System.StringSystem.String}.</returns> - private static Dictionary<string, string> ConvertDictionaryToCaseInSensitive(Dictionary<string, string> dict) - { - return new Dictionary<string, string>(dict, StringComparer.OrdinalIgnoreCase); - } - } -} diff --git a/MediaBrowser.MediaEncoding/Probing/InternalMediaInfoResult.cs b/MediaBrowser.MediaEncoding/Probing/InternalMediaInfoResult.cs deleted file mode 100644 index eef273250..000000000 --- a/MediaBrowser.MediaEncoding/Probing/InternalMediaInfoResult.cs +++ /dev/null @@ -1,341 +0,0 @@ -using System.Collections.Generic; - -namespace MediaBrowser.MediaEncoding.Probing -{ - /// <summary> - /// Class MediaInfoResult - /// </summary> - public class InternalMediaInfoResult - { - /// <summary> - /// Gets or sets the streams. - /// </summary> - /// <value>The streams.</value> - public MediaStreamInfo[] streams { get; set; } - - /// <summary> - /// Gets or sets the format. - /// </summary> - /// <value>The format.</value> - public MediaFormatInfo format { get; set; } - - /// <summary> - /// Gets or sets the chapters. - /// </summary> - /// <value>The chapters.</value> - public MediaChapter[] Chapters { get; set; } - } - - public class MediaChapter - { - public int id { get; set; } - public string time_base { get; set; } - public long start { get; set; } - public string start_time { get; set; } - public long end { get; set; } - public string end_time { get; set; } - public Dictionary<string, string> tags { get; set; } - } - - /// <summary> - /// Represents a stream within the output - /// </summary> - public class MediaStreamInfo - { - /// <summary> - /// Gets or sets the index. - /// </summary> - /// <value>The index.</value> - public int index { get; set; } - - /// <summary> - /// Gets or sets the profile. - /// </summary> - /// <value>The profile.</value> - public string profile { get; set; } - - /// <summary> - /// Gets or sets the codec_name. - /// </summary> - /// <value>The codec_name.</value> - public string codec_name { get; set; } - - /// <summary> - /// Gets or sets the codec_long_name. - /// </summary> - /// <value>The codec_long_name.</value> - public string codec_long_name { get; set; } - - /// <summary> - /// Gets or sets the codec_type. - /// </summary> - /// <value>The codec_type.</value> - public string codec_type { get; set; } - - /// <summary> - /// Gets or sets the sample_rate. - /// </summary> - /// <value>The sample_rate.</value> - public string sample_rate { get; set; } - - /// <summary> - /// Gets or sets the channels. - /// </summary> - /// <value>The channels.</value> - public int channels { get; set; } - - /// <summary> - /// Gets or sets the channel_layout. - /// </summary> - /// <value>The channel_layout.</value> - public string channel_layout { get; set; } - - /// <summary> - /// Gets or sets the avg_frame_rate. - /// </summary> - /// <value>The avg_frame_rate.</value> - public string avg_frame_rate { get; set; } - - /// <summary> - /// Gets or sets the duration. - /// </summary> - /// <value>The duration.</value> - public string duration { get; set; } - - /// <summary> - /// Gets or sets the bit_rate. - /// </summary> - /// <value>The bit_rate.</value> - public string bit_rate { get; set; } - - /// <summary> - /// Gets or sets the width. - /// </summary> - /// <value>The width.</value> - public int width { get; set; } - - /// <summary> - /// Gets or sets the refs. - /// </summary> - /// <value>The refs.</value> - public int refs { get; set; } - - /// <summary> - /// Gets or sets the height. - /// </summary> - /// <value>The height.</value> - public int height { get; set; } - - /// <summary> - /// Gets or sets the display_aspect_ratio. - /// </summary> - /// <value>The display_aspect_ratio.</value> - public string display_aspect_ratio { get; set; } - - /// <summary> - /// Gets or sets the tags. - /// </summary> - /// <value>The tags.</value> - public Dictionary<string, string> tags { get; set; } - - /// <summary> - /// Gets or sets the bits_per_sample. - /// </summary> - /// <value>The bits_per_sample.</value> - public int bits_per_sample { get; set; } - - /// <summary> - /// Gets or sets the bits_per_raw_sample. - /// </summary> - /// <value>The bits_per_raw_sample.</value> - public int bits_per_raw_sample { get; set; } - - /// <summary> - /// Gets or sets the r_frame_rate. - /// </summary> - /// <value>The r_frame_rate.</value> - public string r_frame_rate { get; set; } - - /// <summary> - /// Gets or sets the has_b_frames. - /// </summary> - /// <value>The has_b_frames.</value> - public int has_b_frames { get; set; } - - /// <summary> - /// Gets or sets the sample_aspect_ratio. - /// </summary> - /// <value>The sample_aspect_ratio.</value> - public string sample_aspect_ratio { get; set; } - - /// <summary> - /// Gets or sets the pix_fmt. - /// </summary> - /// <value>The pix_fmt.</value> - public string pix_fmt { get; set; } - - /// <summary> - /// Gets or sets the level. - /// </summary> - /// <value>The level.</value> - public int level { get; set; } - - /// <summary> - /// Gets or sets the time_base. - /// </summary> - /// <value>The time_base.</value> - public string time_base { get; set; } - - /// <summary> - /// Gets or sets the start_time. - /// </summary> - /// <value>The start_time.</value> - public string start_time { get; set; } - - /// <summary> - /// Gets or sets the codec_time_base. - /// </summary> - /// <value>The codec_time_base.</value> - public string codec_time_base { get; set; } - - /// <summary> - /// Gets or sets the codec_tag. - /// </summary> - /// <value>The codec_tag.</value> - public string codec_tag { get; set; } - - /// <summary> - /// Gets or sets the codec_tag_string. - /// </summary> - /// <value>The codec_tag_string.</value> - public string codec_tag_string { get; set; } - - /// <summary> - /// Gets or sets the sample_fmt. - /// </summary> - /// <value>The sample_fmt.</value> - public string sample_fmt { get; set; } - - /// <summary> - /// Gets or sets the dmix_mode. - /// </summary> - /// <value>The dmix_mode.</value> - public string dmix_mode { get; set; } - - /// <summary> - /// Gets or sets the start_pts. - /// </summary> - /// <value>The start_pts.</value> - public string start_pts { get; set; } - - /// <summary> - /// Gets or sets the is_avc. - /// </summary> - /// <value>The is_avc.</value> - public string is_avc { get; set; } - - /// <summary> - /// Gets or sets the nal_length_size. - /// </summary> - /// <value>The nal_length_size.</value> - public string nal_length_size { get; set; } - - /// <summary> - /// Gets or sets the ltrt_cmixlev. - /// </summary> - /// <value>The ltrt_cmixlev.</value> - public string ltrt_cmixlev { get; set; } - - /// <summary> - /// Gets or sets the ltrt_surmixlev. - /// </summary> - /// <value>The ltrt_surmixlev.</value> - public string ltrt_surmixlev { get; set; } - - /// <summary> - /// Gets or sets the loro_cmixlev. - /// </summary> - /// <value>The loro_cmixlev.</value> - public string loro_cmixlev { get; set; } - - /// <summary> - /// Gets or sets the loro_surmixlev. - /// </summary> - /// <value>The loro_surmixlev.</value> - public string loro_surmixlev { get; set; } - - public string field_order { get; set; } - - /// <summary> - /// Gets or sets the disposition. - /// </summary> - /// <value>The disposition.</value> - public Dictionary<string, string> disposition { get; set; } - } - - /// <summary> - /// Class MediaFormat - /// </summary> - public class MediaFormatInfo - { - /// <summary> - /// Gets or sets the filename. - /// </summary> - /// <value>The filename.</value> - public string filename { get; set; } - - /// <summary> - /// Gets or sets the nb_streams. - /// </summary> - /// <value>The nb_streams.</value> - public int nb_streams { get; set; } - - /// <summary> - /// Gets or sets the format_name. - /// </summary> - /// <value>The format_name.</value> - public string format_name { get; set; } - - /// <summary> - /// Gets or sets the format_long_name. - /// </summary> - /// <value>The format_long_name.</value> - public string format_long_name { get; set; } - - /// <summary> - /// Gets or sets the start_time. - /// </summary> - /// <value>The start_time.</value> - public string start_time { get; set; } - - /// <summary> - /// Gets or sets the duration. - /// </summary> - /// <value>The duration.</value> - public string duration { get; set; } - - /// <summary> - /// Gets or sets the size. - /// </summary> - /// <value>The size.</value> - public string size { get; set; } - - /// <summary> - /// Gets or sets the bit_rate. - /// </summary> - /// <value>The bit_rate.</value> - public string bit_rate { get; set; } - - /// <summary> - /// Gets or sets the probe_score. - /// </summary> - /// <value>The probe_score.</value> - public int probe_score { get; set; } - - /// <summary> - /// Gets or sets the tags. - /// </summary> - /// <value>The tags.</value> - public Dictionary<string, string> tags { get; set; } - } -} diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs deleted file mode 100644 index 1e91a8198..000000000 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ /dev/null @@ -1,1372 +0,0 @@ -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Extensions; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Text; -using System.Xml; -using MediaBrowser.Model.IO; - -using MediaBrowser.Controller.IO; -using MediaBrowser.Controller.Library; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.MediaInfo; - -namespace MediaBrowser.MediaEncoding.Probing -{ - public class ProbeResultNormalizer - { - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - private readonly ILogger _logger; - private readonly IFileSystem _fileSystem; - private readonly IMemoryStreamFactory _memoryStreamProvider; - - public ProbeResultNormalizer(ILogger logger, IFileSystem fileSystem, IMemoryStreamFactory memoryStreamProvider) - { - _logger = logger; - _fileSystem = fileSystem; - _memoryStreamProvider = memoryStreamProvider; - } - - public MediaInfo GetMediaInfo(InternalMediaInfoResult data, VideoType videoType, bool isAudio, string path, MediaProtocol protocol) - { - var info = new MediaInfo - { - Path = path, - Protocol = protocol - }; - - FFProbeHelpers.NormalizeFFProbeResult(data); - SetSize(data, info); - - var internalStreams = data.streams ?? new MediaStreamInfo[] { }; - - info.MediaStreams = internalStreams.Select(s => GetMediaStream(isAudio, s, data.format)) - .Where(i => i != null) - // Drop subtitle streams if we don't know the codec because it will just cause failures if we don't know how to handle them - .Where(i => i.Type != MediaStreamType.Subtitle || !string.IsNullOrWhiteSpace(i.Codec)) - .ToList(); - - if (data.format != null) - { - info.Container = data.format.format_name; - - if (!string.IsNullOrEmpty(data.format.bit_rate)) - { - int value; - if (int.TryParse(data.format.bit_rate, NumberStyles.Any, _usCulture, out value)) - { - info.Bitrate = value; - } - } - } - - var tags = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); - var tagStreamType = isAudio ? "audio" : "video"; - - if (data.streams != null) - { - var tagStream = data.streams.FirstOrDefault(i => string.Equals(i.codec_type, tagStreamType, StringComparison.OrdinalIgnoreCase)); - - if (tagStream != null && tagStream.tags != null) - { - foreach (var pair in tagStream.tags) - { - tags[pair.Key] = pair.Value; - } - } - } - - if (data.format != null && data.format.tags != null) - { - foreach (var pair in data.format.tags) - { - tags[pair.Key] = pair.Value; - } - } - - FetchGenres(info, tags); - var overview = FFProbeHelpers.GetDictionaryValue(tags, "synopsis"); - - if (string.IsNullOrWhiteSpace(overview)) - { - overview = FFProbeHelpers.GetDictionaryValue(tags, "description"); - } - if (string.IsNullOrWhiteSpace(overview)) - { - overview = FFProbeHelpers.GetDictionaryValue(tags, "desc"); - } - - if (!string.IsNullOrWhiteSpace(overview)) - { - info.Overview = overview; - } - - var title = FFProbeHelpers.GetDictionaryValue(tags, "title"); - if (!string.IsNullOrWhiteSpace(title)) - { - info.Name = title; - } - - info.ProductionYear = FFProbeHelpers.GetDictionaryNumericValue(tags, "date"); - - // Several different forms of retaildate - info.PremiereDate = FFProbeHelpers.GetDictionaryDateTime(tags, "retaildate") ?? - FFProbeHelpers.GetDictionaryDateTime(tags, "retail date") ?? - FFProbeHelpers.GetDictionaryDateTime(tags, "retail_date") ?? - FFProbeHelpers.GetDictionaryDateTime(tags, "date"); - - if (isAudio) - { - SetAudioRuntimeTicks(data, info); - - // tags are normally located under data.format, but we've seen some cases with ogg where they're part of the info stream - // so let's create a combined list of both - - SetAudioInfoFromTags(info, tags); - } - else - { - FetchStudios(info, tags, "copyright"); - - var iTunEXTC = FFProbeHelpers.GetDictionaryValue(tags, "iTunEXTC"); - if (!string.IsNullOrWhiteSpace(iTunEXTC)) - { - var parts = iTunEXTC.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries); - // Example - // mpaa|G|100|For crude humor - if (parts.Length > 1) - { - info.OfficialRating = parts[1]; - - if (parts.Length > 3) - { - info.OfficialRatingDescription = parts[3]; - } - } - } - - var itunesXml = FFProbeHelpers.GetDictionaryValue(tags, "iTunMOVI"); - if (!string.IsNullOrWhiteSpace(itunesXml)) - { - FetchFromItunesInfo(itunesXml, info); - } - - if (data.format != null && !string.IsNullOrEmpty(data.format.duration)) - { - info.RunTimeTicks = TimeSpan.FromSeconds(double.Parse(data.format.duration, _usCulture)).Ticks; - } - - FetchWtvInfo(info, data); - - if (data.Chapters != null) - { - info.Chapters = data.Chapters.Select(GetChapterInfo).ToList(); - } - - ExtractTimestamp(info); - - var stereoMode = GetDictionaryValue(tags, "stereo_mode"); - if (string.Equals(stereoMode, "left_right", StringComparison.OrdinalIgnoreCase)) - { - info.Video3DFormat = Video3DFormat.FullSideBySide; - } - - foreach (var mediaStream in info.MediaStreams) - { - if (mediaStream.Type == MediaStreamType.Audio && !mediaStream.BitRate.HasValue) - { - mediaStream.BitRate = GetEstimatedAudioBitrate(mediaStream.Codec, mediaStream.Channels); - } - } - - var videoStreamsBitrate = info.MediaStreams.Where(i => i.Type == MediaStreamType.Video).Select(i => i.BitRate ?? 0).Sum(); - // If ffprobe reported the container bitrate as being the same as the video stream bitrate, then it's wrong - if (videoStreamsBitrate == (info.Bitrate ?? 0)) - { - info.InferTotalBitrate(true); - } - } - - return info; - } - - private int? GetEstimatedAudioBitrate(string codec, int? channels) - { - if (!channels.HasValue) - { - return null; - } - - var channelsValue = channels.Value; - - if (string.Equals(codec, "aac", StringComparison.OrdinalIgnoreCase) || - string.Equals(codec, "mp3", StringComparison.OrdinalIgnoreCase)) - { - if (channelsValue <= 2) - { - return 192000; - } - - if (channelsValue >= 5) - { - return 320000; - } - } - - return null; - } - - private void FetchFromItunesInfo(string xml, MediaInfo info) - { - // Make things simpler and strip out the dtd - var plistIndex = xml.IndexOf("<plist", StringComparison.OrdinalIgnoreCase); - - if (plistIndex != -1) - { - xml = xml.Substring(plistIndex); - } - - xml = "<?xml version=\"1.0\"?>" + xml; - - // <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>cast</key>\n\t<array>\n\t\t<dict>\n\t\t\t<key>name</key>\n\t\t\t<string>Blender Foundation</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>name</key>\n\t\t\t<string>Janus Bager Kristensen</string>\n\t\t</dict>\n\t</array>\n\t<key>directors</key>\n\t<array>\n\t\t<dict>\n\t\t\t<key>name</key>\n\t\t\t<string>Sacha Goedegebure</string>\n\t\t</dict>\n\t</array>\n\t<key>studio</key>\n\t<string>Blender Foundation</string>\n</dict>\n</plist>\n - using (var stream = _memoryStreamProvider.CreateNew(Encoding.UTF8.GetBytes(xml))) - { - using (var streamReader = new StreamReader(stream)) - { - try - { - // Use XmlReader for best performance - using (var reader = XmlReader.Create(streamReader)) - { - reader.MoveToContent(); - reader.Read(); - - // Loop through each element - while (!reader.EOF && reader.ReadState == ReadState.Interactive) - { - if (reader.NodeType == XmlNodeType.Element) - { - switch (reader.Name) - { - case "dict": - if (reader.IsEmptyElement) - { - reader.Read(); - continue; - } - using (var subtree = reader.ReadSubtree()) - { - ReadFromDictNode(subtree, info); - } - break; - default: - reader.Skip(); - break; - } - } - else - { - reader.Read(); - } - } - } - } - catch (XmlException) - { - // I've seen probe examples where the iTunMOVI value is just "<" - // So we should not allow this to fail the entire probing operation - } - } - } - } - - private void ReadFromDictNode(XmlReader reader, MediaInfo info) - { - string currentKey = null; - List<NameValuePair> pairs = new List<NameValuePair>(); - - reader.MoveToContent(); - reader.Read(); - - // Loop through each element - while (!reader.EOF && reader.ReadState == ReadState.Interactive) - { - if (reader.NodeType == XmlNodeType.Element) - { - switch (reader.Name) - { - case "key": - if (!string.IsNullOrWhiteSpace(currentKey)) - { - ProcessPairs(currentKey, pairs, info); - } - currentKey = reader.ReadElementContentAsString(); - pairs = new List<NameValuePair>(); - break; - case "string": - var value = reader.ReadElementContentAsString(); - if (!string.IsNullOrWhiteSpace(value)) - { - pairs.Add(new NameValuePair - { - Name = value, - Value = value - }); - } - break; - case "array": - if (reader.IsEmptyElement) - { - reader.Read(); - continue; - } - using (var subtree = reader.ReadSubtree()) - { - if (!string.IsNullOrWhiteSpace(currentKey)) - { - pairs.AddRange(ReadValueArray(subtree)); - } - } - break; - default: - reader.Skip(); - break; - } - } - else - { - reader.Read(); - } - } - } - - private List<NameValuePair> ReadValueArray(XmlReader reader) - { - - List<NameValuePair> pairs = new List<NameValuePair>(); - - reader.MoveToContent(); - reader.Read(); - - // Loop through each element - while (!reader.EOF && reader.ReadState == ReadState.Interactive) - { - if (reader.NodeType == XmlNodeType.Element) - { - switch (reader.Name) - { - case "dict": - - if (reader.IsEmptyElement) - { - reader.Read(); - continue; - } - using (var subtree = reader.ReadSubtree()) - { - var dict = GetNameValuePair(subtree); - if (dict != null) - { - pairs.Add(dict); - } - } - break; - default: - reader.Skip(); - break; - } - } - else - { - reader.Read(); - } - } - - return pairs; - } - - private void ProcessPairs(string key, List<NameValuePair> pairs, MediaInfo info) - { - if (string.Equals(key, "studio", StringComparison.OrdinalIgnoreCase)) - { - foreach (var pair in pairs) - { - info.Studios.Add(pair.Value); - } - - info.Studios = info.Studios - .Where(i => !string.IsNullOrWhiteSpace(i)) - .Distinct(StringComparer.OrdinalIgnoreCase) - .ToList(); - - } - else if (string.Equals(key, "screenwriters", StringComparison.OrdinalIgnoreCase)) - { - foreach (var pair in pairs) - { - info.People.Add(new BaseItemPerson - { - Name = pair.Value, - Type = PersonType.Writer - }); - } - } - else if (string.Equals(key, "producers", StringComparison.OrdinalIgnoreCase)) - { - foreach (var pair in pairs) - { - info.People.Add(new BaseItemPerson - { - Name = pair.Value, - Type = PersonType.Producer - }); - } - } - else if (string.Equals(key, "directors", StringComparison.OrdinalIgnoreCase)) - { - foreach (var pair in pairs) - { - info.People.Add(new BaseItemPerson - { - Name = pair.Value, - Type = PersonType.Director - }); - } - } - } - - private NameValuePair GetNameValuePair(XmlReader reader) - { - string name = null; - string value = null; - - reader.MoveToContent(); - reader.Read(); - - // Loop through each element - while (!reader.EOF && reader.ReadState == ReadState.Interactive) - { - if (reader.NodeType == XmlNodeType.Element) - { - switch (reader.Name) - { - case "key": - name = reader.ReadElementContentAsString(); - break; - case "string": - value = reader.ReadElementContentAsString(); - break; - default: - reader.Skip(); - break; - } - } - else - { - reader.Read(); - } - } - - if (string.IsNullOrWhiteSpace(name) || - string.IsNullOrWhiteSpace(value)) - { - return null; - } - - return new NameValuePair - { - Name = name, - Value = value - }; - } - - private string NormalizeSubtitleCodec(string codec) - { - if (string.Equals(codec, "dvb_subtitle", StringComparison.OrdinalIgnoreCase)) - { - codec = "dvbsub"; - } - else if ((codec ?? string.Empty).IndexOf("PGS", StringComparison.OrdinalIgnoreCase) != -1) - { - codec = "PGSSUB"; - } - else if ((codec ?? string.Empty).IndexOf("DVD", StringComparison.OrdinalIgnoreCase) != -1) - { - codec = "DVDSUB"; - } - - return codec; - } - - /// <summary> - /// Converts ffprobe stream info to our MediaStream class - /// </summary> - /// <param name="isAudio">if set to <c>true</c> [is info].</param> - /// <param name="streamInfo">The stream info.</param> - /// <param name="formatInfo">The format info.</param> - /// <returns>MediaStream.</returns> - private MediaStream GetMediaStream(bool isAudio, MediaStreamInfo streamInfo, MediaFormatInfo formatInfo) - { - // These are mp4 chapters - if (string.Equals(streamInfo.codec_name, "mov_text", StringComparison.OrdinalIgnoreCase)) - { - // Edit: but these are also sometimes subtitles? - //return null; - } - - var stream = new MediaStream - { - Codec = streamInfo.codec_name, - Profile = streamInfo.profile, - Level = streamInfo.level, - Index = streamInfo.index, - PixelFormat = streamInfo.pix_fmt, - NalLengthSize = streamInfo.nal_length_size, - TimeBase = streamInfo.time_base, - CodecTimeBase = streamInfo.codec_time_base - }; - - if (string.Equals(streamInfo.is_avc, "true", StringComparison.OrdinalIgnoreCase) || - string.Equals(streamInfo.is_avc, "1", StringComparison.OrdinalIgnoreCase)) - { - stream.IsAVC = true; - } - else if (string.Equals(streamInfo.is_avc, "false", StringComparison.OrdinalIgnoreCase) || - string.Equals(streamInfo.is_avc, "0", StringComparison.OrdinalIgnoreCase)) - { - stream.IsAVC = false; - } - - if (!string.IsNullOrWhiteSpace(streamInfo.field_order) && !string.Equals(streamInfo.field_order, "progressive", StringComparison.OrdinalIgnoreCase)) - { - stream.IsInterlaced = true; - } - - // Filter out junk - if (!string.IsNullOrWhiteSpace(streamInfo.codec_tag_string) && streamInfo.codec_tag_string.IndexOf("[0]", StringComparison.OrdinalIgnoreCase) == -1) - { - stream.CodecTag = streamInfo.codec_tag_string; - } - - if (streamInfo.tags != null) - { - stream.Language = GetDictionaryValue(streamInfo.tags, "language"); - stream.Comment = GetDictionaryValue(streamInfo.tags, "comment"); - stream.Title = GetDictionaryValue(streamInfo.tags, "title"); - } - - if (string.Equals(streamInfo.codec_type, "audio", StringComparison.OrdinalIgnoreCase)) - { - stream.Type = MediaStreamType.Audio; - - stream.Channels = streamInfo.channels; - - if (!string.IsNullOrEmpty(streamInfo.sample_rate)) - { - int value; - if (int.TryParse(streamInfo.sample_rate, NumberStyles.Any, _usCulture, out value)) - { - stream.SampleRate = value; - } - } - - stream.ChannelLayout = ParseChannelLayout(streamInfo.channel_layout); - - if (streamInfo.bits_per_sample > 0) - { - stream.BitDepth = streamInfo.bits_per_sample; - } - else if (streamInfo.bits_per_raw_sample > 0) - { - stream.BitDepth = streamInfo.bits_per_raw_sample; - } - } - else if (string.Equals(streamInfo.codec_type, "subtitle", StringComparison.OrdinalIgnoreCase)) - { - stream.Type = MediaStreamType.Subtitle; - stream.Codec = NormalizeSubtitleCodec(stream.Codec); - } - else if (string.Equals(streamInfo.codec_type, "video", StringComparison.OrdinalIgnoreCase)) - { - stream.Type = isAudio || string.Equals(stream.Codec, "mjpeg", StringComparison.OrdinalIgnoreCase) || string.Equals(stream.Codec, "gif", StringComparison.OrdinalIgnoreCase) || string.Equals(stream.Codec, "png", StringComparison.OrdinalIgnoreCase) - ? MediaStreamType.EmbeddedImage - : MediaStreamType.Video; - - stream.AverageFrameRate = GetFrameRate(streamInfo.avg_frame_rate); - stream.RealFrameRate = GetFrameRate(streamInfo.r_frame_rate); - - if (isAudio || string.Equals(stream.Codec, "gif", StringComparison.OrdinalIgnoreCase) || - string.Equals(stream.Codec, "png", StringComparison.OrdinalIgnoreCase)) - { - stream.Type = MediaStreamType.EmbeddedImage; - } - else if (string.Equals(stream.Codec, "mjpeg", StringComparison.OrdinalIgnoreCase)) - { - // How to differentiate between video and embedded image? - // The only difference I've seen thus far is presence of codec tag, also embedded images have high (unusual) framerates - if (!string.IsNullOrWhiteSpace(stream.CodecTag)) - { - stream.Type = MediaStreamType.Video; - } - else - { - stream.Type = MediaStreamType.EmbeddedImage; - } - } - else - { - stream.Type = MediaStreamType.Video; - } - - stream.Width = streamInfo.width; - stream.Height = streamInfo.height; - stream.AspectRatio = GetAspectRatio(streamInfo); - - if (streamInfo.bits_per_sample > 0) - { - stream.BitDepth = streamInfo.bits_per_sample; - } - else if (streamInfo.bits_per_raw_sample > 0) - { - stream.BitDepth = streamInfo.bits_per_raw_sample; - } - - //stream.IsAnamorphic = string.Equals(streamInfo.sample_aspect_ratio, "0:1", StringComparison.OrdinalIgnoreCase) || - // string.Equals(stream.AspectRatio, "2.35:1", StringComparison.OrdinalIgnoreCase) || - // string.Equals(stream.AspectRatio, "2.40:1", StringComparison.OrdinalIgnoreCase); - - // http://stackoverflow.com/questions/17353387/how-to-detect-anamorphic-video-with-ffprobe - stream.IsAnamorphic = string.Equals(streamInfo.sample_aspect_ratio, "0:1", StringComparison.OrdinalIgnoreCase); - - if (streamInfo.refs > 0) - { - stream.RefFrames = streamInfo.refs; - } - } - else - { - return null; - } - - // Get stream bitrate - var bitrate = 0; - - if (!string.IsNullOrEmpty(streamInfo.bit_rate)) - { - int value; - if (int.TryParse(streamInfo.bit_rate, NumberStyles.Any, _usCulture, out value)) - { - bitrate = value; - } - } - - if (bitrate == 0 && formatInfo != null && !string.IsNullOrEmpty(formatInfo.bit_rate) && stream.Type == MediaStreamType.Video) - { - // If the stream info doesn't have a bitrate get the value from the media format info - int value; - if (int.TryParse(formatInfo.bit_rate, NumberStyles.Any, _usCulture, out value)) - { - bitrate = value; - } - } - - if (bitrate > 0) - { - stream.BitRate = bitrate; - } - - if (streamInfo.disposition != null) - { - var isDefault = GetDictionaryValue(streamInfo.disposition, "default"); - var isForced = GetDictionaryValue(streamInfo.disposition, "forced"); - - stream.IsDefault = string.Equals(isDefault, "1", StringComparison.OrdinalIgnoreCase); - - stream.IsForced = string.Equals(isForced, "1", StringComparison.OrdinalIgnoreCase); - } - - NormalizeStreamTitle(stream); - - return stream; - } - - private void NormalizeStreamTitle(MediaStream stream) - { - if (string.Equals(stream.Title, "cc", StringComparison.OrdinalIgnoreCase)) - { - stream.Title = null; - } - - if (stream.Type == MediaStreamType.EmbeddedImage) - { - stream.Title = null; - } - } - - /// <summary> - /// Gets a string from an FFProbeResult tags dictionary - /// </summary> - /// <param name="tags">The tags.</param> - /// <param name="key">The key.</param> - /// <returns>System.String.</returns> - private string GetDictionaryValue(Dictionary<string, string> tags, string key) - { - if (tags == null) - { - return null; - } - - string val; - - tags.TryGetValue(key, out val); - return val; - } - - private string ParseChannelLayout(string input) - { - if (string.IsNullOrEmpty(input)) - { - return input; - } - - return input.Split('(').FirstOrDefault(); - } - - private string GetAspectRatio(MediaStreamInfo info) - { - var original = info.display_aspect_ratio; - - int height; - int width; - - var parts = (original ?? string.Empty).Split(':'); - if (!(parts.Length == 2 && - int.TryParse(parts[0], NumberStyles.Any, _usCulture, out width) && - int.TryParse(parts[1], NumberStyles.Any, _usCulture, out height) && - width > 0 && - height > 0)) - { - width = info.width; - height = info.height; - } - - if (width > 0 && height > 0) - { - double ratio = width; - ratio /= height; - - if (IsClose(ratio, 1.777777778, .03)) - { - return "16:9"; - } - - if (IsClose(ratio, 1.3333333333, .05)) - { - return "4:3"; - } - - if (IsClose(ratio, 1.41)) - { - return "1.41:1"; - } - - if (IsClose(ratio, 1.5)) - { - return "1.5:1"; - } - - if (IsClose(ratio, 1.6)) - { - return "1.6:1"; - } - - if (IsClose(ratio, 1.66666666667)) - { - return "5:3"; - } - - if (IsClose(ratio, 1.85, .02)) - { - return "1.85:1"; - } - - if (IsClose(ratio, 2.35, .025)) - { - return "2.35:1"; - } - - if (IsClose(ratio, 2.4, .025)) - { - return "2.40:1"; - } - } - - return original; - } - - private bool IsClose(double d1, double d2, double variance = .005) - { - return Math.Abs(d1 - d2) <= variance; - } - - /// <summary> - /// Gets a frame rate from a string value in ffprobe output - /// This could be a number or in the format of 2997/125. - /// </summary> - /// <param name="value">The value.</param> - /// <returns>System.Nullable{System.Single}.</returns> - private float? GetFrameRate(string value) - { - if (!string.IsNullOrEmpty(value)) - { - var parts = value.Split('/'); - - float result; - - if (parts.Length == 2) - { - result = float.Parse(parts[0], _usCulture) / float.Parse(parts[1], _usCulture); - } - else - { - result = float.Parse(parts[0], _usCulture); - } - - return float.IsNaN(result) ? (float?)null : result; - } - - return null; - } - - private void SetAudioRuntimeTicks(InternalMediaInfoResult result, MediaInfo data) - { - if (result.streams != null) - { - // Get the first info stream - var stream = result.streams.FirstOrDefault(s => string.Equals(s.codec_type, "audio", StringComparison.OrdinalIgnoreCase)); - - if (stream != null) - { - // Get duration from stream properties - var duration = stream.duration; - - // If it's not there go into format properties - if (string.IsNullOrEmpty(duration)) - { - duration = result.format.duration; - } - - // If we got something, parse it - if (!string.IsNullOrEmpty(duration)) - { - data.RunTimeTicks = TimeSpan.FromSeconds(double.Parse(duration, _usCulture)).Ticks; - } - } - } - } - - private void SetSize(InternalMediaInfoResult data, Model.MediaInfo.MediaInfo info) - { - if (data.format != null) - { - if (!string.IsNullOrEmpty(data.format.size)) - { - info.Size = long.Parse(data.format.size, _usCulture); - } - else - { - info.Size = null; - } - } - } - - private void SetAudioInfoFromTags(MediaInfo audio, Dictionary<string, string> tags) - { - var composer = FFProbeHelpers.GetDictionaryValue(tags, "composer"); - if (!string.IsNullOrWhiteSpace(composer)) - { - foreach (var person in Split(composer, false)) - { - audio.People.Add(new BaseItemPerson { Name = person, Type = PersonType.Composer }); - } - } - - //var conductor = FFProbeHelpers.GetDictionaryValue(tags, "conductor"); - //if (!string.IsNullOrWhiteSpace(conductor)) - //{ - // foreach (var person in Split(conductor, false)) - // { - // audio.People.Add(new BaseItemPerson { Name = person, Type = PersonType.Conductor }); - // } - //} - - //var lyricist = FFProbeHelpers.GetDictionaryValue(tags, "lyricist"); - //if (!string.IsNullOrWhiteSpace(lyricist)) - //{ - // foreach (var person in Split(lyricist, false)) - // { - // audio.People.Add(new BaseItemPerson { Name = person, Type = PersonType.Lyricist }); - // } - //} - - // Check for writer some music is tagged that way as alternative to composer/lyricist - var writer = FFProbeHelpers.GetDictionaryValue(tags, "writer"); - - if (!string.IsNullOrWhiteSpace(writer)) - { - foreach (var person in Split(writer, false)) - { - audio.People.Add(new BaseItemPerson { Name = person, Type = PersonType.Writer }); - } - } - - audio.Album = FFProbeHelpers.GetDictionaryValue(tags, "album"); - - var artists = FFProbeHelpers.GetDictionaryValue(tags, "artists"); - - if (!string.IsNullOrWhiteSpace(artists)) - { - audio.Artists = SplitArtists(artists, new[] { '/', ';' }, false) - .DistinctNames() - .ToList(); - } - else - { - var artist = FFProbeHelpers.GetDictionaryValue(tags, "artist"); - if (string.IsNullOrWhiteSpace(artist)) - { - audio.Artists.Clear(); - } - else - { - audio.Artists = SplitArtists(artist, _nameDelimiters, true) - .DistinctNames() - .ToList(); - } - } - - var albumArtist = FFProbeHelpers.GetDictionaryValue(tags, "albumartist"); - if (string.IsNullOrWhiteSpace(albumArtist)) - { - albumArtist = FFProbeHelpers.GetDictionaryValue(tags, "album artist"); - } - if (string.IsNullOrWhiteSpace(albumArtist)) - { - albumArtist = FFProbeHelpers.GetDictionaryValue(tags, "album_artist"); - } - - if (string.IsNullOrWhiteSpace(albumArtist)) - { - audio.AlbumArtists = new List<string>(); - } - else - { - audio.AlbumArtists = SplitArtists(albumArtist, _nameDelimiters, true) - .DistinctNames() - .ToList(); - - } - - if (audio.AlbumArtists.Count == 0) - { - audio.AlbumArtists = audio.Artists.Take(1).ToList(); - } - - // Track number - audio.IndexNumber = GetDictionaryDiscValue(tags, "track"); - - // Disc number - audio.ParentIndexNumber = GetDictionaryDiscValue(tags, "disc"); - - // If we don't have a ProductionYear try and get it from PremiereDate - if (audio.PremiereDate.HasValue && !audio.ProductionYear.HasValue) - { - audio.ProductionYear = audio.PremiereDate.Value.ToLocalTime().Year; - } - - // There's several values in tags may or may not be present - FetchStudios(audio, tags, "organization"); - FetchStudios(audio, tags, "ensemble"); - FetchStudios(audio, tags, "publisher"); - FetchStudios(audio, tags, "label"); - - // These support mulitple values, but for now we only store the first. - var mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Album Artist Id")); - if (mb == null) mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MUSICBRAINZ_ALBUMARTISTID")); - audio.SetProviderId(MetadataProviders.MusicBrainzAlbumArtist, mb); - - mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Artist Id")); - if (mb == null) mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MUSICBRAINZ_ARTISTID")); - audio.SetProviderId(MetadataProviders.MusicBrainzArtist, mb); - - mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Album Id")); - if (mb == null) mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MUSICBRAINZ_ALBUMID")); - audio.SetProviderId(MetadataProviders.MusicBrainzAlbum, mb); - - mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Release Group Id")); - if (mb == null) mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MUSICBRAINZ_RELEASEGROUPID")); - audio.SetProviderId(MetadataProviders.MusicBrainzReleaseGroup, mb); - - mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Release Track Id")); - if (mb == null) mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MUSICBRAINZ_RELEASETRACKID")); - audio.SetProviderId(MetadataProviders.MusicBrainzTrack, mb); - } - - private string GetMultipleMusicBrainzId(string value) - { - if (string.IsNullOrWhiteSpace(value)) - { - return null; - } - - return value.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries) - .Select(i => i.Trim()) - .FirstOrDefault(i => !string.IsNullOrWhiteSpace(i)); - } - - private readonly char[] _nameDelimiters = { '/', '|', ';', '\\' }; - - /// <summary> - /// Splits the specified val. - /// </summary> - /// <param name="val">The val.</param> - /// <param name="allowCommaDelimiter">if set to <c>true</c> [allow comma delimiter].</param> - /// <returns>System.String[][].</returns> - private IEnumerable<string> Split(string val, bool allowCommaDelimiter) - { - // Only use the comma as a delimeter if there are no slashes or pipes. - // We want to be careful not to split names that have commas in them - var delimeter = !allowCommaDelimiter || _nameDelimiters.Any(i => val.IndexOf(i) != -1) ? - _nameDelimiters : - new[] { ',' }; - - return val.Split(delimeter, StringSplitOptions.RemoveEmptyEntries) - .Where(i => !string.IsNullOrWhiteSpace(i)) - .Select(i => i.Trim()); - } - - private const string ArtistReplaceValue = " | "; - - private IEnumerable<string> SplitArtists(string val, char[] delimiters, bool splitFeaturing) - { - if (splitFeaturing) - { - val = val.Replace(" featuring ", ArtistReplaceValue, StringComparison.OrdinalIgnoreCase) - .Replace(" feat. ", ArtistReplaceValue, StringComparison.OrdinalIgnoreCase); - } - - var artistsFound = new List<string>(); - - foreach (var whitelistArtist in GetSplitWhitelist()) - { - var originalVal = val; - val = val.Replace(whitelistArtist, "|", StringComparison.OrdinalIgnoreCase); - - if (!string.Equals(originalVal, val, StringComparison.OrdinalIgnoreCase)) - { - artistsFound.Add(whitelistArtist); - } - } - - var artists = val.Split(delimiters, StringSplitOptions.RemoveEmptyEntries) - .Where(i => !string.IsNullOrWhiteSpace(i)) - .Select(i => i.Trim()); - - artistsFound.AddRange(artists); - return artistsFound; - } - - - private List<string> _splitWhiteList = null; - - private IEnumerable<string> GetSplitWhitelist() - { - if (_splitWhiteList == null) - { - _splitWhiteList = new List<string> - { - "AC/DC" - }; - } - - return _splitWhiteList; - } - - /// <summary> - /// Gets the studios from the tags collection - /// </summary> - /// <param name="info">The info.</param> - /// <param name="tags">The tags.</param> - /// <param name="tagName">Name of the tag.</param> - private void FetchStudios(MediaInfo info, Dictionary<string, string> tags, string tagName) - { - var val = FFProbeHelpers.GetDictionaryValue(tags, tagName); - - if (!string.IsNullOrEmpty(val)) - { - var studios = Split(val, true); - - foreach (var studio in studios) - { - // Sometimes the artist name is listed here, account for that - if (info.Artists.Contains(studio, StringComparer.OrdinalIgnoreCase)) - { - continue; - } - if (info.AlbumArtists.Contains(studio, StringComparer.OrdinalIgnoreCase)) - { - continue; - } - - info.Studios.Add(studio); - } - - info.Studios = info.Studios - .Where(i => !string.IsNullOrWhiteSpace(i)) - .Distinct(StringComparer.OrdinalIgnoreCase) - .ToList(); - } - } - - /// <summary> - /// Gets the genres from the tags collection - /// </summary> - /// <param name="info">The information.</param> - /// <param name="tags">The tags.</param> - private void FetchGenres(MediaInfo info, Dictionary<string, string> tags) - { - var val = FFProbeHelpers.GetDictionaryValue(tags, "genre"); - - if (!string.IsNullOrEmpty(val)) - { - foreach (var genre in Split(val, true)) - { - info.Genres.Add(genre); - } - - info.Genres = info.Genres - .Where(i => !string.IsNullOrWhiteSpace(i)) - .Distinct(StringComparer.OrdinalIgnoreCase) - .ToList(); - } - } - - /// <summary> - /// Gets the disc number, which is sometimes can be in the form of '1', or '1/3' - /// </summary> - /// <param name="tags">The tags.</param> - /// <param name="tagName">Name of the tag.</param> - /// <returns>System.Nullable{System.Int32}.</returns> - private int? GetDictionaryDiscValue(Dictionary<string, string> tags, string tagName) - { - var disc = FFProbeHelpers.GetDictionaryValue(tags, tagName); - - if (!string.IsNullOrEmpty(disc)) - { - disc = disc.Split('/')[0]; - - int num; - - if (int.TryParse(disc, out num)) - { - return num; - } - } - - return null; - } - - private ChapterInfo GetChapterInfo(MediaChapter chapter) - { - var info = new ChapterInfo(); - - if (chapter.tags != null) - { - string name; - if (chapter.tags.TryGetValue("title", out name)) - { - info.Name = name; - } - } - - // Limit accuracy to milliseconds to match xml saving - var secondsString = chapter.start_time; - double seconds; - - if (double.TryParse(secondsString, NumberStyles.Any, CultureInfo.InvariantCulture, out seconds)) - { - var ms = Math.Round(TimeSpan.FromSeconds(seconds).TotalMilliseconds); - info.StartPositionTicks = TimeSpan.FromMilliseconds(ms).Ticks; - } - - return info; - } - - private const int MaxSubtitleDescriptionExtractionLength = 100; // When extracting subtitles, the maximum length to consider (to avoid invalid filenames) - - private void FetchWtvInfo(MediaInfo video, InternalMediaInfoResult data) - { - if (data.format == null || data.format.tags == null) - { - return; - } - - var genres = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/Genre"); - - if (!string.IsNullOrWhiteSpace(genres)) - { - var genreList = genres.Split(new[] { ';', '/', ',' }, StringSplitOptions.RemoveEmptyEntries) - .Where(i => !string.IsNullOrWhiteSpace(i)) - .Select(i => i.Trim()) - .ToList(); - - // If this is empty then don't overwrite genres that might have been fetched earlier - if (genreList.Count > 0) - { - video.Genres = genreList; - } - } - - var officialRating = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/ParentalRating"); - - if (!string.IsNullOrWhiteSpace(officialRating)) - { - video.OfficialRating = officialRating; - } - - var people = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/MediaCredits"); - - if (!string.IsNullOrEmpty(people)) - { - video.People = people.Split(new[] { ';', '/' }, StringSplitOptions.RemoveEmptyEntries) - .Where(i => !string.IsNullOrWhiteSpace(i)) - .Select(i => new BaseItemPerson { Name = i.Trim(), Type = PersonType.Actor }) - .ToList(); - } - - var year = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/OriginalReleaseTime"); - if (!string.IsNullOrWhiteSpace(year)) - { - int val; - - if (int.TryParse(year, NumberStyles.Integer, _usCulture, out val)) - { - video.ProductionYear = val; - } - } - - var premiereDateString = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/MediaOriginalBroadcastDateTime"); - if (!string.IsNullOrWhiteSpace(premiereDateString)) - { - DateTime val; - - // Credit to MCEBuddy: https://mcebuddy2x.codeplex.com/ - // DateTime is reported along with timezone info (typically Z i.e. UTC hence assume None) - if (DateTime.TryParse(year, null, DateTimeStyles.None, out val)) - { - video.PremiereDate = val.ToUniversalTime(); - } - } - - var description = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/SubTitleDescription"); - - var subTitle = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/SubTitle"); - - // For below code, credit to MCEBuddy: https://mcebuddy2x.codeplex.com/ - - // Sometimes for TV Shows the Subtitle field is empty and the subtitle description contains the subtitle, extract if possible. See ticket https://mcebuddy2x.codeplex.com/workitem/1910 - // The format is -> EPISODE/TOTAL_EPISODES_IN_SEASON. SUBTITLE: DESCRIPTION - // OR -> COMMENT. SUBTITLE: DESCRIPTION - // e.g. -> 4/13. The Doctor's Wife: Science fiction drama. When he follows a Time Lord distress signal, the Doctor puts Amy, Rory and his beloved TARDIS in grave danger. Also in HD. [AD,S] - // e.g. -> CBeebies Bedtime Hour. The Mystery: Animated adventures of two friends who live on an island in the middle of the big city. Some of Abney and Teal's favourite objects are missing. [S] - if (String.IsNullOrWhiteSpace(subTitle) && !String.IsNullOrWhiteSpace(description) && description.Substring(0, Math.Min(description.Length, MaxSubtitleDescriptionExtractionLength)).Contains(":")) // Check within the Subtitle size limit, otherwise from description it can get too long creating an invalid filename - { - string[] parts = description.Split(':'); - if (parts.Length > 0) - { - string subtitle = parts[0]; - try - { - if (subtitle.Contains("/")) // It contains a episode number and season number - { - string[] numbers = subtitle.Split(' '); - video.IndexNumber = int.Parse(numbers[0].Replace(".", "").Split('/')[0]); - int totalEpisodesInSeason = int.Parse(numbers[0].Replace(".", "").Split('/')[1]); - - description = String.Join(" ", numbers, 1, numbers.Length - 1).Trim(); // Skip the first, concatenate the rest, clean up spaces and save it - } - else - throw new Exception(); // Switch to default parsing - } - catch // Default parsing - { - if (subtitle.Contains(".")) // skip the comment, keep the subtitle - description = String.Join(".", subtitle.Split('.'), 1, subtitle.Split('.').Length - 1).Trim(); // skip the first - else - description = subtitle.Trim(); // Clean up whitespaces and save it - } - } - } - - if (!string.IsNullOrWhiteSpace(description)) - { - video.Overview = description; - } - } - - private void ExtractTimestamp(MediaInfo video) - { - if (video.VideoType == VideoType.VideoFile) - { - if (string.Equals(video.Container, "mpeg2ts", StringComparison.OrdinalIgnoreCase) || - string.Equals(video.Container, "m2ts", StringComparison.OrdinalIgnoreCase) || - string.Equals(video.Container, "ts", StringComparison.OrdinalIgnoreCase)) - { - try - { - video.Timestamp = GetMpegTimestamp(video.Path); - - _logger.Debug("Video has {0} timestamp", video.Timestamp); - } - catch (Exception ex) - { - _logger.ErrorException("Error extracting timestamp info from {0}", ex, video.Path); - video.Timestamp = null; - } - } - } - } - - private TransportStreamTimestamp GetMpegTimestamp(string path) - { - var packetBuffer = new byte['Å']; - - using (var fs = _fileSystem.GetFileStream(path, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.Read)) - { - fs.Read(packetBuffer, 0, packetBuffer.Length); - } - - if (packetBuffer[0] == 71) - { - return TransportStreamTimestamp.None; - } - - if ((packetBuffer[4] == 71) && (packetBuffer['Ä'] == 71)) - { - if ((packetBuffer[0] == 0) && (packetBuffer[1] == 0) && (packetBuffer[2] == 0) && (packetBuffer[3] == 0)) - { - return TransportStreamTimestamp.Zero; - } - - return TransportStreamTimestamp.Valid; - } - - return TransportStreamTimestamp.None; - } - } -} diff --git a/MediaBrowser.MediaEncoding/Properties/AssemblyInfo.cs b/MediaBrowser.MediaEncoding/Properties/AssemblyInfo.cs deleted file mode 100644 index 53f4eb403..000000000 --- a/MediaBrowser.MediaEncoding/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("MediaBrowser.MediaEncoding")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("MediaBrowser.MediaEncoding")] -[assembly: AssemblyCopyright("Copyright © 2014")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("05f49ab9-2a90-4332-9d41-7817a9cccd90")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")]
\ No newline at end of file diff --git a/MediaBrowser.MediaEncoding/Subtitles/AssParser.cs b/MediaBrowser.MediaEncoding/Subtitles/AssParser.cs deleted file mode 100644 index 6d723a087..000000000 --- a/MediaBrowser.MediaEncoding/Subtitles/AssParser.cs +++ /dev/null @@ -1,120 +0,0 @@ -using MediaBrowser.Model.Extensions; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Text.RegularExpressions; -using System.Threading; -using MediaBrowser.Model.MediaInfo; - -namespace MediaBrowser.MediaEncoding.Subtitles -{ - public class AssParser : ISubtitleParser - { - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - - public SubtitleTrackInfo Parse(Stream stream, CancellationToken cancellationToken) - { - var trackInfo = new SubtitleTrackInfo(); - var eventIndex = 1; - using (var reader = new StreamReader(stream)) - { - string line; - while (reader.ReadLine() != "[Events]") - {} - var headers = ParseFieldHeaders(reader.ReadLine()); - - while ((line = reader.ReadLine()) != null) - { - cancellationToken.ThrowIfCancellationRequested(); - - if (string.IsNullOrWhiteSpace(line)) - { - continue; - } - if(line.StartsWith("[")) - break; - if(string.IsNullOrEmpty(line)) - continue; - var subEvent = new SubtitleTrackEvent { Id = eventIndex.ToString(_usCulture) }; - eventIndex++; - var sections = line.Substring(10).Split(','); - - subEvent.StartPositionTicks = GetTicks(sections[headers["Start"]]); - subEvent.EndPositionTicks = GetTicks(sections[headers["End"]]); - - subEvent.Text = string.Join(",", sections.Skip(headers["Text"])); - RemoteNativeFormatting(subEvent); - - subEvent.Text = subEvent.Text.Replace("\\n", ParserValues.NewLine, StringComparison.OrdinalIgnoreCase); - - subEvent.Text = Regex.Replace(subEvent.Text, @"\{(\\[\w]+\(?([\w\d]+,?)+\)?)+\}", string.Empty, RegexOptions.IgnoreCase); - - trackInfo.TrackEvents.Add(subEvent); - } - } - return trackInfo; - } - - long GetTicks(string time) - { - TimeSpan span; - return TimeSpan.TryParseExact(time, @"h\:mm\:ss\.ff", _usCulture, out span) - ? span.Ticks: 0; - } - - private Dictionary<string,int> ParseFieldHeaders(string line) { - var fields = line.Substring(8).Split(',').Select(x=>x.Trim()).ToList(); - - var result = new Dictionary<string, int> { - {"Start", fields.IndexOf("Start")}, - {"End", fields.IndexOf("End")}, - {"Text", fields.IndexOf("Text")} - }; - return result; - } - - /// <summary> - /// Credit: https://github.com/SubtitleEdit/subtitleedit/blob/master/src/Logic/SubtitleFormats/AdvancedSubStationAlpha.cs - /// </summary> - private void RemoteNativeFormatting(SubtitleTrackEvent p) - { - int indexOfBegin = p.Text.IndexOf('{'); - string pre = string.Empty; - while (indexOfBegin >= 0 && p.Text.IndexOf('}') > indexOfBegin) - { - string s = p.Text.Substring(indexOfBegin); - if (s.StartsWith("{\\an1}", StringComparison.Ordinal) || - s.StartsWith("{\\an2}", StringComparison.Ordinal) || - s.StartsWith("{\\an3}", StringComparison.Ordinal) || - s.StartsWith("{\\an4}", StringComparison.Ordinal) || - s.StartsWith("{\\an5}", StringComparison.Ordinal) || - s.StartsWith("{\\an6}", StringComparison.Ordinal) || - s.StartsWith("{\\an7}", StringComparison.Ordinal) || - s.StartsWith("{\\an8}", StringComparison.Ordinal) || - s.StartsWith("{\\an9}", StringComparison.Ordinal)) - { - pre = s.Substring(0, 6); - } - else if (s.StartsWith("{\\an1\\", StringComparison.Ordinal) || - s.StartsWith("{\\an2\\", StringComparison.Ordinal) || - s.StartsWith("{\\an3\\", StringComparison.Ordinal) || - s.StartsWith("{\\an4\\", StringComparison.Ordinal) || - s.StartsWith("{\\an5\\", StringComparison.Ordinal) || - s.StartsWith("{\\an6\\", StringComparison.Ordinal) || - s.StartsWith("{\\an7\\", StringComparison.Ordinal) || - s.StartsWith("{\\an8\\", StringComparison.Ordinal) || - s.StartsWith("{\\an9\\", StringComparison.Ordinal)) - { - pre = s.Substring(0, 5) + "}"; - } - int indexOfEnd = p.Text.IndexOf('}'); - p.Text = p.Text.Remove(indexOfBegin, (indexOfEnd - indexOfBegin) + 1); - - indexOfBegin = p.Text.IndexOf('{'); - } - p.Text = pre + p.Text; - } - } -} diff --git a/MediaBrowser.MediaEncoding/Subtitles/ConfigurationExtension.cs b/MediaBrowser.MediaEncoding/Subtitles/ConfigurationExtension.cs deleted file mode 100644 index 973c653a4..000000000 --- a/MediaBrowser.MediaEncoding/Subtitles/ConfigurationExtension.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System.Collections.Generic; -using MediaBrowser.Common.Configuration; -using MediaBrowser.Model.Providers; - -namespace MediaBrowser.MediaEncoding.Subtitles -{ - public static class ConfigurationExtension - { - public static SubtitleOptions GetSubtitleConfiguration(this IConfigurationManager manager) - { - return manager.GetConfiguration<SubtitleOptions>("subtitles"); - } - } - - public class SubtitleConfigurationFactory : IConfigurationFactory - { - public IEnumerable<ConfigurationStore> GetConfigurations() - { - return new List<ConfigurationStore> - { - new ConfigurationStore - { - Key = "subtitles", - ConfigurationType = typeof (SubtitleOptions) - } - }; - } - } -} diff --git a/MediaBrowser.MediaEncoding/Subtitles/ISubtitleParser.cs b/MediaBrowser.MediaEncoding/Subtitles/ISubtitleParser.cs deleted file mode 100644 index 75de81f46..000000000 --- a/MediaBrowser.MediaEncoding/Subtitles/ISubtitleParser.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.IO; -using System.Threading; -using MediaBrowser.Model.MediaInfo; - -namespace MediaBrowser.MediaEncoding.Subtitles -{ - public interface ISubtitleParser - { - /// <summary> - /// Parses the specified stream. - /// </summary> - /// <param name="stream">The stream.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>SubtitleTrackInfo.</returns> - SubtitleTrackInfo Parse(Stream stream, CancellationToken cancellationToken); - } -} diff --git a/MediaBrowser.MediaEncoding/Subtitles/ISubtitleWriter.cs b/MediaBrowser.MediaEncoding/Subtitles/ISubtitleWriter.cs deleted file mode 100644 index e28da9185..000000000 --- a/MediaBrowser.MediaEncoding/Subtitles/ISubtitleWriter.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.IO; -using System.Threading; -using MediaBrowser.Model.MediaInfo; - -namespace MediaBrowser.MediaEncoding.Subtitles -{ - /// <summary> - /// Interface ISubtitleWriter - /// </summary> - public interface ISubtitleWriter - { - /// <summary> - /// Writes the specified information. - /// </summary> - /// <param name="info">The information.</param> - /// <param name="stream">The stream.</param> - /// <param name="cancellationToken">The cancellation token.</param> - void Write(SubtitleTrackInfo info, Stream stream, CancellationToken cancellationToken); - } -} diff --git a/MediaBrowser.MediaEncoding/Subtitles/JsonWriter.cs b/MediaBrowser.MediaEncoding/Subtitles/JsonWriter.cs deleted file mode 100644 index 474f712f9..000000000 --- a/MediaBrowser.MediaEncoding/Subtitles/JsonWriter.cs +++ /dev/null @@ -1,28 +0,0 @@ -using MediaBrowser.Model.MediaInfo; -using MediaBrowser.Model.Serialization; -using System.IO; -using System.Text; -using System.Threading; - -namespace MediaBrowser.MediaEncoding.Subtitles -{ - public class JsonWriter : ISubtitleWriter - { - private readonly IJsonSerializer _json; - - public JsonWriter(IJsonSerializer json) - { - _json = json; - } - - public void Write(SubtitleTrackInfo info, Stream stream, CancellationToken cancellationToken) - { - using (var writer = new StreamWriter(stream, Encoding.UTF8, 1024, true)) - { - var json = _json.SerializeToString(info); - - writer.Write(json); - } - } - } -} diff --git a/MediaBrowser.MediaEncoding/Subtitles/OpenSubtitleDownloader.cs b/MediaBrowser.MediaEncoding/Subtitles/OpenSubtitleDownloader.cs deleted file mode 100644 index 3954897ca..000000000 --- a/MediaBrowser.MediaEncoding/Subtitles/OpenSubtitleDownloader.cs +++ /dev/null @@ -1,349 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Common.Configuration; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Controller.Security; -using MediaBrowser.Controller.Subtitles; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Providers; -using MediaBrowser.Model.Serialization; -using OpenSubtitlesHandler; - -namespace MediaBrowser.MediaEncoding.Subtitles -{ - public class OpenSubtitleDownloader : ISubtitleProvider, IDisposable - { - private readonly ILogger _logger; - private readonly IHttpClient _httpClient; - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - - private readonly IServerConfigurationManager _config; - private readonly IEncryptionManager _encryption; - - private readonly IJsonSerializer _json; - private readonly IFileSystem _fileSystem; - - public OpenSubtitleDownloader(ILogManager logManager, IHttpClient httpClient, IServerConfigurationManager config, IEncryptionManager encryption, IJsonSerializer json, IFileSystem fileSystem) - { - _logger = logManager.GetLogger(GetType().Name); - _httpClient = httpClient; - _config = config; - _encryption = encryption; - _json = json; - _fileSystem = fileSystem; - - _config.NamedConfigurationUpdating += _config_NamedConfigurationUpdating; - - Utilities.HttpClient = httpClient; - OpenSubtitles.SetUserAgent("mediabrowser.tv"); - } - - private const string PasswordHashPrefix = "h:"; - void _config_NamedConfigurationUpdating(object sender, ConfigurationUpdateEventArgs e) - { - if (!string.Equals(e.Key, "subtitles", StringComparison.OrdinalIgnoreCase)) - { - return; - } - - var options = (SubtitleOptions)e.NewConfiguration; - - if (options != null && - !string.IsNullOrWhiteSpace(options.OpenSubtitlesPasswordHash) && - !options.OpenSubtitlesPasswordHash.StartsWith(PasswordHashPrefix, StringComparison.OrdinalIgnoreCase)) - { - options.OpenSubtitlesPasswordHash = EncryptPassword(options.OpenSubtitlesPasswordHash); - } - } - - private string EncryptPassword(string password) - { - return PasswordHashPrefix + _encryption.EncryptString(password); - } - - private string DecryptPassword(string password) - { - if (password == null || - !password.StartsWith(PasswordHashPrefix, StringComparison.OrdinalIgnoreCase)) - { - return string.Empty; - } - - return _encryption.DecryptString(password.Substring(2)); - } - - public string Name - { - get { return "Open Subtitles"; } - } - - private SubtitleOptions GetOptions() - { - return _config.GetSubtitleConfiguration(); - } - - public IEnumerable<VideoContentType> SupportedMediaTypes - { - get - { - var options = GetOptions(); - - if (string.IsNullOrWhiteSpace(options.OpenSubtitlesUsername) || - string.IsNullOrWhiteSpace(options.OpenSubtitlesPasswordHash)) - { - return new VideoContentType[] { }; - } - - return new[] { VideoContentType.Episode, VideoContentType.Movie }; - } - } - - public Task<SubtitleResponse> GetSubtitles(string id, CancellationToken cancellationToken) - { - return GetSubtitlesInternal(id, GetOptions(), cancellationToken); - } - - private DateTime _lastRateLimitException; - private async Task<SubtitleResponse> GetSubtitlesInternal(string id, - SubtitleOptions options, - CancellationToken cancellationToken) - { - if (string.IsNullOrWhiteSpace(id)) - { - throw new ArgumentNullException("id"); - } - - var idParts = id.Split(new[] { '-' }, 3); - - var format = idParts[0]; - var language = idParts[1]; - var ossId = idParts[2]; - - var downloadsList = new[] { int.Parse(ossId, _usCulture) }; - - await Login(cancellationToken).ConfigureAwait(false); - - if ((DateTime.UtcNow - _lastRateLimitException).TotalHours < 1) - { - throw new RateLimitExceededException("OpenSubtitles rate limit reached"); - } - - var resultDownLoad = await OpenSubtitles.DownloadSubtitlesAsync(downloadsList, cancellationToken).ConfigureAwait(false); - - if ((resultDownLoad.Status ?? string.Empty).IndexOf("407", StringComparison.OrdinalIgnoreCase) != -1) - { - _lastRateLimitException = DateTime.UtcNow; - throw new RateLimitExceededException("OpenSubtitles rate limit reached"); - } - - if (!(resultDownLoad is MethodResponseSubtitleDownload)) - { - throw new Exception("Invalid response type"); - } - - var results = ((MethodResponseSubtitleDownload)resultDownLoad).Results; - - _lastRateLimitException = DateTime.MinValue; - - if (results.Count == 0) - { - var msg = string.Format("Subtitle with Id {0} was not found. Name: {1}. Status: {2}. Message: {3}", - ossId, - resultDownLoad.Name ?? string.Empty, - resultDownLoad.Status ?? string.Empty, - resultDownLoad.Message ?? string.Empty); - - throw new ResourceNotFoundException(msg); - } - - var data = Convert.FromBase64String(results.First().Data); - - return new SubtitleResponse - { - Format = format, - Language = language, - - Stream = new MemoryStream(Utilities.Decompress(new MemoryStream(data))) - }; - } - - private DateTime _lastLogin; - private async Task Login(CancellationToken cancellationToken) - { - if ((DateTime.UtcNow - _lastLogin).TotalSeconds < 60) - { - return; - } - - var options = GetOptions(); - - var user = options.OpenSubtitlesUsername ?? string.Empty; - var password = DecryptPassword(options.OpenSubtitlesPasswordHash); - - var loginResponse = await OpenSubtitles.LogInAsync(user, password, "en", cancellationToken).ConfigureAwait(false); - - if (!(loginResponse is MethodResponseLogIn)) - { - throw new Exception("Authentication to OpenSubtitles failed."); - } - - _lastLogin = DateTime.UtcNow; - } - - public async Task<IEnumerable<NameIdPair>> GetSupportedLanguages(CancellationToken cancellationToken) - { - await Login(cancellationToken).ConfigureAwait(false); - - var result = OpenSubtitles.GetSubLanguages("en"); - if (!(result is MethodResponseGetSubLanguages)) - { - _logger.Error("Invalid response type"); - return new List<NameIdPair>(); - } - - var results = ((MethodResponseGetSubLanguages)result).Languages; - - return results.Select(i => new NameIdPair - { - Name = i.LanguageName, - Id = i.SubLanguageID - }); - } - - private string NormalizeLanguage(string language) - { - // Problem with Greek subtitle download #1349 - if (string.Equals(language, "gre", StringComparison.OrdinalIgnoreCase)) - { - - return "ell"; - } - - return language; - } - - public async Task<IEnumerable<RemoteSubtitleInfo>> Search(SubtitleSearchRequest request, CancellationToken cancellationToken) - { - var imdbIdText = request.GetProviderId(MetadataProviders.Imdb); - long imdbId = 0; - - switch (request.ContentType) - { - case VideoContentType.Episode: - if (!request.IndexNumber.HasValue || !request.ParentIndexNumber.HasValue || string.IsNullOrEmpty(request.SeriesName)) - { - _logger.Debug("Episode information missing"); - return new List<RemoteSubtitleInfo>(); - } - break; - case VideoContentType.Movie: - if (string.IsNullOrEmpty(request.Name)) - { - _logger.Debug("Movie name missing"); - return new List<RemoteSubtitleInfo>(); - } - if (string.IsNullOrWhiteSpace(imdbIdText) || !long.TryParse(imdbIdText.TrimStart('t'), NumberStyles.Any, _usCulture, out imdbId)) - { - _logger.Debug("Imdb id missing"); - return new List<RemoteSubtitleInfo>(); - } - break; - } - - if (string.IsNullOrEmpty(request.MediaPath)) - { - _logger.Debug("Path Missing"); - return new List<RemoteSubtitleInfo>(); - } - - await Login(cancellationToken).ConfigureAwait(false); - - var subLanguageId = NormalizeLanguage(request.Language); - string hash; - - using (var fileStream = _fileSystem.OpenRead(request.MediaPath)) - { - hash = Utilities.ComputeHash(fileStream); - } - var fileInfo = _fileSystem.GetFileInfo(request.MediaPath); - var movieByteSize = fileInfo.Length; - var searchImdbId = request.ContentType == VideoContentType.Movie ? imdbId.ToString(_usCulture) : ""; - var subtitleSearchParameters = request.ContentType == VideoContentType.Episode - ? new List<SubtitleSearchParameters> { - new SubtitleSearchParameters(subLanguageId, - query: request.SeriesName, - season: request.ParentIndexNumber.Value.ToString(_usCulture), - episode: request.IndexNumber.Value.ToString(_usCulture)) - } - : new List<SubtitleSearchParameters> { - new SubtitleSearchParameters(subLanguageId, imdbid: searchImdbId), - new SubtitleSearchParameters(subLanguageId, query: request.Name, imdbid: searchImdbId) - }; - var parms = new List<SubtitleSearchParameters> { - new SubtitleSearchParameters( subLanguageId, - movieHash: hash, - movieByteSize: movieByteSize, - imdbid: searchImdbId ), - }; - parms.AddRange(subtitleSearchParameters); - var result = await OpenSubtitles.SearchSubtitlesAsync(parms.ToArray(), cancellationToken).ConfigureAwait(false); - if (!(result is MethodResponseSubtitleSearch)) - { - _logger.Error("Invalid response type"); - return new List<RemoteSubtitleInfo>(); - } - - Predicate<SubtitleSearchResult> mediaFilter = - x => - request.ContentType == VideoContentType.Episode - ? !string.IsNullOrEmpty(x.SeriesSeason) && !string.IsNullOrEmpty(x.SeriesEpisode) && - int.Parse(x.SeriesSeason, _usCulture) == request.ParentIndexNumber && - int.Parse(x.SeriesEpisode, _usCulture) == request.IndexNumber - : !string.IsNullOrEmpty(x.IDMovieImdb) && long.Parse(x.IDMovieImdb, _usCulture) == imdbId; - - var results = ((MethodResponseSubtitleSearch)result).Results; - - // Avoid implicitly captured closure - var hasCopy = hash; - - return results.Where(x => x.SubBad == "0" && mediaFilter(x) && (!request.IsPerfectMatch || string.Equals(x.MovieHash, hash, StringComparison.OrdinalIgnoreCase))) - .OrderBy(x => (string.Equals(x.MovieHash, hash, StringComparison.OrdinalIgnoreCase) ? 0 : 1)) - .ThenBy(x => Math.Abs(long.Parse(x.MovieByteSize, _usCulture) - movieByteSize)) - .ThenByDescending(x => int.Parse(x.SubDownloadsCnt, _usCulture)) - .ThenByDescending(x => double.Parse(x.SubRating, _usCulture)) - .Select(i => new RemoteSubtitleInfo - { - Author = i.UserNickName, - Comment = i.SubAuthorComment, - CommunityRating = float.Parse(i.SubRating, _usCulture), - DownloadCount = int.Parse(i.SubDownloadsCnt, _usCulture), - Format = i.SubFormat, - ProviderName = Name, - ThreeLetterISOLanguageName = i.SubLanguageID, - - Id = i.SubFormat + "-" + i.SubLanguageID + "-" + i.IDSubtitleFile, - - Name = i.SubFileName, - DateCreated = DateTime.Parse(i.SubAddDate, _usCulture), - IsHashMatch = i.MovieHash == hasCopy - - }).Where(i => !string.Equals(i.Format, "sub", StringComparison.OrdinalIgnoreCase) && !string.Equals(i.Format, "idx", StringComparison.OrdinalIgnoreCase)); - } - - public void Dispose() - { - _config.NamedConfigurationUpdating -= _config_NamedConfigurationUpdating; - } - } -} diff --git a/MediaBrowser.MediaEncoding/Subtitles/ParserValues.cs b/MediaBrowser.MediaEncoding/Subtitles/ParserValues.cs deleted file mode 100644 index b8c2fef1e..000000000 --- a/MediaBrowser.MediaEncoding/Subtitles/ParserValues.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace MediaBrowser.MediaEncoding.Subtitles -{ - public class ParserValues - { - public const string NewLine = "\r\n"; - } -} diff --git a/MediaBrowser.MediaEncoding/Subtitles/SrtParser.cs b/MediaBrowser.MediaEncoding/Subtitles/SrtParser.cs deleted file mode 100644 index 2a6aa993c..000000000 --- a/MediaBrowser.MediaEncoding/Subtitles/SrtParser.cs +++ /dev/null @@ -1,90 +0,0 @@ -using MediaBrowser.Model.Extensions; -using MediaBrowser.Model.Logging; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Text.RegularExpressions; -using System.Threading; -using MediaBrowser.Model.MediaInfo; - -namespace MediaBrowser.MediaEncoding.Subtitles -{ - public class SrtParser : ISubtitleParser - { - private readonly ILogger _logger; - - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - - public SrtParser(ILogger logger) - { - _logger = logger; - } - - public SubtitleTrackInfo Parse(Stream stream, CancellationToken cancellationToken) - { - var trackInfo = new SubtitleTrackInfo(); - using ( var reader = new StreamReader(stream)) - { - string line; - while ((line = reader.ReadLine()) != null) - { - cancellationToken.ThrowIfCancellationRequested(); - - if (string.IsNullOrWhiteSpace(line)) - { - continue; - } - var subEvent = new SubtitleTrackEvent {Id = line}; - line = reader.ReadLine(); - - if (string.IsNullOrWhiteSpace(line)) - { - continue; - } - - var time = Regex.Split(line, @"[\t ]*-->[\t ]*"); - - if (time.Length < 2) - { - // This occurs when subtitle text has an empty line as part of the text. - // Need to adjust the break statement below to resolve this. - _logger.Warn("Unrecognized line in srt: {0}", line); - continue; - } - subEvent.StartPositionTicks = GetTicks(time[0]); - var endTime = time[1]; - var idx = endTime.IndexOf(" ", StringComparison.Ordinal); - if (idx > 0) - endTime = endTime.Substring(0, idx); - subEvent.EndPositionTicks = GetTicks(endTime); - var multiline = new List<string>(); - while ((line = reader.ReadLine()) != null) - { - if (string.IsNullOrEmpty(line)) - { - break; - } - multiline.Add(line); - } - subEvent.Text = string.Join(ParserValues.NewLine, multiline); - subEvent.Text = subEvent.Text.Replace(@"\N", ParserValues.NewLine, StringComparison.OrdinalIgnoreCase); - subEvent.Text = Regex.Replace(subEvent.Text, @"\{(?:\\\d?[\w.-]+(?:\([^\)]*\)|&H?[0-9A-Fa-f]+&|))+\}", string.Empty, RegexOptions.IgnoreCase); - subEvent.Text = Regex.Replace(subEvent.Text, "<", "<", RegexOptions.IgnoreCase); - subEvent.Text = Regex.Replace(subEvent.Text, ">", ">", RegexOptions.IgnoreCase); - subEvent.Text = Regex.Replace(subEvent.Text, "<(\\/?(font|b|u|i|s))((\\s+(\\w|\\w[\\w\\-]*\\w)(\\s*=\\s*(?:\\\".*?\\\"|'.*?'|[^'\\\">\\s]+))?)+\\s*|\\s*)(\\/?)>", "<$1$3$7>", RegexOptions.IgnoreCase); - trackInfo.TrackEvents.Add(subEvent); - } - } - return trackInfo; - } - - long GetTicks(string time) { - TimeSpan span; - return TimeSpan.TryParseExact(time, @"hh\:mm\:ss\.fff", _usCulture, out span) - ? span.Ticks - : (TimeSpan.TryParseExact(time, @"hh\:mm\:ss\,fff", _usCulture, out span) - ? span.Ticks : 0); - } - } -} diff --git a/MediaBrowser.MediaEncoding/Subtitles/SrtWriter.cs b/MediaBrowser.MediaEncoding/Subtitles/SrtWriter.cs deleted file mode 100644 index c05929fde..000000000 --- a/MediaBrowser.MediaEncoding/Subtitles/SrtWriter.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using System.Globalization; -using System.IO; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading; -using MediaBrowser.Model.MediaInfo; - -namespace MediaBrowser.MediaEncoding.Subtitles -{ - public class SrtWriter : ISubtitleWriter - { - public void Write(SubtitleTrackInfo info, Stream stream, CancellationToken cancellationToken) - { - using (var writer = new StreamWriter(stream, Encoding.UTF8, 1024, true)) - { - var index = 1; - - foreach (var trackEvent in info.TrackEvents) - { - cancellationToken.ThrowIfCancellationRequested(); - - writer.WriteLine(index.ToString(CultureInfo.InvariantCulture)); - writer.WriteLine(@"{0:hh\:mm\:ss\,fff} --> {1:hh\:mm\:ss\,fff}", TimeSpan.FromTicks(trackEvent.StartPositionTicks), TimeSpan.FromTicks(trackEvent.EndPositionTicks)); - - var text = trackEvent.Text; - - // TODO: Not sure how to handle these - text = Regex.Replace(text, @"\\n", " ", RegexOptions.IgnoreCase); - - writer.WriteLine(text); - writer.WriteLine(string.Empty); - - index++; - } - } - } - } -} diff --git a/MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs b/MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs deleted file mode 100644 index 6c760658d..000000000 --- a/MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs +++ /dev/null @@ -1,394 +0,0 @@ -using MediaBrowser.Model.Extensions; -using System; -using System.IO; -using System.Text; -using System.Threading; -using MediaBrowser.Model.MediaInfo; - -namespace MediaBrowser.MediaEncoding.Subtitles -{ - /// <summary> - /// Credit to https://github.com/SubtitleEdit/subtitleedit/blob/a299dc4407a31796364cc6ad83f0d3786194ba22/src/Logic/SubtitleFormats/SubStationAlpha.cs - /// </summary> - public class SsaParser : ISubtitleParser - { - public SubtitleTrackInfo Parse(Stream stream, CancellationToken cancellationToken) - { - var trackInfo = new SubtitleTrackInfo(); - - using (var reader = new StreamReader(stream)) - { - bool eventsStarted = false; - - string[] format = "Marked, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text".Split(','); - int indexLayer = 0; - int indexStart = 1; - int indexEnd = 2; - int indexStyle = 3; - int indexName = 4; - int indexEffect = 8; - int indexText = 9; - int lineNumber = 0; - - var header = new StringBuilder(); - - string line; - - while ((line = reader.ReadLine()) != null) - { - cancellationToken.ThrowIfCancellationRequested(); - - lineNumber++; - if (!eventsStarted) - header.AppendLine(line); - - if (line.Trim().ToLower() == "[events]") - { - eventsStarted = true; - } - else if (!string.IsNullOrEmpty(line) && line.Trim().StartsWith(";")) - { - // skip comment lines - } - else if (eventsStarted && line.Trim().Length > 0) - { - string s = line.Trim().ToLower(); - if (s.StartsWith("format:")) - { - if (line.Length > 10) - { - format = line.ToLower().Substring(8).Split(','); - for (int i = 0; i < format.Length; i++) - { - if (format[i].Trim().ToLower() == "layer") - indexLayer = i; - else if (format[i].Trim().ToLower() == "start") - indexStart = i; - else if (format[i].Trim().ToLower() == "end") - indexEnd = i; - else if (format[i].Trim().ToLower() == "text") - indexText = i; - else if (format[i].Trim().ToLower() == "effect") - indexEffect = i; - else if (format[i].Trim().ToLower() == "style") - indexStyle = i; - } - } - } - else if (!string.IsNullOrEmpty(s)) - { - string text = string.Empty; - string start = string.Empty; - string end = string.Empty; - string style = string.Empty; - string layer = string.Empty; - string effect = string.Empty; - string name = string.Empty; - - string[] splittedLine; - - if (s.StartsWith("dialogue:")) - splittedLine = line.Substring(10).Split(','); - else - splittedLine = line.Split(','); - - for (int i = 0; i < splittedLine.Length; i++) - { - if (i == indexStart) - start = splittedLine[i].Trim(); - else if (i == indexEnd) - end = splittedLine[i].Trim(); - else if (i == indexLayer) - layer = splittedLine[i]; - else if (i == indexEffect) - effect = splittedLine[i]; - else if (i == indexText) - text = splittedLine[i]; - else if (i == indexStyle) - style = splittedLine[i]; - else if (i == indexName) - name = splittedLine[i]; - else if (i > indexText) - text += "," + splittedLine[i]; - } - - try - { - var p = new SubtitleTrackEvent(); - - p.StartPositionTicks = GetTimeCodeFromString(start); - p.EndPositionTicks = GetTimeCodeFromString(end); - p.Text = GetFormattedText(text); - - trackInfo.TrackEvents.Add(p); - } - catch - { - } - } - } - } - - //if (header.Length > 0) - //subtitle.Header = header.ToString(); - - //subtitle.Renumber(1); - } - return trackInfo; - } - - private static long GetTimeCodeFromString(string time) - { - // h:mm:ss.cc - string[] timeCode = time.Split(':', '.'); - return new TimeSpan(0, int.Parse(timeCode[0]), - int.Parse(timeCode[1]), - int.Parse(timeCode[2]), - int.Parse(timeCode[3]) * 10).Ticks; - } - - public static string GetFormattedText(string text) - { - text = text.Replace("\\n", ParserValues.NewLine, StringComparison.OrdinalIgnoreCase); - - bool italic = false; - - for (int i = 0; i < 10; i++) // just look ten times... - { - if (text.Contains(@"{\fn")) - { - int start = text.IndexOf(@"{\fn"); - int end = text.IndexOf('}', start); - if (end > 0 && !text.Substring(start).StartsWith("{\\fn}")) - { - string fontName = text.Substring(start + 4, end - (start + 4)); - string extraTags = string.Empty; - CheckAndAddSubTags(ref fontName, ref extraTags, out italic); - text = text.Remove(start, end - start + 1); - if (italic) - text = text.Insert(start, "<font face=\"" + fontName + "\"" + extraTags + "><i>"); - else - text = text.Insert(start, "<font face=\"" + fontName + "\"" + extraTags + ">"); - - int indexOfEndTag = text.IndexOf("{\\fn}", start); - if (indexOfEndTag > 0) - text = text.Remove(indexOfEndTag, "{\\fn}".Length).Insert(indexOfEndTag, "</font>"); - else - text += "</font>"; - } - } - - if (text.Contains(@"{\fs")) - { - int start = text.IndexOf(@"{\fs"); - int end = text.IndexOf('}', start); - if (end > 0 && !text.Substring(start).StartsWith("{\\fs}")) - { - string fontSize = text.Substring(start + 4, end - (start + 4)); - string extraTags = string.Empty; - CheckAndAddSubTags(ref fontSize, ref extraTags, out italic); - if (IsInteger(fontSize)) - { - text = text.Remove(start, end - start + 1); - if (italic) - text = text.Insert(start, "<font size=\"" + fontSize + "\"" + extraTags + "><i>"); - else - text = text.Insert(start, "<font size=\"" + fontSize + "\"" + extraTags + ">"); - - int indexOfEndTag = text.IndexOf("{\\fs}", start); - if (indexOfEndTag > 0) - text = text.Remove(indexOfEndTag, "{\\fs}".Length).Insert(indexOfEndTag, "</font>"); - else - text += "</font>"; - } - } - } - - if (text.Contains(@"{\c")) - { - int start = text.IndexOf(@"{\c"); - int end = text.IndexOf('}', start); - if (end > 0 && !text.Substring(start).StartsWith("{\\c}")) - { - string color = text.Substring(start + 4, end - (start + 4)); - string extraTags = string.Empty; - CheckAndAddSubTags(ref color, ref extraTags, out italic); - - color = color.Replace("&", string.Empty).TrimStart('H'); - color = color.PadLeft(6, '0'); - - // switch to rrggbb from bbggrr - color = "#" + color.Remove(color.Length - 6) + color.Substring(color.Length - 2, 2) + color.Substring(color.Length - 4, 2) + color.Substring(color.Length - 6, 2); - color = color.ToLower(); - - text = text.Remove(start, end - start + 1); - if (italic) - text = text.Insert(start, "<font color=\"" + color + "\"" + extraTags + "><i>"); - else - text = text.Insert(start, "<font color=\"" + color + "\"" + extraTags + ">"); - int indexOfEndTag = text.IndexOf("{\\c}", start); - if (indexOfEndTag > 0) - text = text.Remove(indexOfEndTag, "{\\c}".Length).Insert(indexOfEndTag, "</font>"); - else - text += "</font>"; - } - } - - if (text.Contains(@"{\1c")) // "1" specifices primary color - { - int start = text.IndexOf(@"{\1c"); - int end = text.IndexOf('}', start); - if (end > 0 && !text.Substring(start).StartsWith("{\\1c}")) - { - string color = text.Substring(start + 5, end - (start + 5)); - string extraTags = string.Empty; - CheckAndAddSubTags(ref color, ref extraTags, out italic); - - color = color.Replace("&", string.Empty).TrimStart('H'); - color = color.PadLeft(6, '0'); - - // switch to rrggbb from bbggrr - color = "#" + color.Remove(color.Length - 6) + color.Substring(color.Length - 2, 2) + color.Substring(color.Length - 4, 2) + color.Substring(color.Length - 6, 2); - color = color.ToLower(); - - text = text.Remove(start, end - start + 1); - if (italic) - text = text.Insert(start, "<font color=\"" + color + "\"" + extraTags + "><i>"); - else - text = text.Insert(start, "<font color=\"" + color + "\"" + extraTags + ">"); - text += "</font>"; - } - } - - } - - text = text.Replace(@"{\i1}", "<i>"); - text = text.Replace(@"{\i0}", "</i>"); - text = text.Replace(@"{\i}", "</i>"); - if (CountTagInText(text, "<i>") > CountTagInText(text, "</i>")) - text += "</i>"; - - text = text.Replace(@"{\u1}", "<u>"); - text = text.Replace(@"{\u0}", "</u>"); - text = text.Replace(@"{\u}", "</u>"); - if (CountTagInText(text, "<u>") > CountTagInText(text, "</u>")) - text += "</u>"; - - text = text.Replace(@"{\b1}", "<b>"); - text = text.Replace(@"{\b0}", "</b>"); - text = text.Replace(@"{\b}", "</b>"); - if (CountTagInText(text, "<b>") > CountTagInText(text, "</b>")) - text += "</b>"; - - return text; - } - - private static bool IsInteger(string s) - { - int i; - if (int.TryParse(s, out i)) - return true; - return false; - } - - private static int CountTagInText(string text, string tag) - { - int count = 0; - int index = text.IndexOf(tag); - while (index >= 0) - { - count++; - if (index == text.Length) - return count; - index = text.IndexOf(tag, index + 1); - } - return count; - } - - private static void CheckAndAddSubTags(ref string tagName, ref string extraTags, out bool italic) - { - italic = false; - int indexOfSPlit = tagName.IndexOf(@"\"); - if (indexOfSPlit > 0) - { - string rest = tagName.Substring(indexOfSPlit).TrimStart('\\'); - tagName = tagName.Remove(indexOfSPlit); - - for (int i = 0; i < 10; i++) - { - if (rest.StartsWith("fs") && rest.Length > 2) - { - indexOfSPlit = rest.IndexOf(@"\"); - string fontSize = rest; - if (indexOfSPlit > 0) - { - fontSize = rest.Substring(0, indexOfSPlit); - rest = rest.Substring(indexOfSPlit).TrimStart('\\'); - } - else - { - rest = string.Empty; - } - extraTags += " size=\"" + fontSize.Substring(2) + "\""; - } - else if (rest.StartsWith("fn") && rest.Length > 2) - { - indexOfSPlit = rest.IndexOf(@"\"); - string fontName = rest; - if (indexOfSPlit > 0) - { - fontName = rest.Substring(0, indexOfSPlit); - rest = rest.Substring(indexOfSPlit).TrimStart('\\'); - } - else - { - rest = string.Empty; - } - extraTags += " face=\"" + fontName.Substring(2) + "\""; - } - else if (rest.StartsWith("c") && rest.Length > 2) - { - indexOfSPlit = rest.IndexOf(@"\"); - string fontColor = rest; - if (indexOfSPlit > 0) - { - fontColor = rest.Substring(0, indexOfSPlit); - rest = rest.Substring(indexOfSPlit).TrimStart('\\'); - } - else - { - rest = string.Empty; - } - - string color = fontColor.Substring(2); - color = color.Replace("&", string.Empty).TrimStart('H'); - color = color.PadLeft(6, '0'); - // switch to rrggbb from bbggrr - color = "#" + color.Remove(color.Length - 6) + color.Substring(color.Length - 2, 2) + color.Substring(color.Length - 4, 2) + color.Substring(color.Length - 6, 2); - color = color.ToLower(); - - extraTags += " color=\"" + color + "\""; - } - else if (rest.StartsWith("i1") && rest.Length > 1) - { - indexOfSPlit = rest.IndexOf(@"\"); - italic = true; - if (indexOfSPlit > 0) - { - rest = rest.Substring(indexOfSPlit).TrimStart('\\'); - } - else - { - rest = string.Empty; - } - } - else if (rest.Length > 0 && rest.Contains("\\")) - { - indexOfSPlit = rest.IndexOf(@"\"); - rest = rest.Substring(indexOfSPlit).TrimStart('\\'); - } - } - } - } - } -} diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs deleted file mode 100644 index 247c5274f..000000000 --- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs +++ /dev/null @@ -1,738 +0,0 @@ -using MediaBrowser.Common.Configuration; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.MediaInfo; -using MediaBrowser.Model.Serialization; -using System; -using System.Collections.Concurrent; -using System.Diagnostics; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Diagnostics; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Text; - -namespace MediaBrowser.MediaEncoding.Subtitles -{ - public class SubtitleEncoder : ISubtitleEncoder - { - private readonly ILibraryManager _libraryManager; - private readonly ILogger _logger; - private readonly IApplicationPaths _appPaths; - private readonly IFileSystem _fileSystem; - private readonly IMediaEncoder _mediaEncoder; - private readonly IJsonSerializer _json; - private readonly IHttpClient _httpClient; - private readonly IMediaSourceManager _mediaSourceManager; - private readonly IMemoryStreamFactory _memoryStreamProvider; - private readonly IProcessFactory _processFactory; - private readonly ITextEncoding _textEncoding; - - public SubtitleEncoder(ILibraryManager libraryManager, ILogger logger, IApplicationPaths appPaths, IFileSystem fileSystem, IMediaEncoder mediaEncoder, IJsonSerializer json, IHttpClient httpClient, IMediaSourceManager mediaSourceManager, IMemoryStreamFactory memoryStreamProvider, IProcessFactory processFactory, ITextEncoding textEncoding) - { - _libraryManager = libraryManager; - _logger = logger; - _appPaths = appPaths; - _fileSystem = fileSystem; - _mediaEncoder = mediaEncoder; - _json = json; - _httpClient = httpClient; - _mediaSourceManager = mediaSourceManager; - _memoryStreamProvider = memoryStreamProvider; - _processFactory = processFactory; - _textEncoding = textEncoding; - } - - private string SubtitleCachePath - { - get - { - return Path.Combine(_appPaths.DataPath, "subtitles"); - } - } - - private Stream ConvertSubtitles(Stream stream, - string inputFormat, - string outputFormat, - long startTimeTicks, - long? endTimeTicks, - bool preserveOriginalTimestamps, - CancellationToken cancellationToken) - { - var ms = _memoryStreamProvider.CreateNew(); - - try - { - var reader = GetReader(inputFormat, true); - - var trackInfo = reader.Parse(stream, cancellationToken); - - FilterEvents(trackInfo, startTimeTicks, endTimeTicks, preserveOriginalTimestamps); - - var writer = GetWriter(outputFormat); - - writer.Write(trackInfo, ms, cancellationToken); - ms.Position = 0; - } - catch - { - ms.Dispose(); - throw; - } - - return ms; - } - - private void FilterEvents(SubtitleTrackInfo track, long startPositionTicks, long? endTimeTicks, bool preserveTimestamps) - { - // Drop subs that are earlier than what we're looking for - track.TrackEvents = track.TrackEvents - .SkipWhile(i => (i.StartPositionTicks - startPositionTicks) < 0 || (i.EndPositionTicks - startPositionTicks) < 0) - .ToList(); - - if (endTimeTicks.HasValue) - { - var endTime = endTimeTicks.Value; - - track.TrackEvents = track.TrackEvents - .TakeWhile(i => i.StartPositionTicks <= endTime) - .ToList(); - } - - if (!preserveTimestamps) - { - foreach (var trackEvent in track.TrackEvents) - { - trackEvent.EndPositionTicks -= startPositionTicks; - trackEvent.StartPositionTicks -= startPositionTicks; - } - } - } - - public async Task<Stream> GetSubtitles(string itemId, - string mediaSourceId, - int subtitleStreamIndex, - string outputFormat, - long startTimeTicks, - long? endTimeTicks, - bool preserveOriginalTimestamps, - CancellationToken cancellationToken) - { - if (string.IsNullOrWhiteSpace(itemId)) - { - throw new ArgumentNullException("itemId"); - } - if (string.IsNullOrWhiteSpace(mediaSourceId)) - { - throw new ArgumentNullException("mediaSourceId"); - } - - var mediaSources = await _mediaSourceManager.GetPlayackMediaSources(itemId, null, false, new[] { MediaType.Audio, MediaType.Video }, cancellationToken).ConfigureAwait(false); - - var mediaSource = mediaSources - .First(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase)); - - var subtitleStream = mediaSource.MediaStreams - .First(i => i.Type == MediaStreamType.Subtitle && i.Index == subtitleStreamIndex); - - var subtitle = await GetSubtitleStream(mediaSource, subtitleStream, cancellationToken) - .ConfigureAwait(false); - - var inputFormat = subtitle.Item2; - var writer = TryGetWriter(outputFormat); - - // Return the original if we don't have any way of converting it - if (writer == null) - { - return subtitle.Item1; - } - - // Return the original if the same format is being requested - // Character encoding was already handled in GetSubtitleStream - if (string.Equals(inputFormat, outputFormat, StringComparison.OrdinalIgnoreCase)) - { - return subtitle.Item1; - } - - using (var stream = subtitle.Item1) - { - return ConvertSubtitles(stream, inputFormat, outputFormat, startTimeTicks, endTimeTicks, preserveOriginalTimestamps, cancellationToken); - } - } - - private async Task<Tuple<Stream, string>> GetSubtitleStream(MediaSourceInfo mediaSource, - MediaStream subtitleStream, - CancellationToken cancellationToken) - { - var inputFiles = new[] { mediaSource.Path }; - - if (mediaSource.VideoType.HasValue) - { - if (mediaSource.VideoType.Value == VideoType.BluRay || mediaSource.VideoType.Value == VideoType.Dvd) - { - var mediaSourceItem = (Video)_libraryManager.GetItemById(new Guid(mediaSource.Id)); - inputFiles = mediaSourceItem.GetPlayableStreamFiles().ToArray(); - } - } - - var fileInfo = await GetReadableFile(mediaSource.Path, inputFiles, mediaSource.Protocol, subtitleStream, cancellationToken).ConfigureAwait(false); - - var stream = await GetSubtitleStream(fileInfo.Item1, subtitleStream.Language, fileInfo.Item2, fileInfo.Item4, cancellationToken).ConfigureAwait(false); - - return new Tuple<Stream, string>(stream, fileInfo.Item3); - } - - private async Task<Stream> GetSubtitleStream(string path, string language, MediaProtocol protocol, bool requiresCharset, CancellationToken cancellationToken) - { - if (requiresCharset) - { - var bytes = await GetBytes(path, protocol, cancellationToken).ConfigureAwait(false); - - var charset = _textEncoding.GetDetectedEncodingName(bytes, language, true); - _logger.Debug("charset {0} detected for {1}", charset ?? "null", path); - - if (!string.IsNullOrEmpty(charset)) - { - using (var inputStream = _memoryStreamProvider.CreateNew(bytes)) - { - using (var reader = new StreamReader(inputStream, _textEncoding.GetEncodingFromCharset(charset))) - { - var text = await reader.ReadToEndAsync().ConfigureAwait(false); - - bytes = Encoding.UTF8.GetBytes(text); - - return _memoryStreamProvider.CreateNew(bytes); - } - } - } - } - - return _fileSystem.OpenRead(path); - } - - private async Task<Tuple<string, MediaProtocol, string, bool>> GetReadableFile(string mediaPath, - string[] inputFiles, - MediaProtocol protocol, - MediaStream subtitleStream, - CancellationToken cancellationToken) - { - if (!subtitleStream.IsExternal) - { - string outputFormat; - string outputCodec; - - if (string.Equals(subtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase) || - string.Equals(subtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase) || - string.Equals(subtitleStream.Codec, "srt", StringComparison.OrdinalIgnoreCase)) - { - // Extract - outputCodec = "copy"; - outputFormat = subtitleStream.Codec; - } - else if (string.Equals(subtitleStream.Codec, "subrip", StringComparison.OrdinalIgnoreCase)) - { - // Extract - outputCodec = "copy"; - outputFormat = "srt"; - } - else - { - // Extract - outputCodec = "srt"; - outputFormat = "srt"; - } - - // Extract - var outputPath = GetSubtitleCachePath(mediaPath, protocol, subtitleStream.Index, "." + outputFormat); - - await ExtractTextSubtitle(inputFiles, protocol, subtitleStream.Index, outputCodec, outputPath, cancellationToken) - .ConfigureAwait(false); - - return new Tuple<string, MediaProtocol, string, bool>(outputPath, MediaProtocol.File, outputFormat, false); - } - - var currentFormat = (Path.GetExtension(subtitleStream.Path) ?? subtitleStream.Codec) - .TrimStart('.'); - - if (GetReader(currentFormat, false) == null) - { - // Convert - var outputPath = GetSubtitleCachePath(mediaPath, protocol, subtitleStream.Index, ".srt"); - - await ConvertTextSubtitleToSrt(subtitleStream.Path, subtitleStream.Language, protocol, outputPath, cancellationToken).ConfigureAwait(false); - - return new Tuple<string, MediaProtocol, string, bool>(outputPath, MediaProtocol.File, "srt", true); - } - - return new Tuple<string, MediaProtocol, string, bool>(subtitleStream.Path, protocol, currentFormat, true); - } - - private ISubtitleParser GetReader(string format, bool throwIfMissing) - { - if (string.IsNullOrEmpty(format)) - { - throw new ArgumentNullException("format"); - } - - if (string.Equals(format, SubtitleFormat.SRT, StringComparison.OrdinalIgnoreCase)) - { - return new SrtParser(_logger); - } - if (string.Equals(format, SubtitleFormat.SSA, StringComparison.OrdinalIgnoreCase)) - { - return new SsaParser(); - } - if (string.Equals(format, SubtitleFormat.ASS, StringComparison.OrdinalIgnoreCase)) - { - return new AssParser(); - } - - if (throwIfMissing) - { - throw new ArgumentException("Unsupported format: " + format); - } - - return null; - } - - private ISubtitleWriter TryGetWriter(string format) - { - if (string.IsNullOrEmpty(format)) - { - throw new ArgumentNullException("format"); - } - - if (string.Equals(format, "json", StringComparison.OrdinalIgnoreCase)) - { - return new JsonWriter(_json); - } - if (string.Equals(format, SubtitleFormat.SRT, StringComparison.OrdinalIgnoreCase)) - { - return new SrtWriter(); - } - if (string.Equals(format, SubtitleFormat.VTT, StringComparison.OrdinalIgnoreCase)) - { - return new VttWriter(); - } - if (string.Equals(format, SubtitleFormat.TTML, StringComparison.OrdinalIgnoreCase)) - { - return new TtmlWriter(); - } - - return null; - } - - private ISubtitleWriter GetWriter(string format) - { - var writer = TryGetWriter(format); - - if (writer != null) - { - return writer; - } - - throw new ArgumentException("Unsupported format: " + format); - } - - /// <summary> - /// The _semaphoreLocks - /// </summary> - private readonly ConcurrentDictionary<string, SemaphoreSlim> _semaphoreLocks = - new ConcurrentDictionary<string, SemaphoreSlim>(); - - /// <summary> - /// Gets the lock. - /// </summary> - /// <param name="filename">The filename.</param> - /// <returns>System.Object.</returns> - private SemaphoreSlim GetLock(string filename) - { - return _semaphoreLocks.GetOrAdd(filename, key => new SemaphoreSlim(1, 1)); - } - - /// <summary> - /// Converts the text subtitle to SRT. - /// </summary> - /// <param name="inputPath">The input path.</param> - /// <param name="inputProtocol">The input protocol.</param> - /// <param name="outputPath">The output path.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> - private async Task ConvertTextSubtitleToSrt(string inputPath, string language, MediaProtocol inputProtocol, string outputPath, CancellationToken cancellationToken) - { - var semaphore = GetLock(outputPath); - - await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); - - try - { - if (!_fileSystem.FileExists(outputPath)) - { - await ConvertTextSubtitleToSrtInternal(inputPath, language, inputProtocol, outputPath, cancellationToken).ConfigureAwait(false); - } - } - finally - { - semaphore.Release(); - } - } - - /// <summary> - /// Converts the text subtitle to SRT internal. - /// </summary> - /// <param name="inputPath">The input path.</param> - /// <param name="inputProtocol">The input protocol.</param> - /// <param name="outputPath">The output path.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> - /// <exception cref="System.ArgumentNullException"> - /// inputPath - /// or - /// outputPath - /// </exception> - private async Task ConvertTextSubtitleToSrtInternal(string inputPath, string language, MediaProtocol inputProtocol, string outputPath, CancellationToken cancellationToken) - { - if (string.IsNullOrEmpty(inputPath)) - { - throw new ArgumentNullException("inputPath"); - } - - if (string.IsNullOrEmpty(outputPath)) - { - throw new ArgumentNullException("outputPath"); - } - - _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(outputPath)); - - var encodingParam = await GetSubtitleFileCharacterSet(inputPath, language, inputProtocol, cancellationToken).ConfigureAwait(false); - - if (!string.IsNullOrEmpty(encodingParam)) - { - encodingParam = " -sub_charenc " + encodingParam; - } - - var process = _processFactory.Create(new ProcessOptions - { - CreateNoWindow = true, - UseShellExecute = false, - FileName = _mediaEncoder.EncoderPath, - Arguments = string.Format("{0} -i \"{1}\" -c:s srt \"{2}\"", encodingParam, inputPath, outputPath), - - IsHidden = true, - ErrorDialog = false - }); - - _logger.Info("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments); - - try - { - process.Start(); - } - catch (Exception ex) - { - _logger.ErrorException("Error starting ffmpeg", ex); - - throw; - } - - var ranToCompletion = await process.WaitForExitAsync(300000).ConfigureAwait(false); - - if (!ranToCompletion) - { - try - { - _logger.Info("Killing ffmpeg subtitle conversion process"); - - process.Kill(); - } - catch (Exception ex) - { - _logger.ErrorException("Error killing subtitle conversion process", ex); - } - } - - var exitCode = ranToCompletion ? process.ExitCode : -1; - - process.Dispose(); - - var failed = false; - - if (exitCode == -1) - { - failed = true; - - if (_fileSystem.FileExists(outputPath)) - { - try - { - _logger.Info("Deleting converted subtitle due to failure: ", outputPath); - _fileSystem.DeleteFile(outputPath); - } - catch (IOException ex) - { - _logger.ErrorException("Error deleting converted subtitle {0}", ex, outputPath); - } - } - } - else if (!_fileSystem.FileExists(outputPath)) - { - failed = true; - } - - if (failed) - { - var msg = string.Format("ffmpeg subtitle conversion failed for {0}", inputPath); - - _logger.Error(msg); - - throw new Exception(msg); - } - await SetAssFont(outputPath).ConfigureAwait(false); - - _logger.Info("ffmpeg subtitle conversion succeeded for {0}", inputPath); - } - - /// <summary> - /// Extracts the text subtitle. - /// </summary> - /// <param name="inputFiles">The input files.</param> - /// <param name="protocol">The protocol.</param> - /// <param name="subtitleStreamIndex">Index of the subtitle stream.</param> - /// <param name="outputCodec">The output codec.</param> - /// <param name="outputPath">The output path.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> - /// <exception cref="System.ArgumentException">Must use inputPath list overload</exception> - private async Task ExtractTextSubtitle(string[] inputFiles, MediaProtocol protocol, int subtitleStreamIndex, - string outputCodec, string outputPath, CancellationToken cancellationToken) - { - var semaphore = GetLock(outputPath); - - await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); - - try - { - if (!_fileSystem.FileExists(outputPath)) - { - await ExtractTextSubtitleInternal(_mediaEncoder.GetInputArgument(inputFiles, protocol), subtitleStreamIndex, outputCodec, outputPath, cancellationToken).ConfigureAwait(false); - } - } - finally - { - semaphore.Release(); - } - } - - private async Task ExtractTextSubtitleInternal(string inputPath, int subtitleStreamIndex, - string outputCodec, string outputPath, CancellationToken cancellationToken) - { - if (string.IsNullOrEmpty(inputPath)) - { - throw new ArgumentNullException("inputPath"); - } - - if (string.IsNullOrEmpty(outputPath)) - { - throw new ArgumentNullException("outputPath"); - } - - _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(outputPath)); - - var processArgs = string.Format("-i {0} -map 0:{1} -an -vn -c:s {2} \"{3}\"", inputPath, - subtitleStreamIndex, outputCodec, outputPath); - - var process = _processFactory.Create(new ProcessOptions - { - CreateNoWindow = true, - UseShellExecute = false, - - FileName = _mediaEncoder.EncoderPath, - Arguments = processArgs, - IsHidden = true, - ErrorDialog = false - }); - - _logger.Info("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments); - - try - { - process.Start(); - } - catch (Exception ex) - { - _logger.ErrorException("Error starting ffmpeg", ex); - - throw; - } - - var ranToCompletion = await process.WaitForExitAsync(300000).ConfigureAwait(false); - - if (!ranToCompletion) - { - try - { - _logger.Info("Killing ffmpeg subtitle extraction process"); - - process.Kill(); - } - catch (Exception ex) - { - _logger.ErrorException("Error killing subtitle extraction process", ex); - } - } - - var exitCode = ranToCompletion ? process.ExitCode : -1; - - process.Dispose(); - - var failed = false; - - if (exitCode == -1) - { - failed = true; - - try - { - _logger.Info("Deleting extracted subtitle due to failure: {0}", outputPath); - _fileSystem.DeleteFile(outputPath); - } - catch (FileNotFoundException) - { - - } - catch (IOException ex) - { - _logger.ErrorException("Error deleting extracted subtitle {0}", ex, outputPath); - } - } - else if (!_fileSystem.FileExists(outputPath)) - { - failed = true; - } - - if (failed) - { - var msg = string.Format("ffmpeg subtitle extraction failed for {0} to {1}", inputPath, outputPath); - - _logger.Error(msg); - - throw new Exception(msg); - } - else - { - var msg = string.Format("ffmpeg subtitle extraction completed for {0} to {1}", inputPath, outputPath); - - _logger.Info(msg); - } - - if (string.Equals(outputCodec, "ass", StringComparison.OrdinalIgnoreCase)) - { - await SetAssFont(outputPath).ConfigureAwait(false); - } - } - - /// <summary> - /// Sets the ass font. - /// </summary> - /// <param name="file">The file.</param> - /// <returns>Task.</returns> - private async Task SetAssFont(string file) - { - _logger.Info("Setting ass font within {0}", file); - - string text; - Encoding encoding; - - using (var fileStream = _fileSystem.OpenRead(file)) - { - using (var reader = new StreamReader(fileStream, 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 fileStream = _fileSystem.GetFileStream(file, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read)) - { - using (var writer = new StreamWriter(fileStream, encoding)) - { - writer.Write(newText); - } - } - } - } - - private string GetSubtitleCachePath(string mediaPath, MediaProtocol protocol, int subtitleStreamIndex, string outputSubtitleExtension) - { - if (protocol == MediaProtocol.File) - { - var ticksParam = string.Empty; - - var date = _fileSystem.GetLastWriteTimeUtc(mediaPath); - - var filename = (mediaPath + "_" + subtitleStreamIndex.ToString(CultureInfo.InvariantCulture) + "_" + date.Ticks.ToString(CultureInfo.InvariantCulture) + ticksParam).GetMD5() + outputSubtitleExtension; - - var prefix = filename.Substring(0, 1); - - return Path.Combine(SubtitleCachePath, prefix, filename); - } - else - { - var filename = (mediaPath + "_" + subtitleStreamIndex.ToString(CultureInfo.InvariantCulture)).GetMD5() + outputSubtitleExtension; - - var prefix = filename.Substring(0, 1); - - return Path.Combine(SubtitleCachePath, prefix, filename); - } - } - - public async Task<string> GetSubtitleFileCharacterSet(string path, string language, MediaProtocol protocol, CancellationToken cancellationToken) - { - var bytes = await GetBytes(path, protocol, cancellationToken).ConfigureAwait(false); - - var charset = _textEncoding.GetDetectedEncodingName(bytes, language, true); - - _logger.Debug("charset {0} detected for {1}", charset ?? "null", path); - - return charset; - } - - private async Task<byte[]> GetBytes(string path, MediaProtocol protocol, CancellationToken cancellationToken) - { - if (protocol == MediaProtocol.Http) - { - using (var file = await _httpClient.Get(path, cancellationToken).ConfigureAwait(false)) - { - using (var memoryStream = new MemoryStream()) - { - await file.CopyToAsync(memoryStream).ConfigureAwait(false); - memoryStream.Position = 0; - - return memoryStream.ToArray(); - } - } - } - if (protocol == MediaProtocol.File) - { - return _fileSystem.ReadAllBytes(path); - } - - throw new ArgumentOutOfRangeException("protocol"); - } - } -}
\ No newline at end of file diff --git a/MediaBrowser.MediaEncoding/Subtitles/TtmlWriter.cs b/MediaBrowser.MediaEncoding/Subtitles/TtmlWriter.cs deleted file mode 100644 index c32005f89..000000000 --- a/MediaBrowser.MediaEncoding/Subtitles/TtmlWriter.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System; -using System.IO; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading; -using MediaBrowser.Model.MediaInfo; - -namespace MediaBrowser.MediaEncoding.Subtitles -{ - public class TtmlWriter : ISubtitleWriter - { - public void Write(SubtitleTrackInfo info, Stream stream, CancellationToken cancellationToken) - { - // Example: https://github.com/zmalltalker/ttml2vtt/blob/master/data/sample.xml - // Parser example: https://github.com/mozilla/popcorn-js/blob/master/parsers/parserTTML/popcorn.parserTTML.js - - using (var writer = new StreamWriter(stream, Encoding.UTF8, 1024, true)) - { - writer.WriteLine("<?xml version=\"1.0\" encoding=\"utf-8\"?>"); - writer.WriteLine("<tt xmlns=\"http://www.w3.org/ns/ttml\" xmlns:tts=\"http://www.w3.org/2006/04/ttaf1#styling\" lang=\"no\">"); - - writer.WriteLine("<head>"); - writer.WriteLine("<styling>"); - writer.WriteLine("<style id=\"italic\" tts:fontStyle=\"italic\" />"); - writer.WriteLine("<style id=\"left\" tts:textAlign=\"left\" />"); - writer.WriteLine("<style id=\"center\" tts:textAlign=\"center\" />"); - writer.WriteLine("<style id=\"right\" tts:textAlign=\"right\" />"); - writer.WriteLine("</styling>"); - writer.WriteLine("</head>"); - - writer.WriteLine("<body>"); - writer.WriteLine("<div>"); - - foreach (var trackEvent in info.TrackEvents) - { - var text = trackEvent.Text; - - text = Regex.Replace(text, @"\\n", "<br/>", RegexOptions.IgnoreCase); - - writer.WriteLine("<p begin=\"{0}\" dur=\"{1}\">{2}</p>", - trackEvent.StartPositionTicks, - (trackEvent.EndPositionTicks - trackEvent.StartPositionTicks), - text); - } - - writer.WriteLine("</div>"); - writer.WriteLine("</body>"); - - writer.WriteLine("</tt>"); - } - } - - private string FormatTime(long ticks) - { - var time = TimeSpan.FromTicks(ticks); - - return string.Format(@"{0:hh\:mm\:ss\,fff}", time); - } - } -} diff --git a/MediaBrowser.MediaEncoding/Subtitles/VttWriter.cs b/MediaBrowser.MediaEncoding/Subtitles/VttWriter.cs deleted file mode 100644 index 092add992..000000000 --- a/MediaBrowser.MediaEncoding/Subtitles/VttWriter.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System; -using System.IO; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading; -using MediaBrowser.Model.MediaInfo; - -namespace MediaBrowser.MediaEncoding.Subtitles -{ - public class VttWriter : ISubtitleWriter - { - public void Write(SubtitleTrackInfo info, Stream stream, CancellationToken cancellationToken) - { - using (var writer = new StreamWriter(stream, Encoding.UTF8, 1024, true)) - { - writer.WriteLine("WEBVTT"); - writer.WriteLine(string.Empty); - foreach (var trackEvent in info.TrackEvents) - { - cancellationToken.ThrowIfCancellationRequested(); - - TimeSpan startTime = TimeSpan.FromTicks(trackEvent.StartPositionTicks); - TimeSpan endTime = TimeSpan.FromTicks(trackEvent.EndPositionTicks); - - // make sure the start and end times are different and seqential - if (endTime.TotalMilliseconds <= startTime.TotalMilliseconds) - { - endTime = startTime.Add(TimeSpan.FromMilliseconds(1)); - } - - writer.WriteLine(@"{0:hh\:mm\:ss\.fff} --> {1:hh\:mm\:ss\.fff}", startTime, endTime); - - var text = trackEvent.Text; - - // TODO: Not sure how to handle these - text = Regex.Replace(text, @"\\n", " ", RegexOptions.IgnoreCase); - - writer.WriteLine(text); - writer.WriteLine(string.Empty); - } - } - } - } -} diff --git a/MediaBrowser.MediaEncoding/packages.config b/MediaBrowser.MediaEncoding/packages.config deleted file mode 100644 index 6b8deb9c9..000000000 --- a/MediaBrowser.MediaEncoding/packages.config +++ /dev/null @@ -1,3 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<packages> -</packages>
\ No newline at end of file |
