update keyframe setting
This commit is contained in:
parent
3060b0c5e9
commit
02938e7bcb
|
@ -2178,6 +2178,8 @@ namespace MediaBrowser.Api.Playback
|
||||||
inputModifier += " " + videoDecoder;
|
inputModifier += " " + videoDecoder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//inputModifier += " -noaccurate_seek";
|
||||||
|
|
||||||
return inputModifier;
|
return inputModifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -291,25 +291,6 @@ namespace MediaBrowser.Api.Playback.Hls
|
||||||
private double[] GetSegmentLengths(StreamState state)
|
private double[] GetSegmentLengths(StreamState state)
|
||||||
{
|
{
|
||||||
var result = new List<double>();
|
var result = new List<double>();
|
||||||
if (state.VideoRequest != null)
|
|
||||||
{
|
|
||||||
var encoder = GetVideoEncoder(state);
|
|
||||||
|
|
||||||
if (string.Equals(encoder, "copy", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
var videoStream = state.VideoStream;
|
|
||||||
if (videoStream.KeyFrames != null && videoStream.KeyFrames.Count > 0)
|
|
||||||
{
|
|
||||||
foreach (var frame in videoStream.KeyFrames)
|
|
||||||
{
|
|
||||||
var seconds = TimeSpan.FromMilliseconds(frame).TotalSeconds;
|
|
||||||
seconds -= result.Sum();
|
|
||||||
result.Add(seconds);
|
|
||||||
}
|
|
||||||
return result.ToArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var ticks = state.RunTimeTicks ?? 0;
|
var ticks = state.RunTimeTicks ?? 0;
|
||||||
|
|
||||||
|
@ -936,27 +917,8 @@ namespace MediaBrowser.Api.Playback.Hls
|
||||||
|
|
||||||
protected override bool CanStreamCopyVideo(VideoStreamRequest request, MediaStream videoStream)
|
protected override bool CanStreamCopyVideo(VideoStreamRequest request, MediaStream videoStream)
|
||||||
{
|
{
|
||||||
if (videoStream.KeyFrames == null || videoStream.KeyFrames.Count == 0)
|
|
||||||
{
|
|
||||||
Logger.Debug("Cannot stream copy video due to missing keyframe info");
|
|
||||||
return false;
|
return false;
|
||||||
}
|
//return base.CanStreamCopyVideo(request, videoStream);
|
||||||
|
|
||||||
var previousSegment = 0;
|
|
||||||
foreach (var frame in videoStream.KeyFrames)
|
|
||||||
{
|
|
||||||
var length = frame - previousSegment;
|
|
||||||
|
|
||||||
// Don't allow really long segments because this could result in long download times
|
|
||||||
if (length > 10000)
|
|
||||||
{
|
|
||||||
Logger.Debug("Cannot stream copy video due to long segment length of {0}ms", length);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
previousSegment = frame;
|
|
||||||
}
|
|
||||||
|
|
||||||
return base.CanStreamCopyVideo(request, videoStream);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -100,6 +100,7 @@ namespace MediaBrowser.Api.Playback.Progressive
|
||||||
{
|
{
|
||||||
// Comparison: https://github.com/jansmolders86/mediacenterjs/blob/master/lib/transcoding/desktop.js
|
// Comparison: https://github.com/jansmolders86/mediacenterjs/blob/master/lib/transcoding/desktop.js
|
||||||
format = " -f mp4 -movflags frag_keyframe+empty_moov";
|
format = " -f mp4 -movflags frag_keyframe+empty_moov";
|
||||||
|
//format = " -avoid_negative_ts disabled -start_at_zero -copyts -f mp4 -movflags frag_keyframe+empty_moov";
|
||||||
}
|
}
|
||||||
|
|
||||||
var threads = GetNumberOfThreads(state, string.Equals(videoCodec, "libvpx", StringComparison.OrdinalIgnoreCase));
|
var threads = GetNumberOfThreads(state, string.Equals(videoCodec, "libvpx", StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
|
@ -15,7 +15,6 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
public IIsoMount MountedIso { get; set; }
|
public IIsoMount MountedIso { get; set; }
|
||||||
public VideoType VideoType { get; set; }
|
public VideoType VideoType { get; set; }
|
||||||
public List<string> PlayableStreamFileNames { get; set; }
|
public List<string> PlayableStreamFileNames { get; set; }
|
||||||
public bool ExtractKeyFrameInterval { get; set; }
|
|
||||||
|
|
||||||
public MediaInfoRequest()
|
public MediaInfoRequest()
|
||||||
{
|
{
|
||||||
|
|
|
@ -135,9 +135,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
|
|
||||||
var inputFiles = MediaEncoderHelpers.GetInputArgument(FileSystem, request.InputPath, request.Protocol, request.MountedIso, request.PlayableStreamFileNames);
|
var inputFiles = MediaEncoderHelpers.GetInputArgument(FileSystem, request.InputPath, request.Protocol, request.MountedIso, request.PlayableStreamFileNames);
|
||||||
|
|
||||||
var extractKeyFrameInterval = request.ExtractKeyFrameInterval && request.Protocol == MediaProtocol.File && request.VideoType == VideoType.VideoFile;
|
return GetMediaInfoInternal(GetInputArgument(inputFiles, request.Protocol), request.InputPath, request.Protocol, extractChapters,
|
||||||
|
|
||||||
return GetMediaInfoInternal(GetInputArgument(inputFiles, request.Protocol), request.InputPath, request.Protocol, extractChapters, extractKeyFrameInterval,
|
|
||||||
GetProbeSizeArgument(inputFiles, request.Protocol), request.MediaType == DlnaProfileType.Audio, request.VideoType, cancellationToken);
|
GetProbeSizeArgument(inputFiles, request.Protocol), request.MediaType == DlnaProfileType.Audio, request.VideoType, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -171,18 +169,16 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
/// <param name="primaryPath">The primary path.</param>
|
/// <param name="primaryPath">The primary path.</param>
|
||||||
/// <param name="protocol">The protocol.</param>
|
/// <param name="protocol">The protocol.</param>
|
||||||
/// <param name="extractChapters">if set to <c>true</c> [extract chapters].</param>
|
/// <param name="extractChapters">if set to <c>true</c> [extract chapters].</param>
|
||||||
/// <param name="extractKeyFrameInterval">if set to <c>true</c> [extract key frame interval].</param>
|
|
||||||
/// <param name="probeSizeArgument">The probe size argument.</param>
|
/// <param name="probeSizeArgument">The probe size argument.</param>
|
||||||
/// <param name="isAudio">if set to <c>true</c> [is audio].</param>
|
/// <param name="isAudio">if set to <c>true</c> [is audio].</param>
|
||||||
/// <param name="videoType">Type of the video.</param>
|
/// <param name="videoType">Type of the video.</param>
|
||||||
/// <param name="cancellationToken">The cancellation token.</param>
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
/// <returns>Task{MediaInfoResult}.</returns>
|
/// <returns>Task{MediaInfoResult}.</returns>
|
||||||
/// <exception cref="System.ApplicationException"></exception>
|
/// <exception cref="System.ApplicationException">ffprobe failed - streams and format are both null.</exception>
|
||||||
private async Task<Model.MediaInfo.MediaInfo> GetMediaInfoInternal(string inputPath,
|
private async Task<Model.MediaInfo.MediaInfo> GetMediaInfoInternal(string inputPath,
|
||||||
string primaryPath,
|
string primaryPath,
|
||||||
MediaProtocol protocol,
|
MediaProtocol protocol,
|
||||||
bool extractChapters,
|
bool extractChapters,
|
||||||
bool extractKeyFrameInterval,
|
|
||||||
string probeSizeArgument,
|
string probeSizeArgument,
|
||||||
bool isAudio,
|
bool isAudio,
|
||||||
VideoType videoType,
|
VideoType videoType,
|
||||||
|
@ -262,31 +258,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
|
|
||||||
var mediaInfo = new ProbeResultNormalizer(_logger, FileSystem).GetMediaInfo(result, videoType, isAudio, primaryPath, protocol);
|
var mediaInfo = new ProbeResultNormalizer(_logger, FileSystem).GetMediaInfo(result, videoType, isAudio, primaryPath, protocol);
|
||||||
|
|
||||||
if (extractKeyFrameInterval && mediaInfo.RunTimeTicks.HasValue)
|
|
||||||
{
|
|
||||||
if (ConfigurationManager.Configuration.EnableVideoFrameByFrameAnalysis && mediaInfo.Size.HasValue)
|
|
||||||
{
|
|
||||||
foreach (var stream in mediaInfo.MediaStreams)
|
|
||||||
{
|
|
||||||
if (EnableKeyframeExtraction(mediaInfo, stream))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
stream.KeyFrames = await GetKeyFrames(inputPath, stream.Index, cancellationToken).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.ErrorException("Error getting key frame interval", ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return mediaInfo;
|
return mediaInfo;
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
|
@ -300,132 +271,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
_ffProbeResourcePool.Release();
|
_ffProbeResourcePool.Release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new ApplicationException(string.Format("FFProbe failed for {0}", inputPath));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool EnableKeyframeExtraction(MediaSourceInfo mediaSource, MediaStream videoStream)
|
|
||||||
{
|
|
||||||
if (videoStream.Type == MediaStreamType.Video && string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase) &&
|
|
||||||
!videoStream.IsInterlaced &&
|
|
||||||
!(videoStream.IsAnamorphic ?? false))
|
|
||||||
{
|
|
||||||
var audioStreams = mediaSource.MediaStreams.Where(i => i.Type == MediaStreamType.Audio).ToList();
|
|
||||||
|
|
||||||
// If it has aac audio then it will probably direct stream anyway, so don't bother with this
|
|
||||||
if (audioStreams.Count == 1 && string.Equals(audioStreams[0].Codec, "aac", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<List<int>> GetKeyFrames(string inputPath, int videoStreamIndex, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
inputPath = inputPath.Split(new[] { ':' }, 2).Last().Trim('"');
|
|
||||||
|
|
||||||
const string args = "-show_packets -print_format compact -select_streams v:{1} -show_entries packet=flags -show_entries packet=pts_time \"{0}\"";
|
|
||||||
|
|
||||||
var process = new Process
|
|
||||||
{
|
|
||||||
StartInfo = new ProcessStartInfo
|
|
||||||
{
|
|
||||||
CreateNoWindow = true,
|
|
||||||
UseShellExecute = false,
|
|
||||||
|
|
||||||
// Must consume both or ffmpeg may hang due to deadlocks. See comments below.
|
|
||||||
RedirectStandardOutput = true,
|
|
||||||
RedirectStandardError = true,
|
|
||||||
FileName = FFProbePath,
|
|
||||||
Arguments = string.Format(args, inputPath, videoStreamIndex.ToString(CultureInfo.InvariantCulture)).Trim(),
|
|
||||||
|
|
||||||
WindowStyle = ProcessWindowStyle.Hidden,
|
|
||||||
ErrorDialog = false
|
|
||||||
},
|
|
||||||
|
|
||||||
EnableRaisingEvents = true
|
|
||||||
};
|
|
||||||
|
|
||||||
_logger.Info("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
|
|
||||||
|
|
||||||
using (process)
|
|
||||||
{
|
|
||||||
var start = DateTime.UtcNow;
|
|
||||||
|
|
||||||
process.Start();
|
|
||||||
|
|
||||||
var lines = new List<int>();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
process.BeginErrorReadLine();
|
|
||||||
|
|
||||||
await StartReadingOutput(process.StandardOutput.BaseStream, lines, cancellationToken).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException)
|
|
||||||
{
|
|
||||||
if (cancellationToken.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
process.WaitForExit();
|
|
||||||
|
|
||||||
_logger.Info("Keyframe extraction took {0} seconds", (DateTime.UtcNow - start).TotalSeconds);
|
|
||||||
//_logger.Debug("Found keyframes {0}", string.Join(",", lines.ToArray()));
|
|
||||||
return lines;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task StartReadingOutput(Stream source, List<int> keyframes, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using (var reader = new StreamReader(source))
|
|
||||||
{
|
|
||||||
var text = await reader.ReadToEndAsync().ConfigureAwait(false);
|
|
||||||
|
|
||||||
var lines = StringHelper.RegexSplit(text, "[\r\n]+");
|
|
||||||
foreach (var line in lines)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(line))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var values = line.Split('|')
|
|
||||||
.Where(i => !string.IsNullOrWhiteSpace(i))
|
|
||||||
.Select(i => i.Split('='))
|
|
||||||
.Where(i => i.Length == 2)
|
|
||||||
.ToDictionary(i => i[0], i => i[1]);
|
|
||||||
|
|
||||||
string flags;
|
|
||||||
if (values.TryGetValue("flags", out flags) && string.Equals(flags, "k", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
string pts_time;
|
|
||||||
double frameSeconds;
|
|
||||||
if (values.TryGetValue("pts_time", out pts_time) && double.TryParse(pts_time, NumberStyles.Any, CultureInfo.InvariantCulture, out frameSeconds))
|
|
||||||
{
|
|
||||||
var ms = frameSeconds * 1000;
|
|
||||||
keyframes.Add(Convert.ToInt32(ms));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException)
|
|
||||||
{
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.ErrorException("Error reading ffprobe output", ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The us culture
|
/// The us culture
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -209,8 +209,6 @@ namespace MediaBrowser.Model.Configuration
|
||||||
|
|
||||||
public bool EnableWindowsShortcuts { get; set; }
|
public bool EnableWindowsShortcuts { get; set; }
|
||||||
|
|
||||||
public bool EnableVideoFrameByFrameAnalysis { get; set; }
|
|
||||||
|
|
||||||
public bool EnableDateLastRefresh { get; set; }
|
public bool EnableDateLastRefresh { get; set; }
|
||||||
|
|
||||||
public string[] Migrations { get; set; }
|
public string[] Migrations { get; set; }
|
||||||
|
|
|
@ -233,8 +233,5 @@ namespace MediaBrowser.Model.Entities
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value><c>null</c> if [is cabac] contains no value, <c>true</c> if [is cabac]; otherwise, <c>false</c>.</value>
|
/// <value><c>null</c> if [is cabac] contains no value, <c>true</c> if [is cabac]; otherwise, <c>false</c>.</value>
|
||||||
public bool? IsCabac { get; set; }
|
public bool? IsCabac { get; set; }
|
||||||
|
|
||||||
[IgnoreDataMember]
|
|
||||||
public List<int> KeyFrames { get; set; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -170,8 +170,7 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||||
VideoType = item.VideoType,
|
VideoType = item.VideoType,
|
||||||
MediaType = DlnaProfileType.Video,
|
MediaType = DlnaProfileType.Video,
|
||||||
InputPath = item.Path,
|
InputPath = item.Path,
|
||||||
Protocol = protocol,
|
Protocol = protocol
|
||||||
ExtractKeyFrameInterval = true
|
|
||||||
|
|
||||||
}, cancellationToken).ConfigureAwait(false);
|
}, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
|
|
@ -127,7 +127,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
|
||||||
_connection = await SqliteExtensions.ConnectToDb(dbFile, _logger).ConfigureAwait(false);
|
_connection = await SqliteExtensions.ConnectToDb(dbFile, _logger).ConfigureAwait(false);
|
||||||
|
|
||||||
var createMediaStreamsTableCommand
|
var createMediaStreamsTableCommand
|
||||||
= "create table if not exists mediastreams (ItemId GUID, StreamIndex INT, StreamType TEXT, Codec TEXT, Language TEXT, ChannelLayout TEXT, Profile TEXT, AspectRatio TEXT, Path TEXT, IsInterlaced BIT, BitRate INT NULL, Channels INT NULL, SampleRate INT NULL, IsDefault BIT, IsForced BIT, IsExternal BIT, Height INT NULL, Width INT NULL, AverageFrameRate FLOAT NULL, RealFrameRate FLOAT NULL, Level FLOAT NULL, PixelFormat TEXT, BitDepth INT NULL, IsAnamorphic BIT NULL, RefFrames INT NULL, IsCabac BIT NULL, KeyFrames TEXT NULL, CodecTag TEXT NULL, PRIMARY KEY (ItemId, StreamIndex))";
|
= "create table if not exists mediastreams (ItemId GUID, StreamIndex INT, StreamType TEXT, Codec TEXT, Language TEXT, ChannelLayout TEXT, Profile TEXT, AspectRatio TEXT, Path TEXT, IsInterlaced BIT, BitRate INT NULL, Channels INT NULL, SampleRate INT NULL, IsDefault BIT, IsForced BIT, IsExternal BIT, Height INT NULL, Width INT NULL, AverageFrameRate FLOAT NULL, RealFrameRate FLOAT NULL, Level FLOAT NULL, PixelFormat TEXT, BitDepth INT NULL, IsAnamorphic BIT NULL, RefFrames INT NULL, IsCabac BIT NULL, CodecTag TEXT NULL, PRIMARY KEY (ItemId, StreamIndex))";
|
||||||
|
|
||||||
string[] queries = {
|
string[] queries = {
|
||||||
|
|
||||||
|
@ -385,7 +385,6 @@ namespace MediaBrowser.Server.Implementations.Persistence
|
||||||
"IsAnamorphic",
|
"IsAnamorphic",
|
||||||
"RefFrames",
|
"RefFrames",
|
||||||
"IsCabac",
|
"IsCabac",
|
||||||
"KeyFrames",
|
|
||||||
"CodecTag"
|
"CodecTag"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -2683,15 +2682,6 @@ namespace MediaBrowser.Server.Implementations.Persistence
|
||||||
_saveStreamCommand.GetParameter(index++).Value = stream.RefFrames;
|
_saveStreamCommand.GetParameter(index++).Value = stream.RefFrames;
|
||||||
_saveStreamCommand.GetParameter(index++).Value = stream.IsCabac;
|
_saveStreamCommand.GetParameter(index++).Value = stream.IsCabac;
|
||||||
|
|
||||||
if (stream.KeyFrames == null || stream.KeyFrames.Count == 0)
|
|
||||||
{
|
|
||||||
_saveStreamCommand.GetParameter(index++).Value = null;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_saveStreamCommand.GetParameter(index++).Value = string.Join(",", stream.KeyFrames.Select(i => i.ToString(CultureInfo.InvariantCulture)).ToArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
_saveStreamCommand.GetParameter(index++).Value = stream.CodecTag;
|
_saveStreamCommand.GetParameter(index++).Value = stream.CodecTag;
|
||||||
|
|
||||||
_saveStreamCommand.Transaction = transaction;
|
_saveStreamCommand.Transaction = transaction;
|
||||||
|
@ -2848,16 +2838,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
|
||||||
|
|
||||||
if (!reader.IsDBNull(26))
|
if (!reader.IsDBNull(26))
|
||||||
{
|
{
|
||||||
var frames = reader.GetString(26);
|
item.CodecTag = reader.GetString(26);
|
||||||
if (!string.IsNullOrWhiteSpace(frames))
|
|
||||||
{
|
|
||||||
item.KeyFrames = frames.Split(',').Select(i => int.Parse(i, CultureInfo.InvariantCulture)).ToList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!reader.IsDBNull(27))
|
|
||||||
{
|
|
||||||
item.CodecTag = reader.GetString(27);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return item;
|
return item;
|
||||||
|
|
|
@ -303,6 +303,11 @@ namespace MediaBrowser.WebDashboard.Api
|
||||||
|
|
||||||
var mode = request.Mode;
|
var mode = request.Mode;
|
||||||
|
|
||||||
|
if (string.Equals(mode, "cordova", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
_fileSystem.DeleteFile(Path.Combine(path, "scripts", "registrationservices.js"));
|
||||||
|
}
|
||||||
|
|
||||||
// Try to trim the output size a bit
|
// Try to trim the output size a bit
|
||||||
var bowerPath = Path.Combine(path, "bower_components");
|
var bowerPath = Path.Combine(path, "bower_components");
|
||||||
DeleteFilesByExtension(bowerPath, ".log");
|
DeleteFilesByExtension(bowerPath, ".log");
|
||||||
|
|
Loading…
Reference in New Issue
Block a user