Multiple audio tracks with different codecs and languages

All about user-defined episode / movie format expressions
deejayexe
Posts: 8
Joined: 19 Jun 2022, 23:41

Re: Multiple audio tracks with different codecs and languages

Post by deejayexe »

kim wrote: 21 Nov 2022, 20:09 sample
ES AC3 5.1 - EN DTS-HD MA 7.1

Code: Select all

{
	def preferredLang = 'ES'
	def fallbackLang = any{languages*.ISO2*.upper().join('-')}{'YourDefaultLandHere'}
	def useChFilter = false
	def filter = { [it.lang, it.codec, it.ch].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}{[bestPreferredLang, bestBitRate].unique().findAll().join(' - ')}{defaultStream}{bestBitRate}{preferredStream}
	}{'NO_AUDIO'}
}
replace

Code: Select all

{audio[0].formatString =~ (/DTS XBR|AC-3|MLP FBA 16-ch|MLP FBA|DTS XLL|E-AC-3 JOC|DTS ES XLL|DTS ES XXCH/) ? {audio[0].formatString.replace ('DTS XBR', ' DTS-HD HR').replace ('AC-3', 'AC3') .replace ('DTS XLL', 'DTS-HD MA') .replace ('MLP FBA 16-ch', 'TrueHD(Atmos)') .replace ('MLP FBA', 'TrueHD').replace ('DTS ES XLL', 'DTS-HD MA').replace ('DTS XLL X', 'DTS X') .replace ('E-AC-3 JOC', 'EAC3(Atmos)') .replace ('DTS ES XXCH', 'DTS-ES Discrete')}: {ac}} {audio[0].channels.replace('1','Mono').replace('2','2.0').replace('6','5.1').replace('7','6.1').replace('8','7.1').replace('9','8.1').replace('10','9.1')} {audio[1].formatString =~ (/DTS XBR|AC-3|MLP FBA 16-ch|MLP FBA|DTS XLL|E-AC-3 JOC|DTS ES XLL|DTS ES XXCH/) ? {audio[1].formatString.replace ('DTS XBR', ' DTS-HD HR').replace ('AC-3', 'AC3') .replace ('DTS XLL', 'DTS-HD MA') .replace ('MLP FBA 16-ch', 'TrueHD(Atmos)') .replace ('MLP FBA', 'TrueHD').replace ('DTS ES XLL', 'DTS-HD MA').replace ('DTS XLL X', 'DTS X') .replace ('E-AC-3 JOC', 'EAC3(Atmos)') .replace ('DTS ES XXCH', 'DTS-ES Discrete')}: {ac}} {audio[1].channels.replace('1','Mono').replace('2','2.0').replace('6','5.1').replace('7','6.1').replace('8','7.1').replace('9','8.1').replace('10','9.1')}
I have been testing it and now it is correct not as I had it, but I see a detail, if the audio is the same in my language ES and in English EN for example it only shows it once like this ES EAC3 5.1 when it should be if the audio is the same same ES-EN EAC3 5.1 or ES|EN with def preferredLang = 'ES'
I tried the code a few posts back but it doesn't work for me, could you indicate what is missing?
Sample:
ES AC3 5.1 - EN DTS-HD MA 7.1 (better audio for both languages)
ES-EN AC3 5.1 if audio it is same


Thanks for your time
kim
Power User
Posts: 1247
Joined: 15 May 2014, 16:17

Re: Multiple audio tracks with different codecs and languages

Post by kim »

cant you just remove the

Code: Select all

.unique()
from

Code: Select all

{[bestPreferredLang, bestBitRate].unique().findAll().join(' - ')}
?
deejayexe
Posts: 8
Joined: 19 Jun 2022, 23:41

Re: Multiple audio tracks with different codecs and languages

Post by deejayexe »

kim wrote: 25 Nov 2022, 15:38 cant you just remove the

Code: Select all

.unique()
from

Code: Select all

{[bestPreferredLang, bestBitRate].unique().findAll().join(' - ')}
?
Ok I have eliminated that function from the code but now in many examples the same language appears repeated like this:

ES EAC3 5.1 - ES EAC3 5.1

And checking the media info has ES and EN.

When what I get should be ES-EN EAC3 5.1
or ES|EN EAC3 5.1 Or if they are different audios ES EAC3 5.1 - EN AC3 5.1
kim
Power User
Posts: 1247
Joined: 15 May 2014, 16:17

Re: Multiple audio tracks with different codecs and languages

Post by kim »

try this:
replace the last part

Code: Select all

	def custom = audioStreams.groupBy{ it.lang }.values()*.findAll{ it.lang == preferredLang || it.bitrate == audioStreams.bitrate.max() }.findAll { it }.collect{ [lang: it.unique{it.lang}.lang.join('|'), codec: it.unique{it.codec}.codec.join(), ch: it.unique{it.ch}.ch.join()] }.sort{ it.lang != preferredLang }.collect{ "${it.lang.replace('null', '')} ${it.codec} ${it.ch}" }.join(' - ')

	any{addToList}{custom}{[bestPreferredLang, bestBitRate].unique().findAll().join(' - ')}{defaultStream}{bestBitRate}{preferredStream}
sample:
ES AC3 5.1 - EN DTS-HD MA 7.1
deejayexe
Posts: 8
Joined: 19 Jun 2022, 23:41

Re: Multiple audio tracks with different codecs and languages

Post by deejayexe »

kim wrote: 27 Nov 2022, 14:41 try this:
replace the last part

Code: Select all

	def custom = audioStreams.groupBy{ it.lang }.values()*.findAll{ it.lang == preferredLang || it.bitrate == audioStreams.bitrate.max() }.findAll { it }.collect{ [lang: it.unique{it.lang}.lang.join('|'), codec: it.unique{it.codec}.codec.join(), ch: it.unique{it.ch}.ch.join()] }.sort{ it.lang != preferredLang }.collect{ "${it.lang.replace('null', '')} ${it.codec} ${it.ch}" }.join(' - ')

	any{addToList}{custom}{[bestPreferredLang, bestBitRate].unique().findAll().join(' - ')}{defaultStream}{bestBitRate}{preferredStream}
sample:
ES AC3 5.1 - EN DTS-HD MA 7.1
Ok I have verified it by removing the last part and adding as it says, but it is not correct, you can see the image with few examples:
Image

This code that u said me with last changes in the end of code

Code: Select all

{
	def preferredLang = 'ES'
	def fallbackLang = any{languages*.ISO2*.upper().join('-')}{'YourDefaultLandHere'}
	def useChFilter = false
	def filter = { [it.lang, it.codec, it.ch].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 custom = audioStreams.groupBy{ it.lang }.values()*.findAll{ it.lang == preferredLang || it.bitrate == audioStreams.bitrate.max() }.findAll { it }.collect{ [lang: it.unique{it.lang}.lang.join('|'), codec: it.unique{it.codec}.codec.join(), ch: it.unique{it.ch}.ch.join()] }.sort{ it.lang != preferredLang }.collect{ "${it.lang.replace('null', '')} ${it.codec} ${it.ch}" }.join(' - ')

	any{addToList}{custom}{[bestPreferredLang, bestBitRate].unique().findAll().join(' - ')}{defaultStream}{bestBitRate}{preferredStream}
	}{'NO_AUDIO'}
}
But i was testing a old post from u https://www.filebot.net/forums/viewtop ... 178#p56178 and here with this code:

Code: Select all

{
	def preferredLang = 'ES'
	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'}
}
Image

I have managed to see this, what I want is to choose the best audio in ES And the following best audio language and in the event that ES and for example EN are the same audio, express it like this, for example ES|EN EAC3 5.1
deejayexe
Posts: 8
Joined: 19 Jun 2022, 23:41

Re: Multiple audio tracks with different codecs and languages

Post by deejayexe »

kim wrote: 27 Nov 2022, 14:41 Can u finally help me about my last post? ;)
kim
Power User
Posts: 1247
Joined: 15 May 2014, 16:17

Re: Multiple audio tracks with different codecs and languages

Post by kim »

I will try to help, but I don't have the energy / time now maybe after Christmas ?

It's not easy, even harder when I can't test it proper with sample files
I need small sample files (5 sec should be enough maybe less)
kim
Power User
Posts: 1247
Joined: 15 May 2014, 16:17

Re: Multiple audio tracks with different codecs and languages

Post by kim »

Hi All
I finally had the energy...
I have tried to make it easy to use and customize...
It should also be better to detect the language if missing tag in file

give it a try:

Code: Select all

{
	types = ['lang', 'codec', 'ch'] /* ['lang','codec', 'ch', 'objects'] */
	replaceSpaces = ' ' /* EN?DTS-HD?MA?7.1?-?EN?DTS?5.1 */
	joinWithin = '.' /* ENG?DTS-HD?MA?7.1 */
	joinStreams = ' - ' /* ENG DTS-HD MA 7.1 ? FRA AC3 5.1 */
	joinLangs = '-' /* ENG?FRA */
	objectsText = 'Objs' /* [11 Objs] */	
	typeLang = 'ISO2' /* ISO2, ISO3 or name (linked to preferredLang and fallbackLang) */
	preferredLang = 'EN' /* 'EN', 'ENG' or 'ENGLISH' (linked to typeLang) */
	secondLang = 'ES' /* 'EN', 'ENG' or 'ENGLISH' */
	fallbackLang = 'EN' /* 'EN', 'ENG' or 'ENGLISH' */
	prefOrder = 'bitrate' /* bitrate, index or default */
	groupByType = 'codec' /* codec, lang or bitrate */
	useChFilter = false /* false or true (hide default ch count e.g. AC3 5.1 vs AC3) */
	excludeCommentary = false /* false or true */
	CommentaryText = '(Commentary)'

	def codecList = /* [Add "DTS XLL" to codecList] = 'DTS XLL':'DTS-HD MA' */
	[ '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' ]

	// Collect Language
	def getLangCode(lang) {
		lang = lang[0].toString().upper()
		def langDB = []
		def languages = Locale.getISOLanguages()

		for (String langCode : languages) {
			Locale locale = new Locale(langCode,langCode)
			langDB << [ISO2: locale.getCountry(), ISO3: locale.getISO3Language().upper(), name: locale.getDisplayLanguage().upper()]
		}
		return any{langDB.find{it.ISO2 == lang}}{langDB.find{it.ISO3 == lang}}{langDB.find{it.name == lang}}
	}

	def fetchLang = any{ getLangCode(omdb.SpokenLanguages)[typeLang]}{getLangCode(info.SpokenLanguages)[typeLang]}{getLangCode(languages)[typeLang]}{getLangCode([fallbackLang])[typeLang]}{' '}
	def getLang = { !it.lang ? it.lang.toString().replace(/null/, fetchLang) : it.lang.toString() }
	def joinSameLang = { it.toUnique{it.lang}.findResults{ getLang(it) }.join([joinLangs]) }
	def auLang = { [ISO2: it.'LanguageString2', ISO3: it.'LanguageString3', name: it.'LanguageString'] }
	def prefLang(lang, streams) { streams.findAll{ it.lang == getLangCode([lang])[typeLang] } }

	// Filters and Sorters
	getObjText = { '['+[it.num, it.text].flatten().join(joinWithin)+']' }
	def onOff(type, types = types) { types.contains(type) }
	def filter = { [it.lang, it.codec, it.ch, it.objects ? getObjText(it.objects) : null, it.commentary ? CommentaryText : null].findAll() }
	def filterLang = { it.findAll{ it.lang == preferredLang || it.lang == secondLang }.sort{ it.lang != preferredLang } }
	def listStream = { it.unique(false).findResults{ filter(it) }*.join(joinWithin) }
	def oneStream = { listStream(it)[0] }
	def sortByType(streams, type = 'bitrate'){ streams.sort{ a, b -> b[type] <=> a[type] } }
	def findBest(streams, type = 'bitrate'){ streams.findAll{ it[type] == streams[type].max() } }
	def noCom = { it.findAll{ !it.commentary } }

	def groupType = { it.groupBy{ it[groupByType] }.findResults{ k,v ->
		allOf{ types.contains('lang') ? joinSameLang(v) : null}{ v.codec[0] }{ v.ch[0] }{ v.objects[0] ? getObjText(v.objects[0]) : null }
		.join(joinWithin) }.join(joinStreams) }

	def audioStreams = []
	def audioClean = { it.replaceAll(/[\p{P}\p{C}]/, ' ').replaceAll(/\p{Space}{2,}/, ' ') }
	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() }
	def separator = { it.replaceAll(/\s/,replaceSpaces) }

	any{ audio.collect{ au ->
		def codec = audioClean(any{ au['CodecID/Hint'] }{ au['Format'] })
		def format_profile = any{ audioClean(au['Format_AdditionalFeatures'])}{}
		def combined = allOf{codec}{format_profile}.join(' ')
		def String ch = any{ channelClean(au.ChannelPositionsString2).tokenize('\\/')*.toDouble().sum().toString() }
			{ 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 )

		audioStreams << ['index' : codecList.findIndexOf { it.key == combined }, 'default' : any {au['default'][0].toBoolean() }{ audio.size == 1 ? true : '' }, 'combined' : combined,
			'bitrate' : any{ toInt(au.BitRate) }{ toInt(au.BitRate_Maximum) }{ dString(au.FrameRate) }{null},
			'codec' : onOff('codec') ? codecList.get(combined, 'Add "' + combined + '" to codecList').space(joinWithin) : null,
			'ch' : onOff('ch') ? useChFilter ? chFilter : ch : null, 'lang' : onOff('lang') ? any{ auLang(au)[typeLang].upper() }{ au.StreamCount == '1' ? fetchLang : null }{null} : null,
			'objects' : onOff('objects') ? (any{def objects = au['NumberOfDynamicObjects']; objects ? [num: toInt(objects), text: objectsText] : ''}{null}) : null, 'commentary' : any {au['Title'].lower() ==~ /.*commentary.*/} {false}]
		excludeCommentary ? audioStreams = noCom(audioStreams) : null
		return audioStreams
	}

	def addToList = audioStreams.codec.findAll{ it.contains('codecList') }.unique().sort()
	def allStreams = separator(listStream(sortByType(audioStreams, prefOrder)).join(joinStreams))
	def groupAllStreams = separator(groupType(sortByType(audioStreams, prefOrder)))
	def bestPrefLang = any{ separator(oneStream(prefLang(preferredLang, audioStreams))) }{}
	def bestPrefSecLang = any{ separator(oneStream(prefLang(secondLang, audioStreams))) }{}
	def customBestPrefOrder = any{ separator(oneStream(findBest(audioStreams, prefOrder))) }{}
	def customGroupBestLang = separator(groupType(sortByType(audioStreams, prefOrder).toUnique{ it.lang }))
	def defaultStream = any{ oneStream(audioStreams.findAll{ it.default == true }) }{ oneStream(audioStreams) }
	def customGroupLangFilter = any{ groupType(filterLang(sortByType(audioStreams, prefOrder))) }{}
	def custom = groupType([(prefLang(preferredLang, audioStreams)), prefLang(secondLang, audioStreams), findBest(audioStreams, prefOrder)].flatten().toUnique{it.lang})

	any{addToList}{custom}{customBestPrefOrder}{defaultStream}
	}{'NO_AUDIO'}
}
to customize edit top part:

Code: Select all

	types = ['lang', 'codec', 'ch'] /* ['lang','codec', 'ch', 'objects'] */
	replaceSpaces = ' ' /* EN?DTS-HD?MA?7.1?-?EN?DTS?5.1 */
	joinWithin = '.' /* ENG?DTS-HD?MA?7.1 */
	joinStreams = ' - ' /* ENG DTS-HD MA 7.1 ? FRA AC3 5.1 */
	joinLangs = '-' /* ENG?FRA */
	objectsText = 'Objs' /* [11 Objs] */	
	typeLang = 'ISO2' /* ISO2, ISO3 or name (linked to preferredLang and fallbackLang) */
	preferredLang = 'EN' /* 'EN', 'ENG' or 'ENGLISH' (linked to typeLang) */
	secondLang = 'ES' /* 'EN', 'ENG' or 'ENGLISH' */
	fallbackLang = 'EN' /* 'EN', 'ENG' or 'ENGLISH' */
	prefOrder = 'bitrate' /* bitrate, index or default */
	groupByType = 'codec' /* codec, lang or bitrate */
	useChFilter = false /* false or true (hide default ch count e.g. AC3 5.1 vs AC3) */
	excludeCommentary = false /* false or true */
	CommentaryText = '(Commentary)'
AND

add or remove (e.g. output)

Code: Select all

{allStreams} = EN.DTS-HD.MA.7.1 - EN.TrueHD.Atmos.7.1 - EN.DTS-HD.MA.5.1 - EN.DTS.5.1 - EN.AC3.5.1 - FR.AC3.5.1 - IT.AC3.5.1 - ES.AC3.5.1 - NL.AC3.5.1 - CA.AC3.5.1
{groupAllStreams} =  EN.DTS-HD.MA.7.1 - EN.TrueHD.Atmos.7.1 - EN.DTS.5.1 - EN-FR-IT-ES-NL-CA.AC3.5.1
{bestPrefLang} = EN.DTS-HD.MA.7.1
{bestPrefSecLang} = ES.AC3.5.1
{customBestPrefOrder} = EN.DTS-HD.MA.7.1
{customGroupBestLang} = EN.DTS-HD.MA.7.1 - FR-IT-ES-NL-CA.AC3.5.1
{defaultStream} = EN.DTS-HD.MA.7.1
{customGroupLangFilter} = EN.DTS-HD.MA.7.1 - EN.TrueHD.Atmos.7.1 - EN.DTS.5.1 - EN-ES.AC3.5.1
{custom} = EN.DTS-HD.MA.7.1 - ES.AC3.5.1
on last part:

Code: Select all

any{addToList}{custom}{customBestPrefOrder}{defaultStream}
deejayexe
Posts: 8
Joined: 19 Jun 2022, 23:41

Re: Multiple audio tracks with different codecs and languages

Post by deejayexe »

kim wrote: 21 Dec 2022, 02:19 Hi All
I finally had the energy...
I have tried to make it easy to use and customize...
It should also be better to detect the language if missing tag in file

give it a try:

Code: Select all

{
	types = ['lang', 'codec', 'ch'] /* ['lang','codec', 'ch', 'objects'] */
	replaceSpaces = ' ' /* EN?DTS-HD?MA?7.1?-?EN?DTS?5.1 */
	joinWithin = '.' /* ENG?DTS-HD?MA?7.1 */
	joinStreams = ' - ' /* ENG DTS-HD MA 7.1 ? FRA AC3 5.1 */
	joinLangs = '-' /* ENG?FRA */
	objectsText = 'Objs' /* [11 Objs] */	
	typeLang = 'ISO2' /* ISO2, ISO3 or name (linked to preferredLang and fallbackLang) */
	preferredLang = 'EN' /* 'EN', 'ENG' or 'ENGLISH' (linked to typeLang) */
	secondLang = 'ES' /* 'EN', 'ENG' or 'ENGLISH' */
	fallbackLang = 'EN' /* 'EN', 'ENG' or 'ENGLISH' */
	prefOrder = 'bitrate' /* bitrate, index or default */
	groupByType = 'codec' /* codec, lang or bitrate */
	useChFilter = false /* false or true (hide default ch count e.g. AC3 5.1 vs AC3) */
	excludeCommentary = false /* false or true */
	CommentaryText = '(Commentary)'

	def codecList = /* [Add "DTS XLL" to codecList] = 'DTS XLL':'DTS-HD MA' */
	[ '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' ]

	// Collect Language
	def getLangCode(lang) {
		lang = lang[0].toString().upper()
		def langDB = []
		def languages = Locale.getISOLanguages()

		for (String langCode : languages) {
			Locale locale = new Locale(langCode,langCode)
			langDB << [ISO2: locale.getCountry(), ISO3: locale.getISO3Language().upper(), name: locale.getDisplayLanguage().upper()]
		}
		return any{langDB.find{it.ISO2 == lang}}{langDB.find{it.ISO3 == lang}}{langDB.find{it.name == lang}}
	}

	def fetchLang = any{ getLangCode(omdb.SpokenLanguages)[typeLang]}{getLangCode(info.SpokenLanguages)[typeLang]}{getLangCode(languages)[typeLang]}{getLangCode([fallbackLang])[typeLang]}{' '}
	def getLang = { !it.lang ? it.lang.toString().replace(/null/, fetchLang) : it.lang.toString() }
	def joinSameLang = { it.toUnique{it.lang}.findResults{ getLang(it) }.join([joinLangs]) }
	def auLang = { [ISO2: it.'LanguageString2', ISO3: it.'LanguageString3', name: it.'LanguageString'] }
	def prefLang(lang, streams) { streams.findAll{ it.lang == getLangCode([lang])[typeLang] } }

	// Filters and Sorters
	getObjText = { '['+[it.num, it.text].flatten().join(joinWithin)+']' }
	def onOff(type, types = types) { types.contains(type) }
	def filter = { [it.lang, it.codec, it.ch, it.objects ? getObjText(it.objects) : null, it.commentary ? CommentaryText : null].findAll() }
	def filterLang = { it.findAll{ it.lang == preferredLang || it.lang == secondLang }.sort{ it.lang != preferredLang } }
	def listStream = { it.unique(false).findResults{ filter(it) }*.join(joinWithin) }
	def oneStream = { listStream(it)[0] }
	def sortByType(streams, type = 'bitrate'){ streams.sort{ a, b -> b[type] <=> a[type] } }
	def findBest(streams, type = 'bitrate'){ streams.findAll{ it[type] == streams[type].max() } }
	def noCom = { it.findAll{ !it.commentary } }

	def groupType = { it.groupBy{ it[groupByType] }.findResults{ k,v ->
		allOf{ types.contains('lang') ? joinSameLang(v) : null}{ v.codec[0] }{ v.ch[0] }{ v.objects[0] ? getObjText(v.objects[0]) : null }
		.join(joinWithin) }.join(joinStreams) }

	def audioStreams = []
	def audioClean = { it.replaceAll(/[\p{P}\p{C}]/, ' ').replaceAll(/\p{Space}{2,}/, ' ') }
	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() }
	def separator = { it.replaceAll(/\s/,replaceSpaces) }

	any{ audio.collect{ au ->
		def codec = audioClean(any{ au['CodecID/Hint'] }{ au['Format'] })
		def format_profile = any{ audioClean(au['Format_AdditionalFeatures'])}{}
		def combined = allOf{codec}{format_profile}.join(' ')
		def String ch = any{ channelClean(au.ChannelPositionsString2).tokenize('\\/')*.toDouble().sum().toString() }
			{ 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 )

		audioStreams << ['index' : codecList.findIndexOf { it.key == combined }, 'default' : any {au['default'][0].toBoolean() }{ audio.size == 1 ? true : '' }, 'combined' : combined,
			'bitrate' : any{ toInt(au.BitRate) }{ toInt(au.BitRate_Maximum) }{ dString(au.FrameRate) }{null},
			'codec' : onOff('codec') ? codecList.get(combined, 'Add "' + combined + '" to codecList').space(joinWithin) : null,
			'ch' : onOff('ch') ? useChFilter ? chFilter : ch : null, 'lang' : onOff('lang') ? any{ auLang(au)[typeLang].upper() }{ au.StreamCount == '1' ? fetchLang : null }{null} : null,
			'objects' : onOff('objects') ? (any{def objects = au['NumberOfDynamicObjects']; objects ? [num: toInt(objects), text: objectsText] : ''}{null}) : null, 'commentary' : any {au['Title'].lower() ==~ /.*commentary.*/} {false}]
		excludeCommentary ? audioStreams = noCom(audioStreams) : null
		return audioStreams
	}

	def addToList = audioStreams.codec.findAll{ it.contains('codecList') }.unique().sort()
	def allStreams = separator(listStream(sortByType(audioStreams, prefOrder)).join(joinStreams))
	def groupAllStreams = separator(groupType(sortByType(audioStreams, prefOrder)))
	def bestPrefLang = any{ separator(oneStream(prefLang(preferredLang, audioStreams))) }{}
	def bestPrefSecLang = any{ separator(oneStream(prefLang(secondLang, audioStreams))) }{}
	def customBestPrefOrder = any{ separator(oneStream(findBest(audioStreams, prefOrder))) }{}
	def customGroupBestLang = separator(groupType(sortByType(audioStreams, prefOrder).toUnique{ it.lang }))
	def defaultStream = any{ oneStream(audioStreams.findAll{ it.default == true }) }{ oneStream(audioStreams) }
	def customGroupLangFilter = any{ groupType(filterLang(sortByType(audioStreams, prefOrder))) }{}
	def custom = groupType([(prefLang(preferredLang, audioStreams)), prefLang(secondLang, audioStreams), findBest(audioStreams, prefOrder)].flatten().toUnique{it.lang})

	any{addToList}{custom}{customBestPrefOrder}{defaultStream}
	}{'NO_AUDIO'}
}
to customize edit top part:

Code: Select all

	types = ['lang', 'codec', 'ch'] /* ['lang','codec', 'ch', 'objects'] */
	replaceSpaces = ' ' /* EN?DTS-HD?MA?7.1?-?EN?DTS?5.1 */
	joinWithin = '.' /* ENG?DTS-HD?MA?7.1 */
	joinStreams = ' - ' /* ENG DTS-HD MA 7.1 ? FRA AC3 5.1 */
	joinLangs = '-' /* ENG?FRA */
	objectsText = 'Objs' /* [11 Objs] */	
	typeLang = 'ISO2' /* ISO2, ISO3 or name (linked to preferredLang and fallbackLang) */
	preferredLang = 'EN' /* 'EN', 'ENG' or 'ENGLISH' (linked to typeLang) */
	secondLang = 'ES' /* 'EN', 'ENG' or 'ENGLISH' */
	fallbackLang = 'EN' /* 'EN', 'ENG' or 'ENGLISH' */
	prefOrder = 'bitrate' /* bitrate, index or default */
	groupByType = 'codec' /* codec, lang or bitrate */
	useChFilter = false /* false or true (hide default ch count e.g. AC3 5.1 vs AC3) */
	excludeCommentary = false /* false or true */
	CommentaryText = '(Commentary)'
AND

add or remove (e.g. output)

Code: Select all

{allStreams} = EN.DTS-HD.MA.7.1 - EN.TrueHD.Atmos.7.1 - EN.DTS-HD.MA.5.1 - EN.DTS.5.1 - EN.AC3.5.1 - FR.AC3.5.1 - IT.AC3.5.1 - ES.AC3.5.1 - NL.AC3.5.1 - CA.AC3.5.1
{groupAllStreams} =  EN.DTS-HD.MA.7.1 - EN.TrueHD.Atmos.7.1 - EN.DTS.5.1 - EN-FR-IT-ES-NL-CA.AC3.5.1
{bestPrefLang} = EN.DTS-HD.MA.7.1
{bestPrefSecLang} = ES.AC3.5.1
{customBestPrefOrder} = EN.DTS-HD.MA.7.1
{customGroupBestLang} = EN.DTS-HD.MA.7.1 - FR-IT-ES-NL-CA.AC3.5.1
{defaultStream} = EN.DTS-HD.MA.7.1
{customGroupLangFilter} = EN.DTS-HD.MA.7.1 - EN.TrueHD.Atmos.7.1 - EN.DTS.5.1 - EN-ES.AC3.5.1
{custom} = EN.DTS-HD.MA.7.1 - ES.AC3.5.1
on last part:

Code: Select all

any{addToList}{custom}{customBestPrefOrder}{defaultStream}
Good evening.

First of all, thank you for your time at Christmas to dedicate your effort and dedication.
I have seen that the code is now simpler and with the final part for customization so that each person who uses it can choose.
I have tried it and now it is very simple and intuitive. I have done a lot of tests and I think that it is already correct.
The only inconvenience has been when adapting it to my code that gives me an error -> Syntax Error: Method definition not expected here. Please define the method at an appropriate place or perhaps try using a block/Closure instead. at line: 31 column: 2. File: Script25.groovy

I put my code here next to yours and it fails in the block of line 31 -> def getLangCode(lang) { :

Code: Select all

{drive}/{n.colon(' - ')} ({y}) {" {tmdb-$id}"}/{n.colon(' - ')} ({y}) {any{fn.match(/Director.?s|director|dir(?=\s)|dir(?=\W)/) ? '[Version Director]' : ''} {fn.match(/Extended|extendida|ext(?=\s)|ext(?=\W)/)? '[V. Extendida]' : ''} {fn.match(/Cine|cinema|cinematográfica|cinematografica|cin(?=\s)|cin(?=\W)/)? '[V. Cine]' : ''} {fn.match(/unrated/)? '- UNRATED' : ''}{fn.match(/uncut/)? '- UNCUT' : ''} {'[Ed. ' + fn.match(/(\d+)(?i:th|º)?.(?i:aniversario|Anniversary|anniv(?=\s)|anniv(?=\W) aniv(?=\s)|aniv(?=\W))/) + " aniversario]"} {fn.match(/Criterion/)? '[Ed. Criterion]' : ''} {fn. match (/remastered|remasterizada|remast(?=\s)|remast(?=\W)/) ? '[Remasterizada]' : ''}} {fn.match(/rotulado castellano|Rotulado Castellano|Rotulado Español|rotulado castellano|ROTULADO CASTELLANO/) && vf=~ /1080|720/ ? '[Rotulado Castellano]': ''}{fn.match(/rotulado castellano|Rotulado Castellano|Rotulado Español|rotulado castellano|ROTULADO CASTELLANO/) && vf=~ 2160 ? '[Rotulado Castellano]': ''} [{ fn.match(/A3P/) + ' ' }{ fn.match(/ATVP/) + ' ' }{ fn.match(/ATVP[+]/) + ' ' }{ fn.match(/ATV/) + ' ' }{ fn.match(/AMZN/) + ' ' }{ fn.match(/DSNP[+]/) + ' ' }{ fn.match(/DSN[+]/) + ' ' }{ fn.match(/DSNP/) + ' ' }{ fn.match(/DSNY/) + ' ' }{ fn.match(/Disney[+]/) + ' ' }{ fn.match(/DisneyPlus/) + ' ' }{ fn.match(/STARZ/) + ' ' }{ fn.match(/HBO/) + ' ' }{ fn.match(/HMAX/) + ' ' }{ fn.match(/HULU/) + ' ' }{ fn.match(/M[+]/) + ' ' }{ fn.match(/NF/) + ' ' }{ fn.match(/IMAX/) + ' ' }{ fn.match(/FLMN/) + ' ' }{allOf {any{any{fn.match(/uhdremux|remux/) && vf=~ 2160 ? 'UHDRemux': ''}{if (megabytes>=26500 && bitrate >= 37000000 && vf=~ 2160) 'UHDRemux'}{fn.match(/uhdrip|UHDrip|mUHD|UHDrip|UHD BluRay/) && vf=~ /1080|720|2160/ ? 'UHDRip': ''}{fn.match(/bdremux|BDRemux|BDremux|BluRay Remux|remux/) && vf=~ /1080|720/ ? 'BDRemux': ''}{if (megabytes>= 915769.6 && bitrate >= 917301504 && vf=~ /1080|720/) 'BDRemux'}}{fn.match(/bluray|Bluray|BluRay|BLURAYRIP|BLURAY/) && vf=~ /1080|720/ ? 'BDRip': ''}{fn.match(/bluray|Bluray|BluRay|BLURAYRIP|BLURAY|BDRip|bdrip/) && vf=~ /2160/ ? 'UHDRip': ''}{fn.match(/microuhd|Microuhd|MICROUHD|microUHD|MICROuhd|MHD|micro/) && vf=~ /2160/ ? 'MicroUHD': ''}{any{fn=~ (/m1080|m720|m480|brm|mhd|M1080|M720|M480|BRM|MHD|MicroHD|micro|Micro|MICROHD/) ? /MicroHD/ : null} {vs.match (/BluRay|WEB-DL/) ? {source.replace ('WEBDL', 'WEB-DL') .replace('webdl', 'WEB-DL') .replace('Webdl', 'WEB-DL') .replace('Web-dl', 'WEB-DL') .replace('web-dl', 'WEB-DL') .replace('WEB-dl', 'WEB-DL') .replace ('WEBrip', 'WEBRip') .replace ('webrip', 'WEBRip') .replace ('WEBRIP', 'WEBRip') .replace ('WEB-rip', 'WEBRip') .replace ('web-rip', 'WEBRip') .replace ('WEB-RIP', 'WEBRip') .replace ('WEB-Rip', 'WEBRip') .replace ('BDRIP', 'BDRip').replace ('bdrip', 'BDRip') .replace ('BDrip', 'BDRip') .replace ('Bdrip', 'BDRip') .replace ('bd-rip', 'BDRip') .replace ('BDRIP', 'BDRip') .replace ('BD-rip', 'BDRip') .replace ('BD-Rip', 'BDRip') .replace ('brrip', 'BRRip') .replace ('Brrip', 'BRRip') .replace ('BRrip', 'BRRip') .replace ('br-rip', 'BRRip') .replace ('BR-rip', 'BRRip') .replace ('BR-Rip', 'BRRip')} : ''}{vs}}}{vf}{hdr.replace('Dolby Vision', 'DV').replace ('Dovi','DV') .replace ('DOVI','DV') .replace ('dovi','DV')} {vc.replace('Microsoft', 'VC-1').replace ('x264','AVC') .replace ('x265','HEVC') .replace ('ATEME','HEVC')} {
	types = ['lang', 'codec', 'ch'] /* ['lang','codec', 'ch', 'objects'] */
	replaceSpaces = ' ' /* EN?DTS-HD?MA?7.1?-?EN?DTS?5.1 */
	joinWithin = ' ' /* ENG?DTS-HD?MA?7.1 */
	joinStreams = ' - ' /* ENG DTS-HD MA 7.1 ? FRA AC3 5.1 */
	joinLangs = '-' /* ENG?FRA */
	objectsText = 'Objs' /* [11 Objs] */	
	typeLang = 'ISO2' /* ISO2, ISO3 or name (linked to preferredLang and fallbackLang) */
	preferredLang = 'ES' /* 'EN', 'ENG' or 'ENGLISH' (linked to typeLang) */
	secondLang = 'EN' /* 'EN', 'ENG' or 'ENGLISH' */
	fallbackLang = 'ES' /* 'EN', 'ENG' or 'ENGLISH' */
	prefOrder = 'bitrate' /* bitrate, index or default */
	groupByType = 'codec' /* codec, lang or bitrate */
	useChFilter = false /* false or true (hide default ch count e.g. AC3 5.1 vs AC3) */
	excludeCommentary = false /* false or true */
	CommentaryText = '(Commentary)'

	def codecList = /* [Add "DTS XLL" to codecList] = 'DTS XLL':'DTS-HD MA' */
	[ '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' ]

	// Collect Language
	def getLangCode(lang) {
		lang = lang[0].toString().upper()
		def langDB = []
		def languages = Locale.getISOLanguages()

		for (String langCode : languages) {
			Locale locale = new Locale(langCode,langCode)
			langDB << [ISO2: locale.getCountry(), ISO3: locale.getISO3Language().upper(), name: locale.getDisplayLanguage().upper()]
		}
		return any{langDB.find{it.ISO2 == lang}}{langDB.find{it.ISO3 == lang}}{langDB.find{it.name == lang}}
	}

	def fetchLang = any{ getLangCode(omdb.SpokenLanguages)[typeLang]}{getLangCode(info.SpokenLanguages)[typeLang]}{getLangCode(languages)[typeLang]}{getLangCode([fallbackLang])[typeLang]}{' '}
	def getLang = { !it.lang ? it.lang.toString().replace(/null/, fetchLang) : it.lang.toString() }
	def joinSameLang = { it.toUnique{it.lang}.findResults{ getLang(it) }.join([joinLangs]) }
	def auLang = { [ISO2: it.'LanguageString2', ISO3: it.'LanguageString3', name: it.'LanguageString'] }
	def prefLang(lang, streams) { streams.findAll{ it.lang == getLangCode([lang])[typeLang] } }

	// Filters and Sorters
	getObjText = { '['+[it.num, it.text].flatten().join(joinWithin)+']' }
	def onOff(type, types = types) { types.contains(type) }
	def filter = { [it.lang, it.codec, it.ch, it.objects ? getObjText(it.objects) : null, it.commentary ? CommentaryText : null].findAll() }
	def filterLang = { it.findAll{ it.lang == preferredLang || it.lang == secondLang }.sort{ it.lang != preferredLang } }
	def listStream = { it.unique(false).findResults{ filter(it) }*.join(joinWithin) }
	def oneStream = { listStream(it)[0] }
	def sortByType(streams, type = 'bitrate'){ streams.sort{ a, b -> b[type] <=> a[type] } }
	def findBest(streams, type = 'bitrate'){ streams.findAll{ it[type] == streams[type].max() } }
	def noCom = { it.findAll{ !it.commentary } }

	def groupType = { it.groupBy{ it[groupByType] }.findResults{ k,v ->
		allOf{ types.contains('lang') ? joinSameLang(v) : null}{ v.codec[0] }{ v.ch[0] }{ v.objects[0] ? getObjText(v.objects[0]) : null }
		.join(joinWithin) }.join(joinStreams) }

	def audioStreams = []
	def audioClean = { it.replaceAll(/[\p{P}\p{C}]/, ' ').replaceAll(/\p{Space}{2,}/, ' ') }
	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() }
	def separator = { it.replaceAll(/\s/,replaceSpaces) }

	any{ audio.collect{ au ->
		def codec = audioClean(any{ au['CodecID/Hint'] }{ au['Format'] })
		def format_profile = any{ audioClean(au['Format_AdditionalFeatures'])}{}
		def combined = allOf{codec}{format_profile}.join(' ')
		def String ch = any{ channelClean(au.ChannelPositionsString2).tokenize('\\/')*.toDouble().sum().toString() }
			{ 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 )

		audioStreams << ['index' : codecList.findIndexOf { it.key == combined }, 'default' : any {au['default'][0].toBoolean() }{ audio.size == 1 ? true : '' }, 'combined' : combined,
			'bitrate' : any{ toInt(au.BitRate) }{ toInt(au.BitRate_Maximum) }{ dString(au.FrameRate) }{null},
			'codec' : onOff('codec') ? codecList.get(combined, 'Add "' + combined + '" to codecList').space(joinWithin) : null,
			'ch' : onOff('ch') ? useChFilter ? chFilter : ch : null, 'lang' : onOff('lang') ? any{ auLang(au)[typeLang].upper() }{ au.StreamCount == '1' ? fetchLang : null }{null} : null,
			'objects' : onOff('objects') ? (any{def objects = au['NumberOfDynamicObjects']; objects ? [num: toInt(objects), text: objectsText] : ''}{null}) : null, 'commentary' : any {au['Title'].lower() ==~ /.*commentary.*/} {false}]
		excludeCommentary ? audioStreams = noCom(audioStreams) : null
		return audioStreams
	}

	def addToList = audioStreams.codec.findAll{ it.contains('codecList') }.unique().sort()
	def allStreams = separator(listStream(sortByType(audioStreams, prefOrder)).join(joinStreams))
	def groupAllStreams = separator(groupType(sortByType(audioStreams, prefOrder)))
	def bestPrefLang = any{ separator(oneStream(prefLang(preferredLang, audioStreams))) }{}
	def bestPrefSecLang = any{ separator(oneStream(prefLang(secondLang, audioStreams))) }{}
	def customBestPrefOrder = any{ separator(oneStream(findBest(audioStreams, prefOrder))) }{}
	def customGroupBestLang = separator(groupType(sortByType(audioStreams, prefOrder).toUnique{ it.lang }))
	def defaultStream = any{ oneStream(audioStreams.findAll{ it.default == true }) }{ oneStream(audioStreams) }
	def customGroupLangFilter = any{ groupType(filterLang(sortByType(audioStreams, prefOrder))) }{}
	def custom = groupType([(prefLang(preferredLang, audioStreams)), prefLang(secondLang, audioStreams), findBest(audioStreams, prefOrder)].flatten().toUnique{it.lang})

	any{addToList}{custom}{customBestPrefOrder}{defaultStream}
	}{'NO_AUDIO'}
} {allOf{audioLanguages}{textLanguages} =~ /spa/ ? null : ' VO'}{textLanguages =~ /spa/ && !(audioLanguages =~ /spa/) ? ' VOSE ' : null}{textLanguages.size() > 0 ? ' Subs' : 'null'}.join(' ')}] {" {tmdb-$id}"}
kim
Power User
Posts: 1247
Joined: 15 May 2014, 16:17

Re: Multiple audio tracks with different codecs and languages

Post by kim »

that needed a lot of work

I changed a lot so something may be broken... try it out:

Code: Select all

{drive}{'/' + n.colon(' - ')}{' (' + y + ')'}{'/' + n.colon(' - ')}{' (' + y + ')'}
{ any{ fn.match(/Director.?s|director|dir(?=\s)|dir(?=\W)/) ? '[Version Director]' : ''}
	{ fn.match(/Extended|extendida|ext(?=\s)|ext(?=\W)/) ? '[V. Extendida]' : '' }
	{ fn.match(/Cine|cinema|cinematográfica|cinematografica|cin(?=\s)|cin(?=\W)/) ? '[V. Cine]' : '' }
	{ '- ' + fn.match(/unrated|uncut/).upper() }
	{ '[Ed. ' + fn.match(/(\d+)(?i:th|º)?.(?i:aniversario|Anniversary|anniv(?=\s)|anniv(?=\W) aniv(?=\s)|aniv(?=\W))/) + ' aniversario]' }
	{ fn.match(/Criterion/) ? '[Ed. Criterion]' : '' }
	{ fn.match (/remastered|remasterizada|remast(?=\s)|remast(?=\W)/) ? '[Remasterizada]' : ''}
	{ fn =~ /(?i)rotulado castellano|Rotulado Español/ && vf=~ /2160|1080|720/ ? '[Rotulado Castellano]' : '' }
}
{ allOf{ fn.match(/A3P|ATVP|ATVP[+]|ATV|AMZN|DSNP[+]|DSN[+]|DSNP|DSNY|Disney[+]|DisneyPlus|STARZ|HBO|HMAX|HULU|M[+]|NF|IMAX|FLMN/) }
	{any{ fn =~ /(?i)uhdremux|remux/ && vf=~ 2160 ? 'UHDRemux': '' }
		{ if (megabytes >= 26500 && bitrate >= 37000000 && vf=~ 2160) 'UHDRemux' }
		{ fn =~ /(?i)UHDrip|mUHD|UHD BluRay/ && vf=~ /1080|720|2160/ ? 'UHDRip': '' }
		{ fn =~ /(?i)BDRemux|BluRay Remux|remux/ && vf=~ /1080|720/ ? 'BDRemux': '' }
		{ if (megabytes >= 915769.6 && bitrate >= 917301504 && vf=~ /1080|720/) 'BDRemux' }
		{ fn =~ /(?i)BluRay|BLURAYRIP/ && vf=~ /1080|720/ ? 'BDRip': '' }
		{ fn =~ /(?i)BluRay|BLURAYRIP|BDRip/ && vf=~ /2160/ ? 'UHDRip': '' }
		{ fn =~ /(?i)microUHD|MHD|micro/ && vf=~ /2160/ ? 'MicroUHD': '' }
		{ fn =~ (/m1080|m720|m480|BRM|MHD|MicroHD|Micro/) ? /MicroHD/ : null }
		{ vs.match(/BluRay|WEB-DL/) ? source.replaceFirst(/(?i)WEB-?DL/, 'WEB-DL').replaceFirst(/(?i)WEB-?Rip/, 'WEB-DL').replaceFirst(/(?i)BD-?Rip/, 'BDRip').replaceFirst(/(?i)BR-?Rip/, 'BDRip') : '' }
		{vs}
	}
		{vf}
		{ hdr.replaceFirst(/(?i)Dolby Vision|Dovi/, 'DV') }
		{ vc.replace('Microsoft', 'VC-1').replaceFirst(/(?i)x264|x265|ATEME/, 'HEVC') }
}
{
	types = ['lang', 'codec', 'ch']
	replaceSpaces = ' '
	joinWithin = ' '
	joinStreams = ' - '
	joinLangs = '-'
	objectsText = 'Objs'
	typeLang = 'ISO2'
	preferredLang = 'ES'
	secondLang = 'EN'
	fallbackLang = 'ES'
	prefOrder = 'bitrate'
	groupByType = 'codec'
	useChFilter = false
	excludeCommentary = false
	CommentaryText = '(Commentary)'

	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' ]

	// Collect Language
	def getLangCode(lang) {
		lang = lang[0].toString().upper()
		def langDB = []
		def languages = Locale.getISOLanguages()

		for (String langCode : languages) {
			Locale locale = new Locale(langCode,langCode)
			langDB << [ISO2: locale.getCountry(), ISO3: locale.getISO3Language().upper(), name: locale.getDisplayLanguage().upper()]
		}
		return any{langDB.find{it.ISO2 == lang}}{langDB.find{it.ISO3 == lang}}{langDB.find{it.name == lang}}
	}

	def fetchLang = any{ getLangCode(omdb.SpokenLanguages)[typeLang]}{getLangCode(info.SpokenLanguages)[typeLang]}{getLangCode(languages)[typeLang]}{getLangCode([fallbackLang])[typeLang]}{' '}
	def getLang = { !it.lang ? it.lang.toString().replace(/null/, fetchLang) : it.lang.toString() }
	def joinSameLang = { it.toUnique{it.lang}.findResults{ getLang(it) }.join([joinLangs]) }
	def auLang = { [ISO2: it.'LanguageString2', ISO3: it.'LanguageString3', name: it.'LanguageString'] }
	def prefLang(lang, streams) { streams.findAll{ it.lang == getLangCode([lang])[typeLang] } }

	// Filters and Sorters
	getObjText = { '['+[it.num, it.text].flatten().join(joinWithin)+']' }
	def onOff(type, types = types) { types.contains(type) }
	def filter = { [it.lang, it.codec, it.ch, it.objects ? getObjText(it.objects) : null, it.commentary ? CommentaryText : null].findAll() }
	def filterLang = { it.findAll{ it.lang == preferredLang || it.lang == secondLang }.sort{ it.lang != preferredLang } }
	def listStream = { it.unique(false).findResults{ filter(it) }*.join(joinWithin) }
	def oneStream = { listStream(it)[0] }
	def sortByType(streams, type = 'bitrate'){ streams.sort{ a, b -> b[type] <=> a[type] } }
	def findBest(streams, type = 'bitrate'){ streams.findAll{ it[type] == streams[type].max() } }
	def noCom = { it.findAll{ !it.commentary } }

	def groupType = { it.groupBy{ it[groupByType] }.findResults{ k,v ->
		allOf{ types.contains('lang') ? joinSameLang(v) : null}{ v.codec[0] }{ v.ch[0] }{ v.objects[0] ? getObjText(v.objects[0]) : null }
		.join(joinWithin) }.join(joinStreams) }

	def audioStreams = []
	def audioClean = { it.replaceAll(/[\p{P}\p{C}]/, ' ').replaceAll(/\p{Space}{2,}/, ' ') }
	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() }
	def separator = { it.replaceAll(/\s/,replaceSpaces) }

	any{ audio.collect{ au ->
		def codec = audioClean(any{ au['CodecID/Hint'] }{ au['Format'] })
		def format_profile = any{ audioClean(au['Format_AdditionalFeatures'])}{}
		def combined = allOf{codec}{format_profile}.join(' ')
		def String ch = any{ channelClean(au.ChannelPositionsString2).tokenize('\\/')*.toDouble().sum().toString() }
			{ 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 )

		audioStreams << ['index' : codecList.findIndexOf { it.key == combined }, 'default' : any {au['default'][0].toBoolean() }{ audio.size == 1 ? true : '' }, 'combined' : combined,
			'bitrate' : any{ toInt(au.BitRate) }{ toInt(au.BitRate_Maximum) }{ dString(au.FrameRate) }{null},
			'codec' : onOff('codec') ? codecList.get(combined, 'Add "' + combined + '" to codecList').space(joinWithin) : null,
			'ch' : onOff('ch') ? useChFilter ? chFilter : ch : null, 'lang' : onOff('lang') ? any{ auLang(au)[typeLang].upper() }{ au.StreamCount == '1' ? fetchLang : null }{null} : null,
			'objects' : onOff('objects') ? (any{def objects = au['NumberOfDynamicObjects']; objects ? [num: toInt(objects), text: objectsText] : ''}{null}) : null, 'commentary' : any {au['Title'].lower() ==~ /.*commentary.*/} {false}]
		excludeCommentary ? audioStreams = noCom(audioStreams) : null
		return audioStreams
	}

	def addToList = audioStreams.codec.findAll{ it.contains('codecList') }.unique().sort()
	def allStreams = separator(listStream(sortByType(audioStreams, prefOrder)).join(joinStreams))
	def groupAllStreams = separator(groupType(sortByType(audioStreams, prefOrder)))
	def bestPrefLang = any{ separator(oneStream(prefLang(preferredLang, audioStreams))) }{}
	def bestPrefSecLang = any{ separator(oneStream(prefLang(secondLang, audioStreams))) }{}
	def customBestPrefOrder = any{ separator(oneStream(findBest(audioStreams, prefOrder))) }{}
	def customGroupBestLang = separator(groupType(sortByType(audioStreams, prefOrder).toUnique{ it.lang }))
	def defaultStream = any{ oneStream(audioStreams.findAll{ it.default == true }) }{ oneStream(audioStreams) }
	def customGroupLangFilter = any{ groupType(filterLang(sortByType(audioStreams, prefOrder))) }{}
	def custom = groupType([(prefLang(preferredLang, audioStreams)), prefLang(secondLang, audioStreams), findBest(audioStreams, prefOrder)].flatten().toUnique{it.lang})

	 '[' + any{addToList}{bestPrefLang}{customBestPrefOrder}{defaultStream} + ']'

	}{'NO_AUDIO'}
}{' ' + (allOf{textLanguages}
{textLanguages =~ /spa/ ? null : ' VO'}
{textLanguages =~ /spa/ && !(audioLanguages =~ /spa/) ? ' VOSE ' : null}
{textLanguages.size() > 0 ? ' Subs' : 'null'}.join(' '))
}
Post Reply