2013-11-20 08:50:54 -07:00
using MediaBrowser.Common.Extensions ;
2013-02-26 21:19:05 -07:00
using MediaBrowser.Common.IO ;
2013-12-15 12:39:21 -07:00
using MediaBrowser.Controller.Configuration ;
2013-04-13 11:02:30 -07:00
using MediaBrowser.Controller.Dto ;
2013-02-26 21:19:05 -07:00
using MediaBrowser.Controller.Entities ;
using MediaBrowser.Controller.Library ;
2013-12-21 11:37:34 -07:00
using MediaBrowser.Controller.LiveTv ;
2014-02-20 09:37:41 -07:00
using MediaBrowser.Controller.MediaEncoding ;
2013-12-05 20:39:44 -07:00
using MediaBrowser.Controller.Persistence ;
2013-12-15 12:39:21 -07:00
using MediaBrowser.Model.Configuration ;
2013-02-26 21:19:05 -07:00
using MediaBrowser.Model.Drawing ;
using MediaBrowser.Model.Dto ;
using MediaBrowser.Model.Entities ;
2013-11-20 08:50:54 -07:00
using MediaBrowser.Model.IO ;
2014-02-20 22:35:56 -07:00
using MediaBrowser.Model.Library ;
2014-01-02 21:58:22 -07:00
using MediaBrowser.Model.LiveTv ;
2013-02-26 21:19:05 -07:00
using System ;
using System.Collections.Generic ;
using System.Diagnostics ;
2013-11-20 08:50:54 -07:00
using System.Globalization ;
2013-02-26 21:19:05 -07:00
using System.IO ;
using System.Linq ;
using System.Threading ;
using System.Threading.Tasks ;
namespace MediaBrowser.Api.Playback
{
/// <summary>
/// Class BaseStreamingService
/// </summary>
2013-03-15 22:52:33 -07:00
public abstract class BaseStreamingService : BaseApiService
2013-02-26 21:19:05 -07:00
{
/// <summary>
/// Gets or sets the application paths.
/// </summary>
/// <value>The application paths.</value>
2013-12-15 12:39:21 -07:00
protected IServerConfigurationManager ServerConfigurationManager { get ; private set ; }
2013-02-26 21:19:05 -07:00
2013-02-27 13:25:45 -07:00
/// <summary>
/// Gets or sets the user manager.
/// </summary>
/// <value>The user manager.</value>
2013-09-04 10:02:19 -07:00
protected IUserManager UserManager { get ; private set ; }
2013-02-28 12:32:41 -07:00
/// <summary>
/// Gets or sets the library manager.
/// </summary>
/// <value>The library manager.</value>
2013-09-04 10:02:19 -07:00
protected ILibraryManager LibraryManager { get ; private set ; }
2013-03-03 22:43:06 -07:00
2013-02-26 21:19:05 -07:00
/// <summary>
2013-03-03 22:43:06 -07:00
/// Gets or sets the iso manager.
2013-02-26 21:19:05 -07:00
/// </summary>
2013-03-03 22:43:06 -07:00
/// <value>The iso manager.</value>
2013-09-04 10:02:19 -07:00
protected IIsoManager IsoManager { get ; private set ; }
2013-02-26 21:19:05 -07:00
2013-04-07 13:55:05 -07:00
/// <summary>
/// Gets or sets the media encoder.
/// </summary>
/// <value>The media encoder.</value>
2013-09-04 10:02:19 -07:00
protected IMediaEncoder MediaEncoder { get ; private set ; }
2014-02-20 09:37:41 -07:00
protected IEncodingManager EncodingManager { get ; private set ; }
2013-09-04 10:02:19 -07:00
protected IDtoService DtoService { get ; private set ; }
2013-04-07 13:55:05 -07:00
2013-10-31 07:03:23 -07:00
protected IFileSystem FileSystem { get ; private set ; }
2013-12-05 20:39:44 -07:00
protected IItemRepository ItemRepository { get ; private set ; }
2013-12-21 11:37:34 -07:00
protected ILiveTvManager LiveTvManager { get ; private set ; }
2013-12-05 20:39:44 -07:00
2013-02-26 21:19:05 -07:00
/// <summary>
/// Initializes a new instance of the <see cref="BaseStreamingService" /> class.
/// </summary>
2013-12-15 12:39:21 -07:00
/// <param name="serverConfig">The server configuration.</param>
2013-02-27 13:25:45 -07:00
/// <param name="userManager">The user manager.</param>
2013-02-28 12:32:41 -07:00
/// <param name="libraryManager">The library manager.</param>
2013-03-03 22:43:06 -07:00
/// <param name="isoManager">The iso manager.</param>
2013-04-07 13:55:05 -07:00
/// <param name="mediaEncoder">The media encoder.</param>
2013-12-15 12:39:21 -07:00
/// <param name="dtoService">The dto service.</param>
/// <param name="fileSystem">The file system.</param>
/// <param name="itemRepository">The item repository.</param>
2014-02-20 09:37:41 -07:00
protected BaseStreamingService ( IServerConfigurationManager serverConfig , IUserManager userManager , ILibraryManager libraryManager , IIsoManager isoManager , IMediaEncoder mediaEncoder , IDtoService dtoService , IFileSystem fileSystem , IItemRepository itemRepository , ILiveTvManager liveTvManager , IEncodingManager encodingManager )
2013-02-26 21:19:05 -07:00
{
2014-02-20 09:37:41 -07:00
EncodingManager = encodingManager ;
2013-12-21 11:37:34 -07:00
LiveTvManager = liveTvManager ;
2013-12-05 20:39:44 -07:00
ItemRepository = itemRepository ;
2013-10-31 07:03:23 -07:00
FileSystem = fileSystem ;
2013-09-04 10:02:19 -07:00
DtoService = dtoService ;
2013-12-15 12:39:21 -07:00
ServerConfigurationManager = serverConfig ;
2013-02-27 13:25:45 -07:00
UserManager = userManager ;
2013-02-28 12:32:41 -07:00
LibraryManager = libraryManager ;
2013-03-03 22:43:06 -07:00
IsoManager = isoManager ;
2013-04-07 13:55:05 -07:00
MediaEncoder = mediaEncoder ;
2013-02-26 21:19:05 -07:00
}
/// <summary>
/// Gets the command line arguments.
/// </summary>
/// <param name="outputPath">The output path.</param>
/// <param name="state">The state.</param>
2013-04-29 09:01:23 -07:00
/// <param name="performSubtitleConversions">if set to <c>true</c> [perform subtitle conversions].</param>
2013-02-26 21:19:05 -07:00
/// <returns>System.String.</returns>
2013-04-29 09:01:23 -07:00
protected abstract string GetCommandLineArguments ( string outputPath , StreamState state , bool performSubtitleConversions ) ;
2013-02-26 21:19:05 -07:00
/// <summary>
/// Gets the type of the transcoding job.
/// </summary>
/// <value>The type of the transcoding job.</value>
protected abstract TranscodingJobType TranscodingJobType { get ; }
/// <summary>
/// Gets the output file extension.
/// </summary>
/// <param name="state">The state.</param>
/// <returns>System.String.</returns>
protected virtual string GetOutputFileExtension ( StreamState state )
{
2013-12-19 14:51:32 -07:00
return Path . GetExtension ( state . RequestedUrl ) ;
2013-02-26 21:19:05 -07:00
}
/// <summary>
/// Gets the output file path.
/// </summary>
/// <param name="state">The state.</param>
/// <returns>System.String.</returns>
2013-06-18 03:36:25 -07:00
protected virtual string GetOutputFilePath ( StreamState state )
2013-02-26 21:19:05 -07:00
{
2014-01-11 11:58:50 -07:00
var folder = ServerConfigurationManager . ApplicationPaths . TranscodingTempPath ;
2013-05-21 10:44:24 -07:00
var outputFileExtension = GetOutputFileExtension ( state ) ;
return Path . Combine ( folder , GetCommandLineArguments ( "dummy\\dummy" , state , false ) . GetMD5 ( ) + ( outputFileExtension ? ? string . Empty ) . ToLower ( ) ) ;
2013-02-26 21:19:05 -07:00
}
2013-07-26 17:58:59 -07:00
protected readonly CultureInfo UsCulture = new CultureInfo ( "en-US" ) ;
2013-08-28 15:18:14 -07:00
2013-02-26 21:19:05 -07:00
/// <summary>
/// The fast seek offset seconds
/// </summary>
private const int FastSeekOffsetSeconds = 1 ;
/// <summary>
/// Gets the fast seek command line parameter.
/// </summary>
/// <param name="request">The request.</param>
/// <returns>System.String.</returns>
/// <value>The fast seek command line parameter.</value>
protected string GetFastSeekCommandLineParameter ( StreamRequest request )
{
var time = request . StartTimeTicks ;
if ( time . HasValue )
{
var seconds = TimeSpan . FromTicks ( time . Value ) . TotalSeconds - FastSeekOffsetSeconds ;
if ( seconds > 0 )
{
2013-07-26 17:58:59 -07:00
return string . Format ( "-ss {0}" , seconds . ToString ( UsCulture ) ) ;
2013-02-26 21:19:05 -07:00
}
}
return string . Empty ;
}
/// <summary>
/// Gets the slow seek command line parameter.
/// </summary>
/// <param name="request">The request.</param>
/// <returns>System.String.</returns>
/// <value>The slow seek command line parameter.</value>
protected string GetSlowSeekCommandLineParameter ( StreamRequest request )
{
var time = request . StartTimeTicks ;
if ( time . HasValue )
{
if ( TimeSpan . FromTicks ( time . Value ) . TotalSeconds - FastSeekOffsetSeconds > 0 )
{
2013-07-26 17:58:59 -07:00
return string . Format ( " -ss {0}" , FastSeekOffsetSeconds . ToString ( UsCulture ) ) ;
2013-02-26 21:19:05 -07:00
}
}
return string . Empty ;
}
/// <summary>
/// Gets the map args.
/// </summary>
/// <param name="state">The state.</param>
/// <returns>System.String.</returns>
2013-02-27 16:33:40 -07:00
protected virtual string GetMapArgs ( StreamState state )
2013-02-26 21:19:05 -07:00
{
var args = string . Empty ;
2013-12-23 10:30:26 -07:00
if ( state . IsRemote | | ! state . HasMediaStreams )
2013-05-01 13:07:20 -07:00
{
return string . Empty ;
}
2013-02-26 21:19:05 -07:00
if ( state . VideoStream ! = null )
{
args + = string . Format ( "-map 0:{0}" , state . VideoStream . Index ) ;
}
else
{
args + = "-map -0:v" ;
}
if ( state . AudioStream ! = null )
{
args + = string . Format ( " -map 0:{0}" , state . AudioStream . Index ) ;
}
2013-05-01 13:07:20 -07:00
2013-02-26 21:19:05 -07:00
else
{
args + = " -map -0:a" ;
}
if ( state . SubtitleStream = = null )
{
args + = " -map -0:s" ;
}
return args ;
}
/// <summary>
/// Determines which stream will be used for playback
/// </summary>
/// <param name="allStream">All stream.</param>
/// <param name="desiredIndex">Index of the desired.</param>
/// <param name="type">The type.</param>
/// <param name="returnFirstIfNoIndex">if set to <c>true</c> [return first if no index].</param>
/// <returns>MediaStream.</returns>
private MediaStream GetMediaStream ( IEnumerable < MediaStream > allStream , int? desiredIndex , MediaStreamType type , bool returnFirstIfNoIndex = true )
{
2013-05-22 09:54:35 -07:00
var streams = allStream . Where ( s = > s . Type = = type ) . OrderBy ( i = > i . Index ) . ToList ( ) ;
2013-02-26 21:19:05 -07:00
if ( desiredIndex . HasValue )
{
var stream = streams . FirstOrDefault ( s = > s . Index = = desiredIndex . Value ) ;
if ( stream ! = null )
{
return stream ;
}
}
2013-08-28 15:18:14 -07:00
if ( returnFirstIfNoIndex & & type = = MediaStreamType . Audio )
{
return streams . FirstOrDefault ( i = > i . Channels . HasValue & & i . Channels . Value > 0 ) ? ?
streams . FirstOrDefault ( ) ;
}
2013-02-26 21:19:05 -07:00
// Just return the first one
return returnFirstIfNoIndex ? streams . FirstOrDefault ( ) : null ;
}
2014-01-10 06:52:01 -07:00
protected EncodingQuality GetQualitySetting ( )
{
var quality = ServerConfigurationManager . Configuration . MediaEncodingQuality ;
if ( quality = = EncodingQuality . Auto )
{
var cpuCount = Environment . ProcessorCount ;
if ( cpuCount > = 4 )
{
2014-03-03 21:58:19 -07:00
//return EncodingQuality.HighQuality;
2014-01-10 06:52:01 -07:00
}
return EncodingQuality . HighSpeed ;
}
return quality ;
}
2013-12-15 12:39:21 -07:00
/// <summary>
/// Gets the number of threads.
/// </summary>
/// <returns>System.Int32.</returns>
2014-01-07 11:39:35 -07:00
/// <exception cref="System.Exception">Unrecognized MediaEncodingQuality value.</exception>
2014-03-12 12:56:12 -07:00
protected int GetNumberOfThreads ( StreamState state , bool isWebm )
2013-12-15 12:39:21 -07:00
{
2014-03-12 12:56:12 -07:00
// Use more when this is true. -re will keep cpu usage under control
if ( state . ReadInputAtNativeFramerate )
{
if ( isWebm )
{
return Math . Max ( Environment . ProcessorCount - 1 , 1 ) ;
}
return 0 ;
}
2014-01-10 06:52:01 -07:00
// Webm: http://www.webmproject.org/docs/encoder-parameters/
// The decoder will usually automatically use an appropriate number of threads according to how many cores are available but it can only use multiple threads
// for the coefficient data if the encoder selected --token-parts > 0 at encode time.
2013-12-15 12:39:21 -07:00
2014-01-10 06:52:01 -07:00
switch ( GetQualitySetting ( ) )
2013-12-15 12:39:21 -07:00
{
case EncodingQuality . HighSpeed :
return 2 ;
case EncodingQuality . HighQuality :
2014-01-14 22:01:58 -07:00
return 2 ;
2013-12-15 12:39:21 -07:00
case EncodingQuality . MaxQuality :
2014-01-14 22:01:58 -07:00
return isWebm ? 2 : 0 ;
2013-12-15 12:39:21 -07:00
default :
2014-01-07 11:39:35 -07:00
throw new Exception ( "Unrecognized MediaEncodingQuality value." ) ;
2013-12-15 12:39:21 -07:00
}
}
/// <summary>
/// Gets the video bitrate to specify on the command line
/// </summary>
/// <param name="state">The state.</param>
/// <param name="videoCodec">The video codec.</param>
/// <returns>System.String.</returns>
2014-02-02 07:47:00 -07:00
protected string GetVideoQualityParam ( StreamState state , string videoCodec , bool isHls )
2013-12-15 12:39:21 -07:00
{
2014-02-02 07:47:00 -07:00
var param = string . Empty ;
2014-02-02 09:21:40 -07:00
var hasFixedResolution = state . VideoRequest . HasFixedResolution ;
2014-02-02 22:35:56 -07:00
var qualitySetting = GetQualitySetting ( ) ;
2014-02-02 07:47:00 -07:00
if ( string . Equals ( videoCodec , "libx264" , StringComparison . OrdinalIgnoreCase ) )
{
2014-02-02 22:35:56 -07:00
switch ( qualitySetting )
2014-02-02 07:47:00 -07:00
{
case EncodingQuality . HighSpeed :
2014-02-02 09:21:40 -07:00
param = "-preset ultrafast" ;
2014-02-02 07:47:00 -07:00
break ;
case EncodingQuality . HighQuality :
2014-02-02 09:21:40 -07:00
param = "-preset superfast" ;
2014-02-02 07:47:00 -07:00
break ;
case EncodingQuality . MaxQuality :
2014-02-02 09:21:40 -07:00
param = "-preset superfast" ;
2014-02-02 07:47:00 -07:00
break ;
}
2014-02-02 09:21:40 -07:00
2014-02-02 22:35:56 -07:00
if ( ! isHls )
2014-02-02 09:21:40 -07:00
{
2014-02-02 22:35:56 -07:00
switch ( qualitySetting )
{
case EncodingQuality . HighSpeed :
param + = " -crf 23" ;
break ;
case EncodingQuality . HighQuality :
param + = " -crf 20" ;
break ;
case EncodingQuality . MaxQuality :
param + = " -crf 18" ;
break ;
}
2014-02-02 09:21:40 -07:00
}
2014-02-02 07:47:00 -07:00
}
2013-12-15 12:39:21 -07:00
// webm
2014-02-02 07:47:00 -07:00
else if ( string . Equals ( videoCodec , "libvpx" , StringComparison . OrdinalIgnoreCase ) )
2013-12-15 12:39:21 -07:00
{
2014-01-10 06:52:01 -07:00
// http://www.webmproject.org/docs/encoder-parameters/
2014-02-02 09:21:40 -07:00
param = "-speed 16 -quality good -profile:v 0 -slices 8" ;
if ( ! hasFixedResolution )
{
2014-02-02 22:35:56 -07:00
switch ( qualitySetting )
{
case EncodingQuality . HighSpeed :
param + = " -crf 18" ;
break ;
case EncodingQuality . HighQuality :
2014-02-03 13:37:18 -07:00
param + = " -crf 10" ;
2014-02-02 22:35:56 -07:00
break ;
case EncodingQuality . MaxQuality :
2014-02-03 13:37:18 -07:00
param + = " -crf 4" ;
2014-02-02 22:35:56 -07:00
break ;
}
2014-02-02 09:21:40 -07:00
}
2014-02-02 07:47:00 -07:00
}
else if ( string . Equals ( videoCodec , "mpeg4" , StringComparison . OrdinalIgnoreCase ) )
{
param = "-mbd rd -flags +mv4+aic -trellis 2 -cmp 2 -subcmp 2 -bf 2" ;
2013-12-15 12:39:21 -07:00
}
// asf/wmv
2014-02-02 07:47:00 -07:00
else if ( string . Equals ( videoCodec , "wmv2" , StringComparison . OrdinalIgnoreCase ) )
2014-01-10 06:52:01 -07:00
{
2014-02-02 07:47:00 -07:00
param = "-qmin 2" ;
2014-01-10 06:52:01 -07:00
}
2014-02-02 07:47:00 -07:00
else if ( string . Equals ( videoCodec , "msmpeg4" , StringComparison . OrdinalIgnoreCase ) )
2014-01-10 06:52:01 -07:00
{
2014-02-02 07:47:00 -07:00
param = "-mbd 2" ;
2014-01-10 06:52:01 -07:00
}
2014-02-02 07:47:00 -07:00
param + = GetVideoBitrateParam ( state , videoCodec , isHls ) ;
var framerate = GetFramerateParam ( state ) ;
if ( framerate . HasValue )
2013-12-15 12:39:21 -07:00
{
2014-02-02 07:47:00 -07:00
param + = string . Format ( " -r {0}" , framerate . Value . ToString ( UsCulture ) ) ;
2013-12-15 12:39:21 -07:00
}
2014-02-02 07:47:00 -07:00
if ( ! string . IsNullOrEmpty ( state . VideoSync ) )
{
param + = " -vsync " + state . VideoSync ;
}
if ( ! string . IsNullOrEmpty ( state . VideoRequest . Profile ) )
{
param + = " -profile:v " + state . VideoRequest . Profile ;
}
if ( ! string . IsNullOrEmpty ( state . VideoRequest . Level ) )
{
param + = " -level " + state . VideoRequest . Level ;
}
return param ;
2014-01-10 06:52:01 -07:00
}
protected string GetAudioFilterParam ( StreamState state , bool isHls )
{
var volParam = string . Empty ;
var audioSampleRate = string . Empty ;
var channels = GetNumAudioChannelsParam ( state . Request , state . AudioStream ) ;
2014-01-20 08:04:50 -07:00
2014-01-10 06:52:01 -07:00
// Boost volume to 200% when downsampling from 6ch to 2ch
if ( channels . HasValue & & channels . Value < = 2 & & state . AudioStream . Channels . HasValue & & state . AudioStream . Channels . Value > 5 )
2013-12-15 12:39:21 -07:00
{
2014-01-10 06:52:01 -07:00
volParam = ",volume=2.000000" ;
2013-12-15 12:39:21 -07:00
}
2014-01-10 06:52:01 -07:00
if ( state . Request . AudioSampleRate . HasValue )
2013-12-15 12:39:21 -07:00
{
2014-01-10 06:52:01 -07:00
audioSampleRate = state . Request . AudioSampleRate . Value + ":" ;
2013-12-15 12:39:21 -07:00
}
2014-01-10 06:52:01 -07:00
var adelay = isHls ? "adelay=1," : string . Empty ;
var pts = string . Empty ;
if ( state . SubtitleStream ! = null )
{
if ( state . SubtitleStream . Codec . IndexOf ( "srt" , StringComparison . OrdinalIgnoreCase ) ! = - 1 | |
state . SubtitleStream . Codec . IndexOf ( "subrip" , StringComparison . OrdinalIgnoreCase ) ! = - 1 | |
string . Equals ( state . SubtitleStream . Codec , "ass" , StringComparison . OrdinalIgnoreCase ) | |
string . Equals ( state . SubtitleStream . Codec , "ssa" , StringComparison . OrdinalIgnoreCase ) )
{
var seconds = TimeSpan . FromTicks ( state . Request . StartTimeTicks ? ? 0 ) . TotalSeconds ;
pts = string . Format ( ",asetpts=PTS-{0}/TB" ,
Math . Round ( seconds ) . ToString ( UsCulture ) ) ;
}
}
2014-01-20 08:04:50 -07:00
return string . Format ( "-af \"{0}aresample={1}async={4}{2}{3}\"" ,
2014-01-10 06:52:01 -07:00
adelay ,
2014-01-20 08:04:50 -07:00
audioSampleRate ,
2014-01-10 06:52:01 -07:00
volParam ,
2014-01-14 22:05:19 -07:00
pts ,
2014-01-24 11:09:50 -07:00
state . AudioSync ) ;
2013-12-15 12:39:21 -07:00
}
2013-12-19 14:51:32 -07:00
2013-02-26 21:19:05 -07:00
/// <summary>
/// If we're going to put a fixed size on the command line, this will calculate it
/// </summary>
/// <param name="state">The state.</param>
/// <param name="outputVideoCodec">The output video codec.</param>
2013-04-29 09:01:23 -07:00
/// <param name="performTextSubtitleConversion">if set to <c>true</c> [perform text subtitle conversion].</param>
2013-02-26 21:19:05 -07:00
/// <returns>System.String.</returns>
2013-04-29 09:01:23 -07:00
protected string GetOutputSizeParam ( StreamState state , string outputVideoCodec , bool performTextSubtitleConversion )
2013-02-26 21:19:05 -07:00
{
// http://sonnati.wordpress.com/2012/10/19/ffmpeg-the-swiss-army-knife-of-internet-streaming-part-vi/
var assSubtitleParam = string . Empty ;
2014-01-10 06:52:01 -07:00
var copyTsParam = string . Empty ;
2014-01-20 08:04:50 -07:00
var yadifParam = state . DeInterlace ? "yadif=0:-1:0," : string . Empty ;
2013-02-26 21:19:05 -07:00
2013-03-08 19:34:54 -07:00
var request = state . VideoRequest ;
2013-02-26 21:19:05 -07:00
if ( state . SubtitleStream ! = null )
{
2013-09-19 19:03:37 -07:00
if ( state . SubtitleStream . Codec . IndexOf ( "srt" , StringComparison . OrdinalIgnoreCase ) ! = - 1 | |
state . SubtitleStream . Codec . IndexOf ( "subrip" , StringComparison . OrdinalIgnoreCase ) ! = - 1 | |
string . Equals ( state . SubtitleStream . Codec , "ass" , StringComparison . OrdinalIgnoreCase ) | |
string . Equals ( state . SubtitleStream . Codec , "ssa" , StringComparison . OrdinalIgnoreCase ) )
2013-02-26 21:19:05 -07:00
{
2014-01-10 06:52:01 -07:00
assSubtitleParam = GetTextSubtitleParam ( state , performTextSubtitleConversion ) ;
copyTsParam = " -copyts" ;
2013-02-26 21:19:05 -07:00
}
}
// If fixed dimensions were supplied
if ( request . Width . HasValue & & request . Height . HasValue )
{
2013-12-21 11:37:34 -07:00
var widthParam = request . Width . Value . ToString ( UsCulture ) ;
var heightParam = request . Height . Value . ToString ( UsCulture ) ;
2014-01-20 08:04:50 -07:00
return string . Format ( "{4} -vf \"{0}scale=trunc({1}/2)*2:trunc({2}/2)*2{3}\"" , yadifParam , widthParam , heightParam , assSubtitleParam , copyTsParam ) ;
2013-02-26 21:19:05 -07:00
}
2014-03-09 15:14:44 -07:00
// If Max dimensions were supplied
//this makes my brain hurt. For width selects lowest even number between input width and width req size and selects lowest even number from in width*display aspect and requested size
if ( request . MaxWidth . HasValue & & request . MaxHeight . HasValue )
{
var MaxwidthParam = request . MaxWidth . Value . ToString ( UsCulture ) ;
var MaxheightParam = request . MaxHeight . Value . ToString ( UsCulture ) ;
return string . Format ( "{4} -vf \"{0}scale=trunc(min(iw\\,{1})/2)*2:trunc(min((iw/dar)\\,{2})/2)*2{3}\"" , yadifParam , MaxwidthParam , MaxheightParam , assSubtitleParam , copyTsParam ) ;
}
2013-02-26 21:19:05 -07:00
var isH264Output = outputVideoCodec . Equals ( "libx264" , StringComparison . OrdinalIgnoreCase ) ;
// If a fixed width was requested
if ( request . Width . HasValue )
{
2013-12-21 11:37:34 -07:00
var widthParam = request . Width . Value . ToString ( UsCulture ) ;
2013-12-22 11:58:51 -07:00
2013-02-26 21:19:05 -07:00
return isH264Output ?
2014-01-20 08:04:50 -07:00
string . Format ( "{3} -vf \"{0}scale={1}:trunc(ow/a/2)*2{2}\"" , yadifParam , widthParam , assSubtitleParam , copyTsParam ) :
string . Format ( "{3} -vf \"{0}scale={1}:-1{2}\"" , yadifParam , widthParam , assSubtitleParam , copyTsParam ) ;
2013-02-26 21:19:05 -07:00
}
2013-10-05 18:04:41 -07:00
// If a fixed height was requested
if ( request . Height . HasValue )
{
2013-12-21 11:37:34 -07:00
var heightParam = request . Height . Value . ToString ( UsCulture ) ;
2013-12-22 11:58:51 -07:00
2013-10-05 18:04:41 -07:00
return isH264Output ?
2014-01-20 08:04:50 -07:00
string . Format ( "{3} -vf \"{0}scale=trunc(oh*a*2)/2:{1}{2}\"" , yadifParam , heightParam , assSubtitleParam , copyTsParam ) :
string . Format ( "{3} -vf \"{0}scale=-1:{1}{2}\"" , yadifParam , heightParam , assSubtitleParam , copyTsParam ) ;
2013-10-05 18:04:41 -07:00
}
2013-02-26 21:19:05 -07:00
// If a max width was requested
2013-05-01 19:44:52 -07:00
if ( request . MaxWidth . HasValue & & ( ! request . MaxHeight . HasValue | | state . VideoStream = = null ) )
2013-02-26 21:19:05 -07:00
{
2013-12-21 11:37:34 -07:00
var maxWidthParam = request . MaxWidth . Value . ToString ( UsCulture ) ;
2013-12-22 11:58:51 -07:00
2013-02-26 21:19:05 -07:00
return isH264Output ?
2014-01-20 08:04:50 -07:00
string . Format ( "{3} -vf \"{0}scale=min(iw\\,{1}):trunc(ow/a/2)*2{2}\"" , yadifParam , maxWidthParam , assSubtitleParam , copyTsParam ) :
string . Format ( "{3} -vf \"{0}scale=min(iw\\,{1}):-1{2}\"" , yadifParam , maxWidthParam , assSubtitleParam , copyTsParam ) ;
2013-02-26 21:19:05 -07:00
}
2013-10-05 18:04:41 -07:00
// If a max height was requested
if ( request . MaxHeight . HasValue & & ( ! request . MaxWidth . HasValue | | state . VideoStream = = null ) )
{
2013-12-21 11:37:34 -07:00
var maxHeightParam = request . MaxHeight . Value . ToString ( UsCulture ) ;
2013-12-22 11:58:51 -07:00
2013-10-05 18:04:41 -07:00
return isH264Output ?
2014-01-20 08:04:50 -07:00
string . Format ( "{3} -vf \"{0}scale=trunc(oh*a*2)/2:min(ih\\,{1}){2}\"" , yadifParam , maxHeightParam , assSubtitleParam , copyTsParam ) :
string . Format ( "{3} -vf \"{0}scale=-1:min(ih\\,{1}){2}\"" , yadifParam , maxHeightParam , assSubtitleParam , copyTsParam ) ;
2013-10-05 18:04:41 -07:00
}
2013-05-01 19:44:52 -07:00
if ( state . VideoStream = = null )
{
// No way to figure this out
return string . Empty ;
}
2013-02-26 21:19:05 -07:00
// Need to perform calculations manually
// Try to account for bad media info
2013-05-01 19:44:52 -07:00
var currentHeight = state . VideoStream . Height ? ? request . MaxHeight ? ? request . Height ? ? 0 ;
var currentWidth = state . VideoStream . Width ? ? request . MaxWidth ? ? request . Width ? ? 0 ;
2013-02-26 21:19:05 -07:00
var outputSize = DrawingUtils . Resize ( currentWidth , currentHeight , request . Width , request . Height , request . MaxWidth , request . MaxHeight ) ;
// If we're encoding with libx264, it can't handle odd numbered widths or heights, so we'll have to fix that
if ( isH264Output )
{
2013-12-21 11:37:34 -07:00
var widthParam = outputSize . Width . ToString ( UsCulture ) ;
var heightParam = outputSize . Height . ToString ( UsCulture ) ;
2014-01-20 08:04:50 -07:00
return string . Format ( "{4} -vf \"{0}scale=trunc({1}/2)*2:trunc({2}/2)*2{3}\"" , yadifParam , widthParam , heightParam , assSubtitleParam , copyTsParam ) ;
2013-02-26 21:19:05 -07:00
}
// Otherwise use -vf scale since ffmpeg will ensure internally that the aspect ratio is preserved
2014-01-20 08:04:50 -07:00
return string . Format ( "{3} -vf \"{0}scale={1}:-1{2}\"" , yadifParam , Convert . ToInt32 ( outputSize . Width ) , assSubtitleParam , copyTsParam ) ;
2013-02-26 21:19:05 -07:00
}
/// <summary>
/// Gets the text subtitle param.
/// </summary>
2013-12-19 14:51:32 -07:00
/// <param name="state">The state.</param>
2013-04-29 09:01:23 -07:00
/// <param name="performConversion">if set to <c>true</c> [perform conversion].</param>
2013-02-26 21:19:05 -07:00
/// <returns>System.String.</returns>
2014-01-10 06:52:01 -07:00
protected string GetTextSubtitleParam ( StreamState state , bool performConversion )
2013-02-26 21:19:05 -07:00
{
2014-01-10 06:52:01 -07:00
var path = state . SubtitleStream . IsExternal ? GetConvertedAssPath ( state . MediaPath , state . SubtitleStream , performConversion ) :
GetExtractedAssPath ( state , performConversion ) ;
2013-02-26 21:19:05 -07:00
if ( string . IsNullOrEmpty ( path ) )
{
return string . Empty ;
}
2014-01-10 06:52:01 -07:00
var seconds = TimeSpan . FromTicks ( state . Request . StartTimeTicks ? ? 0 ) . TotalSeconds ;
2014-01-20 08:04:50 -07:00
return string . Format ( ",ass='{0}',setpts=PTS -{1}/TB" ,
2014-01-10 06:52:01 -07:00
path . Replace ( '\\' , '/' ) . Replace ( ":/" , "\\:/" ) ,
Math . Round ( seconds ) . ToString ( UsCulture ) ) ;
2013-02-26 21:19:05 -07:00
}
/// <summary>
/// Gets the extracted ass path.
/// </summary>
2013-12-19 14:51:32 -07:00
/// <param name="state">The state.</param>
2013-04-29 09:01:23 -07:00
/// <param name="performConversion">if set to <c>true</c> [perform conversion].</param>
2013-02-26 21:19:05 -07:00
/// <returns>System.String.</returns>
2014-01-10 06:52:01 -07:00
private string GetExtractedAssPath ( StreamState state , bool performConversion )
2013-02-26 21:19:05 -07:00
{
2014-02-20 09:37:41 -07:00
var path = EncodingManager . GetSubtitleCachePath ( state . MediaPath , state . SubtitleStream . Index , ".ass" ) ;
2014-03-09 15:14:44 -07:00
2013-10-18 13:02:56 -07:00
if ( performConversion )
2013-02-26 21:19:05 -07:00
{
2013-04-07 13:55:05 -07:00
InputType type ;
2013-12-19 14:51:32 -07:00
var inputPath = MediaEncoderHelpers . GetInputArgument ( state . MediaPath , state . IsRemote , state . VideoType , state . IsoType , null , state . PlayableStreamFileNames , out type ) ;
2013-02-26 21:19:05 -07:00
2013-04-07 13:55:05 -07:00
try
{
2013-06-03 19:02:49 -07:00
var parentPath = Path . GetDirectoryName ( path ) ;
2013-10-01 11:24:27 -07:00
Directory . CreateDirectory ( parentPath ) ;
2013-06-03 19:02:49 -07:00
2014-01-25 13:27:31 -07:00
// Don't re-encode ass/ssa to ass because ffmpeg ass encoder fails if there's more than one ass rectangle. Affect Anime mostly.
// See https://lists.ffmpeg.org/pipermail/ffmpeg-cvslog/2013-April/063616.html
2014-03-04 19:59:23 -07:00
var isAssSubtitle = string . Equals ( state . SubtitleStream . Codec , "ass" , StringComparison . OrdinalIgnoreCase ) | | string . Equals ( state . SubtitleStream . Codec , "ssa" , StringComparison . OrdinalIgnoreCase ) ;
2014-01-25 13:27:31 -07:00
var task = MediaEncoder . ExtractTextSubtitle ( inputPath , type , state . SubtitleStream . Index , isAssSubtitle , path , CancellationToken . None ) ;
2013-04-07 13:55:05 -07:00
Task . WaitAll ( task ) ;
}
catch
2013-02-26 21:19:05 -07:00
{
return null ;
}
}
return path ;
}
/// <summary>
/// Gets the converted ass path.
/// </summary>
2013-12-19 14:51:32 -07:00
/// <param name="mediaPath">The media path.</param>
2013-02-26 21:19:05 -07:00
/// <param name="subtitleStream">The subtitle stream.</param>
2013-04-29 09:01:23 -07:00
/// <param name="performConversion">if set to <c>true</c> [perform conversion].</param>
2013-02-26 21:19:05 -07:00
/// <returns>System.String.</returns>
2014-01-10 06:52:01 -07:00
private string GetConvertedAssPath ( string mediaPath , MediaStream subtitleStream , bool performConversion )
2013-02-26 21:19:05 -07:00
{
2014-02-20 09:37:41 -07:00
var path = EncodingManager . GetSubtitleCachePath ( subtitleStream . Path , ".ass" ) ;
2013-02-26 21:19:05 -07:00
2013-10-18 13:02:56 -07:00
if ( performConversion )
2013-02-26 21:19:05 -07:00
{
2013-04-07 13:55:05 -07:00
try
{
2013-06-03 19:02:49 -07:00
var parentPath = Path . GetDirectoryName ( path ) ;
2013-10-01 11:24:27 -07:00
Directory . CreateDirectory ( parentPath ) ;
2013-06-03 19:02:49 -07:00
2014-01-10 06:52:01 -07:00
var task = MediaEncoder . ConvertTextSubtitleToAss ( subtitleStream . Path , path , subtitleStream . Language , CancellationToken . None ) ;
2013-02-26 21:19:05 -07:00
2013-04-07 13:55:05 -07:00
Task . WaitAll ( task ) ;
}
catch
2013-02-26 21:19:05 -07:00
{
return null ;
}
}
return path ;
}
/// <summary>
/// Gets the internal graphical subtitle param.
/// </summary>
/// <param name="state">The state.</param>
/// <param name="outputVideoCodec">The output video codec.</param>
/// <returns>System.String.</returns>
protected string GetInternalGraphicalSubtitleParam ( StreamState state , string outputVideoCodec )
{
var outputSizeParam = string . Empty ;
2013-03-08 19:34:54 -07:00
var request = state . VideoRequest ;
2013-02-26 21:19:05 -07:00
// Add resolution params, if specified
if ( request . Width . HasValue | | request . Height . HasValue | | request . MaxHeight . HasValue | | request . MaxWidth . HasValue )
{
2013-04-29 09:01:23 -07:00
outputSizeParam = GetOutputSizeParam ( state , outputVideoCodec , false ) . TrimEnd ( '"' ) ;
2013-02-26 21:19:05 -07:00
outputSizeParam = "," + outputSizeParam . Substring ( outputSizeParam . IndexOf ( "scale" , StringComparison . OrdinalIgnoreCase ) ) ;
}
2014-01-05 19:57:30 -07:00
var videoSizeParam = string . Empty ;
if ( state . VideoStream ! = null & & state . VideoStream . Width . HasValue & & state . VideoStream . Height . HasValue )
{
videoSizeParam = string . Format ( ",scale={0}:{1}" , state . VideoStream . Width . Value . ToString ( UsCulture ) , state . VideoStream . Height . Value . ToString ( UsCulture ) ) ;
}
2014-01-10 06:52:01 -07:00
return string . Format ( " -filter_complex \"[0:{0}]format=yuva444p{3},lut=u=128:v=128:y=gammaval(.3)[sub] ; [0:{1}] [sub] overlay{2}\"" ,
state . SubtitleStream . Index ,
state . VideoStream . Index ,
2014-01-05 19:57:30 -07:00
outputSizeParam ,
videoSizeParam ) ;
2013-02-26 21:19:05 -07:00
}
2013-04-07 13:55:05 -07:00
/// <summary>
/// Gets the probe size argument.
/// </summary>
2013-12-19 14:51:32 -07:00
/// <param name="mediaPath">The media path.</param>
/// <param name="isVideo">if set to <c>true</c> [is video].</param>
/// <param name="videoType">Type of the video.</param>
/// <param name="isoType">Type of the iso.</param>
2013-04-07 13:55:05 -07:00
/// <returns>System.String.</returns>
2014-01-21 20:07:33 -07:00
private string GetProbeSizeArgument ( string mediaPath , bool isVideo , VideoType ? videoType , IsoType ? isoType )
2013-04-07 13:55:05 -07:00
{
2014-01-02 21:58:22 -07:00
var type = ! isVideo ? MediaEncoderHelpers . GetInputType ( null , null ) :
MediaEncoderHelpers . GetInputType ( videoType , isoType ) ;
2013-09-29 17:51:04 -07:00
return MediaEncoder . GetProbeSizeArgument ( type ) ;
2013-04-07 13:55:05 -07:00
}
2013-02-26 21:19:05 -07:00
/// <summary>
/// Gets the number of audio channels to specify on the command line
/// </summary>
/// <param name="request">The request.</param>
/// <param name="audioStream">The audio stream.</param>
/// <returns>System.Nullable{System.Int32}.</returns>
protected int? GetNumAudioChannelsParam ( StreamRequest request , MediaStream audioStream )
{
2013-09-30 11:49:19 -07:00
if ( audioStream ! = null )
2013-02-26 21:19:05 -07:00
{
2013-09-30 11:49:19 -07:00
if ( audioStream . Channels > 2 & & request . AudioCodec . HasValue )
2013-02-26 21:19:05 -07:00
{
2013-09-30 11:49:19 -07:00
if ( request . AudioCodec . Value = = AudioCodecs . Wma )
{
// wmav2 currently only supports two channel output
return 2 ;
}
2013-02-26 21:19:05 -07:00
}
}
return request . AudioChannels ;
}
/// <summary>
/// Determines whether the specified stream is H264.
/// </summary>
/// <param name="stream">The stream.</param>
/// <returns><c>true</c> if the specified stream is H264; otherwise, <c>false</c>.</returns>
protected bool IsH264 ( MediaStream stream )
{
return stream . Codec . IndexOf ( "264" , StringComparison . OrdinalIgnoreCase ) ! = - 1 | |
stream . Codec . IndexOf ( "avc" , StringComparison . OrdinalIgnoreCase ) ! = - 1 ;
}
/// <summary>
/// Gets the name of the output audio codec
/// </summary>
/// <param name="request">The request.</param>
/// <returns>System.String.</returns>
protected string GetAudioCodec ( StreamRequest request )
{
var codec = request . AudioCodec ;
if ( codec . HasValue )
{
if ( codec = = AudioCodecs . Aac )
{
2013-11-10 21:50:31 -07:00
return "aac -strict experimental" ;
2013-02-26 21:19:05 -07:00
}
if ( codec = = AudioCodecs . Mp3 )
{
return "libmp3lame" ;
}
if ( codec = = AudioCodecs . Vorbis )
{
return "libvorbis" ;
}
if ( codec = = AudioCodecs . Wma )
{
return "wmav2" ;
}
2013-04-03 20:42:11 -07:00
return codec . ToString ( ) . ToLower ( ) ;
2013-02-26 21:19:05 -07:00
}
return "copy" ;
}
/// <summary>
/// Gets the name of the output video codec
/// </summary>
/// <param name="request">The request.</param>
/// <returns>System.String.</returns>
2013-03-08 19:34:54 -07:00
protected string GetVideoCodec ( VideoStreamRequest request )
2013-02-26 21:19:05 -07:00
{
var codec = request . VideoCodec ;
if ( codec . HasValue )
{
if ( codec = = VideoCodecs . H264 )
{
return "libx264" ;
}
if ( codec = = VideoCodecs . Vpx )
{
return "libvpx" ;
}
if ( codec = = VideoCodecs . Wmv )
{
2014-02-02 07:47:00 -07:00
return "msmpeg4" ;
2013-02-26 21:19:05 -07:00
}
if ( codec = = VideoCodecs . Theora )
{
return "libtheora" ;
}
2013-04-03 20:42:11 -07:00
return codec . ToString ( ) . ToLower ( ) ;
2013-02-26 21:19:05 -07:00
}
return "copy" ;
}
/// <summary>
/// Gets the input argument.
/// </summary>
2013-12-19 14:51:32 -07:00
/// <param name="state">The state.</param>
2013-02-26 21:19:05 -07:00
/// <returns>System.String.</returns>
2013-12-19 14:51:32 -07:00
protected string GetInputArgument ( StreamState state )
2013-02-26 21:19:05 -07:00
{
2014-01-02 21:58:22 -07:00
if ( state . SendInputOverStandardInput )
{
return "-" ;
}
2014-01-11 23:31:21 -07:00
var type = InputType . File ;
2013-04-07 13:55:05 -07:00
2013-12-19 14:51:32 -07:00
var inputPath = new [ ] { state . MediaPath } ;
2013-04-07 13:55:05 -07:00
2013-12-19 14:51:32 -07:00
if ( state . IsInputVideo )
2013-04-07 13:55:05 -07:00
{
2013-12-19 14:51:32 -07:00
if ( ! ( state . VideoType = = VideoType . Iso & & state . IsoMount = = null ) )
2013-05-20 23:17:07 -07:00
{
2013-12-19 14:51:32 -07:00
inputPath = MediaEncoderHelpers . GetInputArgument ( state . MediaPath , state . IsRemote , state . VideoType , state . IsoType , state . IsoMount , state . PlayableStreamFileNames , out type ) ;
2013-05-20 23:17:07 -07:00
}
2013-04-07 13:55:05 -07:00
}
return MediaEncoder . GetInputArgument ( inputPath , type ) ;
2013-02-26 21:19:05 -07:00
}
/// <summary>
/// Starts the FFMPEG.
/// </summary>
/// <param name="state">The state.</param>
/// <param name="outputPath">The output path.</param>
/// <returns>Task.</returns>
2013-04-10 08:32:09 -07:00
protected async Task StartFfMpeg ( StreamState state , string outputPath )
2013-02-26 21:19:05 -07:00
{
2014-01-01 20:53:27 -07:00
if ( ! File . Exists ( MediaEncoder . EncoderPath ) )
{
throw new InvalidOperationException ( "ffmpeg was not found at " + MediaEncoder . EncoderPath ) ;
}
2013-06-04 09:48:23 -07:00
2014-01-01 20:53:27 -07:00
Directory . CreateDirectory ( Path . GetDirectoryName ( outputPath ) ) ;
2013-06-04 09:48:23 -07:00
2013-12-19 14:51:32 -07:00
if ( state . IsInputVideo & & state . VideoType = = VideoType . Iso & & state . IsoType . HasValue & & IsoManager . CanMount ( state . MediaPath ) )
2013-03-03 22:43:06 -07:00
{
2013-12-19 14:51:32 -07:00
state . IsoMount = await IsoManager . Mount ( state . MediaPath , CancellationToken . None ) . ConfigureAwait ( false ) ;
2013-03-03 22:43:06 -07:00
}
2013-02-26 21:19:05 -07:00
2014-01-07 11:39:35 -07:00
var commandLineArgs = GetCommandLineArguments ( outputPath , state , true ) ;
if ( ServerConfigurationManager . Configuration . EnableDebugEncodingLogging )
{
commandLineArgs = "-loglevel debug " + commandLineArgs ;
}
2013-02-26 21:19:05 -07:00
var process = new Process
{
StartInfo = new ProcessStartInfo
{
CreateNoWindow = true ,
UseShellExecute = false ,
// Must consume both stdout and stderr or deadlocks may occur
RedirectStandardOutput = true ,
RedirectStandardError = true ,
2013-04-07 13:55:05 -07:00
FileName = MediaEncoder . EncoderPath ,
WorkingDirectory = Path . GetDirectoryName ( MediaEncoder . EncoderPath ) ,
2014-01-07 11:39:35 -07:00
Arguments = commandLineArgs ,
2013-02-26 21:19:05 -07:00
WindowStyle = ProcessWindowStyle . Hidden ,
2014-01-02 21:58:22 -07:00
ErrorDialog = false ,
RedirectStandardInput = state . SendInputOverStandardInput
2013-02-26 21:19:05 -07:00
} ,
EnableRaisingEvents = true
} ;
2013-12-19 14:51:32 -07:00
ApiEntryPoint . Instance . OnTranscodeBeginning ( outputPath , TranscodingJobType , process , state . IsInputVideo , state . Request . StartTimeTicks , state . MediaPath , state . Request . DeviceId ) ;
2013-02-26 21:19:05 -07:00
2013-03-07 10:39:21 -07:00
Logger . Info ( process . StartInfo . FileName + " " + process . StartInfo . Arguments ) ;
2013-02-26 21:19:05 -07:00
2013-12-15 12:39:21 -07:00
var logFilePath = Path . Combine ( ServerConfigurationManager . ApplicationPaths . LogDirectoryPath , "ffmpeg-" + Guid . NewGuid ( ) + ".txt" ) ;
2013-12-29 07:12:29 -07:00
Directory . CreateDirectory ( Path . GetDirectoryName ( logFilePath ) ) ;
2013-02-26 21:19:05 -07:00
// FFMpeg writes debug/error info to stderr. This is useful when debugging so let's put it in the log directory.
2013-10-31 07:03:23 -07:00
state . LogFileStream = FileSystem . GetFileStream ( logFilePath , FileMode . Create , FileAccess . Write , FileShare . Read , true ) ;
2013-02-26 21:19:05 -07:00
2013-04-10 08:32:09 -07:00
process . Exited + = ( sender , args ) = > OnFfMpegProcessExited ( process , state ) ;
2013-02-26 21:19:05 -07:00
try
{
process . Start ( ) ;
}
2013-10-01 11:24:27 -07:00
catch ( Exception ex )
2013-02-26 21:19:05 -07:00
{
Logger . ErrorException ( "Error starting ffmpeg" , ex ) ;
2013-03-08 12:14:09 -07:00
ApiEntryPoint . Instance . OnTranscodeFailedToStart ( outputPath , TranscodingJobType ) ;
2013-02-26 21:19:05 -07:00
state . LogFileStream . Dispose ( ) ;
throw ;
}
2014-01-02 21:58:22 -07:00
if ( state . SendInputOverStandardInput )
{
StreamToStandardInput ( process , state ) ;
}
2013-02-26 21:19:05 -07:00
// MUST read both stdout and stderr asynchronously or a deadlock may occurr
process . BeginOutputReadLine ( ) ;
2014-03-10 17:54:31 -07:00
#pragma warning disable 4014
2013-02-26 21:19:05 -07:00
// Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback
process . StandardError . BaseStream . CopyToAsync ( state . LogFileStream ) ;
2014-03-10 17:54:31 -07:00
#pragma warning restore 4014
2013-02-26 21:19:05 -07:00
// Wait for the file to exist before proceeeding
while ( ! File . Exists ( outputPath ) )
{
await Task . Delay ( 100 ) . ConfigureAwait ( false ) ;
}
2013-08-29 18:42:34 -07:00
// Allow a small amount of time to buffer a little
2013-12-19 14:51:32 -07:00
if ( state . IsInputVideo )
2013-08-29 18:42:34 -07:00
{
await Task . Delay ( 500 ) . ConfigureAwait ( false ) ;
}
// This is arbitrary, but add a little buffer time when internet streaming
2013-12-19 14:51:32 -07:00
if ( state . IsRemote )
2013-08-29 18:42:34 -07:00
{
2014-01-02 14:21:38 -07:00
await Task . Delay ( 3000 ) . ConfigureAwait ( false ) ;
2013-08-29 18:42:34 -07:00
}
2013-02-26 21:19:05 -07:00
}
2014-01-02 21:58:22 -07:00
private async void StreamToStandardInput ( Process process , StreamState state )
{
state . StandardInputCancellationTokenSource = new CancellationTokenSource ( ) ;
try
{
await StreamToStandardInputInternal ( process , state ) . ConfigureAwait ( false ) ;
}
catch ( OperationCanceledException )
{
Logger . Debug ( "Stream to standard input closed normally." ) ;
}
catch ( Exception ex )
{
Logger . ErrorException ( "Error writing to standard input" , ex ) ;
}
}
private async Task StreamToStandardInputInternal ( Process process , StreamState state )
{
state . StandardInputCancellationTokenSource = new CancellationTokenSource ( ) ;
using ( var fileStream = FileSystem . GetFileStream ( state . MediaPath , FileMode . Open , FileAccess . Read , FileShare . ReadWrite , true ) )
{
await new EndlessStreamCopy ( ) . CopyStream ( fileStream , process . StandardInput . BaseStream , state . StandardInputCancellationTokenSource . Token ) . ConfigureAwait ( false ) ;
}
}
2014-02-02 07:47:00 -07:00
protected int? GetVideoBitrateParamValue ( StreamState state )
2013-08-29 19:13:58 -07:00
{
2014-02-03 13:37:18 -07:00
var bitrate = state . VideoRequest . VideoBitRate ;
if ( state . VideoStream ! = null )
{
2014-03-09 15:14:44 -07:00
var isUpscaling = state . VideoRequest . Height . HasValue & & state . VideoStream . Height . HasValue & &
state . VideoRequest . Height . Value > state . VideoStream . Height . Value ;
2014-02-03 13:37:18 -07:00
if ( state . VideoRequest . Width . HasValue & & state . VideoStream . Width . HasValue & &
state . VideoRequest . Width . Value > state . VideoStream . Width . Value )
{
isUpscaling = true ;
}
2014-03-09 15:14:44 -07:00
2014-02-03 13:37:18 -07:00
// Don't allow bitrate increases unless upscaling
if ( ! isUpscaling )
{
if ( bitrate . HasValue & & state . VideoStream . BitRate . HasValue )
{
bitrate = Math . Min ( bitrate . Value , state . VideoStream . BitRate . Value ) ;
}
}
}
return bitrate ;
2013-08-29 19:13:58 -07:00
}
2014-02-02 07:47:00 -07:00
protected string GetVideoBitrateParam ( StreamState state , string videoCodec , bool isHls )
{
var bitrate = GetVideoBitrateParamValue ( state ) ;
if ( bitrate . HasValue )
{
2014-02-02 09:21:40 -07:00
var hasFixedResolution = state . VideoRequest . HasFixedResolution ;
2014-02-02 07:47:00 -07:00
if ( isHls )
{
return string . Format ( " -b:v {0} -maxrate ({0}*.80) -bufsize {0}" , bitrate . Value . ToString ( UsCulture ) ) ;
}
if ( string . Equals ( videoCodec , "libvpx" , StringComparison . OrdinalIgnoreCase ) )
{
2014-02-02 09:21:40 -07:00
if ( hasFixedResolution )
{
return string . Format ( " -minrate:v ({0}*.90) -maxrate:v ({0}*1.10) -bufsize:v {0} -b:v {0}" , bitrate . Value . ToString ( UsCulture ) ) ;
}
// With vpx when crf is used, b:v becomes a max rate
// https://trac.ffmpeg.org/wiki/vpxEncodingGuide
2014-02-02 07:47:00 -07:00
return string . Format ( " -b:v {0}" , bitrate . Value . ToString ( UsCulture ) ) ;
2014-02-02 22:35:56 -07:00
//return string.Format(" -minrate:v ({0}*.95) -maxrate:v ({0}*1.05) -bufsize:v {0} -b:v {0}", bitrate.Value.ToString(UsCulture));
2014-02-02 07:47:00 -07:00
}
2014-02-02 09:21:40 -07:00
2014-02-02 07:47:00 -07:00
if ( string . Equals ( videoCodec , "msmpeg4" , StringComparison . OrdinalIgnoreCase ) )
{
return string . Format ( " -b:v {0}" , bitrate . Value . ToString ( UsCulture ) ) ;
}
2014-02-02 09:21:40 -07:00
// H264
if ( hasFixedResolution )
{
return string . Format ( " -b:v {0}" , bitrate . Value . ToString ( UsCulture ) ) ;
}
2014-02-02 07:47:00 -07:00
return string . Format ( " -maxrate {0} -bufsize {1}" ,
bitrate . Value . ToString ( UsCulture ) ,
( bitrate . Value * 2 ) . ToString ( UsCulture ) ) ;
}
return string . Empty ;
}
2013-08-29 19:13:58 -07:00
protected int? GetAudioBitrateParam ( StreamState state )
{
if ( state . Request . AudioBitRate . HasValue )
{
// Make sure we don't request a bitrate higher than the source
var currentBitrate = state . AudioStream = = null ? state . Request . AudioBitRate . Value : state . AudioStream . BitRate ? ? state . Request . AudioBitRate . Value ;
return Math . Min ( currentBitrate , state . Request . AudioBitRate . Value ) ;
}
return null ;
}
2013-05-01 13:07:20 -07:00
/// <summary>
/// Gets the user agent param.
/// </summary>
2013-12-19 14:51:32 -07:00
/// <param name="path">The path.</param>
2013-05-01 13:07:20 -07:00
/// <returns>System.String.</returns>
2014-01-21 20:07:33 -07:00
private string GetUserAgentParam ( string path )
2013-05-17 11:05:49 -07:00
{
2013-12-19 14:51:32 -07:00
var useragent = GetUserAgent ( path ) ;
2013-05-17 11:05:49 -07:00
if ( ! string . IsNullOrEmpty ( useragent ) )
{
return "-user-agent \"" + useragent + "\"" ;
}
return string . Empty ;
}
/// <summary>
/// Gets the user agent.
/// </summary>
2013-12-19 14:51:32 -07:00
/// <param name="path">The path.</param>
2013-05-17 11:05:49 -07:00
/// <returns>System.String.</returns>
2013-12-19 14:51:32 -07:00
protected string GetUserAgent ( string path )
2013-05-01 13:07:20 -07:00
{
2013-12-21 13:38:35 -07:00
if ( string . IsNullOrEmpty ( path ) )
{
throw new ArgumentNullException ( "path" ) ;
}
2013-12-19 14:51:32 -07:00
if ( path . IndexOf ( "apple.com" , StringComparison . OrdinalIgnoreCase ) ! = - 1 )
2013-05-01 13:07:20 -07:00
{
2013-09-05 16:04:22 -07:00
return "QuickTime/7.7.4" ;
2013-05-01 13:07:20 -07:00
}
return string . Empty ;
}
2013-02-26 21:19:05 -07:00
/// <summary>
/// Processes the exited.
/// </summary>
/// <param name="process">The process.</param>
/// <param name="state">The state.</param>
2014-01-04 23:50:48 -07:00
protected async void OnFfMpegProcessExited ( Process process , StreamState state )
2013-02-26 21:19:05 -07:00
{
if ( state . IsoMount ! = null )
{
state . IsoMount . Dispose ( ) ;
state . IsoMount = null ;
}
2014-01-02 21:58:22 -07:00
if ( state . StandardInputCancellationTokenSource ! = null )
{
state . StandardInputCancellationTokenSource . Cancel ( ) ;
}
2013-02-26 21:19:05 -07:00
var outputFilePath = GetOutputFilePath ( state ) ;
state . LogFileStream . Dispose ( ) ;
try
{
2013-06-09 21:00:44 -07:00
Logger . Info ( "FFMpeg exited with code {0} for {1}" , process . ExitCode , outputFilePath ) ;
2013-02-26 21:19:05 -07:00
}
catch
{
Logger . Info ( "FFMpeg exited with an error for {0}" , outputFilePath ) ;
}
2014-01-04 23:50:48 -07:00
if ( ! string . IsNullOrEmpty ( state . LiveTvStreamId ) )
{
try
{
await LiveTvManager . CloseLiveStream ( state . LiveTvStreamId , CancellationToken . None ) . ConfigureAwait ( false ) ;
}
catch ( Exception ex )
{
Logger . ErrorException ( "Error closing live tv stream" , ex ) ;
}
}
2013-02-26 21:19:05 -07:00
}
2014-01-24 11:09:50 -07:00
protected double? GetFramerateParam ( StreamState state )
{
if ( state . VideoRequest ! = null & & state . VideoRequest . Framerate . HasValue )
{
return state . VideoRequest . Framerate . Value ;
}
if ( state . VideoStream ! = null )
{
var contentRate = state . VideoStream . AverageFrameRate ? ? state . VideoStream . RealFrameRate ;
if ( contentRate . HasValue & & contentRate . Value > 23.976 )
{
return 23.976 ;
}
}
return null ;
}
2014-02-12 22:11:54 -07:00
/// <summary>
/// Parses the parameters.
/// </summary>
/// <param name="request">The request.</param>
private void ParseParams ( StreamRequest request )
{
var vals = request . Params . Split ( ';' ) ;
var videoRequest = request as VideoStreamRequest ;
for ( var i = 0 ; i < vals . Length ; i + + )
{
var val = vals [ i ] ;
if ( string . IsNullOrWhiteSpace ( val ) )
{
continue ;
}
if ( i = = 0 )
{
request . DeviceId = val ;
}
else if ( i = = 1 )
2014-03-12 12:56:12 -07:00
{
request . Static = string . Equals ( "true" , val , StringComparison . OrdinalIgnoreCase ) ;
}
else if ( i = = 2 )
2014-02-12 22:11:54 -07:00
{
if ( videoRequest ! = null )
{
videoRequest . VideoCodec = ( VideoCodecs ) Enum . Parse ( typeof ( VideoCodecs ) , val , true ) ;
}
}
2014-03-12 12:56:12 -07:00
else if ( i = = 3 )
2014-02-12 22:11:54 -07:00
{
request . AudioCodec = ( AudioCodecs ) Enum . Parse ( typeof ( AudioCodecs ) , val , true ) ;
}
2014-03-12 12:56:12 -07:00
else if ( i = = 4 )
2014-02-12 22:11:54 -07:00
{
if ( videoRequest ! = null )
{
videoRequest . AudioStreamIndex = int . Parse ( val , UsCulture ) ;
}
}
2014-03-12 12:56:12 -07:00
else if ( i = = 5 )
2014-02-12 22:11:54 -07:00
{
if ( videoRequest ! = null )
{
videoRequest . SubtitleStreamIndex = int . Parse ( val , UsCulture ) ;
}
}
2014-03-12 12:56:12 -07:00
else if ( i = = 6 )
2014-02-12 22:11:54 -07:00
{
if ( videoRequest ! = null )
{
videoRequest . VideoBitRate = int . Parse ( val , UsCulture ) ;
}
}
2014-03-12 12:56:12 -07:00
else if ( i = = 7 )
2014-02-12 22:11:54 -07:00
{
request . AudioBitRate = int . Parse ( val , UsCulture ) ;
}
2014-03-12 12:56:12 -07:00
else if ( i = = 8 )
2014-02-12 22:11:54 -07:00
{
request . AudioChannels = int . Parse ( val , UsCulture ) ;
}
2014-03-12 12:56:12 -07:00
else if ( i = = 9 )
2014-02-13 09:38:43 -07:00
{
if ( videoRequest ! = null )
{
request . StartTimeTicks = long . Parse ( val , UsCulture ) ;
}
}
2014-03-12 12:56:12 -07:00
else if ( i = = 10 )
2014-02-13 09:38:43 -07:00
{
if ( videoRequest ! = null )
{
videoRequest . Profile = val ;
}
}
2014-03-12 12:56:12 -07:00
else if ( i = = 11 )
2014-02-13 09:38:43 -07:00
{
if ( videoRequest ! = null )
{
videoRequest . Level = val ;
}
}
2014-03-12 12:56:12 -07:00
else if ( i = = 12 )
{
request . ForcedMimeType = val ;
}
2014-02-12 22:11:54 -07:00
}
}
2013-02-26 21:19:05 -07:00
/// <summary>
/// Gets the state.
/// </summary>
/// <param name="request">The request.</param>
2013-12-21 11:37:34 -07:00
/// <param name="cancellationToken">The cancellation token.</param>
2013-02-26 21:19:05 -07:00
/// <returns>StreamState.</returns>
2013-12-21 11:37:34 -07:00
protected async Task < StreamState > GetState ( StreamRequest request , CancellationToken cancellationToken )
2013-02-26 21:19:05 -07:00
{
2014-02-12 22:11:54 -07:00
if ( ! string . IsNullOrWhiteSpace ( request . Params ) )
{
ParseParams ( request ) ;
}
2014-01-17 22:55:21 -07:00
if ( request . ThrowDebugError )
{
throw new InvalidOperationException ( "You asked for a debug error, you got one." ) ;
}
2014-01-22 19:19:04 -07:00
var user = AuthorizationRequestFilterAttribute . GetCurrentUser ( Request , UserManager ) ;
2013-12-07 08:52:38 -07:00
var url = Request . PathInfo ;
2013-02-26 21:44:41 -07:00
if ( ! request . AudioCodec . HasValue )
{
request . AudioCodec = InferAudioCodec ( url ) ;
}
2013-03-08 19:34:54 -07:00
var state = new StreamState
2013-02-26 21:19:05 -07:00
{
Request = request ,
2013-12-21 11:37:34 -07:00
RequestedUrl = url
2013-02-26 21:19:05 -07:00
} ;
2013-03-08 19:34:54 -07:00
2014-01-14 13:03:35 -07:00
var item = DtoService . GetItemByDtoId ( request . Id ) ;
2013-12-21 11:37:34 -07:00
2014-02-20 22:35:56 -07:00
if ( user ! = null & & item . GetPlayAccess ( user ) ! = PlayAccess . Full )
{
throw new ArgumentException ( string . Format ( "{0} is not allowed to play media." , user . Name ) ) ;
}
2014-01-14 13:03:35 -07:00
if ( item is ILiveTvRecording )
2013-12-21 11:37:34 -07:00
{
var recording = await LiveTvManager . GetInternalRecording ( request . Id , cancellationToken ) . ConfigureAwait ( false ) ;
state . VideoType = VideoType . VideoFile ;
state . IsInputVideo = string . Equals ( recording . MediaType , MediaType . Video , StringComparison . OrdinalIgnoreCase ) ;
state . PlayableStreamFileNames = new List < string > ( ) ;
2014-03-09 15:14:44 -07:00
var path = recording . RecordingInfo . Path ;
var mediaUrl = recording . RecordingInfo . Url ;
if ( string . IsNullOrWhiteSpace ( path ) & & string . IsNullOrWhiteSpace ( mediaUrl ) )
2013-12-21 11:37:34 -07:00
{
2014-01-02 16:07:37 -07:00
var streamInfo = await LiveTvManager . GetRecordingStream ( request . Id , cancellationToken ) . ConfigureAwait ( false ) ;
2013-12-22 11:58:51 -07:00
2014-01-04 23:50:48 -07:00
state . LiveTvStreamId = streamInfo . Id ;
2014-03-09 15:14:44 -07:00
path = streamInfo . Path ;
mediaUrl = streamInfo . Url ;
}
if ( ! string . IsNullOrEmpty ( path ) & & File . Exists ( path ) )
{
state . MediaPath = path ;
2013-12-21 11:37:34 -07:00
state . IsRemote = false ;
2014-03-09 15:14:44 -07:00
state . SendInputOverStandardInput = recording . RecordingInfo . Status = = RecordingStatus . InProgress ;
2013-12-21 11:37:34 -07:00
}
2014-03-09 15:14:44 -07:00
else if ( ! string . IsNullOrEmpty ( mediaUrl ) )
2013-12-21 11:37:34 -07:00
{
2014-03-09 15:14:44 -07:00
state . MediaPath = mediaUrl ;
2013-12-21 11:37:34 -07:00
state . IsRemote = true ;
}
2014-01-04 23:50:48 -07:00
2014-01-12 14:32:13 -07:00
//state.RunTimeTicks = recording.RunTimeTicks;
2014-03-11 20:43:08 -07:00
if ( recording . RecordingInfo . Status = = RecordingStatus . InProgress & & ! state . IsRemote )
{
await Task . Delay ( 1000 , cancellationToken ) . ConfigureAwait ( false ) ;
2013-12-22 11:58:51 -07:00
}
2013-12-19 14:51:32 -07:00
2014-01-21 18:37:01 -07:00
state . ReadInputAtNativeFramerate = recording . RecordingInfo . Status = = RecordingStatus . InProgress ;
2014-01-24 11:09:50 -07:00
state . AudioSync = "1000" ;
2014-01-20 08:04:50 -07:00
state . DeInterlace = true ;
2014-03-20 21:54:18 -07:00
state . InputVideoSync = "-1" ;
state . InputAudioSync = "1" ;
2013-12-21 11:37:34 -07:00
}
2014-01-14 13:03:35 -07:00
else if ( item is LiveTvChannel )
2013-12-29 11:53:56 -07:00
{
2014-01-02 21:58:22 -07:00
var channel = LiveTvManager . GetInternalChannel ( request . Id ) ;
2013-12-29 11:53:56 -07:00
state . VideoType = VideoType . VideoFile ;
state . IsInputVideo = string . Equals ( channel . MediaType , MediaType . Video , StringComparison . OrdinalIgnoreCase ) ;
state . PlayableStreamFileNames = new List < string > ( ) ;
2014-01-02 16:07:37 -07:00
var streamInfo = await LiveTvManager . GetChannelStream ( request . Id , cancellationToken ) . ConfigureAwait ( false ) ;
2013-12-29 11:53:56 -07:00
2014-01-04 23:50:48 -07:00
state . LiveTvStreamId = streamInfo . Id ;
2014-01-02 16:07:37 -07:00
if ( ! string . IsNullOrEmpty ( streamInfo . Path ) & & File . Exists ( streamInfo . Path ) )
{
state . MediaPath = streamInfo . Path ;
state . IsRemote = false ;
2014-03-11 20:43:08 -07:00
await Task . Delay ( 1000 , cancellationToken ) . ConfigureAwait ( false ) ;
2014-01-02 16:07:37 -07:00
}
else if ( ! string . IsNullOrEmpty ( streamInfo . Url ) )
{
state . MediaPath = streamInfo . Url ;
state . IsRemote = true ;
}
2013-12-29 11:53:56 -07:00
2014-01-21 18:37:01 -07:00
state . ReadInputAtNativeFramerate = true ;
2014-01-24 11:09:50 -07:00
state . AudioSync = "1000" ;
2014-01-20 08:04:50 -07:00
state . DeInterlace = true ;
2014-03-20 21:54:18 -07:00
state . InputVideoSync = "-1" ;
state . InputAudioSync = "1" ;
2013-12-29 11:53:56 -07:00
}
2013-12-21 11:37:34 -07:00
else
2013-12-19 14:51:32 -07:00
{
2013-12-21 13:38:35 -07:00
state . MediaPath = item . Path ;
state . IsRemote = item . LocationType = = LocationType . Remote ;
2013-12-22 11:58:51 -07:00
2013-12-21 11:37:34 -07:00
var video = item as Video ;
if ( video ! = null )
{
state . IsInputVideo = true ;
state . VideoType = video . VideoType ;
state . IsoType = video . IsoType ;
state . PlayableStreamFileNames = video . PlayableStreamFileNames = = null
? new List < string > ( )
: video . PlayableStreamFileNames . ToList ( ) ;
}
2014-01-01 11:26:31 -07:00
2014-01-12 14:32:13 -07:00
state . RunTimeTicks = item . RunTimeTicks ;
2013-12-19 14:51:32 -07:00
}
2013-03-08 19:34:54 -07:00
var videoRequest = request as VideoStreamRequest ;
2013-12-05 20:39:44 -07:00
var mediaStreams = ItemRepository . GetMediaStreams ( new MediaStreamQuery
{
2014-01-14 13:03:35 -07:00
ItemId = item . Id
2013-12-05 20:39:44 -07:00
} ) . ToList ( ) ;
2013-03-08 19:34:54 -07:00
if ( videoRequest ! = null )
{
if ( ! videoRequest . VideoCodec . HasValue )
{
videoRequest . VideoCodec = InferVideoCodec ( url ) ;
}
2013-12-05 20:39:44 -07:00
state . VideoStream = GetMediaStream ( mediaStreams , videoRequest . VideoStreamIndex , MediaStreamType . Video ) ;
state . SubtitleStream = GetMediaStream ( mediaStreams , videoRequest . SubtitleStreamIndex , MediaStreamType . Subtitle , false ) ;
state . AudioStream = GetMediaStream ( mediaStreams , videoRequest . AudioStreamIndex , MediaStreamType . Audio ) ;
2014-02-02 08:19:29 -07:00
2014-03-09 15:14:44 -07:00
if ( state . VideoStream ! = null & & state . VideoStream . IsInterlaced )
{
state . DeInterlace = true ;
}
2014-02-02 08:19:29 -07:00
EnforceResolutionLimit ( state , videoRequest ) ;
2013-04-07 14:05:27 -07:00
}
else
{
2013-12-05 20:39:44 -07:00
state . AudioStream = GetMediaStream ( mediaStreams , null , MediaStreamType . Audio , true ) ;
2013-03-08 19:34:54 -07:00
}
2013-03-10 20:44:22 -07:00
2013-12-21 11:37:34 -07:00
state . HasMediaStreams = mediaStreams . Count > 0 ;
2014-03-12 12:56:12 -07:00
state . SegmentLength = state . ReadInputAtNativeFramerate ? 5 : 10 ;
state . HlsListSize = state . ReadInputAtNativeFramerate ? 100 : 1440 ;
2014-03-03 21:58:19 -07:00
2013-03-08 19:34:54 -07:00
return state ;
2013-02-26 21:19:05 -07:00
}
2013-02-26 21:44:41 -07:00
2014-02-02 08:19:29 -07:00
/// <summary>
/// Enforces the resolution limit.
/// </summary>
/// <param name="state">The state.</param>
/// <param name="videoRequest">The video request.</param>
private void EnforceResolutionLimit ( StreamState state , VideoStreamRequest videoRequest )
{
2014-02-02 08:25:42 -07:00
// If enabled, allow whatever the client asks for
if ( ServerConfigurationManager . Configuration . AllowVideoUpscaling )
{
return ;
}
2014-02-02 19:11:25 -07:00
// Switch the incoming params to be ceilings rather than fixed values
2014-02-02 08:25:42 -07:00
videoRequest . MaxWidth = videoRequest . MaxWidth ? ? videoRequest . Width ;
videoRequest . MaxHeight = videoRequest . MaxHeight ? ? videoRequest . Height ;
2014-02-02 08:19:29 -07:00
2014-02-02 08:25:42 -07:00
videoRequest . Width = null ;
videoRequest . Height = null ;
2014-02-02 08:19:29 -07:00
}
2014-01-21 18:37:01 -07:00
protected string GetInputModifier ( StreamState state )
{
var inputModifier = string . Empty ;
2014-01-21 20:07:33 -07:00
var probeSize = GetProbeSizeArgument ( state . MediaPath , state . IsInputVideo , state . VideoType , state . IsoType ) ;
inputModifier + = " " + probeSize ;
inputModifier = inputModifier . Trim ( ) ;
2014-02-02 07:47:00 -07:00
2014-01-21 20:07:33 -07:00
inputModifier + = " " + GetUserAgentParam ( state . MediaPath ) ;
inputModifier = inputModifier . Trim ( ) ;
inputModifier + = " " + GetFastSeekCommandLineParameter ( state . Request ) ;
inputModifier = inputModifier . Trim ( ) ;
if ( state . VideoRequest ! = null )
{
inputModifier + = " -fflags genpts" ;
}
2014-01-21 18:37:01 -07:00
if ( ! string . IsNullOrEmpty ( state . InputFormat ) )
{
inputModifier + = " -f " + state . InputFormat ;
}
if ( ! string . IsNullOrEmpty ( state . InputVideoCodec ) )
{
inputModifier + = " -vcodec " + state . InputVideoCodec ;
}
if ( ! string . IsNullOrEmpty ( state . InputAudioCodec ) )
{
inputModifier + = " -acodec " + state . InputAudioCodec ;
}
2014-01-21 20:07:33 -07:00
2014-03-20 21:52:28 -07:00
if ( ! string . IsNullOrEmpty ( state . InputAudioSync ) )
{
inputModifier + = " -async " + state . InputAudioSync ;
}
if ( ! string . IsNullOrEmpty ( state . InputVideoSync ) )
{
inputModifier + = " -vsync " + state . InputVideoSync ;
}
2014-01-21 18:37:01 -07:00
if ( state . ReadInputAtNativeFramerate )
{
inputModifier + = " -re" ;
}
return inputModifier ;
}
2013-02-26 21:44:41 -07:00
/// <summary>
/// Infers the audio codec based on the url
/// </summary>
/// <param name="url">The URL.</param>
/// <returns>System.Nullable{AudioCodecs}.</returns>
private AudioCodecs ? InferAudioCodec ( string url )
{
var ext = Path . GetExtension ( url ) ;
if ( string . Equals ( ext , ".mp3" , StringComparison . OrdinalIgnoreCase ) )
{
return AudioCodecs . Mp3 ;
}
if ( string . Equals ( ext , ".aac" , StringComparison . OrdinalIgnoreCase ) )
{
return AudioCodecs . Aac ;
}
2013-03-09 08:24:38 -07:00
if ( string . Equals ( ext , ".wma" , StringComparison . OrdinalIgnoreCase ) )
2013-02-26 21:44:41 -07:00
{
return AudioCodecs . Wma ;
}
2013-03-09 08:24:38 -07:00
if ( string . Equals ( ext , ".ogg" , StringComparison . OrdinalIgnoreCase ) )
{
return AudioCodecs . Vorbis ;
}
if ( string . Equals ( ext , ".oga" , StringComparison . OrdinalIgnoreCase ) )
{
return AudioCodecs . Vorbis ;
}
if ( string . Equals ( ext , ".ogv" , StringComparison . OrdinalIgnoreCase ) )
{
return AudioCodecs . Vorbis ;
}
if ( string . Equals ( ext , ".webm" , StringComparison . OrdinalIgnoreCase ) )
{
return AudioCodecs . Vorbis ;
}
if ( string . Equals ( ext , ".webma" , StringComparison . OrdinalIgnoreCase ) )
{
return AudioCodecs . Vorbis ;
}
2013-02-26 21:44:41 -07:00
return null ;
}
/// <summary>
/// Infers the video codec.
/// </summary>
/// <param name="url">The URL.</param>
/// <returns>System.Nullable{VideoCodecs}.</returns>
private VideoCodecs ? InferVideoCodec ( string url )
{
var ext = Path . GetExtension ( url ) ;
if ( string . Equals ( ext , ".asf" , StringComparison . OrdinalIgnoreCase ) )
{
return VideoCodecs . Wmv ;
}
if ( string . Equals ( ext , ".webm" , StringComparison . OrdinalIgnoreCase ) )
{
return VideoCodecs . Vpx ;
}
2013-03-09 08:24:38 -07:00
if ( string . Equals ( ext , ".ogg" , StringComparison . OrdinalIgnoreCase ) | | string . Equals ( ext , ".ogv" , StringComparison . OrdinalIgnoreCase ) )
2013-02-26 21:44:41 -07:00
{
return VideoCodecs . Theora ;
}
2013-03-09 08:24:38 -07:00
if ( string . Equals ( ext , ".m3u8" , StringComparison . OrdinalIgnoreCase ) | | string . Equals ( ext , ".ts" , StringComparison . OrdinalIgnoreCase ) )
{
return VideoCodecs . H264 ;
}
2013-02-26 21:44:41 -07:00
2013-08-16 07:09:29 -07:00
return VideoCodecs . Copy ;
2013-02-26 21:44:41 -07:00
}
2013-02-26 21:19:05 -07:00
}
}