Multiple audio tracks with different codecs and languages

All about user-defined episode / movie format expressions
User avatar
rednoah
The Source
Posts: 16821
Joined: 16 Nov 2011, 08:59
Location: Taipei
Contact:

Re: Multiple audio tracks with different codecs and languages

Post by rednoah » 13 May 2019, 12:08

I've never seen a Title field for an audio stream:

Code: Select all

Title                            : Dolby Digital Plus Audio / 7.1 / 48 kHz / 1280 kbps
I guess this Title tag is supposed to show up when you select a given audio stream in your media player. Someone thought this is a good display value for this audio stream.

Might be useful, but probably won't be defined for the vast majority of files.
:idea: Please read the FAQ and How to Request Help.

devster
Posts: 335
Joined: 06 Jun 2017, 22:56

Re: Multiple audio tracks with different codecs and languages

Post by devster » 13 May 2019, 12:47

My assumption was that the "title" field is an optional metadata tag and I wouldn't want to rely on it.

For naming purposes I found out that the AdditionalFeatures bascically means a hybrid stream.
In this case there seem to be:
  • "base" 5.1 AC-3 stream, decodable by an AC-3 decoder. (The Format field which represents the "minimal" decoder required)
  • Dependant E-AC-3 stream with 2 additional channels, optional and apparently interleaved with the first one (The Format/String apparently)
It seems to be part of E-AC-3 which allows embedding a core AC-3 stream.

The ChannelPositions/String2 seems a bug, a workaround in this specific case would be to tokenize by comma ChannelPositions (Front: L C R, Side: L R, Back: L R, LFE), strip whatever's before the colon, count number of words in the string (3,2,2,1) with the last one being the Low Frequency Effects.
I only work in black and sometimes very, very dark grey. (Batman)

antisgae
Posts: 10
Joined: 29 Aug 2018, 22:48

Re: Multiple audio tracks with different codecs and languages

Post by antisgae » 04 Nov 2019, 19:51

Just in case it helps to somebody i modified the code and added also flac audio.
Looks like this: "ESP ac3 2.0 ENG flac 1.0"
./1961 - Homicidal - Homicidio - William Castle/1961 - Homicidal - Homicidio - William Castle BDR 1080p 35.0Mbps ESP ac3 2.0 ENG flac 1.0 SUB ENG ESP.mkv

Code: Select all

{
	def codecList =
	[
	'MP3' : 'mp3',
	'FLAC' : 'flac',
	'PCM' : 'pcm',
	'AAC LC' : 'aac',
	'AAC LC SBR' : 'aac',
	'AC 3' : 'ac3',
	'AC 3 Dep' : 'eac3',
	'E AC 3' : 'eac3',
	'E AC 3 JOC' : 'eac3 Atmos',
	'DTS' : 'dts',
	'DTS 96 24' : 'dts',
	'DTS ES' : 'dtses',
	'DTS ES XXCH' : 'dtses',
	'DTS XBR' : 'dts',
	'DTS ES XBR' : 'dtses',
	'DTS ES XXCH XBR' : 'dtses',
	'DTS XLL' : 'dts',
	'DTS ES XLL' : 'dtses',
	'DTS ES XXCH XLL' : 'dtses',
	'DTS XLL X' : 'dtsx',
	'MLP FBA' : 'truehd',
	'MLP FBA 16 ch' : 'truehd Atmos'
	]
	def filter = { [it.lang, it.codec, it.ch, it.objects] }

def audioStreams = []
	def audioClean = { it.replaceAll(/[\p{Pd}\p{Space}]/, ' ').replaceAll(/\p{Space}{2,}/, ' ').slash(' ') }
	def channelClean = { it.replaceAll(/Debug.+|Object\sBased\s?\/?|(\d+)?\sobjects\s\/\s|0.(?=\d.\d)|20/).replaceAll(/6.0/,'5.1').replaceAll(/8.0/,'7.1')}
	def oneStream = { it.collect{ filter(it) }*.minus(null).unique().flatten().join(' ') }
	def dString = { it.toDouble().toString() }
	def toInt = { it.toInteger() }

	audio.collect{ au ->
		def codec = audioClean(any{ au['CodecID/Hint'] }{ au['Format'] })
		def format_profile = any{ audioClean(au['Format_AdditionalFeatures'])}{}
		def String ch = any{ channelClean(au.ChannelPositionsString2).tokenize('\\/')*.toDouble().toString().sum() }
			{ channelClean(dString(au.ChannelsOriginal)) } { channelClean(dString(au.Channels)) }

		def chFilter =	(( ( (ac == 'AAC'||ac == 'MP3') && ch != '2.0') || ( (ac == 'AC3'||ac == 'EAC3'||ac == 'DTS'||ac == 'TrueHD'||ac == 'MLPFBA') && ch != '5.1' ) ) ? ch : null)

		def combined = allOf{codec}{format_profile}.join(' ')
		audioStreams << ['index' : codecList.findIndexOf {it.key == combined}, 'default' : au['default'][0].toBoolean(),
		'codec' : codecList.get(combined, 'UNKNOWN_FORMAT'), 'combined' : combined, 'ch' : ch, 
		'bitrate' : any{toInt(au.BitRate)}{toInt(au.BitRate_Maximum)}{au.FrameRate.toDouble()}{null},
		'objects' : any{'[' + au['NumberOfDynamicObjects'] + ' Objs]'}{null}, 'lang' : any{au.'LanguageString3'.upper().replaceAll("SPA","ESP")}{null} ]
		return audioStreams
	}

	def allStreams = audioStreams.collect{ filter(it) }*.minus(null).unique()*.join(' ')
	def preferredStream = oneStream(audioStreams.findAll{ it.index == audioStreams.index.max() })
	def bestBitRate = oneStream(audioStreams.findAll{ it.bitrate == audioStreams.bitrate.max() })
	def defaultStream = oneStream(audioStreams.findAll{it.default == true})

	allStreams.join(' ').space(' ')
	preferredStream.space(' ')
	defaultStream.space(' ')
	bestBitRate.space(' ')
	[bestBitRate, preferredStream].unique().join(' ')
	[defaultStream, bestBitRate].unique().join(' ')
}

Post Reply