From 8e9571e828153b9026242ca3e52ad4d0ee902774 Mon Sep 17 00:00:00 2001 From: LukePulverenti Luke Pulverenti luke pulverenti Date: Sun, 9 Sep 2012 11:15:22 -0400 Subject: [PATCH] Added GetVideoStreamUrl to ApiClient --- MediaBrowser.Api/HttpHandlers/AudioHandler.cs | 3 +- .../HttpHandlers/BaseMediaHandler.cs | 2 +- MediaBrowser.Api/HttpHandlers/VideoHandler.cs | 113 ++++++++++++++---- MediaBrowser.ApiInteraction/BaseApiClient.cs | 70 +++++++++-- .../Resolvers/AudioResolver.cs | 6 +- MediaBrowser.Model/DTO/AudioOutputFormats.cs | 1 - MediaBrowser.Model/DTO/VideoOutputFormats.cs | 22 ++++ MediaBrowser.Model/MediaBrowser.Model.csproj | 1 + 8 files changed, 181 insertions(+), 37 deletions(-) create mode 100644 MediaBrowser.Model/DTO/VideoOutputFormats.cs diff --git a/MediaBrowser.Api/HttpHandlers/AudioHandler.cs b/MediaBrowser.Api/HttpHandlers/AudioHandler.cs index 2ce56c1fd..b2c0213d5 100644 --- a/MediaBrowser.Api/HttpHandlers/AudioHandler.cs +++ b/MediaBrowser.Api/HttpHandlers/AudioHandler.cs @@ -1,7 +1,6 @@ using MediaBrowser.Common.Net.Handlers; using MediaBrowser.Model.DTO; using MediaBrowser.Model.Entities; -using System; using System.Collections.Generic; using System.ComponentModel.Composition; using System.IO; @@ -27,7 +26,7 @@ namespace MediaBrowser.Api.HttpHandlers { get { - return new AudioOutputFormats[] { AudioOutputFormats.Aac, AudioOutputFormats.Flac, AudioOutputFormats.Wav, AudioOutputFormats.Wma }; + return new AudioOutputFormats[] { AudioOutputFormats.Aac, AudioOutputFormats.Flac, AudioOutputFormats.Wma }; } } diff --git a/MediaBrowser.Api/HttpHandlers/BaseMediaHandler.cs b/MediaBrowser.Api/HttpHandlers/BaseMediaHandler.cs index ec321aa14..6d52ea07d 100644 --- a/MediaBrowser.Api/HttpHandlers/BaseMediaHandler.cs +++ b/MediaBrowser.Api/HttpHandlers/BaseMediaHandler.cs @@ -83,7 +83,7 @@ namespace MediaBrowser.Api.HttpHandlers if (string.IsNullOrEmpty(val)) { - return null; + return 44100; } return int.Parse(val); diff --git a/MediaBrowser.Api/HttpHandlers/VideoHandler.cs b/MediaBrowser.Api/HttpHandlers/VideoHandler.cs index ebababd3f..3ca00f0d5 100644 --- a/MediaBrowser.Api/HttpHandlers/VideoHandler.cs +++ b/MediaBrowser.Api/HttpHandlers/VideoHandler.cs @@ -1,5 +1,6 @@ using MediaBrowser.Common.Drawing; using MediaBrowser.Common.Net.Handlers; +using MediaBrowser.Model.DTO; using MediaBrowser.Model.Entities; using System; using System.Collections.Generic; @@ -15,7 +16,7 @@ namespace MediaBrowser.Api.HttpHandlers /// Supported output formats: mkv,m4v,mp4,asf,wmv,mov,webm,ogv,3gp,avi,ts,flv /// [Export(typeof(BaseHandler))] - class VideoHandler : BaseMediaHandler + class VideoHandler : BaseMediaHandler { public override bool HandlesRequest(HttpListenerRequest request) { @@ -25,17 +26,20 @@ namespace MediaBrowser.Api.HttpHandlers /// /// We can output these files directly, but we can't encode them /// - protected override IEnumerable UnsupportedOutputEncodingFormats + protected override IEnumerable UnsupportedOutputEncodingFormats { get { // mp4, 3gp, mov - muxer does not support non-seekable output // avi, mov, mkv, m4v - can't stream these when encoding. the player will try to download them completely before starting playback. // wmv - can't seem to figure out the output format name - return new string[] { "mp4", "3gp", "m4v", "mkv", "avi", "mov", "wmv" }; + return new VideoOutputFormats[] { VideoOutputFormats.Mp4, VideoOutputFormats.ThreeGP, VideoOutputFormats.M4v, VideoOutputFormats.Mkv, VideoOutputFormats.Avi, VideoOutputFormats.Mov, VideoOutputFormats.Wmv }; } } + /// + /// Determines whether or not we can just output the original file directly + /// protected override bool RequiresConversion() { string currentFormat = Path.GetExtension(LibraryItem.Path).Replace(".", string.Empty); @@ -52,11 +56,13 @@ namespace MediaBrowser.Api.HttpHandlers return true; } + // See if the video requires conversion if (RequiresVideoConversion()) { return true; } + // See if the audio requires conversion AudioStream audioStream = (LibraryItem.AudioStreams ?? new List()).FirstOrDefault(); if (audioStream != null) @@ -72,24 +78,24 @@ namespace MediaBrowser.Api.HttpHandlers } /// - /// Translates the file extension to the format param that follows "-f" on the ffmpeg command line + /// Translates the output file extension to the format param that follows "-f" on the ffmpeg command line /// - private string GetFFMpegOutputFormat(string outputFormat) + private string GetFFMpegOutputFormat(VideoOutputFormats outputFormat) { - if (outputFormat.Equals("mkv", StringComparison.OrdinalIgnoreCase)) + if (outputFormat == VideoOutputFormats.Mkv) { return "matroska"; } - else if (outputFormat.Equals("ts", StringComparison.OrdinalIgnoreCase)) + else if (outputFormat == VideoOutputFormats.Ts) { return "mpegts"; } - else if (outputFormat.Equals("ogv", StringComparison.OrdinalIgnoreCase)) + else if (outputFormat == VideoOutputFormats.Ogv) { return "ogg"; } - return outputFormat; + return outputFormat.ToString().ToLower(); } /// @@ -99,7 +105,7 @@ namespace MediaBrowser.Api.HttpHandlers { List audioTranscodeParams = new List(); - string outputFormat = GetConversionOutputFormat(); + VideoOutputFormats outputFormat = GetConversionOutputFormat(); return string.Format("-i \"{0}\" -threads 0 {1} {2} -f {3} -", LibraryItem.Path, @@ -109,14 +115,20 @@ namespace MediaBrowser.Api.HttpHandlers ); } - private string GetVideoArguments(string outputFormat) + /// + /// Gets video arguments to pass to ffmpeg + /// + private string GetVideoArguments(VideoOutputFormats outputFormat) { + // Get the output codec name string codec = GetVideoCodec(outputFormat); string args = "-vcodec " + codec; + // If we're encoding video, add additional params if (!codec.Equals("copy", StringComparison.OrdinalIgnoreCase)) { + // Add resolution params, if specified if (Width.HasValue || Height.HasValue || MaxHeight.HasValue || MaxWidth.HasValue) { Size size = DrawingUtils.Resize(LibraryItem.Width, LibraryItem.Height, Width, Height, MaxWidth, MaxHeight); @@ -128,21 +140,28 @@ namespace MediaBrowser.Api.HttpHandlers return args; } - private string GetAudioArguments(string outputFormat) + /// + /// Gets audio arguments to pass to ffmpeg + /// + private string GetAudioArguments(VideoOutputFormats outputFormat) { AudioStream audioStream = (LibraryItem.AudioStreams ?? new List()).FirstOrDefault(); + // If the video doesn't have an audio stream, return empty if (audioStream == null) { return string.Empty; } + // Get the output codec name string codec = GetAudioCodec(audioStream, outputFormat); string args = "-acodec " + codec; + // If we're encoding audio, add additional params if (!codec.Equals("copy", StringComparison.OrdinalIgnoreCase)) { + // Add the number of audio channels int? channels = GetNumAudioChannelsParam(codec, audioStream.Channels); if (channels.HasValue) @@ -150,6 +169,7 @@ namespace MediaBrowser.Api.HttpHandlers args += " -ac " + channels.Value; } + // Add the audio sample rate int? sampleRate = GetSampleRateParam(audioStream.SampleRate); if (sampleRate.HasValue) @@ -162,26 +182,32 @@ namespace MediaBrowser.Api.HttpHandlers return args; } - private string GetVideoCodec(string outputFormat) + /// + /// Gets the name of the output video codec + /// + private string GetVideoCodec(VideoOutputFormats outputFormat) { - if (outputFormat.Equals("webm")) + // Some output containers require specific codecs + + if (outputFormat == VideoOutputFormats.Webm) { // Per webm specification, it must be vpx return "libvpx"; } - else if (outputFormat.Equals("asf")) + else if (outputFormat == VideoOutputFormats.Asf) { return "wmv2"; } - else if (outputFormat.Equals("wmv")) + else if (outputFormat == VideoOutputFormats.Wmv) { return "wmv2"; } - else if (outputFormat.Equals("ogv")) + else if (outputFormat == VideoOutputFormats.Ogv) { return "libtheora"; } + // Skip encoding when possible if (!RequiresVideoConversion()) { return "copy"; @@ -190,27 +216,32 @@ namespace MediaBrowser.Api.HttpHandlers return "libx264"; } - private string GetAudioCodec(AudioStream audioStream, string outputFormat) + /// + /// Gets the name of the output audio codec + /// + private string GetAudioCodec(AudioStream audioStream, VideoOutputFormats outputFormat) { - if (outputFormat.Equals("webm")) + // Some output containers require specific codecs + + if (outputFormat == VideoOutputFormats.Webm) { // Per webm specification, it must be vorbis return "libvorbis"; } - else if (outputFormat.Equals("asf")) + else if (outputFormat == VideoOutputFormats.Asf) { return "wmav2"; } - else if (outputFormat.Equals("wmv")) + else if (outputFormat == VideoOutputFormats.Wmv) { return "wmav2"; } - else if (outputFormat.Equals("ogv")) + else if (outputFormat == VideoOutputFormats.Ogv) { return "libvorbis"; } - // See if we can just copy the stream + // Skip encoding when possible if (!RequiresAudioConversion(audioStream)) { return "copy"; @@ -219,6 +250,9 @@ namespace MediaBrowser.Api.HttpHandlers return "libvo_aacenc"; } + /// + /// Gets the number of audio channels to specify on the command line + /// private int? GetNumAudioChannelsParam(string audioCodec, int libraryItemChannels) { if (libraryItemChannels > 2) @@ -238,9 +272,14 @@ namespace MediaBrowser.Api.HttpHandlers return GetNumAudioChannelsParam(libraryItemChannels); } + /// + /// Determines if the video stream requires encoding + /// private bool RequiresVideoConversion() { // Check dimensions + + // If a specific width is required, validate that if (Width.HasValue) { if (Width.Value != LibraryItem.Width) @@ -248,6 +287,8 @@ namespace MediaBrowser.Api.HttpHandlers return true; } } + + // If a specific height is required, validate that if (Height.HasValue) { if (Height.Value != LibraryItem.Height) @@ -255,6 +296,8 @@ namespace MediaBrowser.Api.HttpHandlers return true; } } + + // If a max width is required, validate that if (MaxWidth.HasValue) { if (MaxWidth.Value < LibraryItem.Width) @@ -262,6 +305,8 @@ namespace MediaBrowser.Api.HttpHandlers return true; } } + + // If a max height is required, validate that if (MaxHeight.HasValue) { if (MaxHeight.Value < LibraryItem.Height) @@ -270,6 +315,7 @@ namespace MediaBrowser.Api.HttpHandlers } } + // If the codec is already h264, don't encode if (LibraryItem.Codec.IndexOf("264", StringComparison.OrdinalIgnoreCase) != -1 || LibraryItem.Codec.IndexOf("avc", StringComparison.OrdinalIgnoreCase) != -1) { return false; @@ -278,8 +324,13 @@ namespace MediaBrowser.Api.HttpHandlers return false; } + /// + /// Determines if the audio stream requires encoding + /// private bool RequiresAudioConversion(AudioStream audio) { + + // If the input stream has more audio channels than the client can handle, we need to encode if (AudioChannels.HasValue) { if (audio.Channels > AudioChannels.Value) @@ -288,14 +339,18 @@ namespace MediaBrowser.Api.HttpHandlers } } + // Aac, ac-3 and mp3 are all pretty much universally supported. No need to encode them + if (audio.Codec.IndexOf("aac", StringComparison.OrdinalIgnoreCase) != -1) { return false; } + if (audio.Codec.IndexOf("ac-3", StringComparison.OrdinalIgnoreCase) != -1 || audio.Codec.IndexOf("ac3", StringComparison.OrdinalIgnoreCase) != -1) { return false; } + if (audio.Codec.IndexOf("mpeg", StringComparison.OrdinalIgnoreCase) != -1 || audio.Codec.IndexOf("mp3", StringComparison.OrdinalIgnoreCase) != -1) { return false; @@ -304,6 +359,9 @@ namespace MediaBrowser.Api.HttpHandlers return true; } + /// + /// Gets the fixed output video height, in pixels + /// private int? Height { get @@ -319,6 +377,9 @@ namespace MediaBrowser.Api.HttpHandlers } } + /// + /// Gets the fixed output video width, in pixels + /// private int? Width { get @@ -334,6 +395,9 @@ namespace MediaBrowser.Api.HttpHandlers } } + /// + /// Gets the maximum output video height, in pixels + /// private int? MaxHeight { get @@ -349,6 +413,9 @@ namespace MediaBrowser.Api.HttpHandlers } } + /// + /// Gets the maximum output video width, in pixels + /// private int? MaxWidth { get diff --git a/MediaBrowser.ApiInteraction/BaseApiClient.cs b/MediaBrowser.ApiInteraction/BaseApiClient.cs index f3ff2e5ee..7952129cf 100644 --- a/MediaBrowser.ApiInteraction/BaseApiClient.cs +++ b/MediaBrowser.ApiInteraction/BaseApiClient.cs @@ -356,27 +356,83 @@ namespace MediaBrowser.ApiInteraction /// /// The id of the item /// List all the output formats the decice is capable of playing. The more, the better, as it will decrease the likelyhood of having to encode, which will put a load on the server. - /// The maximum number of channels that the device can play. Omit this if it doesn't matter. Phones and tablets should generally specify 2. - /// The maximum sample rate that the device can play. This should generally be omitted. If there's a problem, try 44100. - public string GetAudioStreamUrl(Guid itemId, IEnumerable supportedOutputFormats, int? maxChannels = null, int? maxSampleRate = null) + /// The maximum number of channels that the device can play. Omit this if it doesn't matter. Phones and tablets should generally specify 2. + /// The maximum sample rate that the device can play. This should generally be omitted. The server will default this to 44100, so only override if a different max is needed. + public string GetAudioStreamUrl(Guid itemId, IEnumerable supportedOutputFormats, int? maxAudioChannels = null, int? maxAudioSampleRate = null) { string url = ApiUrl + "/audio"; url += "?outputformats=" + string.Join(",", supportedOutputFormats.Select(s => s.ToString()).ToArray()); - if (maxChannels.HasValue) + if (maxAudioChannels.HasValue) { - url += "&audiochannels=" + maxChannels.Value; + url += "&audiochannels=" + maxAudioChannels.Value; } - if (maxSampleRate.HasValue) + if (maxAudioSampleRate.HasValue) { - url += "&audiosamplerate=" + maxSampleRate.Value; + url += "&audiosamplerate=" + maxAudioSampleRate.Value; } return url; } + /// + /// Gets the url needed to stream a video file + /// + /// The id of the item + /// List all the output formats the decice is capable of playing. The more, the better, as it will decrease the likelyhood of having to encode, which will put a load on the server. + /// The maximum number of channels that the device can play. Omit this if it doesn't matter. Phones and tablets should generally specify 2. + /// The maximum sample rate that the device can play. This should generally be omitted. The server will default this to 44100, so only override if a different max is needed. + /// Specify this is a fixed video width is required + /// Specify this is a fixed video height is required + /// Specify this is a max video width is required + /// Specify this is a max video height is required + public string GetVideoStreamUrl(Guid itemId, + IEnumerable supportedOutputFormats, + int? maxAudioChannels = null, + int? maxAudioSampleRate = null, + int? width = null, + int? height = null, + int? maxWidth = null, + int? maxHeight = null) + { + string url = ApiUrl + "/video"; + + url += "?outputformats=" + string.Join(",", supportedOutputFormats.Select(s => s.ToString()).ToArray()); + + if (maxAudioChannels.HasValue) + { + url += "&audiochannels=" + maxAudioChannels.Value; + } + + if (maxAudioSampleRate.HasValue) + { + url += "&audiosamplerate=" + maxAudioSampleRate.Value; + } + + if (width.HasValue) + { + url += "&width=" + width.Value; + } + + if (height.HasValue) + { + url += "&height=" + height.Value; + } + + if (maxWidth.HasValue) + { + url += "&maxWidth=" + maxWidth.Value; + } + + if (maxHeight.HasValue) + { + url += "&maxHeight=" + maxHeight.Value; + } + return url; + } + protected T DeserializeFromStream(Stream stream) where T : class { diff --git a/MediaBrowser.Controller/Resolvers/AudioResolver.cs b/MediaBrowser.Controller/Resolvers/AudioResolver.cs index 85138b53e..c67bc0d4d 100644 --- a/MediaBrowser.Controller/Resolvers/AudioResolver.cs +++ b/MediaBrowser.Controller/Resolvers/AudioResolver.cs @@ -1,7 +1,7 @@ -using System.ComponentModel.Composition; -using System.IO; -using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Library; using MediaBrowser.Model.Entities; +using System.ComponentModel.Composition; +using System.IO; namespace MediaBrowser.Controller.Resolvers { diff --git a/MediaBrowser.Model/DTO/AudioOutputFormats.cs b/MediaBrowser.Model/DTO/AudioOutputFormats.cs index e2c6037ff..1ae044473 100644 --- a/MediaBrowser.Model/DTO/AudioOutputFormats.cs +++ b/MediaBrowser.Model/DTO/AudioOutputFormats.cs @@ -10,7 +10,6 @@ namespace MediaBrowser.Model.DTO Aac, Flac, Mp3, - Wav, Wma } } diff --git a/MediaBrowser.Model/DTO/VideoOutputFormats.cs b/MediaBrowser.Model/DTO/VideoOutputFormats.cs new file mode 100644 index 000000000..d69797acc --- /dev/null +++ b/MediaBrowser.Model/DTO/VideoOutputFormats.cs @@ -0,0 +1,22 @@ + +namespace MediaBrowser.Model.DTO +{ + /// + /// These are the audio output formats that the api is cabaple of streaming + /// This does not limit the inputs, only the outputs. + /// + public enum VideoOutputFormats + { + Avi, + Asf, + M4v, + Mkv, + Mov, + Mp4, + Ogv, + ThreeGP, + Ts, + Webm, + Wmv + } +} diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index 981ee0bad..ae8b686c2 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -41,6 +41,7 @@ +