Added audio transcoding
This commit is contained in:
parent
7303c6be32
commit
92056c4d3d
|
@ -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);
|
||||
|
|
|
@ -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 LibraryItem.Path;
|
||||
return TranscodedPath;
|
||||
}
|
||||
}
|
||||
|
||||
return base.Path;
|
||||
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 44100;
|
||||
}
|
||||
|
||||
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 + "\"";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
102
MediaBrowser.Api/Transcoding/TranscodingJob.cs
Normal file
102
MediaBrowser.Api/Transcoding/TranscodingJob.cs
Normal file
|
@ -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
|
||||
}
|
||||
}
|
1
MediaBrowser.Api/ffmpeg/ffmpeg.exe.REMOVED.git-id
Normal file
1
MediaBrowser.Api/ffmpeg/ffmpeg.exe.REMOVED.git-id
Normal file
|
@ -0,0 +1 @@
|
|||
a9ba5e8a56932043f5fe75db9b4f3b29fe210dbf
|
Loading…
Reference in New Issue
Block a user