From fbf3916bcec3ff6ba9e34d9756f0583ac90ea0ab Mon Sep 17 00:00:00 2001 From: LukePulverenti Luke Pulverenti luke pulverenti Date: Fri, 10 Aug 2012 09:18:30 -0400 Subject: Added an audio handler --- MediaBrowser.Api/HttpHandlers/AudioHandler.cs | 45 +++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 MediaBrowser.Api/HttpHandlers/AudioHandler.cs (limited to 'MediaBrowser.Api/HttpHandlers/AudioHandler.cs') diff --git a/MediaBrowser.Api/HttpHandlers/AudioHandler.cs b/MediaBrowser.Api/HttpHandlers/AudioHandler.cs new file mode 100644 index 0000000000..8ebaf04bcd --- /dev/null +++ b/MediaBrowser.Api/HttpHandlers/AudioHandler.cs @@ -0,0 +1,45 @@ +using System; +using MediaBrowser.Common.Net.Handlers; +using MediaBrowser.Controller; +using MediaBrowser.Model.Entities; + +namespace MediaBrowser.Api.HttpHandlers +{ + public class AudioHandler : StaticFileHandler + { + private BaseItem _LibraryItem; + /// + /// Gets the library item that will be played, if any + /// + private BaseItem LibraryItem + { + get + { + if (_LibraryItem == null) + { + string id = QueryString["id"]; + + if (!string.IsNullOrEmpty(id)) + { + _LibraryItem = Kernel.Instance.GetItemById(Guid.Parse(id)); + } + } + + return _LibraryItem; + } + } + + public override string Path + { + get + { + if (LibraryItem != null) + { + return LibraryItem.Path; + } + + return base.Path; + } + } + } +} -- cgit v1.2.3 From 92056c4d3d409590b23996eef2795419d4b9617b Mon Sep 17 00:00:00 2001 From: LukePulverenti Luke Pulverenti luke pulverenti Date: Fri, 10 Aug 2012 11:17:52 -0400 Subject: Added audio transcoding --- MediaBrowser.Api/ApiService.cs | 41 +++- MediaBrowser.Api/HttpHandlers/AudioHandler.cs | 286 +++++++++++++++++++++- MediaBrowser.Api/MediaBrowser.Api.csproj | 5 +- MediaBrowser.Api/Transcoding/TranscodingJob.cs | 102 ++++++++ MediaBrowser.Api/ffmpeg/ffmpeg.exe.REMOVED.git-id | 1 + 5 files changed, 426 insertions(+), 9 deletions(-) create mode 100644 MediaBrowser.Api/Transcoding/TranscodingJob.cs create mode 100644 MediaBrowser.Api/ffmpeg/ffmpeg.exe.REMOVED.git-id (limited to 'MediaBrowser.Api/HttpHandlers/AudioHandler.cs') diff --git a/MediaBrowser.Api/ApiService.cs b/MediaBrowser.Api/ApiService.cs index 382d236ae7..5945bf25ad 100644 --- a/MediaBrowser.Api/ApiService.cs +++ b/MediaBrowser.Api/ApiService.cs @@ -1,10 +1,9 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Runtime.Serialization; +using MediaBrowser.Api.Transcoding; using MediaBrowser.Controller; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Users; namespace MediaBrowser.Api { @@ -13,6 +12,44 @@ namespace MediaBrowser.Api /// public static class ApiService { + /// + /// Holds the list of active transcoding jobs + /// + private static List CurrentTranscodingJobs = new List(); + + /// + /// Finds an active transcoding job + /// + public static TranscodingJob GetTranscodingJob(string outputPath) + { + lock (CurrentTranscodingJobs) + { + return CurrentTranscodingJobs.FirstOrDefault(j => j.OutputFile.Equals(outputPath, StringComparison.OrdinalIgnoreCase)); + } + } + + /// + /// Removes a transcoding job from the active list + /// + public static void RemoveTranscodingJob(TranscodingJob job) + { + lock (CurrentTranscodingJobs) + { + CurrentTranscodingJobs.Remove(job); + } + } + + /// + /// Adds a transcoding job to the active list + /// + public static void AddTranscodingJob(TranscodingJob job) + { + lock (CurrentTranscodingJobs) + { + CurrentTranscodingJobs.Add(job); + } + } + public static BaseItem GetItemById(string id) { Guid guid = string.IsNullOrEmpty(id) ? Guid.Empty : new Guid(id); diff --git a/MediaBrowser.Api/HttpHandlers/AudioHandler.cs b/MediaBrowser.Api/HttpHandlers/AudioHandler.cs index 8ebaf04bcd..76a48308b9 100644 --- a/MediaBrowser.Api/HttpHandlers/AudioHandler.cs +++ b/MediaBrowser.Api/HttpHandlers/AudioHandler.cs @@ -1,4 +1,9 @@ using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using MediaBrowser.Api.Transcoding; +using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Net.Handlers; using MediaBrowser.Controller; using MediaBrowser.Model.Entities; @@ -7,11 +12,11 @@ namespace MediaBrowser.Api.HttpHandlers { public class AudioHandler : StaticFileHandler { - private BaseItem _LibraryItem; + private Audio _LibraryItem; /// /// Gets the library item that will be played, if any /// - private BaseItem LibraryItem + private Audio LibraryItem { get { @@ -21,7 +26,7 @@ namespace MediaBrowser.Api.HttpHandlers if (!string.IsNullOrEmpty(id)) { - _LibraryItem = Kernel.Instance.GetItemById(Guid.Parse(id)); + _LibraryItem = Kernel.Instance.GetItemById(Guid.Parse(id)) as Audio; } } @@ -33,13 +38,282 @@ namespace MediaBrowser.Api.HttpHandlers { get { - if (LibraryItem != null) + return TranscodedPath; + } + } + + private string _TranscodedPath; + /// + /// Gets the library item that will be played, if any + /// + private string TranscodedPath + { + 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; + } + } + + public string AudioFormat + { + get + { + string val = QueryString["audiobitrate"]; + + if (string.IsNullOrEmpty(val)) + { + return "mp3"; + } + + return val; + } + } + + public int? AudioBitRate + { + get + { + string val = QueryString["audiobitrate"]; + + if (string.IsNullOrEmpty(val)) + { + return null; + } + + return int.Parse(val); + } + } + + public int? NumAudioChannels + { + get + { + string val = QueryString["audiochannels"]; + + if (string.IsNullOrEmpty(val)) + { + return null; + } + + return int.Parse(val); + } + } + + public int? AudioSampleRate + { + get + { + string val = QueryString["audiosamplerate"]; + + if (string.IsNullOrEmpty(val)) { - return LibraryItem.Path; + return 44100; } - return base.Path; + return int.Parse(val); } } + + 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 + { + 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; + } + + string filename = hash + "." + AudioFormat.ToLower(); + + return System.IO.Path.Combine(StreamsDirectory, filename); + } + + /// + /// Determines whether or not the original file requires transcoding + /// + 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)) + { + return true; + } + + // If the bitrate is greater than our desired bitrate, we need to transcode + if (AudioBitRate.HasValue) + { + if (AudioBitRate.Value < LibraryItem.BitRate) + { + return true; + } + } + + // If the number of channels is greater than our desired channels, we need to transcode + if (NumAudioChannels.HasValue) + { + if (NumAudioChannels.Value < LibraryItem.Channels) + { + return true; + } + } + + // If the sample rate is greater than our desired sample rate, we need to transcode + if (AudioSampleRate.HasValue) + { + if (AudioSampleRate.Value < LibraryItem.SampleRate) + { + return true; + } + } + + // Yay + return false; + } + + /// + /// Creates a new transcoding job + /// + private TranscodingJob GetNewTranscodingJob(string input, string output) + { + return new TranscodingJob() + { + InputFile = input, + OutputFile = output, + TranscoderPath = FFMpegPath, + Arguments = GetAudioArguments(input, output) + }; + } + + /// + /// Creates arguments to pass to ffmpeg + /// + private string GetAudioArguments(string input, string output) + { + List audioTranscodeParams = new List(); + + if (AudioBitRate.HasValue) + { + audioTranscodeParams.Add("-ab " + AudioBitRate.Value); + } + + if (NumAudioChannels.HasValue) + { + audioTranscodeParams.Add("-ac " + NumAudioChannels.Value); + } + + if (AudioSampleRate.HasValue) + { + audioTranscodeParams.Add("-ar " + AudioSampleRate.Value); + } + + audioTranscodeParams.Add("-f " + AudioFormat); + + return "-i \"" + input + "\" -vn " + string.Join(" ", audioTranscodeParams.ToArray()) + " \"" + output + "\""; + } } } diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj index 33c209cd43..bbb8dcbb43 100644 --- a/MediaBrowser.Api/MediaBrowser.Api.csproj +++ b/MediaBrowser.Api/MediaBrowser.Api.csproj @@ -66,6 +66,7 @@ + @@ -84,7 +85,9 @@ - + + + xcopy "$(TargetPath)" "$(SolutionDir)\ProgramData\Plugins\$(ProjectName)\" /y diff --git a/MediaBrowser.Api/Transcoding/TranscodingJob.cs b/MediaBrowser.Api/Transcoding/TranscodingJob.cs new file mode 100644 index 0000000000..e504fec095 --- /dev/null +++ b/MediaBrowser.Api/Transcoding/TranscodingJob.cs @@ -0,0 +1,102 @@ +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 new file mode 100644 index 0000000000..cefa9ca782 --- /dev/null +++ b/MediaBrowser.Api/ffmpeg/ffmpeg.exe.REMOVED.git-id @@ -0,0 +1 @@ +a9ba5e8a56932043f5fe75db9b4f3b29fe210dbf \ No newline at end of file -- cgit v1.2.3 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: 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 - MediaBrowser.Api/Transcoding/TranscodingJob.cs | 102 ----------- MediaBrowser.Controller/Kernel.cs | 1 - 5 files changed, 111 insertions(+), 279 deletions(-) delete mode 100644 MediaBrowser.Api/Transcoding/TranscodingJob.cs (limited to 'MediaBrowser.Api/HttpHandlers/AudioHandler.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; -- cgit v1.2.3 From 3d73e2ce62429dd596b2c43321b8679525789610 Mon Sep 17 00:00:00 2001 From: LukePulverenti Luke Pulverenti luke pulverenti Date: Fri, 10 Aug 2012 23:19:11 -0400 Subject: Property typo fix --- MediaBrowser.Api/HttpHandlers/AudioHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'MediaBrowser.Api/HttpHandlers/AudioHandler.cs') diff --git a/MediaBrowser.Api/HttpHandlers/AudioHandler.cs b/MediaBrowser.Api/HttpHandlers/AudioHandler.cs index 5bfbfd1a45..a4afe1a1db 100644 --- a/MediaBrowser.Api/HttpHandlers/AudioHandler.cs +++ b/MediaBrowser.Api/HttpHandlers/AudioHandler.cs @@ -64,7 +64,7 @@ namespace MediaBrowser.Api.HttpHandlers { get { - string val = QueryString["audioformat"]; + string val = QueryString["audioformats"]; if (string.IsNullOrEmpty(val)) { -- cgit v1.2.3 From 24d2c441b3d265026ee77297ea4b7a3ffb47918b Mon Sep 17 00:00:00 2001 From: LukePulverenti Luke Pulverenti luke pulverenti Date: Sat, 11 Aug 2012 14:07:07 -0400 Subject: Re-worked async actions in BaseHandler, and changed AudioBitRate to AudioBitRates. --- MediaBrowser.Api/HttpHandlers/AudioHandler.cs | 244 ++++++++++----------- MediaBrowser.Api/HttpHandlers/ImageHandler.cs | 8 +- MediaBrowser.Api/HttpHandlers/JsonHandler.cs | 8 +- MediaBrowser.Api/HttpHandlers/VideoHandler.cs | 62 ++++++ MediaBrowser.Api/MediaBrowser.Api.csproj | 4 + MediaBrowser.Api/Plugin.cs | 4 + MediaBrowser.Api/ffmpeg/readme.txt | 3 + .../Net/Handlers/BaseEmbeddedResourceHandler.cs | 5 +- MediaBrowser.Common/Net/Handlers/BaseHandler.cs | 37 ++-- .../Net/Handlers/StaticFileHandler.cs | 12 +- 10 files changed, 224 insertions(+), 163 deletions(-) create mode 100644 MediaBrowser.Api/HttpHandlers/VideoHandler.cs create mode 100644 MediaBrowser.Api/ffmpeg/readme.txt (limited to 'MediaBrowser.Api/HttpHandlers/AudioHandler.cs') diff --git a/MediaBrowser.Api/HttpHandlers/AudioHandler.cs b/MediaBrowser.Api/HttpHandlers/AudioHandler.cs index a4afe1a1db..61f6e7fbc7 100644 --- a/MediaBrowser.Api/HttpHandlers/AudioHandler.cs +++ b/MediaBrowser.Api/HttpHandlers/AudioHandler.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.IO; using System.Linq; using System.Net; +using System.Threading.Tasks; using MediaBrowser.Common.Logging; using MediaBrowser.Common.Net; using MediaBrowser.Common.Net.Handlers; @@ -12,54 +13,8 @@ using MediaBrowser.Model.Entities; namespace MediaBrowser.Api.HttpHandlers { - public class AudioHandler : BaseHandler + public class AudioHandler : BaseMediaHandler protected override string GetOutputFormat() { - return VideoFormats.First(); + return VideoFormats.First(f => !UnsupportedOutputFormats.Any(s => s.Equals(f, StringComparison.OrdinalIgnoreCase))); } protected override bool RequiresConversion() { + // If it's not in a format we can output to, return true + if (UnsupportedOutputFormats.Any(f => LibraryItem.Path.EndsWith(f, StringComparison.OrdinalIgnoreCase))) + { + return true; + } + // If it's not in a format the consumer accepts, return true if (!VideoFormats.Any(f => LibraryItem.Path.EndsWith(f, StringComparison.OrdinalIgnoreCase))) { return true; } - + AudioStream audio = LibraryItem.AudioStreams.FirstOrDefault(); if (audio != null) @@ -54,9 +54,16 @@ namespace MediaBrowser.Api.HttpHandlers return false; } - protected override Task WriteResponseToOutputStream(Stream stream) + /// + /// Creates arguments to pass to ffmpeg + /// + protected override string GetCommandLineArguments() { - throw new NotImplementedException(); + List audioTranscodeParams = new List(); + + string outputFormat = GetOutputFormat(); + outputFormat = "matroska"; + return "-i \"" + LibraryItem.Path + "\" -vcodec copy -acodec copy -f " + outputFormat + " -"; } } } diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj index c628d01d90..dafce95104 100644 --- a/MediaBrowser.Api/MediaBrowser.Api.csproj +++ b/MediaBrowser.Api/MediaBrowser.Api.csproj @@ -86,10 +86,10 @@ - + - + diff --git a/MediaBrowser.Controller/Kernel.cs b/MediaBrowser.Controller/Kernel.cs index 7841a2a5ee..8eed678ba3 100644 --- a/MediaBrowser.Controller/Kernel.cs +++ b/MediaBrowser.Controller/Kernel.cs @@ -5,7 +5,6 @@ using System.IO; using System.Linq; using System.Security.Cryptography; using System.Text; -using System.Threading.Tasks; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Kernel; using MediaBrowser.Controller.Configuration; @@ -15,8 +14,8 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Resolvers; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Users; using MediaBrowser.Model.Progress; +using MediaBrowser.Model.Users; namespace MediaBrowser.Controller { diff --git a/MediaBrowser.sln b/MediaBrowser.sln index 228deb5e5c..4245157488 100644 --- a/MediaBrowser.sln +++ b/MediaBrowser.sln @@ -77,4 +77,7 @@ Global GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(Performance) = preSolution + HasPerformanceSessions = true + EndGlobalSection EndGlobal -- cgit v1.2.3 From 3bb0ae0f13fc9b529f3ad63a28d81039440667ca Mon Sep 17 00:00:00 2001 From: LukePulverenti Luke Pulverenti luke pulverenti Date: Sun, 12 Aug 2012 09:59:09 -0400 Subject: Updated BaseMediaHandler to output the ffmpeg log to our log directory. Made more progress on VideoHandler. --- MediaBrowser.Api/HttpHandlers/AudioHandler.cs | 14 +++++- MediaBrowser.Api/HttpHandlers/VideoHandler.cs | 72 ++++++++++++++++++++++++++- 2 files changed, 83 insertions(+), 3 deletions(-) (limited to 'MediaBrowser.Api/HttpHandlers/AudioHandler.cs') diff --git a/MediaBrowser.Api/HttpHandlers/AudioHandler.cs b/MediaBrowser.Api/HttpHandlers/AudioHandler.cs index 294a5b7afc..51d7ba06ae 100644 --- a/MediaBrowser.Api/HttpHandlers/AudioHandler.cs +++ b/MediaBrowser.Api/HttpHandlers/AudioHandler.cs @@ -5,6 +5,7 @@ using System.IO; using System.Linq; using System.Net; using System.Threading.Tasks; +using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Logging; using MediaBrowser.Common.Net; using MediaBrowser.Common.Net.Handlers; @@ -241,16 +242,26 @@ namespace MediaBrowser.Api.HttpHandlers Process process = new Process(); process.StartInfo = startInfo; + // FFMpeg writes debug info to StdErr. This is useful when debugging so let's put it in the log directory. + FileStream logStream = new FileStream(Path.Combine(ApplicationPaths.LogDirectoryPath, "ffmpeg-" + Guid.NewGuid().ToString() + ".txt"), FileMode.Create); + try { process.Start(); // MUST read both stdout and stderr asynchronously or a deadlock may occurr - process.BeginErrorReadLine(); + // If we ever decide to disable the ffmpeg log then you must uncomment the below line. + //process.BeginErrorReadLine(); + + Task errorTask = Task.Run(async () => { await process.StandardError.BaseStream.CopyToAsync(logStream); }); await process.StandardOutput.BaseStream.CopyToAsync(stream); process.WaitForExit(); + + await errorTask; + + Logger.LogInfo("FFMpeg exited with code " + process.ExitCode); } catch (Exception ex) { @@ -258,6 +269,7 @@ namespace MediaBrowser.Api.HttpHandlers } finally { + logStream.Dispose(); process.Dispose(); } } diff --git a/MediaBrowser.Api/HttpHandlers/VideoHandler.cs b/MediaBrowser.Api/HttpHandlers/VideoHandler.cs index 1b5c50c38a..0b556b886a 100644 --- a/MediaBrowser.Api/HttpHandlers/VideoHandler.cs +++ b/MediaBrowser.Api/HttpHandlers/VideoHandler.cs @@ -54,6 +54,21 @@ namespace MediaBrowser.Api.HttpHandlers return false; } + private string GetFFMpegOutputFormat(string outputFormat) + { + if (outputFormat.Equals("mkv", StringComparison.OrdinalIgnoreCase)) + { + return "matroska"; + } + + return outputFormat; + } + + private int GetOutputAudioStreamIndex(string outputFormat) + { + return 0; + } + /// /// Creates arguments to pass to ffmpeg /// @@ -62,8 +77,61 @@ namespace MediaBrowser.Api.HttpHandlers List audioTranscodeParams = new List(); string outputFormat = GetOutputFormat(); - outputFormat = "matroska"; - return "-i \"" + LibraryItem.Path + "\" -vcodec copy -acodec copy -f " + outputFormat + " -"; + + int audioStreamIndex = GetOutputAudioStreamIndex(outputFormat); + + List maps = new List(); + + // Add the video stream + maps.Add("-map 0:0"); + + // Add the audio stream + if (audioStreamIndex != -1) + { + maps.Add("-map 0:" + (1 + audioStreamIndex)); + } + + // Add all the subtitle streams + for (int i = 0; i < LibraryItem.Subtitles.Count(); i++) + { + maps.Add("-map 0:" + (1 + LibraryItem.AudioStreams.Count() + i)); + + } + + return string.Format("-i \"{0}\" {1} {2} {3} -f {4} -", + LibraryItem.Path, + string.Join(" ", maps.ToArray()), + GetVideoArguments(), + GetAudioArguments(), + GetFFMpegOutputFormat(outputFormat) + ); + } + + private string GetVideoArguments() + { + return "-c:v copy"; + } + + private string GetAudioArguments() + { + return "-c:a copy"; + } + + private string GetSubtitleArguments() + { + string args = ""; + + for (int i = 0; i < LibraryItem.Subtitles.Count(); i++) + { + if (i > 0) + { + args += " "; + } + args += "-c:s copy"; + + } + + return args; } } } -- cgit v1.2.3 From 55aa5cb87388a7422831e795d30ef457395f5267 Mon Sep 17 00:00:00 2001 From: LukePulverenti Luke Pulverenti luke pulverenti Date: Sun, 12 Aug 2012 11:41:40 -0400 Subject: Added more audio streaming improvements and extracted BaseMediaHandler --- MediaBrowser.Api/HttpHandlers/AudioHandler.cs | 157 +------------------- MediaBrowser.Api/HttpHandlers/BaseMediaHandler.cs | 165 ++++++++++++++++++++++ MediaBrowser.Api/MediaBrowser.Api.csproj | 1 + MediaBrowser.Common/Net/MimeTypes.cs | 8 ++ 4 files changed, 180 insertions(+), 151 deletions(-) create mode 100644 MediaBrowser.Api/HttpHandlers/BaseMediaHandler.cs (limited to 'MediaBrowser.Api/HttpHandlers/AudioHandler.cs') diff --git a/MediaBrowser.Api/HttpHandlers/AudioHandler.cs b/MediaBrowser.Api/HttpHandlers/AudioHandler.cs index 51d7ba06ae..c3c27c568c 100644 --- a/MediaBrowser.Api/HttpHandlers/AudioHandler.cs +++ b/MediaBrowser.Api/HttpHandlers/AudioHandler.cs @@ -1,21 +1,16 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Linq; -using System.Net; -using System.Threading.Tasks; -using MediaBrowser.Common.Configuration; -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 : BaseMediaHandler