From bda2c81dab2a1df11ba853a34e047a5be41e0827 Mon Sep 17 00:00:00 2001 From: LukePulverenti Luke Pulverenti luke pulverenti Date: Fri, 10 Aug 2012 23:13:56 -0400 Subject: [PATCH] Reworked audio transcoding to output directly to response --- MediaBrowser.Api/ApiService.cs | 56 +++-- MediaBrowser.Api/HttpHandlers/AudioHandler.cs | 230 +++++++----------- MediaBrowser.Api/MediaBrowser.Api.csproj | 1 - .../Transcoding/TranscodingJob.cs | 102 -------- MediaBrowser.Controller/Kernel.cs | 1 - 5 files changed, 111 insertions(+), 279 deletions(-) delete mode 100644 MediaBrowser.Api/Transcoding/TranscodingJob.cs 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); + } + } + } + } - /// - /// Removes a transcoding job from the active list - /// - public static void RemoveTranscodingJob(TranscodingJob job) - { - lock (CurrentTranscodingJobs) - { - CurrentTranscodingJobs.Remove(job); + return _FFMpegDirectory; } } - /// - /// Adds a transcoding job to the active list - /// - public static void AddTranscodingJob(TranscodingJob job) + public static string FFMpegPath { - lock (CurrentTranscodingJobs) + get { - 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 true; + } + } - return _TranscodedPath; + public override string ContentType + { + get + { + return MimeTypes.GetMimeType("." + GetOutputFormat()); } } - public string AudioFormat + public IEnumerable AudioFormats { get { - string val = QueryString["audiobitrate"]; + 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 - { - get - { - 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 + public override void ProcessRequest(HttpListenerContext ctx) { - get - { - return System.IO.Path.Combine(FFMpegDirectory, "ffmpeg.exe"); - } - } + HttpListenerContext = ctx; - private string GetOutputFilePath(string input) - { - string hash = Kernel.GetMD5(input).ToString(); - - if (AudioBitRate.HasValue) + if (!RequiresTranscoding()) { - hash += "_ab" + AudioBitRate; + new StaticFileHandler() { Path = LibraryItem.Path }.ProcessRequest(ctx); + return; } - if (NumAudioChannels.HasValue) - { - hash += "_ac" + NumAudioChannels; - } - if (AudioSampleRate.HasValue) - { - hash += "_ar" + AudioSampleRate; - } - - 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 \"" + 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; - return "-i \"" + input + "\" -vn " + string.Join(" ", audioTranscodeParams.ToArray()) + " \"" + output + "\""; + 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.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;