2014-04-02 14:55:19 -07:00
using MediaBrowser.Model.Dto ;
using MediaBrowser.Model.Entities ;
2014-05-31 21:11:04 -07:00
using MediaBrowser.Model.Extensions ;
2015-04-04 12:35:29 -07:00
using MediaBrowser.Model.Logging ;
2014-04-30 08:07:02 -07:00
using MediaBrowser.Model.MediaInfo ;
2014-08-05 16:59:24 -07:00
using MediaBrowser.Model.Session ;
2014-04-02 14:55:19 -07:00
using System ;
using System.Collections.Generic ;
2016-10-21 19:08:34 -07:00
using System.Globalization ;
2016-12-13 10:04:37 -07:00
using System.Linq ;
2014-04-01 15:23:07 -07:00
namespace MediaBrowser.Model.Dlna
{
public class StreamBuilder
{
2015-04-04 12:35:29 -07:00
private readonly ILogger _logger ;
2016-04-30 12:16:43 -07:00
private readonly ITranscoderSupport _transcoderSupport ;
2015-03-11 23:06:57 -07:00
2016-07-24 22:12:38 -07:00
public StreamBuilder ( ITranscoderSupport transcoderSupport , ILogger logger )
2015-03-11 23:06:57 -07:00
{
2016-04-30 12:16:43 -07:00
_transcoderSupport = transcoderSupport ;
2015-04-04 12:35:29 -07:00
_logger = logger ;
2015-03-11 23:06:57 -07:00
}
2015-04-04 12:35:29 -07:00
public StreamBuilder ( ILogger logger )
2016-07-24 22:12:38 -07:00
: this ( new FullTranscoderSupport ( ) , logger )
2015-03-11 23:06:57 -07:00
{
}
2014-04-01 15:23:07 -07:00
public StreamInfo BuildAudioItem ( AudioOptions options )
{
ValidateAudioInput ( options ) ;
2014-04-03 15:50:04 -07:00
2015-03-15 18:48:25 -07:00
List < MediaSourceInfo > mediaSources = new List < MediaSourceInfo > ( ) ;
foreach ( MediaSourceInfo i in options . MediaSources )
2014-04-01 15:23:07 -07:00
{
2015-03-15 21:39:55 -07:00
if ( string . IsNullOrEmpty ( options . MediaSourceId ) | |
2015-03-15 18:48:25 -07:00
StringHelper . EqualsIgnoreCase ( i . Id , options . MediaSourceId ) )
2014-05-08 14:23:24 -07:00
{
2015-03-15 18:48:25 -07:00
mediaSources . Add ( i ) ;
2014-05-08 14:23:24 -07:00
}
2014-04-01 15:23:07 -07:00
}
2014-05-08 14:23:24 -07:00
List < StreamInfo > streams = new List < StreamInfo > ( ) ;
foreach ( MediaSourceInfo i in mediaSources )
2015-03-02 11:48:21 -07:00
{
StreamInfo streamInfo = BuildAudioItem ( i , options ) ;
if ( streamInfo ! = null )
{
streams . Add ( streamInfo ) ;
}
}
2014-04-01 15:23:07 -07:00
2014-05-08 13:09:53 -07:00
foreach ( StreamInfo stream in streams )
2014-04-01 15:23:07 -07:00
{
stream . DeviceId = options . DeviceId ;
stream . DeviceProfileId = options . Profile . Id ;
}
2016-08-22 22:08:07 -07:00
return GetOptimalStream ( streams , options . GetMaxBitrate ( true ) ) ;
2014-04-01 15:23:07 -07:00
}
public StreamInfo BuildVideoItem ( VideoOptions options )
{
ValidateInput ( options ) ;
2015-03-15 18:48:25 -07:00
List < MediaSourceInfo > mediaSources = new List < MediaSourceInfo > ( ) ;
foreach ( MediaSourceInfo i in options . MediaSources )
2014-04-01 15:23:07 -07:00
{
2015-03-15 21:39:55 -07:00
if ( string . IsNullOrEmpty ( options . MediaSourceId ) | |
2015-03-15 18:48:25 -07:00
StringHelper . EqualsIgnoreCase ( i . Id , options . MediaSourceId ) )
2014-05-08 14:23:24 -07:00
{
2015-03-15 18:48:25 -07:00
mediaSources . Add ( i ) ;
2014-05-08 14:23:24 -07:00
}
2014-04-01 15:23:07 -07:00
}
2014-05-08 14:23:24 -07:00
List < StreamInfo > streams = new List < StreamInfo > ( ) ;
foreach ( MediaSourceInfo i in mediaSources )
2015-03-02 11:48:21 -07:00
{
StreamInfo streamInfo = BuildVideoItem ( i , options ) ;
if ( streamInfo ! = null )
{
2015-03-11 23:06:57 -07:00
streams . Add ( streamInfo ) ;
2015-03-02 11:48:21 -07:00
}
}
2014-04-01 15:23:07 -07:00
2014-05-08 13:09:53 -07:00
foreach ( StreamInfo stream in streams )
2014-04-01 15:23:07 -07:00
{
stream . DeviceId = options . DeviceId ;
stream . DeviceProfileId = options . Profile . Id ;
}
2016-08-22 22:08:07 -07:00
return GetOptimalStream ( streams , options . GetMaxBitrate ( false ) ) ;
2014-04-01 15:23:07 -07:00
}
2017-01-02 22:15:59 -07:00
private StreamInfo GetOptimalStream ( List < StreamInfo > streams , long? maxBitrate )
2014-04-01 15:23:07 -07:00
{
2016-04-14 12:12:00 -07:00
streams = StreamInfoSorter . SortMediaSources ( streams , maxBitrate ) ;
2014-05-08 14:23:24 -07:00
foreach ( StreamInfo stream in streams )
{
return stream ;
}
2015-03-02 11:48:21 -07:00
2015-03-26 09:58:02 -07:00
return null ;
2014-04-01 15:23:07 -07:00
}
2014-04-06 10:53:23 -07:00
private StreamInfo BuildAudioItem ( MediaSourceInfo item , AudioOptions options )
2014-04-01 15:23:07 -07:00
{
2014-05-08 13:09:53 -07:00
StreamInfo playlistItem = new StreamInfo
2014-04-01 15:23:07 -07:00
{
2014-04-06 10:53:23 -07:00
ItemId = options . ItemId ,
2014-04-01 15:23:07 -07:00
MediaType = DlnaProfileType . Audio ,
2014-04-18 10:16:25 -07:00
MediaSource = item ,
2015-01-01 22:36:27 -07:00
RunTimeTicks = item . RunTimeTicks ,
2015-02-04 22:29:37 -07:00
Context = options . Context ,
DeviceProfile = options . Profile
2014-04-01 15:23:07 -07:00
} ;
2016-07-24 22:12:38 -07:00
if ( options . ForceDirectPlay )
{
playlistItem . PlayMethod = PlayMethod . DirectPlay ;
playlistItem . Container = item . Container ;
return playlistItem ;
}
if ( options . ForceDirectStream )
{
playlistItem . PlayMethod = PlayMethod . DirectStream ;
playlistItem . Container = item . Container ;
return playlistItem ;
}
2015-03-23 10:19:21 -07:00
MediaStream audioStream = item . GetDefaultAudioStream ( null ) ;
List < PlayMethod > directPlayMethods = GetAudioDirectPlayMethods ( item , audioStream , options ) ;
2014-04-20 18:36:12 -07:00
2016-07-09 10:39:04 -07:00
ConditionProcessor conditionProcessor = new ConditionProcessor ( ) ;
int? inputAudioChannels = audioStream = = null ? null : audioStream . Channels ;
int? inputAudioBitrate = audioStream = = null ? null : audioStream . BitDepth ;
2017-05-13 12:31:25 -07:00
int? inputAudioSampleRate = audioStream = = null ? null : audioStream . SampleRate ;
2016-07-09 10:39:04 -07:00
2015-03-12 18:55:22 -07:00
if ( directPlayMethods . Count > 0 )
2014-04-01 15:23:07 -07:00
{
2015-03-12 18:55:22 -07:00
string audioCodec = audioStream = = null ? null : audioStream . Codec ;
2014-04-01 15:23:07 -07:00
2015-03-12 18:55:22 -07:00
// Make sure audio codec profiles are satisfied
if ( ! string . IsNullOrEmpty ( audioCodec ) )
2014-04-01 15:23:07 -07:00
{
2015-03-12 18:55:22 -07:00
List < ProfileCondition > conditions = new List < ProfileCondition > ( ) ;
foreach ( CodecProfile i in options . Profile . CodecProfiles )
2014-04-06 10:53:23 -07:00
{
2015-05-06 20:11:51 -07:00
if ( i . Type = = CodecType . Audio & & i . ContainsCodec ( audioCodec , item . Container ) )
2014-05-09 12:43:06 -07:00
{
2016-07-09 10:39:04 -07:00
bool applyConditions = true ;
foreach ( ProfileCondition applyCondition in i . ApplyConditions )
2014-06-30 21:06:28 -07:00
{
2017-05-13 12:31:25 -07:00
if ( ! conditionProcessor . IsAudioConditionSatisfied ( applyCondition , inputAudioChannels , inputAudioBitrate , inputAudioSampleRate ) )
2016-07-09 10:39:04 -07:00
{
LogConditionFailure ( options . Profile , "AudioCodecProfile" , applyCondition , item ) ;
applyConditions = false ;
break ;
}
}
if ( applyConditions )
{
foreach ( ProfileCondition c in i . Conditions )
{
conditions . Add ( c ) ;
}
2014-06-30 21:06:28 -07:00
}
2014-05-09 12:43:06 -07:00
}
2015-03-12 18:55:22 -07:00
}
2014-04-23 22:08:10 -07:00
2015-03-12 18:55:22 -07:00
bool all = true ;
foreach ( ProfileCondition c in conditions )
{
2017-05-13 12:31:25 -07:00
if ( ! conditionProcessor . IsAudioConditionSatisfied ( c , inputAudioChannels , inputAudioBitrate , inputAudioSampleRate ) )
2014-05-09 12:43:06 -07:00
{
2015-07-10 11:30:42 -07:00
LogConditionFailure ( options . Profile , "AudioCodecProfile" , c , item ) ;
2015-03-12 18:55:22 -07:00
all = false ;
break ;
2014-05-09 12:43:06 -07:00
}
2015-03-12 18:55:22 -07:00
}
2014-05-09 12:43:06 -07:00
2015-03-12 18:55:22 -07:00
if ( all )
{
2016-07-24 22:12:38 -07:00
if ( directPlayMethods . Contains ( PlayMethod . DirectStream ) )
2015-03-12 18:55:22 -07:00
{
playlistItem . PlayMethod = PlayMethod . DirectStream ;
}
2015-03-11 23:06:57 -07:00
2015-03-12 18:55:22 -07:00
playlistItem . Container = item . Container ;
2014-04-23 22:08:10 -07:00
2015-03-12 18:55:22 -07:00
return playlistItem ;
2014-04-06 10:53:23 -07:00
}
2014-04-01 15:23:07 -07:00
}
}
2014-05-09 12:43:06 -07:00
TranscodingProfile transcodingProfile = null ;
foreach ( TranscodingProfile i in options . Profile . TranscodingProfiles )
{
2014-07-16 20:17:14 -07:00
if ( i . Type = = playlistItem . MediaType & & i . Context = = options . Context )
2014-05-09 12:43:06 -07:00
{
2016-04-30 12:16:43 -07:00
if ( _transcoderSupport . CanEncodeToAudioCodec ( i . AudioCodec ? ? i . Container ) )
{
transcodingProfile = i ;
break ;
}
2014-05-09 12:43:06 -07:00
}
}
2014-04-01 15:23:07 -07:00
if ( transcodingProfile ! = null )
{
2015-03-02 11:48:21 -07:00
if ( ! item . SupportsTranscoding )
{
return null ;
}
2014-08-05 16:59:24 -07:00
playlistItem . PlayMethod = PlayMethod . Transcode ;
2014-04-17 22:03:01 -07:00
playlistItem . TranscodeSeekInfo = transcodingProfile . TranscodeSeekInfo ;
2014-04-18 10:16:25 -07:00
playlistItem . EstimateContentLength = transcodingProfile . EstimateContentLength ;
2014-04-01 15:23:07 -07:00
playlistItem . Container = transcodingProfile . Container ;
2016-06-26 09:21:10 -07:00
if ( string . IsNullOrEmpty ( transcodingProfile . AudioCodec ) )
{
playlistItem . AudioCodecs = new string [ ] { } ;
}
else
{
playlistItem . AudioCodecs = transcodingProfile . AudioCodec . Split ( ',' ) ;
}
2017-03-15 12:57:18 -07:00
2015-03-26 09:58:02 -07:00
playlistItem . SubProtocol = transcodingProfile . Protocol ;
2014-04-01 15:23:07 -07:00
2014-05-09 16:08:08 -07:00
List < CodecProfile > audioCodecProfiles = new List < CodecProfile > ( ) ;
foreach ( CodecProfile i in options . Profile . CodecProfiles )
{
2015-05-06 20:11:51 -07:00
if ( i . Type = = CodecType . Audio & & i . ContainsCodec ( transcodingProfile . AudioCodec , transcodingProfile . Container ) )
2014-05-09 16:08:08 -07:00
{
audioCodecProfiles . Add ( i ) ;
}
if ( audioCodecProfiles . Count > = 1 ) break ;
}
List < ProfileCondition > audioTranscodingConditions = new List < ProfileCondition > ( ) ;
foreach ( CodecProfile i in audioCodecProfiles )
2014-06-30 21:06:28 -07:00
{
2016-07-09 10:39:04 -07:00
bool applyConditions = true ;
foreach ( ProfileCondition applyCondition in i . ApplyConditions )
2014-06-30 21:06:28 -07:00
{
2017-05-13 12:31:25 -07:00
if ( ! conditionProcessor . IsAudioConditionSatisfied ( applyCondition , inputAudioChannels , inputAudioBitrate , inputAudioSampleRate ) )
2016-07-09 10:39:04 -07:00
{
LogConditionFailure ( options . Profile , "AudioCodecProfile" , applyCondition , item ) ;
applyConditions = false ;
break ;
}
}
if ( applyConditions )
{
foreach ( ProfileCondition c in i . Conditions )
{
audioTranscodingConditions . Add ( c ) ;
}
2014-06-30 21:06:28 -07:00
}
}
2014-04-01 15:23:07 -07:00
ApplyTranscodingConditions ( playlistItem , audioTranscodingConditions ) ;
2014-04-06 10:53:23 -07:00
// Honor requested max channels
if ( options . MaxAudioChannels . HasValue )
{
2014-05-08 13:09:53 -07:00
int currentValue = playlistItem . MaxAudioChannels ? ? options . MaxAudioChannels . Value ;
2014-04-06 10:53:23 -07:00
playlistItem . MaxAudioChannels = Math . Min ( options . MaxAudioChannels . Value , currentValue ) ;
}
2017-01-02 22:15:59 -07:00
long transcodingBitrate = options . AudioTranscodingBitrate ? ?
2016-08-22 22:08:07 -07:00
options . Profile . MusicStreamingTranscodingBitrate ? ?
2014-08-14 06:24:30 -07:00
128000 ;
2017-01-02 22:15:59 -07:00
var configuredBitrate = options . GetMaxBitrate ( true ) ;
2016-08-22 22:08:07 -07:00
if ( configuredBitrate . HasValue )
{
transcodingBitrate = Math . Min ( configuredBitrate . Value , transcodingBitrate ) ;
}
2017-01-02 22:15:59 -07:00
var longBitrate = Math . Min ( transcodingBitrate , playlistItem . AudioBitrate ? ? transcodingBitrate ) ;
playlistItem . AudioBitrate = longBitrate > int . MaxValue ? int . MaxValue : Convert . ToInt32 ( longBitrate ) ;
2016-08-22 22:08:07 -07:00
2014-04-01 15:23:07 -07:00
}
return playlistItem ;
}
2017-01-02 22:15:59 -07:00
private long? GetBitrateForDirectPlayCheck ( MediaSourceInfo item , AudioOptions options , bool isAudio )
2015-03-28 13:22:27 -07:00
{
if ( item . Protocol = = MediaProtocol . File )
{
return options . Profile . MaxStaticBitrate ;
}
2016-08-22 22:08:07 -07:00
return options . GetMaxBitrate ( isAudio ) ;
2015-03-28 13:22:27 -07:00
}
2015-03-23 10:19:21 -07:00
private List < PlayMethod > GetAudioDirectPlayMethods ( MediaSourceInfo item , MediaStream audioStream , AudioOptions options )
2015-03-12 18:55:22 -07:00
{
DirectPlayProfile directPlayProfile = null ;
foreach ( DirectPlayProfile i in options . Profile . DirectPlayProfiles )
{
if ( i . Type = = DlnaProfileType . Audio & & IsAudioDirectPlaySupported ( i , item , audioStream ) )
{
directPlayProfile = i ;
break ;
}
}
List < PlayMethod > playMethods = new List < PlayMethod > ( ) ;
if ( directPlayProfile ! = null )
{
// While options takes the network and other factors into account. Only applies to direct stream
2017-03-24 08:03:49 -07:00
if ( item . SupportsDirectStream & & IsAudioEligibleForDirectPlay ( item , options . GetMaxBitrate ( true ) , PlayMethod . DirectStream ) & & options . EnableDirectStream )
2015-03-12 18:55:22 -07:00
{
playMethods . Add ( PlayMethod . DirectStream ) ;
}
2015-07-10 11:30:42 -07:00
2015-03-12 18:55:22 -07:00
// The profile describes what the device supports
// If device requirements are satisfied then allow both direct stream and direct play
2015-07-10 11:30:42 -07:00
if ( item . SupportsDirectPlay & &
2017-03-24 08:03:49 -07:00
IsAudioEligibleForDirectPlay ( item , GetBitrateForDirectPlayCheck ( item , options , true ) , PlayMethod . DirectPlay ) & & options . EnableDirectPlay )
2015-03-12 18:55:22 -07:00
{
playMethods . Add ( PlayMethod . DirectPlay ) ;
}
}
2015-07-10 11:30:42 -07:00
else
{
2015-10-04 20:24:24 -07:00
_logger . Info ( "Profile: {0}, No direct play profiles found for Path: {1}" ,
2015-07-10 11:30:42 -07:00
options . Profile . Name ? ? "Unknown Profile" ,
2015-09-20 10:28:32 -07:00
item . Path ? ? "Unknown path" ) ;
2015-07-10 11:30:42 -07:00
}
2015-03-12 18:55:22 -07:00
return playMethods ;
}
2015-03-31 09:24:16 -07:00
private int? GetDefaultSubtitleStreamIndex ( MediaSourceInfo item , SubtitleProfile [ ] subtitleProfiles )
{
int highestScore = - 1 ;
foreach ( MediaStream stream in item . MediaStreams )
{
if ( stream . Type = = MediaStreamType . Subtitle & & stream . Score . HasValue )
{
if ( stream . Score . Value > highestScore )
{
highestScore = stream . Score . Value ;
}
2015-09-20 10:28:32 -07:00
}
2015-03-31 09:24:16 -07:00
}
List < MediaStream > topStreams = new List < MediaStream > ( ) ;
foreach ( MediaStream stream in item . MediaStreams )
{
if ( stream . Type = = MediaStreamType . Subtitle & & stream . Score . HasValue & & stream . Score . Value = = highestScore )
{
topStreams . Add ( stream ) ;
}
}
// If multiple streams have an equal score, try to pick the most efficient one
if ( topStreams . Count > 1 )
{
foreach ( MediaStream stream in topStreams )
{
foreach ( SubtitleProfile profile in subtitleProfiles )
{
if ( profile . Method = = SubtitleDeliveryMethod . External & & StringHelper . EqualsIgnoreCase ( profile . Format , stream . Codec ) )
{
return stream . Index ;
}
}
}
}
// If no optimization panned out, just use the original default
return item . DefaultSubtitleStreamIndex ;
}
2014-04-01 15:23:07 -07:00
private StreamInfo BuildVideoItem ( MediaSourceInfo item , VideoOptions options )
{
2014-05-08 13:09:53 -07:00
StreamInfo playlistItem = new StreamInfo
2014-04-01 15:23:07 -07:00
{
ItemId = options . ItemId ,
MediaType = DlnaProfileType . Video ,
2014-04-18 10:16:25 -07:00
MediaSource = item ,
2015-01-01 22:36:27 -07:00
RunTimeTicks = item . RunTimeTicks ,
2015-02-04 22:29:37 -07:00
Context = options . Context ,
DeviceProfile = options . Profile
2014-04-01 15:23:07 -07:00
} ;
2015-03-31 09:24:16 -07:00
playlistItem . SubtitleStreamIndex = options . SubtitleStreamIndex ? ? GetDefaultSubtitleStreamIndex ( item , options . Profile . SubtitleProfiles ) ;
2014-07-18 12:07:28 -07:00
MediaStream subtitleStream = playlistItem . SubtitleStreamIndex . HasValue ? item . GetMediaStream ( MediaStreamType . Subtitle , playlistItem . SubtitleStreamIndex . Value ) : null ;
2015-03-23 10:19:21 -07:00
MediaStream audioStream = item . GetDefaultAudioStream ( options . AudioStreamIndex ? ? item . DefaultAudioStreamIndex ) ;
2015-03-31 12:33:38 -07:00
int? audioStreamIndex = null ;
if ( audioStream ! = null )
{
audioStreamIndex = audioStream . Index ;
}
2015-03-23 10:19:21 -07:00
2014-05-09 16:08:08 -07:00
MediaStream videoStream = item . VideoStream ;
2014-04-01 15:23:07 -07:00
2015-03-23 13:06:06 -07:00
// TODO: This doesn't accout for situation of device being able to handle media bitrate, but wifi connection not fast enough
2016-08-22 22:08:07 -07:00
bool isEligibleForDirectPlay = options . EnableDirectPlay & & ( options . ForceDirectPlay | | IsEligibleForDirectPlay ( item , GetBitrateForDirectPlayCheck ( item , options , true ) , subtitleStream , options , PlayMethod . DirectPlay ) ) ;
bool isEligibleForDirectStream = options . EnableDirectStream & & ( options . ForceDirectStream | | IsEligibleForDirectPlay ( item , options . GetMaxBitrate ( false ) , subtitleStream , options , PlayMethod . DirectStream ) ) ;
2014-04-20 18:36:12 -07:00
2015-10-04 20:24:24 -07:00
_logger . Info ( "Profile: {0}, Path: {1}, isEligibleForDirectPlay: {2}, isEligibleForDirectStream: {3}" ,
2015-04-04 12:35:29 -07:00
options . Profile . Name ? ? "Unknown Profile" ,
item . Path ? ? "Unknown path" ,
isEligibleForDirectPlay ,
isEligibleForDirectStream ) ;
2015-03-23 13:06:06 -07:00
if ( isEligibleForDirectPlay | | isEligibleForDirectStream )
2014-04-01 15:23:07 -07:00
{
// See if it can be direct played
2016-12-23 10:57:47 -07:00
PlayMethod ? directPlay = GetVideoDirectPlayProfile ( options , item , videoStream , audioStream , isEligibleForDirectPlay , isEligibleForDirectStream ) ;
2014-04-01 15:23:07 -07:00
if ( directPlay ! = null )
{
2014-10-11 18:46:02 -07:00
playlistItem . PlayMethod = directPlay . Value ;
2014-04-23 22:08:10 -07:00
playlistItem . Container = item . Container ;
2014-04-01 15:23:07 -07:00
2014-07-18 12:07:28 -07:00
if ( subtitleStream ! = null )
{
2017-01-29 13:00:29 -07:00
SubtitleProfile subtitleProfile = GetSubtitleProfile ( subtitleStream , options . Profile . SubtitleProfiles , directPlay . Value , null , null ) ;
2015-01-14 22:54:42 -07:00
playlistItem . SubtitleDeliveryMethod = subtitleProfile . Method ;
playlistItem . SubtitleFormat = subtitleProfile . Format ;
2014-07-18 12:07:28 -07:00
}
2014-04-23 22:08:10 -07:00
return playlistItem ;
2014-04-01 15:23:07 -07:00
}
}
// Can't direct play, find the transcoding profile
2014-05-08 14:23:24 -07:00
TranscodingProfile transcodingProfile = null ;
foreach ( TranscodingProfile i in options . Profile . TranscodingProfiles )
{
2014-07-16 20:17:14 -07:00
if ( i . Type = = playlistItem . MediaType & & i . Context = = options . Context )
2014-05-08 14:23:24 -07:00
{
transcodingProfile = i ;
break ;
}
}
2014-04-01 15:23:07 -07:00
if ( transcodingProfile ! = null )
{
2015-03-02 11:48:21 -07:00
if ( ! item . SupportsTranscoding )
{
return null ;
}
2014-07-18 12:07:28 -07:00
if ( subtitleStream ! = null )
{
2017-01-29 13:00:29 -07:00
SubtitleProfile subtitleProfile = GetSubtitleProfile ( subtitleStream , options . Profile . SubtitleProfiles , PlayMethod . Transcode , transcodingProfile . Protocol , transcodingProfile . Container ) ;
2015-01-14 22:54:42 -07:00
playlistItem . SubtitleDeliveryMethod = subtitleProfile . Method ;
playlistItem . SubtitleFormat = subtitleProfile . Format ;
2017-01-29 13:00:29 -07:00
playlistItem . SubtitleCodecs = new [ ] { subtitleProfile . Format } ;
2014-07-18 12:07:28 -07:00
}
2014-08-05 16:59:24 -07:00
playlistItem . PlayMethod = PlayMethod . Transcode ;
2014-04-01 15:23:07 -07:00
playlistItem . Container = transcodingProfile . Container ;
2014-04-18 10:16:25 -07:00
playlistItem . EstimateContentLength = transcodingProfile . EstimateContentLength ;
2014-04-17 22:03:01 -07:00
playlistItem . TranscodeSeekInfo = transcodingProfile . TranscodeSeekInfo ;
2016-03-06 21:56:45 -07:00
2016-06-26 09:21:10 -07:00
playlistItem . AudioCodecs = transcodingProfile . AudioCodec . Split ( ',' ) ;
2016-03-06 21:56:45 -07:00
2017-03-15 12:57:18 -07:00
playlistItem . VideoCodecs = transcodingProfile . VideoCodec . Split ( ',' ) ;
2016-02-19 23:57:17 -07:00
playlistItem . CopyTimestamps = transcodingProfile . CopyTimestamps ;
2016-07-13 12:16:51 -07:00
playlistItem . EnableSubtitlesInManifest = transcodingProfile . EnableSubtitlesInManifest ;
2016-05-13 22:40:01 -07:00
2017-03-31 12:05:19 -07:00
playlistItem . BreakOnNonKeyFrames = transcodingProfile . BreakOnNonKeyFrames ;
2017-03-17 13:23:34 -07:00
if ( transcodingProfile . MinSegments > 0 )
{
playlistItem . MinSegments = transcodingProfile . MinSegments ;
}
if ( transcodingProfile . SegmentLength > 0 )
{
playlistItem . SegmentLength = transcodingProfile . SegmentLength ;
}
2016-05-13 22:40:01 -07:00
if ( ! string . IsNullOrEmpty ( transcodingProfile . MaxAudioChannels ) )
{
int transcodingMaxAudioChannels ;
2016-10-21 19:08:34 -07:00
if ( int . TryParse ( transcodingProfile . MaxAudioChannels , NumberStyles . Any , CultureInfo . InvariantCulture , out transcodingMaxAudioChannels ) )
2016-05-13 22:40:01 -07:00
{
playlistItem . TranscodingMaxAudioChannels = transcodingMaxAudioChannels ;
}
}
2015-03-26 09:58:02 -07:00
playlistItem . SubProtocol = transcodingProfile . Protocol ;
2014-07-18 12:07:28 -07:00
playlistItem . AudioStreamIndex = audioStreamIndex ;
2016-07-09 10:39:04 -07:00
ConditionProcessor conditionProcessor = new ConditionProcessor ( ) ;
2014-04-01 15:23:07 -07:00
2014-05-09 16:08:08 -07:00
List < ProfileCondition > videoTranscodingConditions = new List < ProfileCondition > ( ) ;
foreach ( CodecProfile i in options . Profile . CodecProfiles )
{
2015-05-06 20:11:51 -07:00
if ( i . Type = = CodecType . Video & & i . ContainsCodec ( transcodingProfile . VideoCodec , transcodingProfile . Container ) )
2014-05-09 16:08:08 -07:00
{
2016-07-09 10:39:04 -07:00
bool applyConditions = true ;
foreach ( ProfileCondition applyCondition in i . ApplyConditions )
2014-06-30 21:06:28 -07:00
{
2016-07-09 10:39:04 -07:00
bool? isSecondaryAudio = audioStream = = null ? null : item . IsSecondaryAudio ( audioStream ) ;
int? inputAudioBitrate = audioStream = = null ? null : audioStream . BitRate ;
int? audioChannels = audioStream = = null ? null : audioStream . Channels ;
string audioProfile = audioStream = = null ? null : audioStream . Profile ;
2017-05-13 12:31:25 -07:00
int? inputAudioSampleRate = audioStream = = null ? null : audioStream . SampleRate ;
2016-07-09 10:39:04 -07:00
2017-05-13 12:31:25 -07:00
if ( ! conditionProcessor . IsVideoAudioConditionSatisfied ( applyCondition , audioChannels , inputAudioBitrate , inputAudioSampleRate , audioProfile , isSecondaryAudio ) )
2016-07-09 10:39:04 -07:00
{
LogConditionFailure ( options . Profile , "AudioCodecProfile" , applyCondition , item ) ;
applyConditions = false ;
break ;
}
}
if ( applyConditions )
{
foreach ( ProfileCondition c in i . Conditions )
{
videoTranscodingConditions . Add ( c ) ;
}
break ;
2014-06-30 21:06:28 -07:00
}
2014-05-09 16:08:08 -07:00
}
}
2014-04-01 15:23:07 -07:00
ApplyTranscodingConditions ( playlistItem , videoTranscodingConditions ) ;
2014-05-09 16:08:08 -07:00
List < ProfileCondition > audioTranscodingConditions = new List < ProfileCondition > ( ) ;
foreach ( CodecProfile i in options . Profile . CodecProfiles )
{
2016-06-26 09:21:10 -07:00
if ( i . Type = = CodecType . VideoAudio & & i . ContainsCodec ( playlistItem . TargetAudioCodec , transcodingProfile . Container ) )
2014-05-09 16:08:08 -07:00
{
2016-07-09 10:39:04 -07:00
bool applyConditions = true ;
foreach ( ProfileCondition applyCondition in i . ApplyConditions )
2014-06-30 21:06:28 -07:00
{
2016-07-09 10:39:04 -07:00
int? width = videoStream = = null ? null : videoStream . Width ;
int? height = videoStream = = null ? null : videoStream . Height ;
int? bitDepth = videoStream = = null ? null : videoStream . BitDepth ;
int? videoBitrate = videoStream = = null ? null : videoStream . BitRate ;
double? videoLevel = videoStream = = null ? null : videoStream . Level ;
string videoProfile = videoStream = = null ? null : videoStream . Profile ;
float? videoFramerate = videoStream = = null ? null : videoStream . AverageFrameRate ? ? videoStream . AverageFrameRate ;
bool? isAnamorphic = videoStream = = null ? null : videoStream . IsAnamorphic ;
string videoCodecTag = videoStream = = null ? null : videoStream . CodecTag ;
2016-10-02 23:28:45 -07:00
bool? isAvc = videoStream = = null ? null : videoStream . IsAVC ;
2016-07-09 10:39:04 -07:00
TransportStreamTimestamp ? timestamp = videoStream = = null ? TransportStreamTimestamp . None : item . Timestamp ;
int? packetLength = videoStream = = null ? null : videoStream . PacketLength ;
int? refFrames = videoStream = = null ? null : videoStream . RefFrames ;
int? numAudioStreams = item . GetStreamCount ( MediaStreamType . Audio ) ;
int? numVideoStreams = item . GetStreamCount ( MediaStreamType . Video ) ;
2016-10-02 23:28:45 -07:00
if ( ! conditionProcessor . IsVideoConditionSatisfied ( applyCondition , width , height , bitDepth , videoBitrate , videoProfile , videoLevel , videoFramerate , packetLength , timestamp , isAnamorphic , refFrames , numVideoStreams , numAudioStreams , videoCodecTag , isAvc ) )
2016-07-09 10:39:04 -07:00
{
LogConditionFailure ( options . Profile , "VideoCodecProfile" , applyCondition , item ) ;
applyConditions = false ;
break ;
}
}
if ( applyConditions )
{
foreach ( ProfileCondition c in i . Conditions )
{
audioTranscodingConditions . Add ( c ) ;
}
break ;
2014-06-30 21:06:28 -07:00
}
2014-05-09 16:08:08 -07:00
}
}
2014-04-01 15:23:07 -07:00
ApplyTranscodingConditions ( playlistItem , audioTranscodingConditions ) ;
2014-04-06 10:53:23 -07:00
// Honor requested max channels
if ( options . MaxAudioChannels . HasValue )
{
2014-05-08 13:09:53 -07:00
int currentValue = playlistItem . MaxAudioChannels ? ? options . MaxAudioChannels . Value ;
2014-04-06 10:53:23 -07:00
playlistItem . MaxAudioChannels = Math . Min ( options . MaxAudioChannels . Value , currentValue ) ;
}
2016-08-22 22:08:07 -07:00
int audioBitrate = GetAudioBitrate ( playlistItem . SubProtocol , options . GetMaxBitrate ( false ) , playlistItem . TargetAudioChannels , playlistItem . TargetAudioCodec , audioStream ) ;
2015-04-20 11:04:02 -07:00
playlistItem . AudioBitrate = Math . Min ( playlistItem . AudioBitrate ? ? audioBitrate , audioBitrate ) ;
2014-04-06 10:53:23 -07:00
2017-01-02 22:15:59 -07:00
var maxBitrateSetting = options . GetMaxBitrate ( false ) ;
2014-04-06 10:53:23 -07:00
// Honor max rate
2014-04-20 18:36:12 -07:00
if ( maxBitrateSetting . HasValue )
2014-04-06 10:53:23 -07:00
{
2017-01-02 22:15:59 -07:00
var videoBitrate = maxBitrateSetting . Value ;
2014-04-06 10:53:23 -07:00
if ( playlistItem . AudioBitrate . HasValue )
{
videoBitrate - = playlistItem . AudioBitrate . Value ;
}
2015-04-20 11:04:02 -07:00
// Make sure the video bitrate is lower than bitrate settings but at least 64k
2017-01-02 22:15:59 -07:00
long currentValue = playlistItem . VideoBitrate ? ? videoBitrate ;
var longBitrate = Math . Max ( Math . Min ( videoBitrate , currentValue ) , 64000 ) ;
playlistItem . VideoBitrate = longBitrate > int . MaxValue ? int . MaxValue : Convert . ToInt32 ( longBitrate ) ;
2014-04-06 10:53:23 -07:00
}
2014-07-18 12:07:28 -07:00
}
2014-06-23 09:05:19 -07:00
2014-07-18 12:07:28 -07:00
return playlistItem ;
}
2017-02-18 01:32:17 -07:00
private int GetDefaultAudioBitrateIfUnknown ( MediaStream audioStream )
{
if ( ( audioStream . Channels ? ? 0 ) > = 6 )
{
return 384000 ;
}
return 192000 ;
}
2017-01-02 22:15:59 -07:00
private int GetAudioBitrate ( string subProtocol , long? maxTotalBitrate , int? targetAudioChannels , string targetAudioCodec , MediaStream audioStream )
2014-07-18 12:07:28 -07:00
{
2017-02-18 01:32:17 -07:00
int defaultBitrate = audioStream = = null ? 192000 : audioStream . BitRate ? ? GetDefaultAudioBitrateIfUnknown ( audioStream ) ;
2016-08-24 14:06:04 -07:00
// Reduce the bitrate if we're downmixing
if ( targetAudioChannels . HasValue & & audioStream ! = null & & audioStream . Channels . HasValue & & targetAudioChannels . Value < audioStream . Channels . Value )
2016-04-12 10:37:58 -07:00
{
2016-08-24 14:06:04 -07:00
defaultBitrate = StringHelper . EqualsIgnoreCase ( targetAudioCodec , "ac3" ) ? 192000 : 128000 ;
2016-07-29 21:21:26 -07:00
}
2015-04-24 20:30:44 -07:00
2016-09-30 20:33:26 -07:00
if ( StringHelper . EqualsIgnoreCase ( subProtocol , "hls" ) )
2014-07-18 12:07:28 -07:00
{
2016-09-30 20:33:26 -07:00
defaultBitrate = Math . Min ( 384000 , defaultBitrate ) ;
}
else
{
defaultBitrate = Math . Min ( 448000 , defaultBitrate ) ;
2015-04-24 20:30:44 -07:00
}
int encoderAudioBitrateLimit = int . MaxValue ;
if ( audioStream ! = null )
{
// Seeing webm encoding failures when source has 1 audio channel and 22k bitrate.
// Any attempts to transcode over 64k will fail
if ( audioStream . Channels . HasValue & &
audioStream . Channels . Value = = 1 )
{
if ( ( audioStream . BitRate ? ? 0 ) < 64000 )
{
encoderAudioBitrateLimit = 64000 ;
}
2014-06-23 09:05:19 -07:00
}
2014-04-01 15:23:07 -07:00
}
2016-09-30 20:33:26 -07:00
if ( maxTotalBitrate . HasValue )
{
if ( maxTotalBitrate . Value < 640000 )
{
defaultBitrate = Math . Min ( 128000 , defaultBitrate ) ;
}
}
2015-04-24 20:30:44 -07:00
return Math . Min ( defaultBitrate , encoderAudioBitrateLimit ) ;
2014-04-01 15:23:07 -07:00
}
2016-07-24 22:12:38 -07:00
private PlayMethod ? GetVideoDirectPlayProfile ( VideoOptions options ,
2014-04-23 22:08:10 -07:00
MediaSourceInfo mediaSource ,
MediaStream videoStream ,
2015-03-23 13:06:06 -07:00
MediaStream audioStream ,
bool isEligibleForDirectPlay ,
2016-12-23 10:57:47 -07:00
bool isEligibleForDirectStream )
2014-04-23 22:08:10 -07:00
{
2016-07-24 22:12:38 -07:00
DeviceProfile profile = options . Profile ;
if ( options . ForceDirectPlay )
{
return PlayMethod . DirectPlay ;
}
if ( options . ForceDirectStream )
{
return PlayMethod . DirectStream ;
}
2015-11-02 10:25:01 -07:00
if ( videoStream = = null )
{
_logger . Info ( "Profile: {0}, Cannot direct stream with no known video stream. Path: {1}" ,
profile . Name ? ? "Unknown Profile" ,
mediaSource . Path ? ? "Unknown path" ) ;
return null ;
}
2014-04-23 22:08:10 -07:00
// See if it can be direct played
2014-05-08 14:23:24 -07:00
DirectPlayProfile directPlay = null ;
foreach ( DirectPlayProfile i in profile . DirectPlayProfiles )
{
if ( i . Type = = DlnaProfileType . Video & & IsVideoDirectPlaySupported ( i , mediaSource , videoStream , audioStream ) )
{
directPlay = i ;
break ;
}
}
2014-04-23 22:08:10 -07:00
if ( directPlay = = null )
{
2015-10-04 20:24:24 -07:00
_logger . Info ( "Profile: {0}, No direct play profiles found for Path: {1}" ,
2015-04-04 12:35:29 -07:00
profile . Name ? ? "Unknown Profile" ,
2015-09-20 10:28:32 -07:00
mediaSource . Path ? ? "Unknown path" ) ;
2014-04-23 22:08:10 -07:00
return null ;
}
2014-05-08 13:09:53 -07:00
string container = mediaSource . Container ;
2014-04-23 22:08:10 -07:00
2014-05-09 12:43:06 -07:00
List < ProfileCondition > conditions = new List < ProfileCondition > ( ) ;
foreach ( ContainerProfile i in profile . ContainerProfiles )
{
if ( i . Type = = DlnaProfileType . Video & &
2016-12-13 10:04:37 -07:00
i . ContainsContainer ( container ) )
2014-05-09 12:43:06 -07:00
{
2014-09-07 08:52:25 -07:00
foreach ( ProfileCondition c in i . Conditions )
2014-06-30 21:06:28 -07:00
{
conditions . Add ( c ) ;
}
2014-05-09 12:43:06 -07:00
}
}
2014-04-23 22:08:10 -07:00
2014-05-08 13:09:53 -07:00
ConditionProcessor conditionProcessor = new ConditionProcessor ( ) ;
2014-04-23 22:08:10 -07:00
2014-05-08 13:09:53 -07:00
int? width = videoStream = = null ? null : videoStream . Width ;
int? height = videoStream = = null ? null : videoStream . Height ;
int? bitDepth = videoStream = = null ? null : videoStream . BitDepth ;
int? videoBitrate = videoStream = = null ? null : videoStream . BitRate ;
double? videoLevel = videoStream = = null ? null : videoStream . Level ;
string videoProfile = videoStream = = null ? null : videoStream . Profile ;
float? videoFramerate = videoStream = = null ? null : videoStream . AverageFrameRate ? ? videoStream . AverageFrameRate ;
2014-06-22 09:25:47 -07:00
bool? isAnamorphic = videoStream = = null ? null : videoStream . IsAnamorphic ;
2015-10-19 09:05:03 -07:00
string videoCodecTag = videoStream = = null ? null : videoStream . CodecTag ;
2016-10-02 23:28:45 -07:00
bool? isAvc = videoStream = = null ? null : videoStream . IsAVC ;
2014-04-23 22:08:10 -07:00
2014-05-08 13:09:53 -07:00
int? audioBitrate = audioStream = = null ? null : audioStream . BitRate ;
int? audioChannels = audioStream = = null ? null : audioStream . Channels ;
string audioProfile = audioStream = = null ? null : audioStream . Profile ;
2017-05-13 12:31:25 -07:00
int? audioSampleRate = audioStream = = null ? null : audioStream . SampleRate ;
2014-04-23 22:08:10 -07:00
2014-05-08 13:09:53 -07:00
TransportStreamTimestamp ? timestamp = videoStream = = null ? TransportStreamTimestamp . None : mediaSource . Timestamp ;
int? packetLength = videoStream = = null ? null : videoStream . PacketLength ;
2014-09-08 18:15:31 -07:00
int? refFrames = videoStream = = null ? null : videoStream . RefFrames ;
2014-04-23 22:08:10 -07:00
2015-03-30 09:16:34 -07:00
int? numAudioStreams = mediaSource . GetStreamCount ( MediaStreamType . Audio ) ;
int? numVideoStreams = mediaSource . GetStreamCount ( MediaStreamType . Video ) ;
2014-04-23 22:08:10 -07:00
// Check container conditions
2014-05-09 12:43:06 -07:00
foreach ( ProfileCondition i in conditions )
2014-04-23 22:08:10 -07:00
{
2016-10-02 23:28:45 -07:00
if ( ! conditionProcessor . IsVideoConditionSatisfied ( i , width , height , bitDepth , videoBitrate , videoProfile , videoLevel , videoFramerate , packetLength , timestamp , isAnamorphic , refFrames , numVideoStreams , numAudioStreams , videoCodecTag , isAvc ) )
2014-05-09 12:43:06 -07:00
{
2015-04-14 12:11:29 -07:00
LogConditionFailure ( profile , "VideoContainerProfile" , i , mediaSource ) ;
2015-04-04 12:35:29 -07:00
2014-05-09 12:43:06 -07:00
return null ;
}
2014-04-23 22:08:10 -07:00
}
2014-05-08 13:09:53 -07:00
string videoCodec = videoStream = = null ? null : videoStream . Codec ;
2014-04-23 22:08:10 -07:00
if ( string . IsNullOrEmpty ( videoCodec ) )
{
2015-10-04 20:24:24 -07:00
_logger . Info ( "Profile: {0}, DirectPlay=false. Reason=Unknown video codec. Path: {1}" ,
2015-04-04 12:35:29 -07:00
profile . Name ? ? "Unknown Profile" ,
mediaSource . Path ? ? "Unknown path" ) ;
2014-04-23 22:08:10 -07:00
return null ;
}
2014-05-09 12:43:06 -07:00
conditions = new List < ProfileCondition > ( ) ;
foreach ( CodecProfile i in profile . CodecProfiles )
{
2015-05-06 20:11:51 -07:00
if ( i . Type = = CodecType . Video & & i . ContainsCodec ( videoCodec , container ) )
2014-06-30 21:06:28 -07:00
{
2016-07-09 10:39:04 -07:00
bool applyConditions = true ;
foreach ( ProfileCondition applyCondition in i . ApplyConditions )
2014-06-30 21:06:28 -07:00
{
2016-10-02 23:28:45 -07:00
if ( ! conditionProcessor . IsVideoConditionSatisfied ( applyCondition , width , height , bitDepth , videoBitrate , videoProfile , videoLevel , videoFramerate , packetLength , timestamp , isAnamorphic , refFrames , numVideoStreams , numAudioStreams , videoCodecTag , isAvc ) )
2016-07-09 10:39:04 -07:00
{
LogConditionFailure ( profile , "VideoCodecProfile" , applyCondition , mediaSource ) ;
applyConditions = false ;
break ;
}
}
if ( applyConditions )
{
foreach ( ProfileCondition c in i . Conditions )
{
conditions . Add ( c ) ;
}
2014-06-30 21:06:28 -07:00
}
}
2014-05-09 12:43:06 -07:00
}
2014-04-23 22:08:10 -07:00
2014-05-09 12:43:06 -07:00
foreach ( ProfileCondition i in conditions )
2014-04-23 22:08:10 -07:00
{
2016-10-02 23:28:45 -07:00
if ( ! conditionProcessor . IsVideoConditionSatisfied ( i , width , height , bitDepth , videoBitrate , videoProfile , videoLevel , videoFramerate , packetLength , timestamp , isAnamorphic , refFrames , numVideoStreams , numAudioStreams , videoCodecTag , isAvc ) )
2014-05-09 12:43:06 -07:00
{
2015-04-14 12:11:29 -07:00
LogConditionFailure ( profile , "VideoCodecProfile" , i , mediaSource ) ;
2015-04-04 12:35:29 -07:00
2014-05-09 12:43:06 -07:00
return null ;
}
2014-04-23 22:08:10 -07:00
}
if ( audioStream ! = null )
{
2014-05-08 13:09:53 -07:00
string audioCodec = audioStream . Codec ;
2014-04-23 22:08:10 -07:00
if ( string . IsNullOrEmpty ( audioCodec ) )
{
2015-10-04 20:24:24 -07:00
_logger . Info ( "Profile: {0}, DirectPlay=false. Reason=Unknown audio codec. Path: {1}" ,
2015-04-04 12:35:29 -07:00
profile . Name ? ? "Unknown Profile" ,
mediaSource . Path ? ? "Unknown path" ) ;
2014-04-23 22:08:10 -07:00
return null ;
}
2014-05-09 12:43:06 -07:00
conditions = new List < ProfileCondition > ( ) ;
2016-07-09 10:39:04 -07:00
bool? isSecondaryAudio = audioStream = = null ? null : mediaSource . IsSecondaryAudio ( audioStream ) ;
2014-05-09 12:43:06 -07:00
foreach ( CodecProfile i in profile . CodecProfiles )
{
2015-05-06 20:11:51 -07:00
if ( i . Type = = CodecType . VideoAudio & & i . ContainsCodec ( audioCodec , container ) )
2014-06-30 21:06:28 -07:00
{
2016-07-09 10:39:04 -07:00
bool applyConditions = true ;
foreach ( ProfileCondition applyCondition in i . ApplyConditions )
2014-06-30 21:06:28 -07:00
{
2017-05-13 12:31:25 -07:00
if ( ! conditionProcessor . IsVideoAudioConditionSatisfied ( applyCondition , audioChannels , audioBitrate , audioSampleRate , audioProfile , isSecondaryAudio ) )
2016-07-09 10:39:04 -07:00
{
LogConditionFailure ( profile , "VideoAudioCodecProfile" , applyCondition , mediaSource ) ;
applyConditions = false ;
break ;
}
}
if ( applyConditions )
{
foreach ( ProfileCondition c in i . Conditions )
{
conditions . Add ( c ) ;
}
2014-06-30 21:06:28 -07:00
}
}
2014-05-09 12:43:06 -07:00
}
2014-04-23 22:08:10 -07:00
2014-05-09 12:43:06 -07:00
foreach ( ProfileCondition i in conditions )
2014-04-23 22:08:10 -07:00
{
2017-05-13 12:31:25 -07:00
if ( ! conditionProcessor . IsVideoAudioConditionSatisfied ( i , audioChannels , audioBitrate , audioSampleRate , audioProfile , isSecondaryAudio ) )
2014-05-09 12:43:06 -07:00
{
2015-04-14 12:11:29 -07:00
LogConditionFailure ( profile , "VideoAudioCodecProfile" , i , mediaSource ) ;
2015-09-20 10:28:32 -07:00
2014-05-09 12:43:06 -07:00
return null ;
}
2014-04-23 22:08:10 -07:00
}
}
2015-04-17 10:46:41 -07:00
if ( isEligibleForDirectStream & & mediaSource . SupportsDirectStream )
2015-03-16 20:51:35 -07:00
{
2015-04-17 10:46:41 -07:00
return PlayMethod . DirectStream ;
2015-03-16 20:51:35 -07:00
}
2015-03-23 13:06:06 -07:00
return null ;
2014-04-23 22:08:10 -07:00
}
2015-04-14 12:11:29 -07:00
private void LogConditionFailure ( DeviceProfile profile , string type , ProfileCondition condition , MediaSourceInfo mediaSource )
{
2015-10-04 20:24:24 -07:00
_logger . Info ( "Profile: {0}, DirectPlay=false. Reason={1}.{2} Condition: {3}. ConditionValue: {4}. IsRequired: {5}. Path: {6}" ,
2015-04-14 12:11:29 -07:00
type ,
profile . Name ? ? "Unknown Profile" ,
condition . Property ,
condition . Condition ,
condition . Value ? ? string . Empty ,
condition . IsRequired ,
mediaSource . Path ? ? "Unknown path" ) ;
}
2014-07-18 12:07:28 -07:00
private bool IsEligibleForDirectPlay ( MediaSourceInfo item ,
2017-01-02 22:15:59 -07:00
long? maxBitrate ,
2014-07-18 12:07:28 -07:00
MediaStream subtitleStream ,
2015-07-13 14:26:11 -07:00
VideoOptions options ,
PlayMethod playMethod )
2014-04-01 15:23:07 -07:00
{
2014-07-18 12:07:28 -07:00
if ( subtitleStream ! = null )
2014-04-01 15:23:07 -07:00
{
2017-01-29 13:00:29 -07:00
SubtitleProfile subtitleProfile = GetSubtitleProfile ( subtitleStream , options . Profile . SubtitleProfiles , playMethod , null , null ) ;
2014-07-18 12:07:28 -07:00
2015-01-14 22:54:42 -07:00
if ( subtitleProfile . Method ! = SubtitleDeliveryMethod . External & & subtitleProfile . Method ! = SubtitleDeliveryMethod . Embed )
2014-07-18 12:07:28 -07:00
{
2015-10-04 20:24:24 -07:00
_logger . Info ( "Not eligible for {0} due to unsupported subtitles" , playMethod ) ;
2014-07-18 12:07:28 -07:00
return false ;
}
2014-04-01 15:23:07 -07:00
}
2017-03-24 08:03:49 -07:00
return IsAudioEligibleForDirectPlay ( item , maxBitrate , playMethod ) ;
2014-04-06 10:53:23 -07:00
}
2017-01-29 13:00:29 -07:00
public static SubtitleProfile GetSubtitleProfile ( MediaStream subtitleStream , SubtitleProfile [ ] subtitleProfiles , PlayMethod playMethod , string transcodingSubProtocol , string transcodingContainer )
2014-07-18 12:07:28 -07:00
{
2017-01-29 13:00:29 -07:00
if ( ! subtitleStream . IsExternal & & ( playMethod ! = PlayMethod . Transcode | | ! string . Equals ( transcodingSubProtocol , "hls" , StringComparison . OrdinalIgnoreCase ) ) )
2015-07-14 09:39:34 -07:00
{
2017-01-29 13:00:29 -07:00
// Look for supported embedded subs of the same format
2015-07-14 09:39:34 -07:00
foreach ( SubtitleProfile profile in subtitleProfiles )
{
if ( ! profile . SupportsLanguage ( subtitleStream . Language ) )
{
continue ;
}
2015-07-30 19:34:15 -07:00
if ( profile . Method ! = SubtitleDeliveryMethod . Embed )
{
continue ;
}
2017-01-29 13:00:29 -07:00
if ( playMethod = = PlayMethod . Transcode & & ! IsSubtitleEmbedSupported ( subtitleStream , profile , transcodingSubProtocol , transcodingContainer ) )
{
continue ;
}
2015-07-30 19:34:15 -07:00
if ( subtitleStream . IsTextSubtitleStream = = MediaStream . IsTextFormat ( profile . Format ) & & StringHelper . EqualsIgnoreCase ( profile . Format , subtitleStream . Codec ) )
2015-07-14 09:39:34 -07:00
{
return profile ;
}
}
2017-01-29 13:00:29 -07:00
// Look for supported embedded subs of a convertible format
foreach ( SubtitleProfile profile in subtitleProfiles )
{
if ( ! profile . SupportsLanguage ( subtitleStream . Language ) )
{
continue ;
}
if ( profile . Method ! = SubtitleDeliveryMethod . Embed )
{
continue ;
}
if ( playMethod = = PlayMethod . Transcode & & ! IsSubtitleEmbedSupported ( subtitleStream , profile , transcodingSubProtocol , transcodingContainer ) )
{
continue ;
}
if ( subtitleStream . IsTextSubtitleStream & & subtitleStream . SupportsSubtitleConversionTo ( profile . Format ) )
{
return profile ;
}
}
2015-07-14 09:39:34 -07:00
}
2016-04-02 16:39:08 -07:00
// Look for an external or hls profile that matches the stream type (text/graphical) and doesn't require conversion
2016-04-02 18:19:27 -07:00
return GetExternalSubtitleProfile ( subtitleStream , subtitleProfiles , playMethod , false ) ? ? GetExternalSubtitleProfile ( subtitleStream , subtitleProfiles , playMethod , true ) ? ? new SubtitleProfile
2016-04-02 16:39:08 -07:00
{
Method = SubtitleDeliveryMethod . Encode ,
Format = subtitleStream . Codec
} ;
}
2017-01-29 13:00:29 -07:00
private static bool IsSubtitleEmbedSupported ( MediaStream subtitleStream , SubtitleProfile subtitleProfile , string transcodingSubProtocol , string transcodingContainer )
{
if ( string . Equals ( transcodingContainer , "ts" , StringComparison . OrdinalIgnoreCase ) )
{
return false ;
}
if ( string . Equals ( transcodingContainer , "mpegts" , StringComparison . OrdinalIgnoreCase ) )
{
return false ;
}
if ( string . Equals ( transcodingContainer , "mp4" , StringComparison . OrdinalIgnoreCase ) )
{
return false ;
}
if ( string . Equals ( transcodingContainer , "mkv" , StringComparison . OrdinalIgnoreCase ) )
{
return true ;
}
return false ;
}
2016-04-02 16:39:08 -07:00
private static SubtitleProfile GetExternalSubtitleProfile ( MediaStream subtitleStream , SubtitleProfile [ ] subtitleProfiles , PlayMethod playMethod , bool allowConversion )
{
2015-03-23 10:19:21 -07:00
foreach ( SubtitleProfile profile in subtitleProfiles )
2015-09-20 10:28:32 -07:00
{
2016-03-06 14:31:56 -07:00
if ( profile . Method ! = SubtitleDeliveryMethod . External & & profile . Method ! = SubtitleDeliveryMethod . Hls )
2015-09-20 10:28:32 -07:00
{
continue ;
}
2015-03-31 09:24:16 -07:00
2016-03-07 11:50:58 -07:00
if ( profile . Method = = SubtitleDeliveryMethod . Hls & & playMethod ! = PlayMethod . Transcode )
{
continue ;
}
2015-03-28 13:22:27 -07:00
if ( ! profile . SupportsLanguage ( subtitleStream . Language ) )
{
continue ;
}
2016-03-06 14:31:56 -07:00
if ( ( profile . Method = = SubtitleDeliveryMethod . External & & subtitleStream . IsTextSubtitleStream = = MediaStream . IsTextFormat ( profile . Format ) ) | |
( profile . Method = = SubtitleDeliveryMethod . Hls & & subtitleStream . IsTextSubtitleStream ) )
2014-07-18 12:07:28 -07:00
{
2015-09-20 10:28:32 -07:00
bool requiresConversion = ! StringHelper . EqualsIgnoreCase ( subtitleStream . Codec , profile . Format ) ;
2015-09-13 14:32:02 -07:00
2016-06-24 22:16:54 -07:00
if ( ! requiresConversion )
2015-03-31 09:24:16 -07:00
{
2016-06-24 22:16:54 -07:00
return profile ;
2016-04-02 16:39:08 -07:00
}
2016-06-24 22:16:54 -07:00
if ( ! allowConversion )
2016-04-02 16:39:08 -07:00
{
2016-06-24 22:16:54 -07:00
continue ;
2016-04-02 16:39:08 -07:00
}
2016-06-24 22:16:54 -07:00
if ( subtitleStream . IsTextSubtitleStream & & subtitleStream . SupportsExternalStream & & subtitleStream . SupportsSubtitleConversionTo ( profile . Format ) )
2016-04-02 16:39:08 -07:00
{
return profile ;
2015-02-07 14:03:09 -07:00
}
2014-07-18 12:07:28 -07:00
}
2015-02-02 11:14:02 -07:00
}
2014-07-18 12:07:28 -07:00
2016-04-02 16:39:08 -07:00
return null ;
2014-07-18 12:07:28 -07:00
}
2017-03-24 08:03:49 -07:00
private bool IsAudioEligibleForDirectPlay ( MediaSourceInfo item , long? maxBitrate , PlayMethod playMethod )
2014-04-06 10:53:23 -07:00
{
2017-03-18 16:37:29 -07:00
// Don't restrict by bitrate if coming from an external domain
if ( item . IsRemote )
{
return true ;
}
2016-04-13 13:49:16 -07:00
if ( ! maxBitrate . HasValue )
2015-07-10 11:30:42 -07:00
{
2017-03-24 08:03:49 -07:00
_logger . Info ( "Cannot " + playMethod + " due to unknown supported bitrate" ) ;
2016-04-13 13:49:16 -07:00
return false ;
2015-07-10 11:30:42 -07:00
}
2016-04-13 13:49:16 -07:00
if ( ! item . Bitrate . HasValue )
{
2017-03-24 08:03:49 -07:00
_logger . Info ( "Cannot " + playMethod + " due to unknown content bitrate" ) ;
2016-04-13 13:49:16 -07:00
return false ;
}
if ( item . Bitrate . Value > maxBitrate . Value )
{
2017-03-24 08:03:49 -07:00
_logger . Info ( "Bitrate exceeds " + playMethod + " limit: media bitrate: {0}, max bitrate: {1}" , item . Bitrate . Value . ToString ( CultureInfo . InvariantCulture ) , maxBitrate . Value . ToString ( CultureInfo . InvariantCulture ) ) ;
2016-04-13 13:49:16 -07:00
return false ;
}
return true ;
2014-04-01 15:23:07 -07:00
}
private void ValidateInput ( VideoOptions options )
{
ValidateAudioInput ( options ) ;
if ( options . AudioStreamIndex . HasValue & & string . IsNullOrEmpty ( options . MediaSourceId ) )
{
throw new ArgumentException ( "MediaSourceId is required when a specific audio stream is requested" ) ;
}
if ( options . SubtitleStreamIndex . HasValue & & string . IsNullOrEmpty ( options . MediaSourceId ) )
{
throw new ArgumentException ( "MediaSourceId is required when a specific subtitle stream is requested" ) ;
}
}
private void ValidateAudioInput ( AudioOptions options )
{
if ( string . IsNullOrEmpty ( options . ItemId ) )
{
throw new ArgumentException ( "ItemId is required" ) ;
}
if ( string . IsNullOrEmpty ( options . DeviceId ) )
{
throw new ArgumentException ( "DeviceId is required" ) ;
}
if ( options . Profile = = null )
{
throw new ArgumentException ( "Profile is required" ) ;
}
if ( options . MediaSources = = null )
{
throw new ArgumentException ( "MediaSources is required" ) ;
}
}
private void ApplyTranscodingConditions ( StreamInfo item , IEnumerable < ProfileCondition > conditions )
{
2014-05-09 12:43:06 -07:00
foreach ( ProfileCondition condition in conditions )
2014-04-01 15:23:07 -07:00
{
2014-05-08 13:09:53 -07:00
string value = condition . Value ;
2014-04-01 15:23:07 -07:00
2014-05-09 12:43:06 -07:00
if ( string . IsNullOrEmpty ( value ) )
{
continue ;
}
2014-12-14 13:01:26 -07:00
// No way to express this
if ( condition . Condition = = ProfileConditionType . GreaterThanEqual )
{
continue ;
}
2014-04-01 15:23:07 -07:00
switch ( condition . Property )
{
case ProfileConditionValue . AudioBitrate :
{
int num ;
2016-10-21 19:08:34 -07:00
if ( int . TryParse ( value , NumberStyles . Any , CultureInfo . InvariantCulture , out num ) )
2014-04-01 15:23:07 -07:00
{
item . AudioBitrate = num ;
}
break ;
}
case ProfileConditionValue . AudioChannels :
{
int num ;
2016-10-21 19:08:34 -07:00
if ( int . TryParse ( value , NumberStyles . Any , CultureInfo . InvariantCulture , out num ) )
2014-04-01 15:23:07 -07:00
{
item . MaxAudioChannels = num ;
}
break ;
}
2016-11-14 00:28:20 -07:00
case ProfileConditionValue . IsAvc :
{
bool isAvc ;
if ( bool . TryParse ( value , out isAvc ) )
{
if ( isAvc & & condition . Condition = = ProfileConditionType . Equals )
{
item . RequireAvc = true ;
}
else if ( ! isAvc & & condition . Condition = = ProfileConditionType . NotEquals )
{
item . RequireAvc = true ;
}
}
break ;
}
2014-06-22 09:25:47 -07:00
case ProfileConditionValue . IsAnamorphic :
2017-03-15 12:57:18 -07:00
{
bool isAnamorphic ;
if ( bool . TryParse ( value , out isAnamorphic ) )
{
if ( isAnamorphic & & condition . Condition = = ProfileConditionType . Equals )
{
item . RequireNonAnamorphic = true ;
}
else if ( ! isAnamorphic & & condition . Condition = = ProfileConditionType . NotEquals )
{
item . RequireNonAnamorphic = true ;
}
}
break ;
}
case ProfileConditionValue . IsInterlaced :
{
bool isInterlaced ;
if ( bool . TryParse ( value , out isInterlaced ) )
{
if ( isInterlaced & & condition . Condition = = ProfileConditionType . Equals )
{
item . DeInterlace = true ;
}
else if ( ! isInterlaced & & condition . Condition = = ProfileConditionType . NotEquals )
{
item . DeInterlace = true ;
}
}
break ;
}
2014-12-14 13:01:26 -07:00
case ProfileConditionValue . AudioProfile :
2014-04-01 15:23:07 -07:00
case ProfileConditionValue . Has64BitOffsets :
2014-04-23 22:08:10 -07:00
case ProfileConditionValue . PacketLength :
2015-03-30 09:16:34 -07:00
case ProfileConditionValue . NumAudioStreams :
case ProfileConditionValue . NumVideoStreams :
case ProfileConditionValue . IsSecondaryAudio :
2014-04-23 22:08:10 -07:00
case ProfileConditionValue . VideoTimestamp :
2014-04-01 15:23:07 -07:00
{
// Not supported yet
break ;
}
2014-09-22 21:05:29 -07:00
case ProfileConditionValue . RefFrames :
{
int num ;
2016-10-21 19:08:34 -07:00
if ( int . TryParse ( value , NumberStyles . Any , CultureInfo . InvariantCulture , out num ) )
2014-09-22 21:05:29 -07:00
{
item . MaxRefFrames = num ;
}
break ;
}
case ProfileConditionValue . VideoBitDepth :
{
int num ;
2016-10-21 19:08:34 -07:00
if ( int . TryParse ( value , NumberStyles . Any , CultureInfo . InvariantCulture , out num ) )
2014-09-22 21:05:29 -07:00
{
item . MaxVideoBitDepth = num ;
}
break ;
}
2014-04-23 22:08:10 -07:00
case ProfileConditionValue . VideoProfile :
{
2014-10-19 20:04:45 -07:00
item . VideoProfile = ( value ? ? string . Empty ) . Split ( '|' ) [ 0 ] ;
2014-04-23 22:08:10 -07:00
break ;
}
2014-04-01 15:23:07 -07:00
case ProfileConditionValue . Height :
{
int num ;
2016-10-21 19:08:34 -07:00
if ( int . TryParse ( value , NumberStyles . Any , CultureInfo . InvariantCulture , out num ) )
2014-04-01 15:23:07 -07:00
{
item . MaxHeight = num ;
}
break ;
}
case ProfileConditionValue . VideoBitrate :
{
int num ;
2016-10-21 19:08:34 -07:00
if ( int . TryParse ( value , NumberStyles . Any , CultureInfo . InvariantCulture , out num ) )
2014-04-01 15:23:07 -07:00
{
item . VideoBitrate = num ;
}
break ;
}
case ProfileConditionValue . VideoFramerate :
{
2014-06-23 09:05:19 -07:00
float num ;
2016-10-23 12:47:34 -07:00
if ( float . TryParse ( value , NumberStyles . Any , CultureInfo . InvariantCulture , out num ) )
2014-04-01 15:23:07 -07:00
{
item . MaxFramerate = num ;
}
break ;
}
case ProfileConditionValue . VideoLevel :
{
int num ;
2016-10-21 19:08:34 -07:00
if ( int . TryParse ( value , NumberStyles . Any , CultureInfo . InvariantCulture , out num ) )
2014-04-01 15:23:07 -07:00
{
item . VideoLevel = num ;
}
break ;
}
case ProfileConditionValue . Width :
{
int num ;
2016-10-21 19:08:34 -07:00
if ( int . TryParse ( value , NumberStyles . Any , CultureInfo . InvariantCulture , out num ) )
2014-04-01 15:23:07 -07:00
{
item . MaxWidth = num ;
}
break ;
}
2016-11-14 00:28:20 -07:00
default :
break ;
2014-04-01 15:23:07 -07:00
}
}
}
2014-04-06 10:53:23 -07:00
private bool IsAudioDirectPlaySupported ( DirectPlayProfile profile , MediaSourceInfo item , MediaStream audioStream )
2014-04-01 15:23:07 -07:00
{
if ( profile . Container . Length > 0 )
{
// Check container type
2014-05-08 13:09:53 -07:00
string mediaContainer = item . Container ? ? string . Empty ;
2014-05-09 12:43:06 -07:00
bool any = false ;
foreach ( string i in profile . GetContainers ( ) )
{
2014-05-31 21:11:04 -07:00
if ( StringHelper . EqualsIgnoreCase ( i , mediaContainer ) )
2014-05-09 12:43:06 -07:00
{
any = true ;
break ;
}
}
if ( ! any )
2014-04-01 15:23:07 -07:00
{
return false ;
}
}
2016-04-28 10:21:10 -07:00
// Check audio codec
List < string > audioCodecs = profile . GetAudioCodecs ( ) ;
if ( audioCodecs . Count > 0 )
{
// Check audio codecs
string audioCodec = audioStream = = null ? null : audioStream . Codec ;
if ( string . IsNullOrEmpty ( audioCodec ) | | ! ListHelper . ContainsIgnoreCase ( audioCodecs , audioCodec ) )
{
return false ;
}
}
2014-04-01 15:23:07 -07:00
return true ;
}
2014-04-06 10:53:23 -07:00
private bool IsVideoDirectPlaySupported ( DirectPlayProfile profile , MediaSourceInfo item , MediaStream videoStream , MediaStream audioStream )
2014-04-01 15:23:07 -07:00
{
if ( profile . Container . Length > 0 )
{
// Check container type
2014-05-08 13:09:53 -07:00
string mediaContainer = item . Container ? ? string . Empty ;
2014-05-09 12:43:06 -07:00
bool any = false ;
foreach ( string i in profile . GetContainers ( ) )
{
2014-05-31 21:11:04 -07:00
if ( StringHelper . EqualsIgnoreCase ( i , mediaContainer ) )
2014-05-09 12:43:06 -07:00
{
any = true ;
break ;
}
}
if ( ! any )
2014-04-01 15:23:07 -07:00
{
return false ;
}
}
// Check video codec
2014-05-08 13:09:53 -07:00
List < string > videoCodecs = profile . GetVideoCodecs ( ) ;
2014-04-01 15:23:07 -07:00
if ( videoCodecs . Count > 0 )
{
2014-05-08 13:09:53 -07:00
string videoCodec = videoStream = = null ? null : videoStream . Codec ;
2014-06-04 19:32:40 -07:00
if ( string . IsNullOrEmpty ( videoCodec ) | | ! ListHelper . ContainsIgnoreCase ( videoCodecs , videoCodec ) )
2014-04-01 15:23:07 -07:00
{
return false ;
}
}
2016-04-28 10:21:10 -07:00
// Check audio codec
2014-05-08 13:09:53 -07:00
List < string > audioCodecs = profile . GetAudioCodecs ( ) ;
2014-04-01 15:23:07 -07:00
if ( audioCodecs . Count > 0 )
{
// Check audio codecs
2014-05-08 13:09:53 -07:00
string audioCodec = audioStream = = null ? null : audioStream . Codec ;
2014-06-04 19:32:40 -07:00
if ( string . IsNullOrEmpty ( audioCodec ) | | ! ListHelper . ContainsIgnoreCase ( audioCodecs , audioCodec ) )
2014-04-01 15:23:07 -07:00
{
return false ;
}
}
return true ;
}
}
2016-09-30 20:33:26 -07:00
}