mirror of
https://github.com/jellyfin/jellyfin.git
synced 2025-07-09 03:04:24 -04:00
subtitle extraction fixes
This commit is contained in:
parent
6d3849d98d
commit
33f4b2ed53
@ -77,8 +77,9 @@ namespace MediaBrowser.Api.Playback
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="outputPath">The output path.</param>
|
/// <param name="outputPath">The output path.</param>
|
||||||
/// <param name="state">The state.</param>
|
/// <param name="state">The state.</param>
|
||||||
|
/// <param name="performSubtitleConversions">if set to <c>true</c> [perform subtitle conversions].</param>
|
||||||
/// <returns>System.String.</returns>
|
/// <returns>System.String.</returns>
|
||||||
protected abstract string GetCommandLineArguments(string outputPath, StreamState state);
|
protected abstract string GetCommandLineArguments(string outputPath, StreamState state, bool performSubtitleConversions);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the type of the transcoding job.
|
/// Gets the type of the transcoding job.
|
||||||
@ -104,7 +105,7 @@ namespace MediaBrowser.Api.Playback
|
|||||||
protected string GetOutputFilePath(StreamState state)
|
protected string GetOutputFilePath(StreamState state)
|
||||||
{
|
{
|
||||||
var folder = ApplicationPaths.EncodedMediaCachePath;
|
var folder = ApplicationPaths.EncodedMediaCachePath;
|
||||||
return Path.Combine(folder, GetCommandLineArguments("dummy\\dummy", state).GetMD5() + GetOutputFileExtension(state).ToLower());
|
return Path.Combine(folder, GetCommandLineArguments("dummy\\dummy", state, false).GetMD5() + GetOutputFileExtension(state).ToLower());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -222,8 +223,9 @@ namespace MediaBrowser.Api.Playback
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="state">The state.</param>
|
/// <param name="state">The state.</param>
|
||||||
/// <param name="outputVideoCodec">The output video codec.</param>
|
/// <param name="outputVideoCodec">The output video codec.</param>
|
||||||
|
/// <param name="performTextSubtitleConversion">if set to <c>true</c> [perform text subtitle conversion].</param>
|
||||||
/// <returns>System.String.</returns>
|
/// <returns>System.String.</returns>
|
||||||
protected string GetOutputSizeParam(StreamState state, string outputVideoCodec)
|
protected string GetOutputSizeParam(StreamState state, string outputVideoCodec, bool performTextSubtitleConversion)
|
||||||
{
|
{
|
||||||
// http://sonnati.wordpress.com/2012/10/19/ffmpeg-the-swiss-army-knife-of-internet-streaming-part-vi/
|
// http://sonnati.wordpress.com/2012/10/19/ffmpeg-the-swiss-army-knife-of-internet-streaming-part-vi/
|
||||||
|
|
||||||
@ -235,7 +237,7 @@ namespace MediaBrowser.Api.Playback
|
|||||||
{
|
{
|
||||||
if (state.SubtitleStream.Codec.IndexOf("srt", StringComparison.OrdinalIgnoreCase) != -1 || state.SubtitleStream.Codec.IndexOf("subrip", StringComparison.OrdinalIgnoreCase) != -1)
|
if (state.SubtitleStream.Codec.IndexOf("srt", StringComparison.OrdinalIgnoreCase) != -1 || state.SubtitleStream.Codec.IndexOf("subrip", StringComparison.OrdinalIgnoreCase) != -1)
|
||||||
{
|
{
|
||||||
assSubtitleParam = GetTextSubtitleParam((Video)state.Item, state.SubtitleStream, request.StartTimeTicks);
|
assSubtitleParam = GetTextSubtitleParam((Video)state.Item, state.SubtitleStream, request.StartTimeTicks, performTextSubtitleConversion);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -287,10 +289,11 @@ namespace MediaBrowser.Api.Playback
|
|||||||
/// <param name="video">The video.</param>
|
/// <param name="video">The video.</param>
|
||||||
/// <param name="subtitleStream">The subtitle stream.</param>
|
/// <param name="subtitleStream">The subtitle stream.</param>
|
||||||
/// <param name="startTimeTicks">The start time ticks.</param>
|
/// <param name="startTimeTicks">The start time ticks.</param>
|
||||||
|
/// <param name="performConversion">if set to <c>true</c> [perform conversion].</param>
|
||||||
/// <returns>System.String.</returns>
|
/// <returns>System.String.</returns>
|
||||||
protected string GetTextSubtitleParam(Video video, MediaStream subtitleStream, long? startTimeTicks)
|
protected string GetTextSubtitleParam(Video video, MediaStream subtitleStream, long? startTimeTicks, bool performConversion)
|
||||||
{
|
{
|
||||||
var path = subtitleStream.IsExternal ? GetConvertedAssPath(video, subtitleStream, startTimeTicks) : GetExtractedAssPath(video, subtitleStream, startTimeTicks);
|
var path = subtitleStream.IsExternal ? GetConvertedAssPath(video, subtitleStream, startTimeTicks, performConversion) : GetExtractedAssPath(video, subtitleStream, startTimeTicks, performConversion);
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(path))
|
if (string.IsNullOrEmpty(path))
|
||||||
{
|
{
|
||||||
@ -306,14 +309,15 @@ namespace MediaBrowser.Api.Playback
|
|||||||
/// <param name="video">The video.</param>
|
/// <param name="video">The video.</param>
|
||||||
/// <param name="subtitleStream">The subtitle stream.</param>
|
/// <param name="subtitleStream">The subtitle stream.</param>
|
||||||
/// <param name="startTimeTicks">The start time ticks.</param>
|
/// <param name="startTimeTicks">The start time ticks.</param>
|
||||||
|
/// <param name="performConversion">if set to <c>true</c> [perform conversion].</param>
|
||||||
/// <returns>System.String.</returns>
|
/// <returns>System.String.</returns>
|
||||||
private string GetExtractedAssPath(Video video, MediaStream subtitleStream, long? startTimeTicks)
|
private string GetExtractedAssPath(Video video, MediaStream subtitleStream, long? startTimeTicks, bool performConversion)
|
||||||
{
|
{
|
||||||
var offset = TimeSpan.FromTicks(startTimeTicks ?? 0);
|
var offset = TimeSpan.FromTicks(startTimeTicks ?? 0);
|
||||||
|
|
||||||
var path = Kernel.Instance.FFMpegManager.GetSubtitleCachePath(video, subtitleStream.Index, offset, ".ass");
|
var path = Kernel.Instance.FFMpegManager.GetSubtitleCachePath(video, subtitleStream.Index, offset, ".ass");
|
||||||
|
|
||||||
if (!File.Exists(path))
|
if (performConversion && !File.Exists(path))
|
||||||
{
|
{
|
||||||
InputType type;
|
InputType type;
|
||||||
|
|
||||||
@ -340,8 +344,9 @@ namespace MediaBrowser.Api.Playback
|
|||||||
/// <param name="video">The video.</param>
|
/// <param name="video">The video.</param>
|
||||||
/// <param name="subtitleStream">The subtitle stream.</param>
|
/// <param name="subtitleStream">The subtitle stream.</param>
|
||||||
/// <param name="startTimeTicks">The start time ticks.</param>
|
/// <param name="startTimeTicks">The start time ticks.</param>
|
||||||
|
/// <param name="performConversion">if set to <c>true</c> [perform conversion].</param>
|
||||||
/// <returns>System.String.</returns>
|
/// <returns>System.String.</returns>
|
||||||
private string GetConvertedAssPath(Video video, MediaStream subtitleStream, long? startTimeTicks)
|
private string GetConvertedAssPath(Video video, MediaStream subtitleStream, long? startTimeTicks, bool performConversion)
|
||||||
{
|
{
|
||||||
var offset = startTimeTicks.HasValue
|
var offset = startTimeTicks.HasValue
|
||||||
? TimeSpan.FromTicks(startTimeTicks.Value)
|
? TimeSpan.FromTicks(startTimeTicks.Value)
|
||||||
@ -349,7 +354,7 @@ namespace MediaBrowser.Api.Playback
|
|||||||
|
|
||||||
var path = Kernel.Instance.FFMpegManager.GetSubtitleCachePath(video, subtitleStream.Index, offset, ".ass");
|
var path = Kernel.Instance.FFMpegManager.GetSubtitleCachePath(video, subtitleStream.Index, offset, ".ass");
|
||||||
|
|
||||||
if (!File.Exists(path))
|
if (performConversion && !File.Exists(path))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -381,7 +386,7 @@ namespace MediaBrowser.Api.Playback
|
|||||||
// Add resolution params, if specified
|
// Add resolution params, if specified
|
||||||
if (request.Width.HasValue || request.Height.HasValue || request.MaxHeight.HasValue || request.MaxWidth.HasValue)
|
if (request.Width.HasValue || request.Height.HasValue || request.MaxHeight.HasValue || request.MaxWidth.HasValue)
|
||||||
{
|
{
|
||||||
outputSizeParam = GetOutputSizeParam(state, outputVideoCodec).TrimEnd('"');
|
outputSizeParam = GetOutputSizeParam(state, outputVideoCodec, false).TrimEnd('"');
|
||||||
outputSizeParam = "," + outputSizeParam.Substring(outputSizeParam.IndexOf("scale", StringComparison.OrdinalIgnoreCase));
|
outputSizeParam = "," + outputSizeParam.Substring(outputSizeParam.IndexOf("scale", StringComparison.OrdinalIgnoreCase));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -552,7 +557,7 @@ namespace MediaBrowser.Api.Playback
|
|||||||
|
|
||||||
FileName = MediaEncoder.EncoderPath,
|
FileName = MediaEncoder.EncoderPath,
|
||||||
WorkingDirectory = Path.GetDirectoryName(MediaEncoder.EncoderPath),
|
WorkingDirectory = Path.GetDirectoryName(MediaEncoder.EncoderPath),
|
||||||
Arguments = GetCommandLineArguments(outputPath, state),
|
Arguments = GetCommandLineArguments(outputPath, state, true),
|
||||||
|
|
||||||
WindowStyle = ProcessWindowStyle.Hidden,
|
WindowStyle = ProcessWindowStyle.Hidden,
|
||||||
ErrorDialog = false
|
ErrorDialog = false
|
||||||
|
@ -19,13 +19,24 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Class GetHlsAudioSegment
|
||||||
|
/// </summary>
|
||||||
[Route("/Audio/{Id}/segments/{SegmentId}/stream.mp3", "GET")]
|
[Route("/Audio/{Id}/segments/{SegmentId}/stream.mp3", "GET")]
|
||||||
[Route("/Audio/{Id}/segments/{SegmentId}/stream.aac", "GET")]
|
[Route("/Audio/{Id}/segments/{SegmentId}/stream.aac", "GET")]
|
||||||
[Api(Description = "Gets an Http live streaming segment file. Internal use only.")]
|
[Api(Description = "Gets an Http live streaming segment file. Internal use only.")]
|
||||||
public class GetHlsAudioSegment
|
public class GetHlsAudioSegment
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the id.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The id.</value>
|
||||||
public string Id { get; set; }
|
public string Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the segment id.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The segment id.</value>
|
||||||
public string SegmentId { get; set; }
|
public string SegmentId { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,11 +45,24 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class AudioHlsService : BaseHlsService
|
public class AudioHlsService : BaseHlsService
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="AudioHlsService" /> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="appPaths">The app paths.</param>
|
||||||
|
/// <param name="userManager">The user manager.</param>
|
||||||
|
/// <param name="libraryManager">The library manager.</param>
|
||||||
|
/// <param name="isoManager">The iso manager.</param>
|
||||||
|
/// <param name="mediaEncoder">The media encoder.</param>
|
||||||
public AudioHlsService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder)
|
public AudioHlsService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder)
|
||||||
: base(appPaths, userManager, libraryManager, isoManager, mediaEncoder)
|
: base(appPaths, userManager, libraryManager, isoManager, mediaEncoder)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the specified request.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="request">The request.</param>
|
||||||
|
/// <returns>System.Object.</returns>
|
||||||
public object Get(GetHlsAudioSegment request)
|
public object Get(GetHlsAudioSegment request)
|
||||||
{
|
{
|
||||||
var file = SegmentFilePrefix + request.SegmentId + Path.GetExtension(RequestContext.PathInfo);
|
var file = SegmentFilePrefix + request.SegmentId + Path.GetExtension(RequestContext.PathInfo);
|
||||||
@ -93,8 +117,9 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||||||
/// Gets the video arguments.
|
/// Gets the video arguments.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="state">The state.</param>
|
/// <param name="state">The state.</param>
|
||||||
|
/// <param name="performSubtitleConversion">if set to <c>true</c> [perform subtitle conversion].</param>
|
||||||
/// <returns>System.String.</returns>
|
/// <returns>System.String.</returns>
|
||||||
protected override string GetVideoArguments(StreamState state)
|
protected override string GetVideoArguments(StreamState state, bool performSubtitleConversion)
|
||||||
{
|
{
|
||||||
// No video
|
// No video
|
||||||
return string.Empty;
|
return string.Empty;
|
||||||
@ -105,6 +130,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="state">The state.</param>
|
/// <param name="state">The state.</param>
|
||||||
/// <returns>System.String.</returns>
|
/// <returns>System.String.</returns>
|
||||||
|
/// <exception cref="System.ArgumentException">Must specify either aac or mp3 audio codec.</exception>
|
||||||
/// <exception cref="System.InvalidOperationException">Only aac and mp3 audio codecs are supported.</exception>
|
/// <exception cref="System.InvalidOperationException">Only aac and mp3 audio codecs are supported.</exception>
|
||||||
protected override string GetSegmentFileExtension(StreamState state)
|
protected override string GetSegmentFileExtension(StreamState state)
|
||||||
{
|
{
|
||||||
|
@ -11,6 +11,9 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace MediaBrowser.Api.Playback.Hls
|
namespace MediaBrowser.Api.Playback.Hls
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Class BaseHlsService
|
||||||
|
/// </summary>
|
||||||
public abstract class BaseHlsService : BaseStreamingService
|
public abstract class BaseHlsService : BaseStreamingService
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -18,6 +21,14 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public const string SegmentFilePrefix = "segment-";
|
public const string SegmentFilePrefix = "segment-";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="BaseStreamingService" /> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="appPaths">The app paths.</param>
|
||||||
|
/// <param name="userManager">The user manager.</param>
|
||||||
|
/// <param name="libraryManager">The library manager.</param>
|
||||||
|
/// <param name="isoManager">The iso manager.</param>
|
||||||
|
/// <param name="mediaEncoder">The media encoder.</param>
|
||||||
protected BaseHlsService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder)
|
protected BaseHlsService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder)
|
||||||
: base(appPaths, userManager, libraryManager, isoManager, mediaEncoder)
|
: base(appPaths, userManager, libraryManager, isoManager, mediaEncoder)
|
||||||
{
|
{
|
||||||
@ -26,13 +37,16 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the audio arguments.
|
/// Gets the audio arguments.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="state">The state.</param>
|
||||||
/// <returns>System.String.</returns>
|
/// <returns>System.String.</returns>
|
||||||
protected abstract string GetAudioArguments(StreamState state);
|
protected abstract string GetAudioArguments(StreamState state);
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the video arguments.
|
/// Gets the video arguments.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="state">The state.</param>
|
||||||
|
/// <param name="performSubtitleConversion">if set to <c>true</c> [perform subtitle conversion].</param>
|
||||||
/// <returns>System.String.</returns>
|
/// <returns>System.String.</returns>
|
||||||
protected abstract string GetVideoArguments(StreamState state);
|
protected abstract string GetVideoArguments(StreamState state, bool performSubtitleConversion);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the segment file extension.
|
/// Gets the segment file extension.
|
||||||
@ -40,7 +54,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||||||
/// <param name="state">The state.</param>
|
/// <param name="state">The state.</param>
|
||||||
/// <returns>System.String.</returns>
|
/// <returns>System.String.</returns>
|
||||||
protected abstract string GetSegmentFileExtension(StreamState state);
|
protected abstract string GetSegmentFileExtension(StreamState state);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the type of the transcoding job.
|
/// Gets the type of the transcoding job.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -53,6 +67,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Processes the request.
|
/// Processes the request.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="request">The request.</param>
|
||||||
/// <returns>System.Object.</returns>
|
/// <returns>System.Object.</returns>
|
||||||
protected object ProcessRequest(StreamRequest request)
|
protected object ProcessRequest(StreamRequest request)
|
||||||
{
|
{
|
||||||
@ -162,14 +177,15 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||||||
}
|
}
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the command line arguments.
|
/// Gets the command line arguments.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="outputPath">The output path.</param>
|
/// <param name="outputPath">The output path.</param>
|
||||||
/// <param name="state">The state.</param>
|
/// <param name="state">The state.</param>
|
||||||
|
/// <param name="performSubtitleConversions">if set to <c>true</c> [perform subtitle conversions].</param>
|
||||||
/// <returns>System.String.</returns>
|
/// <returns>System.String.</returns>
|
||||||
protected override string GetCommandLineArguments(string outputPath, StreamState state)
|
protected override string GetCommandLineArguments(string outputPath, StreamState state, bool performSubtitleConversions)
|
||||||
{
|
{
|
||||||
var segmentOutputPath = Path.GetDirectoryName(outputPath);
|
var segmentOutputPath = Path.GetDirectoryName(outputPath);
|
||||||
var segmentOutputName = SegmentFilePrefix + Path.GetFileNameWithoutExtension(outputPath);
|
var segmentOutputName = SegmentFilePrefix + Path.GetFileNameWithoutExtension(outputPath);
|
||||||
@ -184,7 +200,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||||||
GetInputArgument(state.Item, state.IsoMount),
|
GetInputArgument(state.Item, state.IsoMount),
|
||||||
GetSlowSeekCommandLineParameter(state.Request),
|
GetSlowSeekCommandLineParameter(state.Request),
|
||||||
GetMapArgs(state),
|
GetMapArgs(state),
|
||||||
GetVideoArguments(state),
|
GetVideoArguments(state, performSubtitleConversions),
|
||||||
GetAudioArguments(state),
|
GetAudioArguments(state),
|
||||||
outputPath,
|
outputPath,
|
||||||
segmentOutputPath
|
segmentOutputPath
|
||||||
|
@ -8,6 +8,9 @@ using ServiceStack.ServiceHost;
|
|||||||
|
|
||||||
namespace MediaBrowser.Api.Playback.Hls
|
namespace MediaBrowser.Api.Playback.Hls
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Class GetHlsVideoStream
|
||||||
|
/// </summary>
|
||||||
[Route("/Videos/{Id}/stream.m3u8", "GET")]
|
[Route("/Videos/{Id}/stream.m3u8", "GET")]
|
||||||
[Api(Description = "Gets a video stream using HTTP live streaming.")]
|
[Api(Description = "Gets a video stream using HTTP live streaming.")]
|
||||||
public class GetHlsVideoStream : VideoStreamRequest
|
public class GetHlsVideoStream : VideoStreamRequest
|
||||||
@ -15,22 +18,49 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Class GetHlsVideoSegment
|
||||||
|
/// </summary>
|
||||||
[Route("/Videos/{Id}/segments/{SegmentId}/stream.ts", "GET")]
|
[Route("/Videos/{Id}/segments/{SegmentId}/stream.ts", "GET")]
|
||||||
[Api(Description = "Gets an Http live streaming segment file. Internal use only.")]
|
[Api(Description = "Gets an Http live streaming segment file. Internal use only.")]
|
||||||
public class GetHlsVideoSegment
|
public class GetHlsVideoSegment
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the id.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The id.</value>
|
||||||
public string Id { get; set; }
|
public string Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the segment id.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The segment id.</value>
|
||||||
public string SegmentId { get; set; }
|
public string SegmentId { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Class VideoHlsService
|
||||||
|
/// </summary>
|
||||||
public class VideoHlsService : BaseHlsService
|
public class VideoHlsService : BaseHlsService
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="BaseStreamingService" /> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="appPaths">The app paths.</param>
|
||||||
|
/// <param name="userManager">The user manager.</param>
|
||||||
|
/// <param name="libraryManager">The library manager.</param>
|
||||||
|
/// <param name="isoManager">The iso manager.</param>
|
||||||
|
/// <param name="mediaEncoder">The media encoder.</param>
|
||||||
public VideoHlsService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder)
|
public VideoHlsService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder)
|
||||||
: base(appPaths, userManager, libraryManager, isoManager, mediaEncoder)
|
: base(appPaths, userManager, libraryManager, isoManager, mediaEncoder)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the specified request.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="request">The request.</param>
|
||||||
|
/// <returns>System.Object.</returns>
|
||||||
public object Get(GetHlsVideoSegment request)
|
public object Get(GetHlsVideoSegment request)
|
||||||
{
|
{
|
||||||
var file = SegmentFilePrefix + request.SegmentId + Path.GetExtension(RequestContext.PathInfo);
|
var file = SegmentFilePrefix + request.SegmentId + Path.GetExtension(RequestContext.PathInfo);
|
||||||
@ -49,7 +79,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||||||
{
|
{
|
||||||
return ProcessRequest(request);
|
return ProcessRequest(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the audio arguments.
|
/// Gets the audio arguments.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -105,8 +135,9 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||||||
/// Gets the video arguments.
|
/// Gets the video arguments.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="state">The state.</param>
|
/// <param name="state">The state.</param>
|
||||||
|
/// <param name="performSubtitleConversion">if set to <c>true</c> [perform subtitle conversion].</param>
|
||||||
/// <returns>System.String.</returns>
|
/// <returns>System.String.</returns>
|
||||||
protected override string GetVideoArguments(StreamState state)
|
protected override string GetVideoArguments(StreamState state, bool performSubtitleConversion)
|
||||||
{
|
{
|
||||||
var codec = GetVideoCodec(state.VideoRequest);
|
var codec = GetVideoCodec(state.VideoRequest);
|
||||||
|
|
||||||
@ -128,7 +159,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||||||
// Add resolution params, if specified
|
// Add resolution params, if specified
|
||||||
if (state.VideoRequest.Width.HasValue || state.VideoRequest.Height.HasValue || state.VideoRequest.MaxHeight.HasValue || state.VideoRequest.MaxWidth.HasValue)
|
if (state.VideoRequest.Width.HasValue || state.VideoRequest.Height.HasValue || state.VideoRequest.MaxHeight.HasValue || state.VideoRequest.MaxWidth.HasValue)
|
||||||
{
|
{
|
||||||
args += GetOutputSizeParam(state, codec);
|
args += GetOutputSizeParam(state, codec, performSubtitleConversion);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the output framerate based on the FrameRate param
|
// Get the output framerate based on the FrameRate param
|
||||||
|
@ -37,6 +37,14 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class AudioService : BaseProgressiveStreamingService
|
public class AudioService : BaseProgressiveStreamingService
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="AudioService"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="appPaths">The app paths.</param>
|
||||||
|
/// <param name="userManager">The user manager.</param>
|
||||||
|
/// <param name="libraryManager">The library manager.</param>
|
||||||
|
/// <param name="isoManager">The iso manager.</param>
|
||||||
|
/// <param name="mediaEncoder">The media encoder.</param>
|
||||||
public AudioService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder)
|
public AudioService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder)
|
||||||
: base(appPaths, userManager, libraryManager, isoManager, mediaEncoder)
|
: base(appPaths, userManager, libraryManager, isoManager, mediaEncoder)
|
||||||
{
|
{
|
||||||
@ -67,9 +75,10 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="outputPath">The output path.</param>
|
/// <param name="outputPath">The output path.</param>
|
||||||
/// <param name="state">The state.</param>
|
/// <param name="state">The state.</param>
|
||||||
|
/// <param name="performSubtitleConversions">if set to <c>true</c> [perform subtitle conversions].</param>
|
||||||
/// <returns>System.String.</returns>
|
/// <returns>System.String.</returns>
|
||||||
/// <exception cref="System.InvalidOperationException">Only aac and mp3 audio codecs are supported.</exception>
|
/// <exception cref="System.InvalidOperationException">Only aac and mp3 audio codecs are supported.</exception>
|
||||||
protected override string GetCommandLineArguments(string outputPath, StreamState state)
|
protected override string GetCommandLineArguments(string outputPath, StreamState state, bool performSubtitleConversions)
|
||||||
{
|
{
|
||||||
var request = state.Request;
|
var request = state.Request;
|
||||||
|
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
using System.IO;
|
using MediaBrowser.Common.IO;
|
||||||
using MediaBrowser.Common.IO;
|
|
||||||
using MediaBrowser.Common.MediaInfo;
|
using MediaBrowser.Common.MediaInfo;
|
||||||
using MediaBrowser.Controller;
|
using MediaBrowser.Controller;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using System;
|
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using ServiceStack.ServiceHost;
|
using ServiceStack.ServiceHost;
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
namespace MediaBrowser.Api.Playback.Progressive
|
namespace MediaBrowser.Api.Playback.Progressive
|
||||||
{
|
{
|
||||||
@ -51,6 +51,14 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class VideoService : BaseProgressiveStreamingService
|
public class VideoService : BaseProgressiveStreamingService
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="VideoService"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="appPaths">The app paths.</param>
|
||||||
|
/// <param name="userManager">The user manager.</param>
|
||||||
|
/// <param name="libraryManager">The library manager.</param>
|
||||||
|
/// <param name="isoManager">The iso manager.</param>
|
||||||
|
/// <param name="mediaEncoder">The media encoder.</param>
|
||||||
public VideoService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder)
|
public VideoService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder)
|
||||||
: base(appPaths, userManager, libraryManager, isoManager, mediaEncoder)
|
: base(appPaths, userManager, libraryManager, isoManager, mediaEncoder)
|
||||||
{
|
{
|
||||||
@ -66,6 +74,11 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||||||
return ProcessRequest(request, false);
|
return ProcessRequest(request, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Heads the specified request.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="request">The request.</param>
|
||||||
|
/// <returns>System.Object.</returns>
|
||||||
public object Head(GetVideoStream request)
|
public object Head(GetVideoStream request)
|
||||||
{
|
{
|
||||||
return ProcessRequest(request, true);
|
return ProcessRequest(request, true);
|
||||||
@ -76,8 +89,9 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="outputPath">The output path.</param>
|
/// <param name="outputPath">The output path.</param>
|
||||||
/// <param name="state">The state.</param>
|
/// <param name="state">The state.</param>
|
||||||
|
/// <param name="performSubtitleConversions">if set to <c>true</c> [perform subtitle conversions].</param>
|
||||||
/// <returns>System.String.</returns>
|
/// <returns>System.String.</returns>
|
||||||
protected override string GetCommandLineArguments(string outputPath, StreamState state)
|
protected override string GetCommandLineArguments(string outputPath, StreamState state, bool performSubtitleConversions)
|
||||||
{
|
{
|
||||||
var video = (Video)state.Item;
|
var video = (Video)state.Item;
|
||||||
|
|
||||||
@ -103,7 +117,7 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||||||
GetSlowSeekCommandLineParameter(state.Request),
|
GetSlowSeekCommandLineParameter(state.Request),
|
||||||
keyFrame,
|
keyFrame,
|
||||||
GetMapArgs(state),
|
GetMapArgs(state),
|
||||||
GetVideoArguments(state, videoCodec),
|
GetVideoArguments(state, videoCodec, performSubtitleConversions),
|
||||||
threads,
|
threads,
|
||||||
GetAudioArguments(state),
|
GetAudioArguments(state),
|
||||||
format,
|
format,
|
||||||
@ -116,8 +130,9 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="state">The state.</param>
|
/// <param name="state">The state.</param>
|
||||||
/// <param name="codec">The video codec.</param>
|
/// <param name="codec">The video codec.</param>
|
||||||
|
/// <param name="performSubtitleConversion">if set to <c>true</c> [perform subtitle conversion].</param>
|
||||||
/// <returns>System.String.</returns>
|
/// <returns>System.String.</returns>
|
||||||
private string GetVideoArguments(StreamState state, string codec)
|
private string GetVideoArguments(StreamState state, string codec, bool performSubtitleConversion)
|
||||||
{
|
{
|
||||||
var args = "-vcodec " + codec;
|
var args = "-vcodec " + codec;
|
||||||
|
|
||||||
@ -136,7 +151,7 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||||||
// Add resolution params, if specified
|
// Add resolution params, if specified
|
||||||
if (request.Width.HasValue || request.Height.HasValue || request.MaxHeight.HasValue || request.MaxWidth.HasValue)
|
if (request.Width.HasValue || request.Height.HasValue || request.MaxHeight.HasValue || request.MaxWidth.HasValue)
|
||||||
{
|
{
|
||||||
args += GetOutputSizeParam(state, codec);
|
args += GetOutputSizeParam(state, codec, performSubtitleConversion);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (request.Framerate.HasValue)
|
if (request.Framerate.HasValue)
|
||||||
|
@ -558,6 +558,9 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
|
|||||||
{
|
{
|
||||||
StartInfo = new ProcessStartInfo
|
StartInfo = new ProcessStartInfo
|
||||||
{
|
{
|
||||||
|
RedirectStandardOutput = false,
|
||||||
|
RedirectStandardError = true,
|
||||||
|
|
||||||
CreateNoWindow = true,
|
CreateNoWindow = true,
|
||||||
UseShellExecute = false,
|
UseShellExecute = false,
|
||||||
FileName = FFMpegPath,
|
FileName = FFMpegPath,
|
||||||
@ -571,9 +574,57 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
|
|||||||
|
|
||||||
await _subtitleExtractionResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
|
await _subtitleExtractionResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
var ranToCompletion = StartAndWaitForProcess(process);
|
var logFilePath = Path.Combine(_appPaths.LogDirectoryPath, "ffmpeg-sub-convert-" + Guid.NewGuid() + ".txt");
|
||||||
|
|
||||||
_subtitleExtractionResourcePool.Release();
|
var logFileStream = new FileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
process.Start();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_subtitleExtractionResourcePool.Release();
|
||||||
|
|
||||||
|
logFileStream.Dispose();
|
||||||
|
|
||||||
|
_logger.ErrorException("Error starting ffmpeg", ex);
|
||||||
|
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
process.StandardError.BaseStream.CopyToAsync(logFileStream);
|
||||||
|
|
||||||
|
var ranToCompletion = process.WaitForExit(60000);
|
||||||
|
|
||||||
|
if (!ranToCompletion)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_logger.Info("Killing ffmpeg process");
|
||||||
|
|
||||||
|
process.Kill();
|
||||||
|
|
||||||
|
process.WaitForExit(1000);
|
||||||
|
}
|
||||||
|
catch (Win32Exception ex)
|
||||||
|
{
|
||||||
|
_logger.ErrorException("Error killing process", ex);
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException ex)
|
||||||
|
{
|
||||||
|
_logger.ErrorException("Error killing process", ex);
|
||||||
|
}
|
||||||
|
catch (NotSupportedException ex)
|
||||||
|
{
|
||||||
|
_logger.ErrorException("Error killing process", ex);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
logFileStream.Dispose();
|
||||||
|
_subtitleExtractionResourcePool.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var exitCode = ranToCompletion ? process.ExitCode : -1;
|
var exitCode = ranToCompletion ? process.ExitCode : -1;
|
||||||
|
|
||||||
@ -594,7 +645,7 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
|
|||||||
}
|
}
|
||||||
catch (IOException ex)
|
catch (IOException ex)
|
||||||
{
|
{
|
||||||
_logger.ErrorException("Error deleting converted subtitle {0}", ex, outputPath);
|
_logger.ErrorException("Error converted extracted subtitle {0}", ex, outputPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -605,7 +656,7 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
|
|||||||
|
|
||||||
if (failed)
|
if (failed)
|
||||||
{
|
{
|
||||||
var msg = string.Format("ffmpeg subtitle conversion failed for {0}", inputPath);
|
var msg = string.Format("ffmpeg subtitle converted failed for {0}", inputPath);
|
||||||
|
|
||||||
_logger.Error(msg);
|
_logger.Error(msg);
|
||||||
|
|
||||||
@ -669,6 +720,10 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
|
|||||||
{
|
{
|
||||||
CreateNoWindow = true,
|
CreateNoWindow = true,
|
||||||
UseShellExecute = false,
|
UseShellExecute = false,
|
||||||
|
|
||||||
|
RedirectStandardOutput = false,
|
||||||
|
RedirectStandardError = true,
|
||||||
|
|
||||||
FileName = FFMpegPath,
|
FileName = FFMpegPath,
|
||||||
Arguments = string.Format("{0}-i {1} -map 0:{2} -an -vn -c:s ass \"{3}\"", offsetParam, inputPath, subtitleStreamIndex, outputPath),
|
Arguments = string.Format("{0}-i {1} -map 0:{2} -an -vn -c:s ass \"{3}\"", offsetParam, inputPath, subtitleStreamIndex, outputPath),
|
||||||
WindowStyle = ProcessWindowStyle.Hidden,
|
WindowStyle = ProcessWindowStyle.Hidden,
|
||||||
@ -680,9 +735,57 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
|
|||||||
|
|
||||||
await _subtitleExtractionResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
|
await _subtitleExtractionResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
var ranToCompletion = StartAndWaitForProcess(process);
|
var logFilePath = Path.Combine(_appPaths.LogDirectoryPath, "ffmpeg-sub-extract-" + Guid.NewGuid() + ".txt");
|
||||||
|
|
||||||
_subtitleExtractionResourcePool.Release();
|
var logFileStream = new FileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
process.Start();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_subtitleExtractionResourcePool.Release();
|
||||||
|
|
||||||
|
logFileStream.Dispose();
|
||||||
|
|
||||||
|
_logger.ErrorException("Error starting ffmpeg", ex);
|
||||||
|
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
process.StandardError.BaseStream.CopyToAsync(logFileStream);
|
||||||
|
|
||||||
|
var ranToCompletion = process.WaitForExit(60000);
|
||||||
|
|
||||||
|
if (!ranToCompletion)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_logger.Info("Killing ffmpeg process");
|
||||||
|
|
||||||
|
process.Kill();
|
||||||
|
|
||||||
|
process.WaitForExit(1000);
|
||||||
|
}
|
||||||
|
catch (Win32Exception ex)
|
||||||
|
{
|
||||||
|
_logger.ErrorException("Error killing process", ex);
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException ex)
|
||||||
|
{
|
||||||
|
_logger.ErrorException("Error killing process", ex);
|
||||||
|
}
|
||||||
|
catch (NotSupportedException ex)
|
||||||
|
{
|
||||||
|
_logger.ErrorException("Error killing process", ex);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
logFileStream.Dispose();
|
||||||
|
_subtitleExtractionResourcePool.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var exitCode = ranToCompletion ? process.ExitCode : -1;
|
var exitCode = ranToCompletion ? process.ExitCode : -1;
|
||||||
|
|
||||||
@ -857,12 +960,13 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
|
|||||||
/// Starts the and wait for process.
|
/// Starts the and wait for process.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="process">The process.</param>
|
/// <param name="process">The process.</param>
|
||||||
|
/// <param name="timeout">The timeout.</param>
|
||||||
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
|
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
|
||||||
private bool StartAndWaitForProcess(Process process)
|
private bool StartAndWaitForProcess(Process process, int timeout = 10000)
|
||||||
{
|
{
|
||||||
process.Start();
|
process.Start();
|
||||||
|
|
||||||
var ranToCompletion = process.WaitForExit(10000);
|
var ranToCompletion = process.WaitForExit(timeout);
|
||||||
|
|
||||||
if (!ranToCompletion)
|
if (!ranToCompletion)
|
||||||
{
|
{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user