using MediaBrowser.Common.IO; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Model.IO; using MediaBrowser.Model.Serialization; using ServiceStack; using System; using System.IO; namespace MediaBrowser.Api.Playback.Progressive { /// /// Class GetVideoStream /// [Route("/Videos/{Id}/stream.ts", "GET")] [Route("/Videos/{Id}/stream.webm", "GET")] [Route("/Videos/{Id}/stream.asf", "GET")] [Route("/Videos/{Id}/stream.wmv", "GET")] [Route("/Videos/{Id}/stream.ogv", "GET")] [Route("/Videos/{Id}/stream.mp4", "GET")] [Route("/Videos/{Id}/stream.m4v", "GET")] [Route("/Videos/{Id}/stream.mkv", "GET")] [Route("/Videos/{Id}/stream.mpeg", "GET")] [Route("/Videos/{Id}/stream.mpg", "GET")] [Route("/Videos/{Id}/stream.avi", "GET")] [Route("/Videos/{Id}/stream.m2ts", "GET")] [Route("/Videos/{Id}/stream.3gp", "GET")] [Route("/Videos/{Id}/stream.wmv", "GET")] [Route("/Videos/{Id}/stream.wtv", "GET")] [Route("/Videos/{Id}/stream.mov", "GET")] [Route("/Videos/{Id}/stream", "GET")] [Route("/Videos/{Id}/stream.ts", "HEAD")] [Route("/Videos/{Id}/stream.webm", "HEAD")] [Route("/Videos/{Id}/stream.asf", "HEAD")] [Route("/Videos/{Id}/stream.wmv", "HEAD")] [Route("/Videos/{Id}/stream.ogv", "HEAD")] [Route("/Videos/{Id}/stream.mp4", "HEAD")] [Route("/Videos/{Id}/stream.m4v", "HEAD")] [Route("/Videos/{Id}/stream.mkv", "HEAD")] [Route("/Videos/{Id}/stream.mpeg", "HEAD")] [Route("/Videos/{Id}/stream.mpg", "HEAD")] [Route("/Videos/{Id}/stream.avi", "HEAD")] [Route("/Videos/{Id}/stream.3gp", "HEAD")] [Route("/Videos/{Id}/stream.wmv", "HEAD")] [Route("/Videos/{Id}/stream.wtv", "HEAD")] [Route("/Videos/{Id}/stream.m2ts", "HEAD")] [Route("/Videos/{Id}/stream.mov", "HEAD")] [Route("/Videos/{Id}/stream", "HEAD")] [Api(Description = "Gets a video stream")] public class GetVideoStream : VideoStreamRequest { } /// /// Class VideoService /// public class VideoService : BaseProgressiveStreamingService { public VideoService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IJsonSerializer jsonSerializer, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient, jsonSerializer, imageProcessor, httpClient) { } /// /// Gets the specified request. /// /// The request. /// System.Object. public object Get(GetVideoStream request) { return ProcessRequest(request, false); } /// /// Heads the specified request. /// /// The request. /// System.Object. public object Head(GetVideoStream request) { return ProcessRequest(request, true); } protected override string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding) { // Get the output codec name var videoCodec = GetVideoEncoder(state.VideoRequest); var format = string.Empty; var keyFrame = string.Empty; if (string.Equals(Path.GetExtension(outputPath), ".mp4", StringComparison.OrdinalIgnoreCase)) { // Comparison: https://github.com/jansmolders86/mediacenterjs/blob/master/lib/transcoding/desktop.js format = " -f mp4 -movflags frag_keyframe+empty_moov"; } var threads = GetNumberOfThreads(state, string.Equals(videoCodec, "libvpx", StringComparison.OrdinalIgnoreCase)); var inputModifier = GetInputModifier(state); return string.Format("{0} {1}{2} {3} {4} -map_metadata -1 -threads {5} {6}{7} -y \"{8}\"", inputModifier, GetInputArgument(state), keyFrame, GetMapArgs(state), GetVideoArguments(state, videoCodec), threads, GetAudioArguments(state), format, outputPath ).Trim(); } /// /// Gets video arguments to pass to ffmpeg /// /// The state. /// The video codec. /// System.String. private string GetVideoArguments(StreamState state, string codec) { var args = "-codec:v:0 " + codec; if (state.EnableMpegtsM2TsMode) { args += " -mpegts_m2ts_mode 1"; } // See if we can save come cpu cycles by avoiding encoding if (codec.Equals("copy", StringComparison.OrdinalIgnoreCase)) { return state.VideoStream != null && IsH264(state.VideoStream) && string.Equals(state.OutputContainer, "ts", StringComparison.OrdinalIgnoreCase) ? args + " -bsf:v h264_mp4toannexb" : 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; // Add resolution params, if specified if (!hasGraphicalSubs) { args += GetOutputSizeParam(state, codec); } var qualityParam = GetVideoQualityParam(state, codec, false); if (!string.IsNullOrEmpty(qualityParam)) { args += " " + qualityParam.Trim(); } // This is for internal graphical subs if (hasGraphicalSubs) { args += GetGraphicalSubtitleParam(state, codec); } return args; } /// /// Gets audio arguments to pass to ffmpeg /// /// The state. /// System.String. private string GetAudioArguments(StreamState state) { // 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.Request); 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, false); return args; } } }