Multiple audio tracks with different codecs and languages

All about user-defined episode / movie format expressions
kim
Power User
Posts: 1199
Joined: 15 May 2014, 16:17

Re: Multiple audio tracks with different codecs and languages

Post by kim »

(it gives me a result but not the "best bitrate").
What is the "best" and "NOT the Best" ?

output with ?:

Code: Select all

audioStreams.findAll{ it.bitrate == audioStreams.bitrate.max() }
(FYI: in GUI you can click on result to copy the output)
I only get 1 result even if there should be 2 independent results for some movies
same result... to confirm remove unique part

e.g. "bestBitRate" is from line

Code: Select all

def bestBitRate
btw:
FAIL =

Code: Select all

{[bestPreferredLang, bestBitRate]...}
OK =

Code: Select all

[bestPreferredLang, bestBitRate]...
kim
Power User
Posts: 1199
Joined: 15 May 2014, 16:17

Re: Multiple audio tracks with different codecs and languages

Post by kim »

maybe you like this better
from

Code: Select all

{ toInt(au.BitRate) }{ toInt(au.BitRate_Maximum) }
to

Code: Select all

{ toInt(au.BitRate_Maximum) }{ toInt(au.BitRate) }
looks like it will prefer e.g. TrueHD Atmos 7.1 [11 Objs] over DTS-HD MA 7.1 even if StreamSize and BitRate is smaller

I'm not sure what is the "Best" ?
RudyBzh
Posts: 23
Joined: 31 Mar 2013, 14:05

Re: Multiple audio tracks with different codecs and languages

Post by RudyBzh »

Thanks a lot.
My bad. I was just using "{}" and the results was not as expected... and I'm not dealing with limit cases where order can be discussed.

Exemple when comparing "FR [email protected]/s" vs "EN [email protected]/s" :
Image

Strange result when using {bestBitRate}, the reason I was not understanding what was "BestBitRate" (how [email protected] can be considered better than [email protected]) :
Image
The fact is that {} shouldn't be there...

I just want to notice here that the result is the same if you remove everything (you choose nothing and you have a result...) :
Image
I still don't understand why... and it didn't help me to troubleshoot myself (now we know the problem is myself :lol: )
Perhaps there's a way to return null if no option is chosen at the end (and so switch to "no_audio") ? Well, it's not really important.

And the working test I was looking for, removing the {} (not sure it's the "good way" to do for someone wanting only the higher bitrate as result) :
Image


To talk about "what is the best", I totally agree that it's not that simple to order things, because it could be sorted by :
  • Codec from the codecList (but hard to organize) : MP2 < MP3 < PCM < AAC < AC3 < EAC3 < ... But which one is better from AAC/AC3 or TrueHD Atmos/DTS-X or ... and what between AC3 5.1 & EAC3 2.0 (don't know if it exists but you get the idea...)
  • Bitrate (what is done actually). Must be the most relevant even if, in some rare cases, bitrate can be lower but quality higher due to better compression of the algorithm...
  • Max bitrate, thanks for the idea... but average must be better
  • Number of channels : 2.0 < 5.1 < 7.1 < 9.1
Well, really hard to find a way to sort.

Now, it's working as expected with this (even if I don't know what is "addToList" which can be preferred by the "any" because it's at the front :

Code: Select all

any{addToList}{[bestPreferredLang, bestBitRate].findAll().unique().join(' & ')}{defaultStream}{bestBitRate}{preferredStream}
Also working with this, that I shoud propably use, because I think I understood (more or less... :roll: ) :

Code: Select all

[bestPreferredLang, bestBitRate].findAll().unique().join(' & ')
Well, to be honest it's really cool & good enough to me, even if it could be even better. Let's take some cases :
  • For a movie, like above, with [email protected] FR & [email protected] EN => Show both (best FR then best "other/foreign").
  • For a movie with [email protected] FR & [email protected] FR & [email protected] EN => Show only DTS FR (the best of all available) ;
    Is it sure that "bestPreferredLang" will always take [email protected] FR over [email protected] FR ? I mean select the highest bitrate of all FR, because it's only selecting against "preferedLang", not best bitrate. I guess it's because listStream is sorted by bitrate... (but if so, why not only taking the first of the list for bestBitRate ? ; bestBitRate = listStream(it)[0])
  • For a movie with [email protected] FR & [email protected] EN => Show only AC3 FR (best of the best)
  • For a movie with [email protected] FR & [email protected] EN => I would prefer to only display FR, but it's not working this way now (350 > 340...). Would it be possible/easy to display the alternative audio (other that French) IF bitrate is higher (already done) but also codec is different ?
Something like :
bestPreferredLang.codec <> bestBitRate.codec ? Show both : show bestPreferredLang only.
Just not sure we can play with bestPreferredLang & bestBitRate like this because they are Strings as I understand... probably needed to take it from audioStreams like :
def betterForeign = ?!? I could probably try things but it will not probably be ideal...
seems working :

Code: Select all

audioStreams.findAll{ it.bitrate == audioStreams.bitrate.max() }.codec == audioStreams.findAll{ it.lang == preferredLang }.codec ? bestPreferredLang:[bestPreferredLang, bestBitRate].join(' & ')
Thanks again for your answers and for the time you take to read all of this :oops:
RudyBzh
Posts: 23
Joined: 31 Mar 2013, 14:05

Re: Multiple audio tracks with different codecs and languages

Post by RudyBzh »

Well, it's not working as expected...
Because
"audioStreams.findAll{ it.bitrate == audioStreams.bitrate.max()" can contain multiple results (if differents bitrates are equals)
And also because "audioStreams.findAll{ it.lang == preferredLang }" can contain multiple results and, not yet sorted by bitrate (to have the bestPreferrendLang...)
So comparing both may result of strange things ^^

The solution could be to :
1) construct the array : audioStreams << ... as it is actually
2) Sort the array by bitrate (but keep it as an array), to replace the all in one "listStream" who sort and convert as String list... loosing the ability to do things...
3) Next, we can use this sorted array to find things... The 1st in the array, without any filter, is bestBitRate ; The 1st in the array, with lang filter, is bestPreferredLang ; The first with default ... and so on.

What do you think ?
kim
Power User
Posts: 1199
Joined: 15 May 2014, 16:17

Re: Multiple audio tracks with different codecs and languages

Post by kim »

This was not easy ;)

Try it out:

Code: Select all

audioStreams.findAll{ it.bitrate == audioStreams.bitrate.max() || it.lang == preferredLang }.sort{ a, b ->  b.bitrate <=> a.bitrate }.sort{ it.lang != preferredLang }.unique{ it.lang }.unique{ it.bitrate }.collect{ filter(it) }*.join(' ').join(' & ')
or

Code: Select all

audioStreams.findAll{ it.bitrate == audioStreams.bitrate.max() || it.lang == preferredLang }.sort{ a, b ->  b.bitrate <=> a.bitrate ?: preferredLang <=> b.lang}.unique{ it.lang }.unique{ it.bitrate }.collect{ filter(it) }*.join(' ').join(' & ')

maybe you can reduce the unique's and sort's ?
do rednoah have any advanced info ?
RudyBzh
Posts: 23
Joined: 31 Mar 2013, 14:05

Re: Multiple audio tracks with different codecs and languages

Post by RudyBzh »

Thanks a lot.
Looking at the lines, it's effectively not easy :o
Also, unfortunately, it's not working.

With 1st expression

Code: Select all

audioStreams.findAll{ it.bitrate == audioStreams.bitrate.max() || it.lang == preferredLang }.sort{ a, b ->  b.bitrate <=> a.bitrate }.sort{ it.lang != preferredLang }.unique{ it.lang }.unique{ it.bitrate }.collect{ filter(it) }*.join(' ').join(' & ')
Image
OK on lines 1,2,3
KO on lines 4,5,6

With 2nd expression :

Code: Select all

audioStreams.findAll{ it.bitrate == audioStreams.bitrate.max() || it.lang == preferredLang }.sort{ a, b ->  b.bitrate <=> a.bitrate ?: preferredLang <=> b.lang}.unique{ it.lang }.unique{ it.bitrate }.collect{ filter(it) }*.join(' ').join(' & ')
Image
OK on lines 1,2,3 (but I'd prefer FR first as previously)
KO on lines 4,5,6
kim
Power User
Posts: 1199
Joined: 15 May 2014, 16:17

Re: Multiple audio tracks with different codecs and languages

Post by kim »

Try:

Code: Select all

( audioStreams.findAll{ it.lang == preferredLang }.sort{ a, b -> b.bitrate <=> a.bitrate } 999 audioStreams.findAll{ it.bitrate == audioStreams.bitrate.max() }.sort{ a, b -> b.default <=> a.default }.unique{ it.bitrate } ).unique{ it.lang }.collect{ filter(it) }*.join(' ').join(' 777 ')
change:
999 to +
777 to &

@rednoah
PS: I'm super annoyed at the "WAF"
User avatar
rednoah
The Source
Posts: 20275
Joined: 16 Nov 2011, 08:59

Re: Multiple audio tracks with different codecs and languages

Post by rednoah »

kim wrote: 06 Feb 2021, 04:20 PS: I'm super annoyed at the "WAF"
Me too... Might need to move custom format talk to the Discord channel.



EDIT:

Created a dedicated channel for this kind of discussion:
https://discord.com/channels/2287231828 ... 5177269290

Discord will sharing code and upload screenshots easier, plus it allows for real-time messaging, so it should make the experience better in this regard as well.
:idea: Please read the FAQ and How to Request Help.
RudyBzh
Posts: 23
Joined: 31 Mar 2013, 14:05

Re: Multiple audio tracks with different codecs and languages

Post by RudyBzh »

Here is the result with this formula :

Code: Select all

( audioStreams.findAll{ it.lang == preferredLang }.sort{ a, b -> b.bitrate <=> a.bitrate } + audioStreams.findAll{ it.bitrate == audioStreams.bitrate.max() }.sort{ a, b -> b.default <=> a.default }.unique{ it.bitrate } ).unique{ it.lang }.collect{ filter(it) }*.join(' ').join(' 777 ')
Had to change "999" to "+" for it to work. If not, I get an error "SyntaxError: expecting ')', found '999'"

Image

Thanks
kim
Power User
Posts: 1199
Joined: 15 May 2014, 16:17

Re: Multiple audio tracks with different codecs and languages

Post by kim »

put in and you all set:

Code: Select all

.unique{ it.codec }

Code: Select all

.unique{ it.lang }.unique{ it.codec }.collect{ filter(it) }*.join(' ').join(' & ')
RudyBzh
Posts: 23
Joined: 31 Mar 2013, 14:05

Re: Multiple audio tracks with different codecs and languages

Post by RudyBzh »

Thank you so much & sorry for the disturb on the forum :oops:
As you told, I'm all set with this :

Code: Select all

audioStreams.findAll{ it.bitrate == audioStreams.bitrate.max() || it.lang == preferredLang }.sort{ a, b ->  b.bitrate <=> a.bitrate }.sort{ it.lang != preferredLang }.unique{ it.lang }.unique{ it.codec }.collect{ filter(it) }*.join(' ').join(' & ')
The full piece of code after a little bit of cleaning, for those who are interested :

Code: Select all

		def preferredLang = 'FR'
		def useChFilter = false
		//def filter = { [it.codec, it.ch, it.objects, it.lang].findAll() }
		def filter = { [it.codec, it.ch, it.lang].findAll() }

		def codecList =
		[
		'MPEG Audio' : 'MP2',
		'MP3' : 'MP3',
		'PCM' : 'PCM',
		'FLAC' : 'FLAC',
		'AAC LC' : 'AAC',
		'AAC LC SBR' : 'AAC',
		'AAC LC SBR PS' : 'AAC',
		'AC 3' : 'AC3',
		'AC 3 Dep' : 'EAC3',
		'E AC 3' : 'EAC3',
		'E AC 3 JOC' : 'EAC3 Atmos',
		'AC 3 Dep JOC' : 'EAC3 Atmos',
		'DTS' : 'DTS',
		'DTS 96 24' : 'DTS 96-24',
		'DTS ES' : 'DTS-ES',
		'DTS ES XXCH' : 'DTS-ES',
		'DTS XBR' : 'DTS-HD HRA',
		'DTS ES XBR' : 'DTS-HD HRA',
		'DTS ES XXCH XBR' : 'DTS-HD HRA',
		'DTS XLL' : 'DTS-HD MA',
		'DTS ES XLL' : 'DTS-HD MA',
		'DTS ES XXCH XLL' : 'DTS-HD MA',
		'DTS XLL X' : 'DTS X',
		'MLP FBA' : 'TrueHD',
		'MLP FBA 16 ch' : 'TrueHD Atmos'
		]
		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 dString = { it.toDouble().toString() }
		def toInt = { it.toInteger() }
		any{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' : any {au['default'][0].toBoolean() }{ audio.size == 1 ? true : '' },
			'codec' : codecList.get(combined, 'Add to "' + combined + '" codecList'), 'combined' : combined, 'ch' : useChFilter ? chFilter : ch,
			'bitrate' : any{ toInt(au.BitRate) }{ toInt(au.BitRate_Maximum) }{ au.FrameRate.toDouble() }{null},
			'objects' : any{def objects = au['NumberOfDynamicObjects']; objects ? "[$objects Objs]" : ''}{null}, 'lang' : any{ au.'LanguageString2'.upper() }{null} ]
			return audioStreams
		}
		audioStreams.findAll{ it.bitrate == audioStreams.bitrate.max() || it.lang == preferredLang }.sort{ a, b ->  b.bitrate <=> a.bitrate }.sort{ it.lang != preferredLang }.unique{ it.lang }.unique{ it.codec }.collect{ filter(it) }*.join(' ').join(' & ')
		}{'NO_AUDIO'}
It's possible to remove more useless things in this example but I keep it for those who are interested :
- the useChFilter parts (cleaning a little bit the output, removing the nb of channels if it's "standard" values)
- I still didn't get the goal of "return audioStreams" & "Add To" parts, so keep it ^^

Thanks a lot to Kim for his time ^^
kim
Power User
Posts: 1199
Joined: 15 May 2014, 16:17

Re: Multiple audio tracks with different codecs and languages

Post by kim »

e.g. addToList = [Add "AC 3" to codecList, Add "DTS XLL" to codecList, Add "DTS" to codecList]

This is the final result:

Code: Select all

{
		def preferredLang = 'FR'
		def filter = { [it.codec, it.ch, it.lang].findAll() }

		def codecList =
		[
		'MPEG Audio' : 'MP2',
		'MP3' : 'MP3',
		'PCM' : 'PCM',
		'FLAC' : 'FLAC',
		'AAC LC' : 'AAC',
		'AAC LC SBR' : 'AAC',
		'AAC LC SBR PS' : 'AAC',
		'AC 3' : 'AC3',
		'AC 3 Dep' : 'EAC3',
		'E AC 3' : 'EAC3',
		'E AC 3 JOC' : 'EAC3 Atmos',
		'AC 3 Dep JOC' : 'EAC3 Atmos',
		'DTS' : 'DTS',
		'DTS 96 24' : 'DTS 96-24',
		'DTS ES' : 'DTS-ES',
		'DTS ES XXCH' : 'DTS-ES',
		'DTS XBR' : 'DTS-HD HRA',
		'DTS ES XBR' : 'DTS-HD HRA',
		'DTS ES XXCH XBR' : 'DTS-HD HRA',
		'DTS XLL' : 'DTS-HD MA',
		'DTS ES XLL' : 'DTS-HD MA',
		'DTS ES XXCH XLL' : 'DTS-HD MA',
		'DTS XLL X' : 'DTS X',
		'MLP FBA' : 'TrueHD',
		'MLP FBA 16 ch' : 'TrueHD Atmos'
		]

		def audioStreams = []
		def audioClean = { it.replaceAll(/\p{Punct}/, ' ') }
		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 listStream = { it.sort{ a, b -> b.bitrate <=> a.bitrate }.collect{ filter(it) }.unique()*.join(' ') }
		def oneStream = { listStream(it)[0] }
		def dString = { it.toDouble().toString() }
		def toInt = { it.toInteger() }

		any{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().sum() }
				{ channelClean(au.ChannelLayout_Original).split().collect{ it == 'LFE' ? 0.1 : 1 }.sum() }
				{ channelClean(dString(au.ChannelsOriginal)) } { channelClean(dString(au.Channels)) }
			def combined = allOf{codec}{format_profile}.join(' ')

			audioStreams << ['index' : codecList.findIndexOf { it.key == combined }, 'default' : any {au['default'][0].toBoolean() }{ audio.size == 1 ? true : '' },
			'codec' : codecList.get(combined, 'Add "' + combined + '" to codecList'), 'combined' : combined, 'ch' : ch,
			'bitrate' : any{ toInt(au.BitRate) }{ toInt(au.BitRate_Maximum) }{ au.FrameRate.toDouble() }{null}, 'lang' : any{ au.'LanguageString2'.upper() }{null} ]
		}

			def addToList = audioStreams.codec.findAll{ it.contains('to codecList') }.unique().sort()
			any{addToList}{( audioStreams.findAll{ it.lang == preferredLang }.sort{ a, b -> b.bitrate <=> a.bitrate } + audioStreams.findAll{ it.bitrate == audioStreams.bitrate.max() }.sort{ a, b -> b.default <=> a.default }.unique{ it.bitrate } ).unique{ it.lang }.unique{ it.codec }.collect{ filter(it) }*.join(' ').join(' & ')}
		}{'NO_AUDIO'}
}
Vittatus
Posts: 2
Joined: 12 May 2019, 09:01

Re: Multiple audio tracks with different codecs and languages

Post by Vittatus »

kim wrote: 08 Feb 2021, 04:28 e.g. addToList = [Add "AC 3" to codecList, Add "DTS XLL" to codecList, Add "DTS" to codecList]

This is the final result:

Code: Select all

{
		def preferredLang = 'FR'
		def filter = { [it.codec, it.ch, it.lang].findAll() }

		def codecList =
		[
		'MPEG Audio' : 'MP2',
		'MP3' : 'MP3',
		'PCM' : 'PCM',
		'FLAC' : 'FLAC',
		'AAC LC' : 'AAC',
		'AAC LC SBR' : 'AAC',
		'AAC LC SBR PS' : 'AAC',
		'AC 3' : 'AC3',
		'AC 3 Dep' : 'EAC3',
		'E AC 3' : 'EAC3',
		'E AC 3 JOC' : 'EAC3 Atmos',
		'AC 3 Dep JOC' : 'EAC3 Atmos',
		'DTS' : 'DTS',
		'DTS 96 24' : 'DTS 96-24',
		'DTS ES' : 'DTS-ES',
		'DTS ES XXCH' : 'DTS-ES',
		'DTS XBR' : 'DTS-HD HRA',
		'DTS ES XBR' : 'DTS-HD HRA',
		'DTS ES XXCH XBR' : 'DTS-HD HRA',
		'DTS XLL' : 'DTS-HD MA',
		'DTS ES XLL' : 'DTS-HD MA',
		'DTS ES XXCH XLL' : 'DTS-HD MA',
		'DTS XLL X' : 'DTS X',
		'MLP FBA' : 'TrueHD',
		'MLP FBA 16 ch' : 'TrueHD Atmos'
		]

		def audioStreams = []
		def audioClean = { it.replaceAll(/\p{Punct}/, ' ') }
		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 listStream = { it.sort{ a, b -> b.bitrate <=> a.bitrate }.collect{ filter(it) }.unique()*.join(' ') }
		def oneStream = { listStream(it)[0] }
		def dString = { it.toDouble().toString() }
		def toInt = { it.toInteger() }

		any{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().sum() }
				{ channelClean(au.ChannelLayout_Original).split().collect{ it == 'LFE' ? 0.1 : 1 }.sum() }
				{ channelClean(dString(au.ChannelsOriginal)) } { channelClean(dString(au.Channels)) }
			def combined = allOf{codec}{format_profile}.join(' ')

			audioStreams << ['index' : codecList.findIndexOf { it.key == combined }, 'default' : any {au['default'][0].toBoolean() }{ audio.size == 1 ? true : '' },
			'codec' : codecList.get(combined, 'Add "' + combined + '" to codecList'), 'combined' : combined, 'ch' : ch,
			'bitrate' : any{ toInt(au.BitRate) }{ toInt(au.BitRate_Maximum) }{ au.FrameRate.toDouble() }{null}, 'lang' : any{ au.'LanguageString2'.upper() }{null} ]
		}

			def addToList = audioStreams.codec.findAll{ it.contains('to codecList') }.unique().sort()
			any{addToList}{( audioStreams.findAll{ it.lang == preferredLang }.sort{ a, b -> b.bitrate <=> a.bitrate } + audioStreams.findAll{ it.bitrate == audioStreams.bitrate.max() }.sort{ a, b -> b.default <=> a.default }.unique{ it.bitrate } ).unique{ it.lang }.unique{ it.codec }.collect{ filter(it) }*.join(' ').join(' & ')}
		}{'NO_AUDIO'}
}
Hello, I would like you to add all the audios, but only the ones with the highest rate, that is, imagine that I have a video with these audios:

Spanish AC3 5.1
Spanish DTS-HD 5.1
English AC3 5.1
English TrueHD 7.1

let it be like this:

[ES DTS-HD 5.1 - EN TrueHD 7.1]

I would only add the best audios of each language.

Is it possible to do this? What would I have to modify in the code?

Thank you.
kim
Power User
Posts: 1199
Joined: 15 May 2014, 16:17

Re: Multiple audio tracks with different codecs and languages

Post by kim »

this was not easy... but give it a try:

Code: Select all

{
	def preferredLang = 'FR'
	def useChFilter = false
	def filter = { [it.lang, it.codec, it.ch, it.objects].findAll() }

	def codecList =
	[
	'MPEG Audio' : 'MP2',
	'MP3' : 'MP3',
	'PCM' : 'PCM',
	'FLAC' : 'FLAC',
	'AAC LC' : 'AAC',
	'AAC LC SBR' : 'AAC',
	'AAC LC SBR PS' : 'AAC',
	'AC 3' : 'AC3',
	'AC 3 Dep' : 'EAC3',
	'E AC 3' : 'EAC3',
	'E AC 3 JOC' : 'EAC3 Atmos',
	'AC 3 Dep JOC' : 'EAC3 Atmos',
	'DTS' : 'DTS',
	'DTS 96 24' : 'DTS 96-24',
	'DTS ES' : 'DTS-ES',
	'DTS ES XXCH' : 'DTS-ES',
	'DTS XBR' : 'DTS-HD HRA',
	'DTS ES XBR' : 'DTS-HD HRA',
	'DTS ES XXCH XBR' : 'DTS-HD HRA',
	'DTS XLL' : 'DTS-HD MA',
	'DTS ES XLL' : 'DTS-HD MA',
	'DTS ES XXCH XLL' : 'DTS-HD MA',
	'DTS XLL X' : 'DTS X',
	'MLP FBA' : 'TrueHD',
	'MLP FBA 16 ch' : 'TrueHD Atmos'
	]

	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 listStream = { it.sort{ a, b -> b.bitrate <=> a.bitrate }.collect{ filter(it) }.unique()*.join(' ') }
	def oneStream = { listStream(it)[0] }
	def dString = { it.toDouble().toString() }
	def toInt = { it.toInteger() }

	any{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' : any {au['default'][0].toBoolean() }{ audio.size == 1 ? true : '' },
		'codec' : codecList.get(combined, 'Add to "' + combined + '" codecList'), 'combined' : combined, 'ch' : useChFilter ? chFilter : ch,
		'bitrate' : any{ toInt(au.BitRate) }{ toInt(au.BitRate_Maximum) }{ dString(au.FrameRate) }{null},
		'objects' : any{def objects = au['NumberOfDynamicObjects']; objects ? "[$objects Objs]" : ''}{null}, 'lang' : any{ au.'LanguageString2'.upper() }{null} ]
		return audioStreams
	}

	def addToList = audioStreams.codec.findAll{ it.contains('Add to') }.unique().sort()
	def allStreams = listStream(audioStreams)
	def preferredStream = oneStream(audioStreams.findAll{ it.index == audioStreams.index.max() })
	def bestBitRate = oneStream(audioStreams.findAll{ it.bitrate == audioStreams.bitrate.max() })
	def defaultStream = any{ oneStream(audioStreams.findAll{ it.default == true }) }{ oneStream(audioStreams) }
	def bestPreferredLang = any{ oneStream(audioStreams.findAll{ it.lang == preferredLang }) }{}
	def bestBitRateAllLang = listStream(audioStreams.groupBy{ it.lang }.values()*.sort{ a, b -> b.bitrate <=> a.bitrate }*.find { it }).join(' - ')

	any{addToList}{bestBitRateAllLang}{[bestPreferredLang, bestBitRate].findAll().join(' & ')}{defaultStream}{bestBitRate}{preferredStream}
	
	}{'NO_AUDIO'}
}
sample:
EN DTS-HD MA 7.1 - FR AC3 5.1 - IT AC3 5.1 - ES AC3 5.1 - NL AC3 5.1 - CA AC3 5.1

EDIT:
if you really want with the [...]
you can replace

Code: Select all

def bestBitRateAllLang = listStream(audioStreams.groupBy{ it.lang }.values()*.sort{ a, b -> b.bitrate <=> a.bitrate }*.find { it }).join(' - ')
with

Code: Select all

def bestBitRateAllLang = listStream(audioStreams.groupBy{ it.lang }.values()*.sort{ a, b -> b.bitrate <=> a.bitrate }*.find { it }).toString().split(', ').join(' - ')
or

Code: Select all

def bestBitRateAllLang = listStream(audioStreams.groupBy{ it.lang }.values()*.sort{ a, b -> b.bitrate <=> a.bitrate }*.find { it }).joining(' - ', ' [', ']')
sample:
[EN DTS-HD MA 7.1 - FR AC3 5.1 - IT AC3 5.1 - ES AC3 5.1 - NL AC3 5.1 - CA AC3 5.1]
lazar0ps
Posts: 2
Joined: 28 Nov 2021, 18:14

Re: Multiple audio tracks with different codecs and languages

Post by lazar0ps »

Thank you for this great piece of code, it's perfect to me.

I would like to ask if there is an easy way to adapt it to group audio languages by audio codec and channel.
I think this would be interesting to shorten file names but keeping all the info.

Sample of how current code works:
EN EAC Atmos 5.1 - ES EAC3 5.1 - FR EAC3 5.1 - EN EAC3 5.1
The desired result:
EN EAC Atmos 5.1 - ES|FR|EN EAC3 5.1
Another feature I find interesting would be to be able to choose a 'default' audio language in the same way you allow to define the preferreded language.
This way, you could group in a folder all those movies or episodes ripped from VHS or DVB, that maybe don't contain language info but that you know they are in English or Spanish, and rename them all at the same time, knowing that if any of them do not have language information, the file will be renamed using the default desired language.

Sample of how current code works:
MP3 2.0
The desired result:
EN MP3 2.0
Although this could be manually achieved by changing 'null' in this line for 'EN' or any other language string.

Code: Select all

'lang' : any{ au.'LanguageString3'.upper() }{null} ]

Thank you again!
Last edited by lazar0ps on 28 Nov 2021, 19:57, edited 8 times in total.
kim
Power User
Posts: 1199
Joined: 15 May 2014, 16:17

Re: Multiple audio tracks with different codecs and languages

Post by kim »

Try it out (Not pretty, Not easy):

sample:
EN DTS-HD MA 7.1 - EN TrueHD Atmos 7.1 - EN DTS 5.1 - EN|FR|IT|ES|NL|CA AC3 5.1
warning you cant use pipes in filename on windows... change "pipes" in

Code: Select all

it.unique{it.lang}.lang.join('|')

Code: Select all

{
	def preferredLang = 'FR'
	def useChFilter = false
	def filter = { [it.lang, it.codec, it.ch, it.objects].findAll() }

	def codecList =
	[
	'MPEG Audio' : 'MP2',
	'MP3' : 'MP3',
	'PCM' : 'PCM',
	'FLAC' : 'FLAC',
	'AAC LC' : 'AAC',
	'AAC LC SBR' : 'AAC',
	'AAC LC SBR PS' : 'AAC',
	'AC 3' : 'AC3',
	'AC 3 Dep' : 'EAC3',
	'E AC 3' : 'EAC3',
	'E AC 3 JOC' : 'EAC3 Atmos',
	'AC 3 Dep JOC' : 'EAC3 Atmos',
	'DTS' : 'DTS',
	'DTS 96 24' : 'DTS 96-24',
	'DTS ES' : 'DTS-ES',
	'DTS ES XXCH' : 'DTS-ES',
	'DTS XBR' : 'DTS-HD HRA',
	'DTS ES XBR' : 'DTS-HD HRA',
	'DTS ES XXCH XBR' : 'DTS-HD HRA',
	'DTS XLL' : 'DTS-HD MA',
	'DTS ES XLL' : 'DTS-HD MA',
	'DTS ES XXCH XLL' : 'DTS-HD MA',
	'DTS XLL X' : 'DTS X',
	'MLP FBA' : 'TrueHD',
	'MLP FBA 16 ch' : 'TrueHD Atmos'
	]

	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 listStream = { it.sort{ a, b -> b.bitrate <=> a.bitrate }.collect{ filter(it) }.unique()*.join(' ') }
	def oneStream = { listStream(it)[0] }
	def dString = { it.toDouble().toString() }
	def toInt = { it.toInteger() }

	any{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' : any {au['default'][0].toBoolean() }{ audio.size == 1 ? true : '' },
		'codec' : codecList.get(combined, 'Add to "' + combined + '" codecList'), 'combined' : combined, 'ch' : useChFilter ? chFilter : ch,
		'bitrate' : any{ toInt(au.BitRate) }{ toInt(au.BitRate_Maximum) }{ dString(au.FrameRate) }{null},
		'objects' : any{def objects = au['NumberOfDynamicObjects']; objects ? "[$objects Objs]" : ''}{null}, 'lang' : any{ au.'LanguageString2'.upper() }{null} ]
		return audioStreams
	}

	def addToList = audioStreams.codec.findAll{ it.contains('Add to') }.unique().sort()
	def allStreams = listStream(audioStreams)
	def preferredStream = oneStream(audioStreams.findAll{ it.index == audioStreams.index.max() })
	def bestBitRate = oneStream(audioStreams.findAll{ it.bitrate == audioStreams.bitrate.max() })
	def defaultStream = any{ oneStream(audioStreams.findAll{ it.default == true }) }{ oneStream(audioStreams) }
	def bestPreferredLang = any{ oneStream(audioStreams.findAll{ it.lang == preferredLang }) }{}
	def bestBitRateAllLang = listStream(audioStreams.groupBy{ it.lang }.values()*.sort{ a, b -> b.bitrate <=> a.bitrate }*.find { it }).join(' - ')
	def groupCodec = audioStreams.sort{ a, b -> b.bitrate <=> a.bitrate }.groupBy{ it.codec }.values().collect{ [lang: it.unique{it.lang}.lang.join('|'), codec: it.unique{it.codec}.codec.join(), ch: it.unique{it.ch}.ch.join()] }.collect{ "${it.lang.replace('null', 'EN')} ${it.codec} ${it.ch}" }.join(' - ')

	any{addToList}{groupCodec}{bestBitRateAllLang}{[bestPreferredLang, bestBitRate].findAll().join(' & ')}{defaultStream}{bestBitRate}{preferredStream}
	}{'NO_AUDIO'}
}
change this to your "default" lang

Code: Select all

it.lang.replace('null', 'EN')

Or maybe more like this ?

Code: Select all

{
	def preferredLang = 'FR'
	def fallbackLang = any{languages*.ISO2*.upper().join('-')}{'YourDefaultLandHere'}
	def useChFilter = false
	def filter = { [it.lang, it.codec, it.ch, it.objects].findAll() }

	def codecList =
	[
	'MPEG Audio' : 'MP2',
	'MP3' : 'MP3',
	'PCM' : 'PCM',
	'FLAC' : 'FLAC',
	'AAC LC' : 'AAC',
	'AAC LC SBR' : 'AAC',
	'AAC LC SBR PS' : 'AAC',
	'AC 3' : 'AC3',
	'AC 3 Dep' : 'EAC3',
	'E AC 3' : 'EAC3',
	'E AC 3 JOC' : 'EAC3 Atmos',
	'AC 3 Dep JOC' : 'EAC3 Atmos',
	'DTS' : 'DTS',
	'DTS 96 24' : 'DTS 96-24',
	'DTS ES' : 'DTS-ES',
	'DTS ES XXCH' : 'DTS-ES',
	'DTS XBR' : 'DTS-HD HRA',
	'DTS ES XBR' : 'DTS-HD HRA',
	'DTS ES XXCH XBR' : 'DTS-HD HRA',
	'DTS XLL' : 'DTS-HD MA',
	'DTS ES XLL' : 'DTS-HD MA',
	'DTS ES XXCH XLL' : 'DTS-HD MA',
	'DTS XLL X' : 'DTS X',
	'MLP FBA' : 'TrueHD',
	'MLP FBA 16 ch' : 'TrueHD Atmos'
	]

	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 listStream = { it.sort{ a, b -> b.bitrate <=> a.bitrate }.collect{ filter(it) }.unique()*.join(' ') }
	def oneStream = { listStream(it)[0] }
	def dString = { it.toDouble().toString() }
	def toInt = { it.toInteger() }

	any{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' : any {au['default'][0].toBoolean() }{ audio.size == 1 ? true : '' },
		'codec' : codecList.get(combined, 'Add to "' + combined + '" codecList'), 'combined' : combined, 'ch' : useChFilter ? chFilter : ch,
		'bitrate' : any{ toInt(au.BitRate) }{ toInt(au.BitRate_Maximum) }{ dString(au.FrameRate) }{null},
		'objects' : any{def objects = au['NumberOfDynamicObjects']; objects ? "[$objects Objs]" : ''}{null}, 'lang' : any{ au.'LanguageString2'.upper() }{null} ]
		return audioStreams
	}

	def addToList = audioStreams.codec.findAll{ it.contains('Add to') }.unique().sort()
	def allStreams = listStream(audioStreams)
	def preferredStream = oneStream(audioStreams.findAll{ it.index == audioStreams.index.max() })
	def bestBitRate = oneStream(audioStreams.findAll{ it.bitrate == audioStreams.bitrate.max() })
	def defaultStream = any{ oneStream(audioStreams.findAll{ it.default == true }) }{ oneStream(audioStreams) }
	def bestPreferredLang = any{ oneStream(audioStreams.findAll{ it.lang == preferredLang }) }{}
	def bestBitRateAllLang = listStream(audioStreams.groupBy{ it.lang }.values()*.sort{ a, b -> b.bitrate <=> a.bitrate }*.find { it }).join(' - ')
	def groupCodec = audioStreams.sort{ a, b -> b.bitrate <=> a.bitrate }.groupBy{ it.codec }.values().collect{ [lang: it.unique{it.lang}.lang.join('|'), codec: it.unique{it.codec}.codec.join(), ch: it.unique{it.ch}.ch.join()] }.collect{ "${it.lang.replace('null', fallbackLang)} ${it.codec} ${it.ch}" }.join(' - ')

	any{addToList}{groupCodec}{bestBitRateAllLang}{[bestPreferredLang, bestBitRate].findAll().join(' & ')}{defaultStream}{bestBitRate}{preferredStream}
	}{'NO_AUDIO'}
}
lazar0ps
Posts: 2
Joined: 28 Nov 2021, 18:14

Re: Multiple audio tracks with different codecs and languages

Post by lazar0ps »

Wow, you did it kim, that's exactly what I was looking for, and it works like a charm!

Thank you so much :)
Post Reply