diff --git a/MediaBrowser.Api/ApiService.cs b/MediaBrowser.Api/ApiService.cs index 5945bf25ad..852c4e7db6 100644 --- a/MediaBrowser.Api/ApiService.cs +++ b/MediaBrowser.Api/ApiService.cs @@ -1,7 +1,8 @@ using System; -using System.Collections.Generic; +using System.IO; using System.Linq; -using MediaBrowser.Api.Transcoding; +using System.Reflection; +using MediaBrowser.Common.Configuration; using MediaBrowser.Controller; using MediaBrowser.Model.Entities; @@ -12,41 +13,42 @@ namespace MediaBrowser.Api /// public static class ApiService { + private static string _FFMpegDirectory = null; /// - /// Holds the list of active transcoding jobs + /// Gets the folder path to ffmpeg /// - private static List CurrentTranscodingJobs = new List(); - - /// - /// Finds an active transcoding job - /// - public static TranscodingJob GetTranscodingJob(string outputPath) + public static string FFMpegDirectory { - lock (CurrentTranscodingJobs) + get { - return CurrentTranscodingJobs.FirstOrDefault(j => j.OutputFile.Equals(outputPath, StringComparison.OrdinalIgnoreCase)); + if (_FFMpegDirectory == null) + { + _FFMpegDirectory = System.IO.Path.Combine(ApplicationPaths.ProgramDataPath, "ffmpeg"); + + if (!Directory.Exists(_FFMpegDirectory)) + { + Directory.CreateDirectory(_FFMpegDirectory); + + // Extract ffmpeg + using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("MediaBrowser.Api.ffmpeg.ffmpeg.exe")) + { + using (FileStream fileStream = new FileStream(FFMpegPath, FileMode.Create)) + { + stream.CopyTo(fileStream); + } + } + } + } + + return _FFMpegDirectory; } } - /// - /// Removes a transcoding job from the active list - /// - public static void RemoveTranscodingJob(TranscodingJob job) + public static string FFMpegPath { - lock (CurrentTranscodingJobs) + get { - CurrentTranscodingJobs.Remove(job); - } - } - - /// - /// Adds a transcoding job to the active list - /// - public static void AddTranscodingJob(TranscodingJob job) - { - lock (CurrentTranscodingJobs) - { - CurrentTranscodingJobs.Add(job); + return System.IO.Path.Combine(FFMpegDirectory, "ffmpeg.exe"); } } diff --git a/MediaBrowser.Api/HttpHandlers/AudioHandler.cs b/MediaBrowser.Api/HttpHandlers/AudioHandler.cs index 76a48308b9..5bfbfd1a45 100644 --- a/MediaBrowser.Api/HttpHandlers/AudioHandler.cs +++ b/MediaBrowser.Api/HttpHandlers/AudioHandler.cs @@ -1,16 +1,18 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; -using System.Reflection; -using MediaBrowser.Api.Transcoding; -using MediaBrowser.Common.Configuration; +using System.Linq; +using System.Net; +using MediaBrowser.Common.Logging; +using MediaBrowser.Common.Net; using MediaBrowser.Common.Net.Handlers; using MediaBrowser.Controller; using MediaBrowser.Model.Entities; namespace MediaBrowser.Api.HttpHandlers { - public class AudioHandler : StaticFileHandler + public class AudioHandler : BaseHandler { private Audio _LibraryItem; /// @@ -34,68 +36,42 @@ namespace MediaBrowser.Api.HttpHandlers } } - public override string Path + public override bool CompressResponse { get { - return TranscodedPath; + return false; } } - private string _TranscodedPath; - /// - /// Gets the library item that will be played, if any - /// - private string TranscodedPath + protected override bool IsAsyncHandler { get { - if (_TranscodedPath == null) - { - string originalMediaPath = LibraryItem == null ? base.Path : LibraryItem.Path; - - if (!RequiresTranscoding()) - { - _TranscodedPath = originalMediaPath; - } - else - { - string outputPath = GetOutputFilePath(originalMediaPath); - - // Find the job in the list - TranscodingJob job = ApiService.GetTranscodingJob(outputPath); - - if (job == null && !File.Exists(outputPath)) - { - job = GetNewTranscodingJob(originalMediaPath, outputPath); - job.Start(); - } - - if (job != null) - { - job.WaitForExit(); - } - - _TranscodedPath = outputPath; - } - } - - return _TranscodedPath; + return true; } } - public string AudioFormat + public override string ContentType { get { - string val = QueryString["audiobitrate"]; + return MimeTypes.GetMimeType("." + GetOutputFormat()); + } + } + + public IEnumerable AudioFormats + { + get + { + string val = QueryString["audioformat"]; if (string.IsNullOrEmpty(val)) { - return "mp3"; + return new string[] { "mp3" }; } - return val; + return val.Split(','); } } @@ -114,7 +90,7 @@ namespace MediaBrowser.Api.HttpHandlers } } - public int? NumAudioChannels + public int? AudioChannels { get { @@ -144,87 +120,17 @@ namespace MediaBrowser.Api.HttpHandlers } } - private static string _StreamsDirectory = null; - /// - /// Gets the folder path to where transcodes will be cached - /// - public static string StreamsDirectory + public override void ProcessRequest(HttpListenerContext ctx) { - get + HttpListenerContext = ctx; + + if (!RequiresTranscoding()) { - if (_StreamsDirectory == null) - { - _StreamsDirectory = System.IO.Path.Combine(ApplicationPaths.ProgramDataPath, "streams"); - - if (!Directory.Exists(_StreamsDirectory)) - { - Directory.CreateDirectory(_StreamsDirectory); - } - } - - return _StreamsDirectory; - } - } - - private static string _FFMpegDirectory = null; - /// - /// Gets the folder path to ffmpeg - /// - public static string FFMpegDirectory - { - get - { - if (_FFMpegDirectory == null) - { - _FFMpegDirectory = System.IO.Path.Combine(ApplicationPaths.ProgramDataPath, "ffmpeg"); - - if (!Directory.Exists(_FFMpegDirectory)) - { - Directory.CreateDirectory(_FFMpegDirectory); - - // Extract ffmpeg - using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("MediaBrowser.Api.ffmpeg.ffmpeg.exe")) - { - using (FileStream fileStream = new FileStream(FFMpegPath, FileMode.Create)) - { - stream.CopyTo(fileStream); - } - } - } - } - - return _FFMpegDirectory; - } - } - - private static string FFMpegPath - { - get - { - return System.IO.Path.Combine(FFMpegDirectory, "ffmpeg.exe"); - } - } - - private string GetOutputFilePath(string input) - { - string hash = Kernel.GetMD5(input).ToString(); - - if (AudioBitRate.HasValue) - { - hash += "_ab" + AudioBitRate; - } - if (NumAudioChannels.HasValue) - { - hash += "_ac" + NumAudioChannels; - } - if (AudioSampleRate.HasValue) - { - hash += "_ar" + AudioSampleRate; + new StaticFileHandler() { Path = LibraryItem.Path }.ProcessRequest(ctx); + return; } - string filename = hash + "." + AudioFormat.ToLower(); - - return System.IO.Path.Combine(StreamsDirectory, filename); + base.ProcessRequest(ctx); } /// @@ -232,14 +138,8 @@ namespace MediaBrowser.Api.HttpHandlers /// private bool RequiresTranscoding() { - // Only support skipping transcoding for library items - if (LibraryItem == null) - { - return true; - } - - // If it's not in the same format, we need to transcode - if (!LibraryItem.Path.EndsWith(AudioFormat, StringComparison.OrdinalIgnoreCase)) + // If it's not in a format the consumer accepts, return true + if (!AudioFormats.Any(f => LibraryItem.Path.EndsWith(f, StringComparison.OrdinalIgnoreCase))) { return true; } @@ -254,9 +154,9 @@ namespace MediaBrowser.Api.HttpHandlers } // If the number of channels is greater than our desired channels, we need to transcode - if (NumAudioChannels.HasValue) + if (AudioChannels.HasValue) { - if (NumAudioChannels.Value < LibraryItem.Channels) + if (AudioChannels.Value < LibraryItem.Channels) { return true; } @@ -270,29 +170,27 @@ namespace MediaBrowser.Api.HttpHandlers return true; } } - + // Yay return false; } - /// - /// Creates a new transcoding job - /// - private TranscodingJob GetNewTranscodingJob(string input, string output) + private string GetOutputFormat() { - return new TranscodingJob() + string format = AudioFormats.FirstOrDefault(f => LibraryItem.Path.EndsWith(f, StringComparison.OrdinalIgnoreCase)); + + if (!string.IsNullOrWhiteSpace(format)) { - InputFile = input, - OutputFile = output, - TranscoderPath = FFMpegPath, - Arguments = GetAudioArguments(input, output) - }; + return format; + } + + return AudioFormats.First(); } /// /// Creates arguments to pass to ffmpeg /// - private string GetAudioArguments(string input, string output) + private string GetAudioArguments() { List audioTranscodeParams = new List(); @@ -301,9 +199,9 @@ namespace MediaBrowser.Api.HttpHandlers audioTranscodeParams.Add("-ab " + AudioBitRate.Value); } - if (NumAudioChannels.HasValue) + if (AudioChannels.HasValue) { - audioTranscodeParams.Add("-ac " + NumAudioChannels.Value); + audioTranscodeParams.Add("-ac " + AudioChannels.Value); } if (AudioSampleRate.HasValue) @@ -311,9 +209,45 @@ namespace MediaBrowser.Api.HttpHandlers audioTranscodeParams.Add("-ar " + AudioSampleRate.Value); } - audioTranscodeParams.Add("-f " + AudioFormat); + audioTranscodeParams.Add("-f " + GetOutputFormat()); - return "-i \"" + input + "\" -vn " + string.Join(" ", audioTranscodeParams.ToArray()) + " \"" + output + "\""; + return "-i \"" + LibraryItem.Path + "\" -vn " + string.Join(" ", audioTranscodeParams.ToArray()) + " -"; + } + + protected async override void WriteResponseToOutputStream(Stream stream) + { + ProcessStartInfo startInfo = new ProcessStartInfo(); + + startInfo.CreateNoWindow = true; + + startInfo.UseShellExecute = false; + startInfo.RedirectStandardOutput = true; + + startInfo.FileName = ApiService.FFMpegPath; + startInfo.WorkingDirectory = ApiService.FFMpegDirectory; + startInfo.Arguments = GetAudioArguments(); + + Logger.LogInfo("Audio Handler Transcode: " + ApiService.FFMpegPath + " " + startInfo.Arguments); + + Process process = new Process(); + process.StartInfo = startInfo; + + try + { + process.Start(); + + await process.StandardOutput.BaseStream.CopyToAsync(stream); + } + catch (Exception ex) + { + Logger.LogException(ex); + } + finally + { + DisposeResponseStream(); + + process.Dispose(); + } } } } diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj index bbb8dcbb43..83ab5a7ee9 100644 --- a/MediaBrowser.Api/MediaBrowser.Api.csproj +++ b/MediaBrowser.Api/MediaBrowser.Api.csproj @@ -66,7 +66,6 @@ - diff --git a/MediaBrowser.Api/Transcoding/TranscodingJob.cs b/MediaBrowser.Api/Transcoding/TranscodingJob.cs deleted file mode 100644 index e504fec095..0000000000 --- a/MediaBrowser.Api/Transcoding/TranscodingJob.cs +++ /dev/null @@ -1,102 +0,0 @@ -using System; -using System.Diagnostics; -using System.IO; -using System.Threading; -using MediaBrowser.Common.Logging; - -namespace MediaBrowser.Api.Transcoding -{ - /// - /// Represents an active transcoding job - /// - public class TranscodingJob - { - public string InputFile { get; set; } - public string OutputFile { get; set; } - public string TranscoderPath { get; set; } - public string Arguments { get; set; } - - public TranscoderJobStatus Status { get; private set; } - - /// - /// Starts the job - /// - public void Start() - { - ApiService.AddTranscodingJob(this); - - ProcessStartInfo startInfo = new ProcessStartInfo(); - - startInfo.CreateNoWindow = true; - - startInfo.UseShellExecute = false; - - startInfo.FileName = TranscoderPath; - startInfo.WorkingDirectory = Path.GetDirectoryName(TranscoderPath); - startInfo.Arguments = Arguments; - - Logger.LogInfo("TranscodingJob.Start: " + TranscoderPath + " " + Arguments); - - Process process = new Process(); - - process.StartInfo = startInfo; - - process.EnableRaisingEvents = true; - - process.Start(); - - process.Exited += process_Exited; - } - - void process_Exited(object sender, EventArgs e) - { - ApiService.RemoveTranscodingJob(this); - - Process process = sender as Process; - - // If it terminated with an error - if (process.ExitCode != 0) - { - Status = TranscoderJobStatus.Error; - - // Delete this since it won't be valid - if (File.Exists(OutputFile)) - { - File.Delete(OutputFile); - } - } - else - { - Status = TranscoderJobStatus.Completed; - } - - process.Dispose(); - } - - /// - /// Provides a helper to wait for the job to exit - /// - public void WaitForExit() - { - while (true) - { - TranscoderJobStatus status = Status; - - if (status == TranscoderJobStatus.Completed || status == TranscoderJobStatus.Error) - { - break; - } - - Thread.Sleep(500); - } - } - } - - public enum TranscoderJobStatus - { - Queued, - Started, - Completed, - Error - } -} diff --git a/MediaBrowser.Api/ffmpeg/ffmpeg.exe.REMOVED.git-id b/MediaBrowser.Api/ffmpeg/ffmpeg.exe.REMOVED.git-id index cefa9ca782..cd77f682ac 100644 --- a/MediaBrowser.Api/ffmpeg/ffmpeg.exe.REMOVED.git-id +++ b/MediaBrowser.Api/ffmpeg/ffmpeg.exe.REMOVED.git-id @@ -1 +1 @@ -a9ba5e8a56932043f5fe75db9b4f3b29fe210dbf \ No newline at end of file +56b4040e2eeb431bb27af6d66dc5a9305eb0879f \ No newline at end of file diff --git a/MediaBrowser.Common/Logging/Logger.cs b/MediaBrowser.Common/Logging/Logger.cs index d1ae9b8f35..e66c1d8444 100644 --- a/MediaBrowser.Common/Logging/Logger.cs +++ b/MediaBrowser.Common/Logging/Logger.cs @@ -21,6 +21,11 @@ namespace MediaBrowser.Common.Logging LoggerInstance.LogError(message, paramList); } + public static void LogException(Exception ex, params object[] paramList) + { + LogException(string.Empty, ex, paramList); + } + public static void LogException(string message, Exception ex, params object[] paramList) { LoggerInstance.LogException(message, ex, paramList); diff --git a/MediaBrowser.Common/Net/Handlers/BaseHandler.cs b/MediaBrowser.Common/Net/Handlers/BaseHandler.cs index 5cb476e027..120d2ce7b2 100644 --- a/MediaBrowser.Common/Net/Handlers/BaseHandler.cs +++ b/MediaBrowser.Common/Net/Handlers/BaseHandler.cs @@ -59,7 +59,7 @@ namespace MediaBrowser.Common.Net.Handlers /// /// The original HttpListenerContext /// - protected HttpListenerContext HttpListenerContext { get; private set; } + protected HttpListenerContext HttpListenerContext { get; set; } /// /// The original QueryString @@ -196,7 +196,7 @@ namespace MediaBrowser.Common.Net.Handlers } } - public void ProcessRequest(HttpListenerContext ctx) + public virtual void ProcessRequest(HttpListenerContext ctx) { HttpListenerContext = ctx; diff --git a/MediaBrowser.Common/Net/Handlers/StaticFileHandler.cs b/MediaBrowser.Common/Net/Handlers/StaticFileHandler.cs index 9bffd8e597..35d38fb4a7 100644 --- a/MediaBrowser.Common/Net/Handlers/StaticFileHandler.cs +++ b/MediaBrowser.Common/Net/Handlers/StaticFileHandler.cs @@ -10,12 +10,22 @@ namespace MediaBrowser.Common.Net.Handlers { public class StaticFileHandler : BaseHandler { + private string _Path; public virtual string Path { get { + if (!string.IsNullOrWhiteSpace(_Path)) + { + return _Path; + } + return QueryString["path"]; } + set + { + _Path = value; + } } private bool FileStreamDiscovered = false; diff --git a/MediaBrowser.Controller/Kernel.cs b/MediaBrowser.Controller/Kernel.cs index 7b257e90dc..9f9ce924d2 100644 --- a/MediaBrowser.Controller/Kernel.cs +++ b/MediaBrowser.Controller/Kernel.cs @@ -8,7 +8,6 @@ using System.Text; using System.Threading.Tasks; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Kernel; -using MediaBrowser.Common.Serialization; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Events; using MediaBrowser.Controller.IO;