diff options
| author | Luke <luke.pulverenti@gmail.com> | 2017-03-29 02:25:37 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2017-03-29 02:25:37 -0400 |
| commit | 88e3fcfdc78fcab201fad72466bf2aa50b453210 (patch) | |
| tree | 1e40408a0167aa2dca47ac07b16afa26e94d18d1 /MediaBrowser.Controller | |
| parent | 75f58d5665322a5045649aed89aecfef011b315c (diff) | |
| parent | 9f9e81089f5606e958bc8c3f0e4d73475d02372a (diff) | |
Merge pull request #2554 from MediaBrowser/beta
Beta
Diffstat (limited to 'MediaBrowser.Controller')
17 files changed, 456 insertions, 32 deletions
diff --git a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs index 20b2529c0..8d83f8a35 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs @@ -289,7 +289,12 @@ namespace MediaBrowser.Controller.Entities.Audio } } - public static string GetPath(string name, bool normalizeName = true) + public static string GetPath(string name) + { + return GetPath(name, true); + } + + public static string GetPath(string name, bool normalizeName) { // Trim the period at the end because windows will have a hard time with that var validName = normalizeName ? diff --git a/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs b/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs index 74679b474..e26e0dfce 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs @@ -118,7 +118,12 @@ namespace MediaBrowser.Controller.Entities.Audio return LibraryManager.GetItemList(query); } - public static string GetPath(string name, bool normalizeName = true) + public static string GetPath(string name) + { + return GetPath(name, true); + } + + public static string GetPath(string name, bool normalizeName) { // Trim the period at the end because windows will have a hard time with that var validName = normalizeName ? diff --git a/MediaBrowser.Controller/Entities/GameGenre.cs b/MediaBrowser.Controller/Entities/GameGenre.cs index 22a8675c5..4187167b9 100644 --- a/MediaBrowser.Controller/Entities/GameGenre.cs +++ b/MediaBrowser.Controller/Entities/GameGenre.cs @@ -96,7 +96,12 @@ namespace MediaBrowser.Controller.Entities } } - public static string GetPath(string name, bool normalizeName = true) + public static string GetPath(string name) + { + return GetPath(name, true); + } + + public static string GetPath(string name, bool normalizeName) { // Trim the period at the end because windows will have a hard time with that var validName = normalizeName ? diff --git a/MediaBrowser.Controller/Entities/Genre.cs b/MediaBrowser.Controller/Entities/Genre.cs index 1b746ae51..9769efdd0 100644 --- a/MediaBrowser.Controller/Entities/Genre.cs +++ b/MediaBrowser.Controller/Entities/Genre.cs @@ -108,7 +108,12 @@ namespace MediaBrowser.Controller.Entities } } - public static string GetPath(string name, bool normalizeName = true) + public static string GetPath(string name) + { + return GetPath(name, true); + } + + public static string GetPath(string name, bool normalizeName) { // Trim the period at the end because windows will have a hard time with that var validName = normalizeName ? diff --git a/MediaBrowser.Controller/Entities/IHasImages.cs b/MediaBrowser.Controller/Entities/IHasImages.cs index 4c033dc00..2104bee09 100644 --- a/MediaBrowser.Controller/Entities/IHasImages.cs +++ b/MediaBrowser.Controller/Entities/IHasImages.cs @@ -206,6 +206,8 @@ namespace MediaBrowser.Controller.Entities void SetImage(ItemImageInfo image, int index); double? GetDefaultPrimaryImageAspectRatio(); + + int? ProductionYear { get; set; } } public static class HasImagesExtensions diff --git a/MediaBrowser.Controller/Entities/Person.cs b/MediaBrowser.Controller/Entities/Person.cs index ee1aea938..b68681d36 100644 --- a/MediaBrowser.Controller/Entities/Person.cs +++ b/MediaBrowser.Controller/Entities/Person.cs @@ -133,7 +133,12 @@ namespace MediaBrowser.Controller.Entities } } - public static string GetPath(string name, bool normalizeName = true) + public static string GetPath(string name) + { + return GetPath(name, true); + } + + public static string GetPath(string name, bool normalizeName) { // Trim the period at the end because windows will have a hard time with that var validFilename = normalizeName ? diff --git a/MediaBrowser.Controller/Entities/Studio.cs b/MediaBrowser.Controller/Entities/Studio.cs index b8ad691a9..2e5e6ce43 100644 --- a/MediaBrowser.Controller/Entities/Studio.cs +++ b/MediaBrowser.Controller/Entities/Studio.cs @@ -114,7 +114,12 @@ namespace MediaBrowser.Controller.Entities } } - public static string GetPath(string name, bool normalizeName = true) + public static string GetPath(string name) + { + return GetPath(name, true); + } + + public static string GetPath(string name, bool normalizeName) { // Trim the period at the end because windows will have a hard time with that var validName = normalizeName ? diff --git a/MediaBrowser.Controller/Entities/Video.cs b/MediaBrowser.Controller/Entities/Video.cs index 78f907d61..890626419 100644 --- a/MediaBrowser.Controller/Entities/Video.cs +++ b/MediaBrowser.Controller/Entities/Video.cs @@ -614,7 +614,8 @@ namespace MediaBrowser.Controller.Entities Timestamp = i.Timestamp, Type = type, PlayableStreamFileNames = i.PlayableStreamFileNames.ToList(), - SupportsDirectStream = i.VideoType == VideoType.VideoFile + SupportsDirectStream = i.VideoType == VideoType.VideoFile, + IsRemote = i.IsShortcut }; if (info.Protocol == MediaProtocol.File) diff --git a/MediaBrowser.Controller/Entities/Year.cs b/MediaBrowser.Controller/Entities/Year.cs index 75fb69435..b352950a0 100644 --- a/MediaBrowser.Controller/Entities/Year.cs +++ b/MediaBrowser.Controller/Entities/Year.cs @@ -122,7 +122,12 @@ namespace MediaBrowser.Controller.Entities } } - public static string GetPath(string name, bool normalizeName = true) + public static string GetPath(string name) + { + return GetPath(name, true); + } + + public static string GetPath(string name, bool normalizeName) { // Trim the period at the end because windows will have a hard time with that var validName = normalizeName ? diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs index a908d2d3f..5242c5b1f 100644 --- a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs +++ b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs @@ -376,19 +376,14 @@ namespace MediaBrowser.Controller.LiveTv /// <returns>Task.</returns> Task OnRecordingFileDeleted(BaseItem recording); - /// <summary> - /// Gets the sat ini mappings. - /// </summary> - /// <returns>List<NameValuePair>.</returns> - List<NameValuePair> GetSatIniMappings(); - - Task<List<ChannelInfo>> GetSatChannelScanResult(TunerHostInfo info, CancellationToken cancellationToken); - Task<List<ChannelInfo>> GetChannelsForListingsProvider(string id, CancellationToken cancellationToken); Task<List<ChannelInfo>> GetChannelsFromListingsProviderData(string id, CancellationToken cancellationToken); List<IListingsProvider> ListingProviders { get; } + List<NameIdPair> GetTunerHostTypes(); + Task<List<TunerHostInfo>> DiscoverTuners(bool newDevicesOnly, CancellationToken cancellationToken); + event EventHandler<GenericEventArgs<TimerEventInfo>> SeriesTimerCancelled; event EventHandler<GenericEventArgs<TimerEventInfo>> TimerCancelled; event EventHandler<GenericEventArgs<TimerEventInfo>> TimerCreated; diff --git a/MediaBrowser.Controller/LiveTv/ITunerHost.cs b/MediaBrowser.Controller/LiveTv/ITunerHost.cs index 5615649c2..fc344298b 100644 --- a/MediaBrowser.Controller/LiveTv/ITunerHost.cs +++ b/MediaBrowser.Controller/LiveTv/ITunerHost.cs @@ -44,6 +44,8 @@ namespace MediaBrowser.Controller.LiveTv /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task<List<MediaSourceInfo>>.</returns> Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(string channelId, CancellationToken cancellationToken); + + Task<List<TunerHostInfo>> DiscoverDevices(int discoveryDurationMs, CancellationToken cancellationToken); } public interface IConfigurableTunerHost { diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 4ebee531f..88153868f 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -190,6 +190,7 @@ <Compile Include="MediaEncoding\ImageEncodingOptions.cs" /> <Compile Include="MediaEncoding\IMediaEncoder.cs" /> <Compile Include="MediaEncoding\ISubtitleEncoder.cs" /> + <Compile Include="MediaEncoding\JobLogger.cs" /> <Compile Include="MediaEncoding\MediaInfoRequest.cs" /> <Compile Include="MediaEncoding\MediaStreamSelector.cs" /> <Compile Include="Net\AuthenticatedAttribute.cs" /> diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 80b9cc154..b7b31509c 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -154,6 +154,7 @@ namespace MediaBrowser.Controller.MediaEncoding { return "mpegts"; } + // For these need to find out the ffmpeg names if (string.Equals(container, "m2ts", StringComparison.OrdinalIgnoreCase)) { @@ -163,6 +164,10 @@ namespace MediaBrowser.Controller.MediaEncoding { return null; } + if (string.Equals(container, "mts", StringComparison.OrdinalIgnoreCase)) + { + return null; + } if (string.Equals(container, "vob", StringComparison.OrdinalIgnoreCase)) { return null; @@ -179,12 +184,23 @@ namespace MediaBrowser.Controller.MediaEncoding { return null; } + if (string.Equals(container, "dvr-ms", StringComparison.OrdinalIgnoreCase)) + { + return null; + } + // Seeing reported failures here, not sure yet if this is related to specfying input format if (string.Equals(container, "m4v", StringComparison.OrdinalIgnoreCase)) { return null; } + // obviously don't do this for strm files + if (string.Equals(container, "strm", StringComparison.OrdinalIgnoreCase)) + { + return null; + } + return container; } @@ -661,9 +677,8 @@ namespace MediaBrowser.Controller.MediaEncoding var level = NormalizeTranscodingLevel(state.OutputVideoCodec, request.Level); // h264_qsv and h264_nvenc expect levels to be expressed as a decimal. libx264 supports decimal and non-decimal format - // also needed for libx264 due to https://trac.ffmpeg.org/ticket/3307 + // also needed for libx264 due to https://trac.ffmpeg.org/ticket/3307 if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) || - string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) || string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase)) { switch (level) @@ -700,10 +715,15 @@ namespace MediaBrowser.Controller.MediaEncoding break; } } + // nvenc doesn't decode with param -level set ?! + if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)){ + param += ""; + } else if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase)) { param += " -level " + level; } + } if (string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase)) @@ -727,12 +747,18 @@ namespace MediaBrowser.Controller.MediaEncoding if (videoStream.IsInterlaced) { - return false; + if (request.DeInterlace) + { + return false; + } } if (videoStream.IsAnamorphic ?? false) { - return false; + if (request.RequireNonAnamorphic) + { + return false; + } } // Can't stream copy if we're burning in subtitles @@ -1561,6 +1587,15 @@ namespace MediaBrowser.Controller.MediaEncoding MediaSourceInfo mediaSource, string requestedUrl) { + if (state == null) + { + throw new ArgumentNullException("state"); + } + if (mediaSource == null) + { + throw new ArgumentNullException("mediaSource"); + } + state.MediaPath = mediaSource.Path; state.InputProtocol = mediaSource.Protocol; state.InputContainer = mediaSource.Container; @@ -1670,9 +1705,21 @@ namespace MediaBrowser.Controller.MediaEncoding case "h264": if (_mediaEncoder.SupportsDecoder("h264_qsv")) { + // qsv decoder does not support 10-bit input + if ((state.VideoStream.BitDepth ?? 8) > 8) + { + return null; + } return "-c:v h264_qsv "; } break; + //case "hevc": + //case "h265": + // if (_mediaEncoder.SupportsDecoder("hevc_qsv")) + // { + // return "-c:v hevc_qsv "; + // } + // break; case "mpeg2video": if (_mediaEncoder.SupportsDecoder("mpeg2_qsv")) { @@ -1715,5 +1762,187 @@ namespace MediaBrowser.Controller.MediaEncoding return threads; } + + public string GetSubtitleEmbedArguments(EncodingJobInfo state) + { + if (state.SubtitleStream == null || state.SubtitleDeliveryMethod != SubtitleDeliveryMethod.Embed) + { + return string.Empty; + } + + var format = state.SupportedSubtitleCodecs.FirstOrDefault(); + string codec; + + if (string.IsNullOrWhiteSpace(format) || string.Equals(format, state.SubtitleStream.Codec, StringComparison.OrdinalIgnoreCase)) + { + codec = "copy"; + } + else + { + codec = format; + } + + // Muxing in dvbsub via either copy or -codec dvbsub does not seem to work + // It doesn't throw any errors but vlc on android will not render them + // They will need to be converted to an alternative format + // TODO: This is incorrectly assuming that dvdsub will be supported by the player + // The api will need to be expanded to accomodate this. + if (string.Equals(state.SubtitleStream.Codec, "DVBSUB", StringComparison.OrdinalIgnoreCase)) + { + codec = "dvdsub"; + } + + var args = " -codec:s:0 " + codec; + + args += " -disposition:s:0 default"; + + return args; + } + + public string GetProgressiveVideoFullCommandLine(EncodingJobInfo state, EncodingOptions encodingOptions, string outputPath, string defaultH264Preset) + { + // Get the output codec name + var videoCodec = GetVideoEncoder(state, encodingOptions); + + var format = string.Empty; + var keyFrame = string.Empty; + + if (string.Equals(Path.GetExtension(outputPath), ".mp4", StringComparison.OrdinalIgnoreCase) && + state.BaseRequest.Context == EncodingContext.Streaming) + { + // Comparison: https://github.com/jansmolders86/mediacenterjs/blob/master/lib/transcoding/desktop.js + format = " -f mp4 -movflags frag_keyframe+empty_moov"; + } + + var threads = GetNumberOfThreads(state, encodingOptions, string.Equals(videoCodec, "libvpx", StringComparison.OrdinalIgnoreCase)); + + var inputModifier = GetInputModifier(state, encodingOptions); + + return string.Format("{0} {1}{2} {3} {4} -map_metadata -1 -map_chapters -1 -threads {5} {6}{7}{8} -y \"{9}\"", + inputModifier, + GetInputArgument(state, encodingOptions), + keyFrame, + GetMapArgs(state), + GetProgressiveVideoArguments(state, encodingOptions, videoCodec, defaultH264Preset), + threads, + GetProgressiveVideoAudioArguments(state, encodingOptions), + GetSubtitleEmbedArguments(state), + format, + outputPath + ).Trim(); + } + + public string GetProgressiveVideoArguments(EncodingJobInfo state, EncodingOptions encodingOptions, string videoCodec, string defaultH264Preset) + { + var args = "-codec:v:0 " + videoCodec; + + if (state.EnableMpegtsM2TsMode) + { + args += " -mpegts_m2ts_mode 1"; + } + + if (string.Equals(videoCodec, "copy", StringComparison.OrdinalIgnoreCase)) + { + if (state.VideoStream != null && IsH264(state.VideoStream) && string.Equals(state.OutputContainer, "ts", StringComparison.OrdinalIgnoreCase) && !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase)) + { + args += " -bsf:v h264_mp4toannexb"; + } + + if (state.RunTimeTicks.HasValue && state.BaseRequest.CopyTimestamps) + { + args += " -copyts -avoid_negative_ts disabled -start_at_zero"; + } + + if (!state.RunTimeTicks.HasValue) + { + args += " -flags -global_header -fflags +genpts"; + } + + return args; + } + + var keyFrameArg = string.Format(" -force_key_frames \"expr:gte(t,n_forced*{0})\"", + 5.ToString(_usCulture)); + + args += keyFrameArg; + + var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.BaseRequest.SubtitleMethod == SubtitleDeliveryMethod.Encode; + + var hasCopyTs = false; + // Add resolution params, if specified + if (!hasGraphicalSubs) + { + var outputSizeParam = GetOutputSizeParam(state, videoCodec); + args += outputSizeParam; + hasCopyTs = outputSizeParam.IndexOf("copyts", StringComparison.OrdinalIgnoreCase) != -1; + } + + if (state.RunTimeTicks.HasValue && state.BaseRequest.CopyTimestamps) + { + if (!hasCopyTs) + { + args += " -copyts"; + } + args += " -avoid_negative_ts disabled -start_at_zero"; + } + + var qualityParam = GetVideoQualityParam(state, videoCodec, encodingOptions, defaultH264Preset); + + if (!string.IsNullOrEmpty(qualityParam)) + { + args += " " + qualityParam.Trim(); + } + + // This is for internal graphical subs + if (hasGraphicalSubs) + { + args += GetGraphicalSubtitleParam(state, videoCodec); + } + + if (!state.RunTimeTicks.HasValue) + { + args += " -flags -global_header"; + } + + return args; + } + + public string GetProgressiveVideoAudioArguments(EncodingJobInfo state, EncodingOptions encodingOptions) + { + // If the video doesn't have an audio stream, return a default. + if (state.AudioStream == null && state.VideoStream != null) + { + return string.Empty; + } + + // Get the output codec name + var codec = GetAudioEncoder(state); + + var args = "-codec:a:0 " + codec; + + if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase)) + { + return args; + } + + // Add the number of audio channels + var channels = state.OutputAudioChannels; + + if (channels.HasValue) + { + args += " -ac " + channels.Value; + } + + var bitrate = state.OutputAudioBitrate; + + if (bitrate.HasValue) + { + args += " -ab " + bitrate.Value.ToString(_usCulture); + } + + args += " " + GetAudioFilterParam(state, encodingOptions, false); + + return args; + } } } diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs index a18b86432..f5878864b 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs @@ -12,7 +12,7 @@ using MediaBrowser.Model.MediaInfo; namespace MediaBrowser.Controller.MediaEncoding { // For now, a common base class until the API and MediaEncoding classes are unified - public class EncodingJobInfo + public abstract class EncodingJobInfo { private readonly ILogger _logger; @@ -29,6 +29,7 @@ namespace MediaBrowser.Controller.MediaEncoding public int? OutputVideoBitrate { get; set; } public MediaStream SubtitleStream { get; set; } public SubtitleDeliveryMethod SubtitleDeliveryMethod { get; set; } + public List<string> SupportedSubtitleCodecs { get; set; } public int InternalSubtitleStreamOffset { get; set; } public MediaSourceInfo MediaSource { get; set; } @@ -52,6 +53,8 @@ namespace MediaBrowser.Controller.MediaEncoding public string InputContainer { get; set; } public IsoType? IsoType { get; set; } + public bool EnableMpegtsM2TsMode { get; set; } + public BaseEncodingJobOptions BaseRequest { get; set; } public long? StartTimeTicks @@ -64,6 +67,7 @@ namespace MediaBrowser.Controller.MediaEncoding get { return BaseRequest.CopyTimestamps; } } + public int? OutputAudioBitrate; public int? OutputAudioChannels; public int? OutputAudioSampleRate; public bool DeInterlace { get; set; } @@ -74,8 +78,9 @@ namespace MediaBrowser.Controller.MediaEncoding _logger = logger; RemoteHttpHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); PlayableStreamFileNames = new List<string>(); + SupportedAudioCodecs = new List<string>(); SupportedVideoCodecs = new List<string>(); - SupportedVideoCodecs = new List<string>(); + SupportedSubtitleCodecs = new List<string>(); } /// <summary> @@ -110,5 +115,7 @@ namespace MediaBrowser.Controller.MediaEncoding IsoMount = null; } } + + public abstract void ReportTranscodingProgress(TimeSpan? transcodingPosition, float? framerate, double? percentComplete, long? bytesTranscoded, int? bitRate); } } diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs index 4bb180d4b..73be78dc9 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs @@ -14,7 +14,6 @@ namespace MediaBrowser.Controller.MediaEncoding public string AudioCodec { get; set; } public DeviceProfile DeviceProfile { get; set; } - public EncodingContext Context { get; set; } public bool ReadInputAtNativeFramerate { get; set; } @@ -46,7 +45,7 @@ namespace MediaBrowser.Controller.MediaEncoding AudioBitRate = info.AudioBitrate; AudioSampleRate = info.TargetAudioSampleRate; DeviceProfile = deviceProfile; - VideoCodec = info.VideoCodec; + VideoCodec = info.TargetVideoCodec; VideoBitRate = info.VideoBitrate; AudioStreamIndex = info.AudioStreamIndex; MaxRefFrames = info.MaxRefFrames; @@ -185,6 +184,8 @@ namespace MediaBrowser.Controller.MediaEncoding [ApiMember(Name = "MaxVideoBitDepth", Description = "Optional.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] public int? MaxVideoBitDepth { get; set; } public bool RequireAvc { get; set; } + public bool DeInterlace { get; set; } + public bool RequireNonAnamorphic { get; set; } public int? TranscodingMaxAudioChannels { get; set; } public int? CpuCoreLimit { get; set; } public string OutputContainer { get; set; } @@ -196,6 +197,8 @@ namespace MediaBrowser.Controller.MediaEncoding [ApiMember(Name = "VideoCodec", Description = "Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension. Options: h264, mpeg4, theora, vpx, wmv.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] public string VideoCodec { get; set; } + public string SubtitleCodec { get; set; } + /// <summary> /// Gets or sets the index of the audio stream. /// </summary> @@ -210,9 +213,12 @@ namespace MediaBrowser.Controller.MediaEncoding [ApiMember(Name = "VideoStreamIndex", Description = "Optional. The index of the video stream to use. If omitted the first video stream will be used.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] public int? VideoStreamIndex { get; set; } + public EncodingContext Context { get; set; } + public BaseEncodingJobOptions() { EnableAutoStreamCopy = true; + Context = EncodingContext.Streaming; } } } diff --git a/MediaBrowser.Controller/MediaEncoding/JobLogger.cs b/MediaBrowser.Controller/MediaEncoding/JobLogger.cs new file mode 100644 index 000000000..03e4f7771 --- /dev/null +++ b/MediaBrowser.Controller/MediaEncoding/JobLogger.cs @@ -0,0 +1,149 @@ +using MediaBrowser.Model.Extensions; +using MediaBrowser.Model.Logging; +using System; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; + +namespace MediaBrowser.Controller.MediaEncoding +{ + public class JobLogger + { + private readonly CultureInfo _usCulture = new CultureInfo("en-US"); + private readonly ILogger _logger; + + public JobLogger(ILogger logger) + { + _logger = logger; + } + + public async void StartStreamingLog(EncodingJobInfo 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); + await target.FlushAsync().ConfigureAwait(false); + } + } + } + catch (ObjectDisposedException) + { + // Don't spam the log. This doesn't seem to throw in windows, but sometimes under linux + } + catch (Exception ex) + { + _logger.ErrorException("Error reading ffmpeg log", ex); + } + } + + private void ParseLogLine(string line, EncodingJobInfo state) + { + float? framerate = null; + double? percent = null; + TimeSpan? transcodingPosition = null; + long? bytesTranscoded = null; + int? bitRate = null; + + var parts = line.Split(' '); + + var totalMs = state.RunTimeTicks.HasValue + ? TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalMilliseconds + : 0; + + var startMs = state.BaseRequest.StartTimeTicks.HasValue + ? TimeSpan.FromTicks(state.BaseRequest.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; + + transcodingPosition = val; + } + } + else if (part.StartsWith("size=", StringComparison.OrdinalIgnoreCase)) + { + var size = part.Split(new[] { '=' }, 2).Last(); + + int? scale = null; + if (size.IndexOf("kb", StringComparison.OrdinalIgnoreCase) != -1) + { + scale = 1024; + size = size.Replace("kb", string.Empty, StringComparison.OrdinalIgnoreCase); + } + + if (scale.HasValue) + { + long val; + + if (long.TryParse(size, NumberStyles.Any, _usCulture, out val)) + { + bytesTranscoded = val * scale.Value; + } + } + } + else if (part.StartsWith("bitrate=", StringComparison.OrdinalIgnoreCase)) + { + var rate = part.Split(new[] { '=' }, 2).Last(); + + int? scale = null; + if (rate.IndexOf("kbits/s", StringComparison.OrdinalIgnoreCase) != -1) + { + scale = 1024; + rate = rate.Replace("kbits/s", string.Empty, StringComparison.OrdinalIgnoreCase); + } + + if (scale.HasValue) + { + float val; + + if (float.TryParse(rate, NumberStyles.Any, _usCulture, out val)) + { + bitRate = (int)Math.Ceiling(val * scale.Value); + } + } + } + } + + if (framerate.HasValue || percent.HasValue) + { + state.ReportTranscodingProgress(transcodingPosition, framerate, percent, bytesTranscoded, bitRate); + } + } + } +} diff --git a/MediaBrowser.Controller/Net/StaticResultOptions.cs b/MediaBrowser.Controller/Net/StaticResultOptions.cs index 272fa8b3b..62c13fbb5 100644 --- a/MediaBrowser.Controller/Net/StaticResultOptions.cs +++ b/MediaBrowser.Controller/Net/StaticResultOptions.cs @@ -23,21 +23,18 @@ namespace MediaBrowser.Controller.Net public Action OnComplete { get; set; } public Action OnError { get; set; } + public string Path { get; set; } + + public FileShareMode FileShare { get; set; } + public StaticResultOptions() { ResponseHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); + FileShare = FileShareMode.Read; } } public class StaticFileResultOptions : StaticResultOptions { - public string Path { get; set; } - - public FileShareMode FileShare { get; set; } - - public StaticFileResultOptions() - { - FileShare = FileShareMode.Read; - } } } |
