display additional transcoding info in dashboard

This commit is contained in:
Luke Pulverenti
2014-06-05 20:39:02 -04:00
parent 7049ad66f4
commit f7cd7182d5
15 changed files with 260 additions and 74 deletions

View File

@@ -1,5 +1,7 @@
using MediaBrowser.Controller;
using MediaBrowser.Api.Playback;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Plugins;
using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Logging;
using System;
using System.Collections.Generic;
@@ -9,6 +11,7 @@ using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.Session;
namespace MediaBrowser.Api
{
@@ -33,15 +36,18 @@ namespace MediaBrowser.Api
/// </summary>
private readonly IServerApplicationPaths _appPaths;
private readonly ISessionManager _sessionManager;
/// <summary>
/// Initializes a new instance of the <see cref="ApiEntryPoint" /> class.
/// </summary>
/// <param name="logger">The logger.</param>
/// <param name="appPaths">The application paths.</param>
public ApiEntryPoint(ILogger logger, IServerApplicationPaths appPaths)
public ApiEntryPoint(ILogger logger, IServerApplicationPaths appPaths, ISessionManager sessionManager)
{
Logger = logger;
_appPaths = appPaths;
_sessionManager = sessionManager;
Instance = this;
}
@@ -115,10 +121,16 @@ namespace MediaBrowser.Api
/// <param name="type">The type.</param>
/// <param name="process">The process.</param>
/// <param name="startTimeTicks">The start time ticks.</param>
/// <param name="sourcePath">The source path.</param>
/// <param name="deviceId">The device id.</param>
/// <param name="state">The state.</param>
/// <param name="cancellationTokenSource">The cancellation token source.</param>
public void OnTranscodeBeginning(string path, TranscodingJobType type, Process process, long? startTimeTicks, string sourcePath, string deviceId, CancellationTokenSource cancellationTokenSource)
public void OnTranscodeBeginning(string path,
TranscodingJobType type,
Process process,
long? startTimeTicks,
string deviceId,
StreamState state,
CancellationTokenSource cancellationTokenSource)
{
lock (_activeTranscodingJobs)
{
@@ -129,10 +141,43 @@ namespace MediaBrowser.Api
Process = process,
ActiveRequestCount = 1,
StartTimeTicks = startTimeTicks,
SourcePath = sourcePath,
DeviceId = deviceId,
CancellationTokenSource = cancellationTokenSource
});
ReportTranscodingProgress(state, null, null);
}
}
public void ReportTranscodingProgress(StreamState state, float? framerate, double? percentComplete)
{
var deviceId = state.Request.DeviceId;
if (!string.IsNullOrWhiteSpace(deviceId))
{
var audioCodec = state.Request.AudioCodec;
var videoCodec = state.VideoRequest == null ? null : state.VideoRequest.VideoCodec;
if (string.Equals(state.OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase) ||
string.IsNullOrEmpty(audioCodec))
{
audioCodec = state.OutputAudioCodec;
}
if (string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase) ||
string.IsNullOrEmpty(videoCodec))
{
videoCodec = state.OutputVideoCodec;
}
_sessionManager.ReportTranscodingInfo(deviceId, new TranscodingInfo
{
Bitrate = state.TotalOutputBitrate,
AudioCodec = audioCodec,
VideoCodec = videoCodec,
Container = state.OutputContainer,
Framerate = framerate,
CompletionPercentage = percentComplete
});
}
}
@@ -144,7 +189,8 @@ namespace MediaBrowser.Api
/// </summary>
/// <param name="path">The path.</param>
/// <param name="type">The type.</param>
public void OnTranscodeFailedToStart(string path, TranscodingJobType type)
/// <param name="state">The state.</param>
public void OnTranscodeFailedToStart(string path, TranscodingJobType type, StreamState state)
{
lock (_activeTranscodingJobs)
{
@@ -152,6 +198,11 @@ namespace MediaBrowser.Api
_activeTranscodingJobs.Remove(job);
}
if (!string.IsNullOrWhiteSpace(state.Request.DeviceId))
{
_sessionManager.ClearTranscodingInfo(state.Request.DeviceId);
}
}
/// <summary>
@@ -437,7 +488,6 @@ namespace MediaBrowser.Api
public Timer KillTimer { get; set; }
public long? StartTimeTicks { get; set; }
public string SourcePath { get; set; }
public string DeviceId { get; set; }
public CancellationTokenSource CancellationTokenSource { get; set; }

View File

@@ -126,7 +126,7 @@ namespace MediaBrowser.Api.Playback
/// </summary>
/// <param name="state">The state.</param>
/// <returns>System.String.</returns>
protected string GetOutputFilePath(StreamState state)
private string GetOutputFilePath(StreamState state)
{
var folder = ServerConfigurationManager.ApplicationPaths.TranscodingTempPath;
@@ -726,12 +726,13 @@ namespace MediaBrowser.Api.Playback
/// </summary>
/// <param name="request">The request.</param>
/// <param name="audioStream">The audio stream.</param>
/// <param name="outputAudioCodec">The output audio codec.</param>
/// <returns>System.Nullable{System.Int32}.</returns>
private int? GetNumAudioChannelsParam(StreamRequest request, MediaStream audioStream)
private int? GetNumAudioChannelsParam(StreamRequest request, MediaStream audioStream, string outputAudioCodec)
{
if (audioStream != null)
{
var codec = request.AudioCodec ?? string.Empty;
var codec = outputAudioCodec ?? string.Empty;
if (audioStream.Channels > 2 && codec.IndexOf("wma", StringComparison.OrdinalIgnoreCase) != -1)
{
@@ -769,7 +770,7 @@ namespace MediaBrowser.Api.Playback
/// </summary>
/// <param name="request">The request.</param>
/// <returns>System.String.</returns>
protected string GetAudioCodec(StreamRequest request)
private string GetAudioCodec(StreamRequest request)
{
var codec = request.AudioCodec;
@@ -798,7 +799,7 @@ namespace MediaBrowser.Api.Playback
/// </summary>
/// <param name="request">The request.</param>
/// <returns>System.String.</returns>
protected string GetVideoCodec(VideoStreamRequest request)
private string GetVideoCodec(VideoStreamRequest request)
{
var codec = request.VideoCodec;
@@ -866,7 +867,7 @@ namespace MediaBrowser.Api.Playback
Directory.CreateDirectory(Path.GetDirectoryName(outputPath));
if (state.IsInputVideo && state.VideoType == VideoType.Iso && state.IsoType.HasValue && IsoManager.CanMount(state.MediaPath))
if (state.VideoType == VideoType.Iso && state.IsoType.HasValue && IsoManager.CanMount(state.MediaPath))
{
state.IsoMount = await IsoManager.Mount(state.MediaPath, cancellationTokenSource.Token).ConfigureAwait(false);
}
@@ -900,7 +901,13 @@ namespace MediaBrowser.Api.Playback
EnableRaisingEvents = true
};
ApiEntryPoint.Instance.OnTranscodeBeginning(outputPath, TranscodingJobType, process, state.Request.StartTimeTicks, state.MediaPath, state.Request.DeviceId, cancellationTokenSource);
ApiEntryPoint.Instance.OnTranscodeBeginning(outputPath,
TranscodingJobType,
process,
state.Request.StartTimeTicks,
state.Request.DeviceId,
state,
cancellationTokenSource);
var commandLineLogMessage = process.StartInfo.FileName + " " + process.StartInfo.Arguments;
Logger.Info(commandLineLogMessage);
@@ -924,7 +931,7 @@ namespace MediaBrowser.Api.Playback
{
Logger.ErrorException("Error starting ffmpeg", ex);
ApiEntryPoint.Instance.OnTranscodeFailedToStart(outputPath, TranscodingJobType);
ApiEntryPoint.Instance.OnTranscodeFailedToStart(outputPath, TranscodingJobType, state);
throw;
}
@@ -932,10 +939,8 @@ namespace MediaBrowser.Api.Playback
// MUST read both stdout and stderr asynchronously or a deadlock may occurr
process.BeginOutputReadLine();
#pragma warning disable 4014
// Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback
process.StandardError.BaseStream.CopyToAsync(state.LogFileStream);
#pragma warning restore 4014
StartStreamingLog(state, process.StandardError.BaseStream, state.LogFileStream);
// Wait for the file to exist before proceeeding
while (!File.Exists(outputPath))
@@ -956,6 +961,82 @@ namespace MediaBrowser.Api.Playback
}
}
private async void StartStreamingLog(StreamState state, Stream source, Stream target)
{
try
{
using (var reader = new StreamReader(source))
{
while (!reader.EndOfStream)
{
var line = await reader.ReadLineAsync().ConfigureAwait(false);
ParseLogLine(line, state);
var bytes = Encoding.UTF8.GetBytes(Environment.NewLine + line);
await target.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
}
}
}
catch (Exception ex)
{
Logger.ErrorException("Error reading ffmpeg log", ex);
}
}
private void ParseLogLine(string line, StreamState state)
{
float? framerate = null;
double? percent = null;
var parts = line.Split(' ');
var totalMs = state.RunTimeTicks.HasValue
? TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalMilliseconds
: 0;
var startMs = state.Request.StartTimeTicks.HasValue
? TimeSpan.FromTicks(state.Request.StartTimeTicks.Value).TotalMilliseconds
: 0;
for (var i = 0; i < parts.Length; i++)
{
var part = parts[i];
if (string.Equals(part, "fps=", StringComparison.OrdinalIgnoreCase) &&
(i + 1 < parts.Length))
{
var rate = parts[i + 1];
float val;
if (float.TryParse(rate, NumberStyles.Any, UsCulture, out val))
{
framerate = val;
}
}
else if (state.RunTimeTicks.HasValue &&
part.StartsWith("time=", StringComparison.OrdinalIgnoreCase))
{
var time = part.Split(new[] { '=' }, 2).Last();
TimeSpan val;
if (TimeSpan.TryParse(time, UsCulture, out val))
{
var currentMs = startMs + val.TotalMilliseconds;
var percentVal = currentMs / totalMs;
percent = 100 * percentVal;
}
}
}
if (framerate.HasValue || percent.HasValue)
{
ApiEntryPoint.Instance.ReportTranscodingProgress(state, framerate, percent);
}
}
private int? GetVideoBitrateParamValue(VideoStreamRequest request, MediaStream videoStream)
{
var bitrate = request.VideoBitRate;
@@ -1500,23 +1581,27 @@ namespace MediaBrowser.Api.Playback
state.OutputAudioBitrate = GetAudioBitrateParam(state.Request, state.AudioStream);
state.OutputAudioSampleRate = request.AudioSampleRate;
state.OutputAudioChannels = GetNumAudioChannelsParam(state.Request, state.AudioStream);
state.OutputAudioCodec = GetAudioCodec(state.Request);
if (videoRequest != null)
{
state.OutputVideoCodec = GetVideoCodec(videoRequest);
state.OutputVideoBitrate = GetVideoBitrateParamValue(state.VideoRequest, state.VideoStream);
if (state.VideoStream != null && CanStreamCopyVideo(videoRequest, state.VideoStream))
{
videoRequest.VideoCodec = "copy";
state.OutputVideoCodec = "copy";
}
if (state.AudioStream != null && CanStreamCopyAudio(request, state.AudioStream, state.SupportedAudioCodecs))
{
request.AudioCodec = "copy";
state.OutputAudioCodec = "copy";
}
}
state.OutputFilePath = GetOutputFilePath(state);
return state;
}
@@ -1729,14 +1814,14 @@ namespace MediaBrowser.Api.Playback
return;
}
var audioCodec = state.Request.AudioCodec;
var audioCodec = state.OutputAudioCodec;
if (string.Equals(audioCodec, "copy", StringComparison.OrdinalIgnoreCase) && state.AudioStream != null)
{
audioCodec = state.AudioStream.Codec;
}
var videoCodec = state.VideoRequest == null ? null : state.VideoRequest.VideoCodec;
var videoCodec = state.OutputVideoCodec;
if (string.Equals(videoCodec, "copy", StringComparison.OrdinalIgnoreCase) && state.VideoStream != null)
{
@@ -1807,7 +1892,12 @@ namespace MediaBrowser.Api.Playback
profile = DlnaManager.GetDefaultProfile();
}
var audioCodec = state.Request.AudioCodec;
var audioCodec = state.OutputAudioCodec;
if (string.Equals(audioCodec, "copy", StringComparison.OrdinalIgnoreCase) && state.AudioStream != null)
{
audioCodec = state.AudioStream.Codec;
}
if (state.VideoRequest == null)
{
@@ -1825,12 +1915,7 @@ namespace MediaBrowser.Api.Playback
}
else
{
if (string.Equals(audioCodec, "copy", StringComparison.OrdinalIgnoreCase) && state.AudioStream != null)
{
audioCodec = state.AudioStream.Codec;
}
var videoCodec = state.VideoRequest == null ? null : state.VideoRequest.VideoCodec;
var videoCodec = state.OutputVideoCodec;
if (string.Equals(videoCodec, "copy", StringComparison.OrdinalIgnoreCase) && state.VideoStream != null)
{

View File

@@ -86,7 +86,7 @@ namespace MediaBrowser.Api.Playback.Hls
var state = GetState(request, cancellationTokenSource.Token).Result;
var playlist = GetOutputFilePath(state);
var playlist = state.OutputFilePath;
if (File.Exists(playlist))
{
@@ -307,7 +307,7 @@ namespace MediaBrowser.Api.Playback.Hls
if (hlsVideoRequest != null)
{
if (hlsVideoRequest.AppendBaselineStream && state.IsInputVideo)
if (hlsVideoRequest.AppendBaselineStream)
{
var lowBitratePath = Path.Combine(Path.GetDirectoryName(outputPath), Path.GetFileNameWithoutExtension(outputPath) + "-low.m3u8");

View File

@@ -89,7 +89,7 @@ namespace MediaBrowser.Api.Playback.Hls
var state = await GetState(request, CancellationToken.None).ConfigureAwait(false);
var playlistPath = Path.ChangeExtension(GetOutputFilePath(state), ".m3u8");
var playlistPath = Path.ChangeExtension(state.OutputFilePath, ".m3u8");
var path = GetSegmentPath(playlistPath, index);
@@ -231,7 +231,7 @@ namespace MediaBrowser.Api.Playback.Hls
protected override string GetAudioArguments(StreamState state)
{
var codec = GetAudioCodec(state.Request);
var codec = state.OutputAudioCodec;
if (codec.Equals("copy", StringComparison.OrdinalIgnoreCase))
{
@@ -266,7 +266,7 @@ namespace MediaBrowser.Api.Playback.Hls
protected override string GetVideoArguments(StreamState state, bool performSubtitleConversion)
{
var codec = GetVideoCodec(state.VideoRequest);
var codec = state.OutputVideoCodec;
// See if we can save come cpu cycles by avoiding encoding
if (codec.Equals("copy", StringComparison.OrdinalIgnoreCase))

View File

@@ -118,7 +118,7 @@ namespace MediaBrowser.Api.Playback.Hls
/// <returns>System.String.</returns>
protected override string GetAudioArguments(StreamState state)
{
var codec = GetAudioCodec(state.Request);
var codec = state.OutputAudioCodec;
if (codec.Equals("copy", StringComparison.OrdinalIgnoreCase))
{
@@ -160,7 +160,7 @@ namespace MediaBrowser.Api.Playback.Hls
protected override string GetVideoArguments(StreamState state,
bool performSubtitleConversion)
{
var codec = GetVideoCodec(state.VideoRequest);
var codec = state.OutputVideoCodec;
// See if we can save come cpu cycles by avoiding encoding
if (codec.Equals("copy", StringComparison.OrdinalIgnoreCase))

View File

@@ -78,8 +78,6 @@ namespace MediaBrowser.Api.Playback.Progressive
/// <exception cref="System.InvalidOperationException">Only aac and mp3 audio codecs are supported.</exception>
protected override string GetCommandLineArguments(string outputPath, StreamState state, bool performSubtitleConversions)
{
var request = state.Request;
var audioTranscodeParams = new List<string>();
var bitrate = state.OutputAudioBitrate;

View File

@@ -45,53 +45,51 @@ namespace MediaBrowser.Api.Playback.Progressive
return ext;
}
var videoRequest = state.Request as VideoStreamRequest;
var isVideoRequest = state.VideoRequest != null;
// Try to infer based on the desired video codec
if (videoRequest != null && !string.IsNullOrEmpty(videoRequest.VideoCodec))
if (isVideoRequest)
{
if (state.IsInputVideo)
{
if (string.Equals(videoRequest.VideoCodec, "h264", StringComparison.OrdinalIgnoreCase))
var videoCodec = state.VideoRequest.VideoCodec;
if (string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase))
{
return ".ts";
}
if (string.Equals(videoRequest.VideoCodec, "theora", StringComparison.OrdinalIgnoreCase))
if (string.Equals(videoCodec, "theora", StringComparison.OrdinalIgnoreCase))
{
return ".ogv";
}
if (string.Equals(videoRequest.VideoCodec, "vpx", StringComparison.OrdinalIgnoreCase))
if (string.Equals(videoCodec, "vpx", StringComparison.OrdinalIgnoreCase))
{
return ".webm";
}
if (string.Equals(videoRequest.VideoCodec, "wmv", StringComparison.OrdinalIgnoreCase))
if (string.Equals(videoCodec, "wmv", StringComparison.OrdinalIgnoreCase))
{
return ".asf";
}
}
}
// Try to infer based on the desired audio codec
if (!string.IsNullOrEmpty(state.Request.AudioCodec))
if (!isVideoRequest)
{
if (!state.IsInputVideo)
var audioCodec = state.Request.AudioCodec;
if (string.Equals("aac", audioCodec, StringComparison.OrdinalIgnoreCase))
{
if (string.Equals("aac", state.Request.AudioCodec, StringComparison.OrdinalIgnoreCase))
{
return ".aac";
}
if (string.Equals("mp3", state.Request.AudioCodec, StringComparison.OrdinalIgnoreCase))
{
return ".mp3";
}
if (string.Equals("vorbis", state.Request.AudioCodec, StringComparison.OrdinalIgnoreCase))
{
return ".ogg";
}
if (string.Equals("wma", state.Request.AudioCodec, StringComparison.OrdinalIgnoreCase))
{
return ".wma";
}
return ".aac";
}
if (string.Equals("mp3", audioCodec, StringComparison.OrdinalIgnoreCase))
{
return ".mp3";
}
if (string.Equals("vorbis", audioCodec, StringComparison.OrdinalIgnoreCase))
{
return ".ogg";
}
if (string.Equals("wma", audioCodec, StringComparison.OrdinalIgnoreCase))
{
return ".wma";
}
}
@@ -134,7 +132,7 @@ namespace MediaBrowser.Api.Playback.Progressive
}
}
var outputPath = GetOutputFilePath(state);
var outputPath = state.OutputFilePath;
var outputPathExists = File.Exists(outputPath);
var isStatic = request.Static ||
@@ -243,7 +241,7 @@ namespace MediaBrowser.Api.Playback.Progressive
private async Task<object> GetStreamResult(StreamState state, IDictionary<string, string> responseHeaders, bool isHeadRequest)
{
// Use the command line args with a dummy playlist path
var outputPath = GetOutputFilePath(state);
var outputPath = state.OutputFilePath;
responseHeaders["Accept-Ranges"] = "none";

View File

@@ -95,7 +95,7 @@ namespace MediaBrowser.Api.Playback.Progressive
protected override string GetCommandLineArguments(string outputPath, StreamState state, bool performSubtitleConversions)
{
// Get the output codec name
var videoCodec = GetVideoCodec(state.VideoRequest);
var videoCodec = state.OutputVideoCodec;
var format = string.Empty;
var keyFrame = string.Empty;
@@ -190,10 +190,8 @@ namespace MediaBrowser.Api.Playback.Progressive
return string.Empty;
}
var request = state.Request;
// Get the output codec name
var codec = GetAudioCodec(request);
var codec = state.OutputAudioCodec;
if (codec.Equals("copy", StringComparison.OrdinalIgnoreCase))
{

View File

@@ -163,6 +163,9 @@ namespace MediaBrowser.Api.Playback
}
}
public string OutputFilePath { get; set; }
public string OutputVideoCodec { get; set; }
public string OutputAudioCodec { get; set; }
public int? OutputAudioChannels;
public int? OutputAudioSampleRate;
public int? OutputAudioBitrate;