This commit is contained in:
LukePulverenti Luke Pulverenti luke pulverenti 2012-08-10 23:14:52 -04:00
commit 3919cab092
9 changed files with 132 additions and 285 deletions

View File

@ -1,7 +1,8 @@
using System; using System;
using System.Collections.Generic; using System.IO;
using System.Linq; using System.Linq;
using MediaBrowser.Api.Transcoding; using System.Reflection;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller; using MediaBrowser.Controller;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
@ -12,41 +13,42 @@ namespace MediaBrowser.Api
/// </summary> /// </summary>
public static class ApiService public static class ApiService
{ {
private static string _FFMpegDirectory = null;
/// <summary> /// <summary>
/// Holds the list of active transcoding jobs /// Gets the folder path to ffmpeg
/// </summary> /// </summary>
private static List<TranscodingJob> CurrentTranscodingJobs = new List<TranscodingJob>(); public static string FFMpegDirectory
/// <summary>
/// Finds an active transcoding job
/// </summary>
public static TranscodingJob GetTranscodingJob(string outputPath)
{ {
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;
} }
} }
/// <summary> public static string FFMpegPath
/// Removes a transcoding job from the active list
/// </summary>
public static void RemoveTranscodingJob(TranscodingJob job)
{ {
lock (CurrentTranscodingJobs) get
{ {
CurrentTranscodingJobs.Remove(job); return System.IO.Path.Combine(FFMpegDirectory, "ffmpeg.exe");
}
}
/// <summary>
/// Adds a transcoding job to the active list
/// </summary>
public static void AddTranscodingJob(TranscodingJob job)
{
lock (CurrentTranscodingJobs)
{
CurrentTranscodingJobs.Add(job);
} }
} }

View File

@ -1,16 +1,18 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Reflection; using System.Linq;
using MediaBrowser.Api.Transcoding; using System.Net;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Logging;
using MediaBrowser.Common.Net;
using MediaBrowser.Common.Net.Handlers; using MediaBrowser.Common.Net.Handlers;
using MediaBrowser.Controller; using MediaBrowser.Controller;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
namespace MediaBrowser.Api.HttpHandlers namespace MediaBrowser.Api.HttpHandlers
{ {
public class AudioHandler : StaticFileHandler public class AudioHandler : BaseHandler
{ {
private Audio _LibraryItem; private Audio _LibraryItem;
/// <summary> /// <summary>
@ -34,68 +36,42 @@ namespace MediaBrowser.Api.HttpHandlers
} }
} }
public override string Path public override bool CompressResponse
{ {
get get
{ {
return TranscodedPath; return false;
} }
} }
private string _TranscodedPath; protected override bool IsAsyncHandler
/// <summary>
/// Gets the library item that will be played, if any
/// </summary>
private string TranscodedPath
{ {
get get
{ {
if (_TranscodedPath == null) return true;
{
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 public override string ContentType
{ {
get get
{ {
string val = QueryString["audiobitrate"]; return MimeTypes.GetMimeType("." + GetOutputFormat());
}
}
public IEnumerable<string> AudioFormats
{
get
{
string val = QueryString["audioformat"];
if (string.IsNullOrEmpty(val)) 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 get
{ {
@ -144,87 +120,17 @@ namespace MediaBrowser.Api.HttpHandlers
} }
} }
private static string _StreamsDirectory = null; public override void ProcessRequest(HttpListenerContext ctx)
/// <summary>
/// Gets the folder path to where transcodes will be cached
/// </summary>
public static string StreamsDirectory
{ {
get HttpListenerContext = ctx;
if (!RequiresTranscoding())
{ {
if (_StreamsDirectory == null) new StaticFileHandler() { Path = LibraryItem.Path }.ProcessRequest(ctx);
{ return;
_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(); base.ProcessRequest(ctx);
return System.IO.Path.Combine(StreamsDirectory, filename);
} }
/// <summary> /// <summary>
@ -232,14 +138,8 @@ namespace MediaBrowser.Api.HttpHandlers
/// </summary> /// </summary>
private bool RequiresTranscoding() private bool RequiresTranscoding()
{ {
// Only support skipping transcoding for library items // If it's not in a format the consumer accepts, return true
if (LibraryItem == null) if (!AudioFormats.Any(f => LibraryItem.Path.EndsWith(f, StringComparison.OrdinalIgnoreCase)))
{
return true;
}
// If it's not in the same format, we need to transcode
if (!LibraryItem.Path.EndsWith(AudioFormat, StringComparison.OrdinalIgnoreCase))
{ {
return true; 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 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; return true;
} }
@ -270,29 +170,27 @@ namespace MediaBrowser.Api.HttpHandlers
return true; return true;
} }
} }
// Yay // Yay
return false; return false;
} }
/// <summary> private string GetOutputFormat()
/// Creates a new transcoding job
/// </summary>
private TranscodingJob GetNewTranscodingJob(string input, string output)
{ {
return new TranscodingJob() string format = AudioFormats.FirstOrDefault(f => LibraryItem.Path.EndsWith(f, StringComparison.OrdinalIgnoreCase));
if (!string.IsNullOrWhiteSpace(format))
{ {
InputFile = input, return format;
OutputFile = output, }
TranscoderPath = FFMpegPath,
Arguments = GetAudioArguments(input, output) return AudioFormats.First();
};
} }
/// <summary> /// <summary>
/// Creates arguments to pass to ffmpeg /// Creates arguments to pass to ffmpeg
/// </summary> /// </summary>
private string GetAudioArguments(string input, string output) private string GetAudioArguments()
{ {
List<string> audioTranscodeParams = new List<string>(); List<string> audioTranscodeParams = new List<string>();
@ -301,9 +199,9 @@ namespace MediaBrowser.Api.HttpHandlers
audioTranscodeParams.Add("-ab " + AudioBitRate.Value); audioTranscodeParams.Add("-ab " + AudioBitRate.Value);
} }
if (NumAudioChannels.HasValue) if (AudioChannels.HasValue)
{ {
audioTranscodeParams.Add("-ac " + NumAudioChannels.Value); audioTranscodeParams.Add("-ac " + AudioChannels.Value);
} }
if (AudioSampleRate.HasValue) if (AudioSampleRate.HasValue)
@ -311,9 +209,45 @@ namespace MediaBrowser.Api.HttpHandlers
audioTranscodeParams.Add("-ar " + AudioSampleRate.Value); 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();
}
} }
} }
} }

View File

@ -66,7 +66,6 @@
<Compile Include="ImageProcessor.cs" /> <Compile Include="ImageProcessor.cs" />
<Compile Include="Plugin.cs" /> <Compile Include="Plugin.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Transcoding\TranscodingJob.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj"> <ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj">

View File

@ -1,102 +0,0 @@
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
}
}

View File

@ -1 +1 @@
a9ba5e8a56932043f5fe75db9b4f3b29fe210dbf 56b4040e2eeb431bb27af6d66dc5a9305eb0879f

View File

@ -21,6 +21,11 @@ namespace MediaBrowser.Common.Logging
LoggerInstance.LogError(message, paramList); 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) public static void LogException(string message, Exception ex, params object[] paramList)
{ {
LoggerInstance.LogException(message, ex, paramList); LoggerInstance.LogException(message, ex, paramList);

View File

@ -59,7 +59,7 @@ namespace MediaBrowser.Common.Net.Handlers
/// <summary> /// <summary>
/// The original HttpListenerContext /// The original HttpListenerContext
/// </summary> /// </summary>
protected HttpListenerContext HttpListenerContext { get; private set; } protected HttpListenerContext HttpListenerContext { get; set; }
/// <summary> /// <summary>
/// The original QueryString /// 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; HttpListenerContext = ctx;

View File

@ -10,12 +10,22 @@ namespace MediaBrowser.Common.Net.Handlers
{ {
public class StaticFileHandler : BaseHandler public class StaticFileHandler : BaseHandler
{ {
private string _Path;
public virtual string Path public virtual string Path
{ {
get get
{ {
if (!string.IsNullOrWhiteSpace(_Path))
{
return _Path;
}
return QueryString["path"]; return QueryString["path"];
} }
set
{
_Path = value;
}
} }
private bool FileStreamDiscovered = false; private bool FileStreamDiscovered = false;

View File

@ -8,7 +8,6 @@ using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Kernel; using MediaBrowser.Common.Kernel;
using MediaBrowser.Common.Serialization;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Events; using MediaBrowser.Controller.Events;
using MediaBrowser.Controller.IO; using MediaBrowser.Controller.IO;