diff options
| author | LukePulverenti Luke Pulverenti luke pulverenti <LukePulverenti Luke Pulverenti luke.pulverenti@gmail.com> | 2012-08-10 11:17:52 -0400 |
|---|---|---|
| committer | LukePulverenti Luke Pulverenti luke pulverenti <LukePulverenti Luke Pulverenti luke.pulverenti@gmail.com> | 2012-08-10 11:17:52 -0400 |
| commit | 92056c4d3d409590b23996eef2795419d4b9617b (patch) | |
| tree | a86fe4b43f7da09c376d56f13512b4073afd6cb7 | |
| parent | 7303c6be3213891330da1dc44e87d44206dd653d (diff) | |
Added audio transcoding
| -rw-r--r-- | MediaBrowser.Api/ApiService.cs | 41 | ||||
| -rw-r--r-- | MediaBrowser.Api/HttpHandlers/AudioHandler.cs | 286 | ||||
| -rw-r--r-- | MediaBrowser.Api/MediaBrowser.Api.csproj | 5 | ||||
| -rw-r--r-- | MediaBrowser.Api/Transcoding/TranscodingJob.cs | 102 | ||||
| -rw-r--r-- | MediaBrowser.Api/ffmpeg/ffmpeg.exe.REMOVED.git-id | 1 |
5 files changed, 426 insertions, 9 deletions
diff --git a/MediaBrowser.Api/ApiService.cs b/MediaBrowser.Api/ApiService.cs index 382d236ae..5945bf25a 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 /// </summary>
public static class ApiService
{
+ /// <summary>
+ /// Holds the list of active transcoding jobs
+ /// </summary>
+ private static List<TranscodingJob> CurrentTranscodingJobs = new List<TranscodingJob>();
+
+ /// <summary>
+ /// Finds an active transcoding job
+ /// </summary>
+ public static TranscodingJob GetTranscodingJob(string outputPath)
+ {
+ lock (CurrentTranscodingJobs)
+ {
+ return CurrentTranscodingJobs.FirstOrDefault(j => j.OutputFile.Equals(outputPath, StringComparison.OrdinalIgnoreCase));
+ }
+ }
+
+ /// <summary>
+ /// Removes a transcoding job from the active list
+ /// </summary>
+ public static void RemoveTranscodingJob(TranscodingJob job)
+ {
+ lock (CurrentTranscodingJobs)
+ {
+ CurrentTranscodingJobs.Remove(job);
+ }
+ }
+
+ /// <summary>
+ /// Adds a transcoding job to the active list
+ /// </summary>
+ 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 8ebaf04bc..76a48308b 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;
/// <summary>
/// Gets the library item that will be played, if any
/// </summary>
- 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;
+ /// <summary>
+ /// Gets the library item that will be played, if any
+ /// </summary>
+ 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;
+ /// <summary>
+ /// Gets the folder path to where transcodes will be cached
+ /// </summary>
+ 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;
+ /// <summary>
+ /// Gets the folder path to ffmpeg
+ /// </summary>
+ 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);
+ }
+
+ /// <summary>
+ /// Determines whether or not the original file requires transcoding
+ /// </summary>
+ 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;
+ }
+
+ /// <summary>
+ /// Creates a new transcoding job
+ /// </summary>
+ private TranscodingJob GetNewTranscodingJob(string input, string output)
+ {
+ return new TranscodingJob()
+ {
+ InputFile = input,
+ OutputFile = output,
+ TranscoderPath = FFMpegPath,
+ Arguments = GetAudioArguments(input, output)
+ };
+ }
+
+ /// <summary>
+ /// Creates arguments to pass to ffmpeg
+ /// </summary>
+ private string GetAudioArguments(string input, string output)
+ {
+ List<string> audioTranscodeParams = new List<string>();
+
+ 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 33c209cd4..bbb8dcbb4 100644 --- a/MediaBrowser.Api/MediaBrowser.Api.csproj +++ b/MediaBrowser.Api/MediaBrowser.Api.csproj @@ -66,6 +66,7 @@ <Compile Include="ImageProcessor.cs" />
<Compile Include="Plugin.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="Transcoding\TranscodingJob.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj">
@@ -84,7 +85,9 @@ <ItemGroup>
<None Include="packages.config" />
</ItemGroup>
- <ItemGroup />
+ <ItemGroup>
+ <EmbeddedResource Include="ffmpeg\ffmpeg.exe" />
+ </ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<PostBuildEvent>xcopy "$(TargetPath)" "$(SolutionDir)\ProgramData\Plugins\$(ProjectName)\" /y</PostBuildEvent>
diff --git a/MediaBrowser.Api/Transcoding/TranscodingJob.cs b/MediaBrowser.Api/Transcoding/TranscodingJob.cs new file mode 100644 index 000000000..e504fec09 --- /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
+{
+ /// <summary>
+ /// Represents an active transcoding job
+ /// </summary>
+ 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; }
+
+ /// <summary>
+ /// Starts the job
+ /// </summary>
+ 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();
+ }
+
+ /// <summary>
+ /// Provides a helper to wait for the job to exit
+ /// </summary>
+ 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 000000000..cefa9ca78 --- /dev/null +++ b/MediaBrowser.Api/ffmpeg/ffmpeg.exe.REMOVED.git-id @@ -0,0 +1 @@ +a9ba5e8a56932043f5fe75db9b4f3b29fe210dbf
\ No newline at end of file |
