Some questions regarding renaming

Any questions? Need some help?
kim
Power User
Posts: 1251
Joined: 15 May 2014, 16:17

Re: Some questions regarding renaming

Post by kim »

So you use this ? :

Code: Select all

{
	def preferredGer = 'DEU'
	def preferredEng = 'ENG'
	def useChFilter = false
	def filter = { [it.codec, it.ch, it.objects, it.lang] }

	def codecList =
	[
	'MP3' : 'MP3',
	'PCM' : 'PCM',
	'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 oneStream = { it.collect{ filter(it) }*.minus(null).unique().flatten().join(' ') }
	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{ '[' + au['NumberOfDynamicObjects'] + ' Objs]' }{null}, 'lang' : any{ au.'LanguageString3'.upperInitial() }{null} ]
		return audioStreams
	}

	def addToList = audioStreams.codec.findAll{ it.contains('Add to') }.unique().sort()
	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 })
	def bestPreferredGer = any{audioStreams.findAll{it.lang == preferredGer }.sort{it.bitrate}.reverse().collect{ filter(it) }*.minus(null).unique().get(0).join(' ')}{preferredStream}
	def bestPreferredEng = any{audioStreams.findAll{it.lang == preferredEng }.sort{it.bitrate}.reverse().collect{ filter(it) }*.minus(null).unique().get(0).join(' ')}{bestBitRate}

	any{addToList}{bestPreferredLang}{defaultStream}{bestBitRate}{preferredStream}
	}{'NO_AUDIO'}
}
Wrong:

Code: Select all

	def preferredGer = 'DEU'
	def preferredEng = 'ENG'
Correct:

Code: Select all

	def preferredGer = 'Deu'
	def preferredEng = 'Eng'
or change to

Code: Select all

.findAll{it.lang.upper() == preferredGer }
else
I do not see a problem, maybe you use a dif. version of mediainfo and/or filebot
also I don't have bluray, so can't test.

Code: Select all

FileBot -script fn:sysinfo
FileBot 4.8.5 (r6224)
MediaInfo: 18.12
justkidding
Posts: 38
Joined: 12 Oct 2015, 20:42

Re: Some questions regarding renaming

Post by justkidding »

No, I tried with this:

Code: Select all

{
	def preferredLang = 'Eng'
	def useChFilter = false
	def filter = { [it.codec, it.ch, it.objects, it.lang] }

	def codecList =
	[
	'MP3' : 'MP3',
	'PCM' : 'PCM',
	'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 oneStream = { it.collect{ filter(it) }*.minus(null).unique().flatten().join(' ') }
	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{ '[' + au['NumberOfDynamicObjects'] + ' Objs]' }{null}, 'lang' : any{ au.'LanguageString3'.upperInitial() }{null} ]
		return audioStreams
	}

	def addToList = audioStreams.codec.findAll{ it.contains('Add to') }.unique().sort()
	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 })
	def bestPreferredLang = any{ audioStreams.findAll{ it.lang == preferredLang }.sort{ a, b -> b.bitrate <=> a.bitrate }.collect{ filter(it) }*.minus(null).unique().get(0).join(' ') }{}

	any{addToList}{bestPreferredLang}{defaultStream}{bestBitRate}{preferredStream}
	}{'NO_AUDIO'}
}
It's your script without any changes. I wanted to make sure that it is not due that the changes I made.

The format I posted is using your old version which is working perfectly fine. In this I use

Code: Select all

{null}, 'lang' : any{au.'LanguageString3'.upper()	}
instead of

Code: Select all

{null}, 'lang' : any{au.'LanguageString3'.upperInitial()	}
The format I posted is working great for all files.

I am using the newest of filebot (4.9.0 BETA). I think it includes the newest version of mediainfo as well. I don't exactly know how to check :).
kim
Power User
Posts: 1251
Joined: 15 May 2014, 16:17

Re: Some questions regarding renaming

Post by kim »

output if you replace ?

Code: Select all

any{addToList}{bestPreferredLang}{defaultStream}{bestBitRate}{preferredStream}
with

Code: Select all

audioStreams
does it fail ?

Code: Select all

audioStreams.lang
you also try:
audioStreams.index
audioStreams.default
audioStreams.codec
audioStreams.combined
audioStreams.bitrate
audioStreams.objects


sample

Code: Select all

[{index=17, default=true, codec=DTS-HD MA, combined=DTS XLL, ch=7.1, bitrate=4094371, objects=null, lang=Eng}, {index=22, default=false, codec=TrueHD Atmos, combined=MLP FBA 16 ch, ch=7.1, bitrate=3158581, objects=[11 Objs], lang=Eng}, {index=5, default=false, codec=AC3, combined=AC 3, ch=5.1, bitrate=448000, objects=null, lang=Fra}]
justkidding
Posts: 38
Joined: 12 Oct 2015, 20:42

Re: Some questions regarding renaming

Post by justkidding »

It all returns the same value 'NO_AUDIO'.
I think it is due to the any{au.collect......}{'NO_AUDIO'} part.
For testing purpose I deleted the "any"-condition around "au.collect" and then I got the following error message:

Code: Select all

[Expression yields empty value: Cannot compare java.lang.String with value '93.75' and java.lang.Integer with value '640,000']
Does this maybe help? :)
kim
Power User
Posts: 1251
Joined: 15 May 2014, 16:17

Re: Some questions regarding renaming

Post by kim »

For now it looks like it's best to remove

Code: Select all

{ dString(au.FrameRate) }
the problem (not good to compare to different things )

Code: Select all

{def bitrate = '640000'.toInteger(); def framerate = '93.750'.toDouble().toString(); [bitrate,framerate].max()}
kim
Power User
Posts: 1251
Joined: 15 May 2014, 16:17

Re: Some questions regarding renaming

Post by kim »

Can you test this out?

Code: Select all

{
	def preferredGer = 'DEU'
	def preferredEng = 'ENG'
	def useChFilter = false
	def filter = { [it.codec, it.ch, it.objects, it.lang] }

	def codecList =
	[
	'MP3' : 'MP3',
	'PCM' : 'PCM',
	'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 oneStream = { it.collect{ filter(it) }*.minus(null).unique().flatten().join(' ') }
	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) }{null}, 'frameRate' : any{ au.FrameRate.toDouble() * ch.toDouble() }{null},
		'objects' : any{ '[' + au['NumberOfDynamicObjects'] + ' Objs]' }{null}, 'lang' : any{ au.'LanguageString3'.upper() }{null} ]	
		return audioStreams

	}

	def addToList = audioStreams.codec.findAll{ it.contains('Add to') }.unique().sort()
	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 bestFrameRate = oneStream(audioStreams.findAll{ it.frameRate == audioStreams.frameRate.max() })
	def defaultStream = oneStream(audioStreams.findAll{ it.default == true })
	def bestPreferredGer = any{audioStreams.findAll{it.lang == preferredGer }.sort{it.bitrate}.reverse().collect{ filter(it) }*.minus(null).unique().get(0).join(' ')}{''}
	def bestPreferredEng = any{audioStreams.findAll{it.lang == preferredEng }.sort{it.bitrate}.reverse().collect{ filter(it) }*.minus(null).unique().get(0).join(' ')}{''}

	bestFrameRate
	}{'NO_AUDIO'}
}
justkidding
Posts: 38
Joined: 12 Oct 2015, 20:42

Re: Some questions regarding renaming

Post by justkidding »

This one is working great :) Thanks a lot!
nartana
Posts: 35
Joined: 02 May 2019, 22:28

Re: Some questions regarding renaming

Post by nartana »

Hi Kim. I've spent a few days w/ FAQs and forum posts, trying to learn how to implement your edited codecList script.

I used Superimpose to save a UTF-8 encoded version of your def script in a folder on my Synology.

I added [{best.Codec+'.'+best.FormatProfile.replaceAll(/\s?\/.*|E-AC-3\+/)}] to my CLI expression.

I expected this to produce a rename of the movie using the highest-bitrate, biggest-file "Stream #2" contained in the .mkv. That is the TrueHD Atmos stream (MediaInfo app confirms it's there, as Stream #2, but the renamed file always has the #1 stream, which EAC.)

My problem seems to be: I don't know where to save custom .groovy scripts. I've looked all over. I know it's me, missing something... but I can't figure out where to put them.

And I've tried renaming operations four diff. ways, hoping to see the biggest audio stream selected: Windows10 via CLI, MacMini via CLI, Windows10 via SSH to Syno DS1819+, and MacMini via SSH to Syno DS1815+. All of which produce renamed files, as always. None of which produce a rename using the TrueHD Atmos 8ch. stream, which is the biggest stream.

Wildcard: the GUI version of FileBot via Windows 10 *does* rename with the TrueHD Atmos 8ch stream. But I'd prefer to continue using FileBot via CLI. If I can find my way past this hurdle w/ custom scripts. Everything else is working great, and has been for a year, re. CLI/FileBot.

Any links or info that could lead this big dumb horse to water, would be most appreciated. I looked a lot, but that doesn't mean it's not totally obvious. Thank you...
nartana
Posts: 35
Joined: 02 May 2019, 22:28

Re: Some questions regarding renaming

Post by nartana »

tl;dr

SPECIFIC GOAL
I want to use yr codecList (def) edit as a custom script, so my CLI rename ops can include attributes like "bestStream.space('.')" etc.

SPECIFIC ISSUE
When I copy/paste yr def info into Superimpose... what's the recommended 'bin' for saving one's custom groovy scripts?

GENERAL ISSUE
When I read answers in posts (example) there may be 2-3 snippets of code, and I don't always know what goes where. If there were an example or 'this code goes here' right below the selectable snippet... I think I'd get it. I also think that's just me, most people get it as-is. Anyways, am I correct that yr longer snippet "def codecList" is what I copy/paste into Superimpose and make into a script, and save somewhere? And is it correct to say the "bestStream.space('.')" example is part of an attribute in a CLI expression for renaming?
nartana
Posts: 35
Joined: 02 May 2019, 22:28

Re: Some questions regarding renaming

Post by nartana »

After two days more trial/error, I figured out (a) where to save and (b) how to call a custom script.

I'd like to go to the next level with automation and I'm willing to do the work. Hoping for help with a couple questions.

1. Kim, is the "def codecList" code intended to be a script that defines a specific term (i.e. the "best" audio stream)?
2. Is this a particular "type" of script defining what do with a variable (i.e. "best") to modify an attribute (i.e. best.audio.stream)?
3. In short, I'd "def codecList" because I want -rename to choose the highest-bitrate audio stream for {aco}{channels}{af} attribs.
4. When I want to define a variable, this way, I always have to call the custom script I make, so the variable makes sense to FB?
5. Can I put other recurring elements of my expressions in the same "def codecList" script, to automate additional actions?

For example, this (below) is my current CLI expression, that I use via SSH from Terminal while logged into my Synology server:

Code: Select all

filebot -rename -r /volume1/Media/Unsorted/Movies --db TheMovieDB -non-strict --conflict auto --output /volume1/Media/SORTED/Movies --format "{n}  { def bestaudio = audio.max{ it.Channels as int } 
  bestaudio.Format_AdditionalFeatures + ' ' + bestaudio.language + ' ' }   ({y})/{audio.ChannelPositionsString2*.split(' / ')*.collect{ it.split('/')*.toBigDecimal().sum() }*.max().max()} {n} ({y})/ {def au = audio.ChannelPositionsString2*.split(' / ')*.collect{ it.split('/')*.toBigDecimal().sum() }*.max(); if(au[1] > au[0]) return au[1] else channels} {channels(s.'ChannelPositions/String2'.split(' / ').collect{ it.split('/')*.toBigDecimal().sum() }.max().setScale(1, java.math.RoundingMode.HALF_UP).toPlainString())}
/{n} {minutes}mins [{mbps}] ({resolution}) {aco} {channels} [{af}]/{ny} @{mbps} {tags.upper[]} {' CD'+pi}" --action move
Here's the script call (below) I need to add to my CLI expression so "best" makes sense to FileBot?

Code: Select all

filebot -script /volume1/Media/FileBot/scripts/bestAudioStream.groovy
A. Can I put any/all of the other CLI code I'm using... in the bestAudioStream script?
B. Can/should one custom script do many things (i.e. define "best" AND automate ADDT'L rename actions/attributes?)
C. Can/should a CLI expression call multiple, custom groovy scripts at a time?
D. Or should I keep definitions separate from automated actions (in scripts)?

Movies in my Sorted folder have a simple {ny} title for the actual video file.
However, the video file lives in a subfolder with more info (example below):
John Wick Chapter 2 (2017) 122mins [36.7 Mbps] (1920x1080) Dolby TrueHD with Dolby Atmos 7.1 [8ch]

FileBot does an awesome job of making all that happen — but sometimes the 'best' audio stream is #2 (thus, FileBot renames using the inferior #1 audio stream, which is not FileBot's fault, but it's also not the output I prefer.)

["May God forgive me for colonizing this thread." -me, right now]
nartana
Posts: 35
Joined: 02 May 2019, 22:28

Re: Some questions regarding renaming

Post by nartana »

I've been trying to find my own answers but the majority of scripting FAQ links here = 404s.

So, I'll leave this here and I'll stop, now. I want to learn but it's like there are pages missing.

Goal: -rename using 'best' (highest bitrate) audio.

Problem: highest bitrate isn't always default stream.

Solution: use the def codecList code Kim created.

Code: Select all

{
	def preferredLang = 'Eng'
	def useChFilter = false
	def filter = { [it.codec, it.ch, it.objects, it.lang] }

	def codecList =
	[
	'MP3' : 'MP3',
	'PCM' : 'PCM',
	'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 oneStream = { it.collect{ filter(it) }*.minus(null).unique().flatten().join(' ') }
	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{ '[' + au['NumberOfDynamicObjects'] + ' Objs]' }{null}, 'lang' : any{ au.'LanguageString3'.upperInitial() }{null} ]
		return audioStreams
	}

	def addToList = audioStreams.codec.findAll{ it.contains('Add to') }.unique().sort()
	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 })
	def bestPreferredLang = any{ audioStreams.findAll{ it.lang == preferredLang }.sort{ a, b -> b.bitrate <=> a.bitrate }.collect{ filter(it) }*.minus(null).unique().get(0).join(' ') }{}

	allStreams.join(' & ').space('.')
	preferredStream.space('.')
	defaultStream.space('.')
	bestBitRate.space('.')
	[defaultStream, bestBitRate].unique().join(' & ').space('.')
	[bestBitRate, preferredStream].unique().join(' & ').space('.')
	any{addToList}{bestPreferredLang}{defaultStream}{bestBitRate}{preferredStream}
	}{'NO_AUDIO'}
}

{
a	udio.collect { s ->
	allOf
		{any
			{s.'CodecID/Hint'}
			{s.'Codec'.contains('DTS-HD') ? s.'Codec': null}
			{s.'Format'}
		}
		{s.'Format_Profile'}*.replaceAll('/ MA|/ Core|/ ES Matrix|/ TrueHD')*.replaceAll('AC-3', 'AC3').join('.')
}.flatten()*.trim().join(', ').matches(/(?i).*Atmos.*/) ? "TrueHD+Atmos" : ""
}

{
	def allAudioStringsVar = (audio.collect { s ->
	allOf
		{any
			{s.'CodecID/Hint'}
			{s.'Codec'.contains('DTS-HD') ? s.'Codec': null}
			{s.'Format'}
		}
		{s.'Format_Profile'}*.replaceAll('/ MA|/ Core|/ ES Matrix')*.replaceAll('AC-3', 'AC3')*.replaceAll('TrueHD[+]Atmos / TrueHD', 'Atmos').join('.')
}.flatten()*.trim().join(', ').replaceAll('DTS-HD.X', 'DTS-X').replaceAll('TrueHD.Atmos', 'TrueHD+Atmos'))
}
Problem: Unsure how to 'call' that def (above) in my CLI expression (below):

Code: Select all

-rename -r /volume1/Media/UNSORTED/ --db TheMovieDB -non-strict --conflict auto --output /volume1/Media/SORTED/ --format "{n} ({y})/{n} {minutes}mins [{mbps}] ({resolution}) {aco} {channels} [{af}]/{n}{bestBitRate.space('.')}[]{allAudioStringsVar.matches(/(?i).*Atmos.*/) ? "TrueHD+Atmos" : ""}]/{ny} @{mbps} {tags.upper[]}" --action move
I must have to add something (above) so "bestBitRate" means something. What do I add to that?

My local path to custom scripts: /volume1/Media/FileBot/scripts/codecList.groovy

Without defining bestBitRate my CLI (above) uses "Default" #1 audio stream, producing:
1917 118mins [18.0 Mbps] (1920x800) Dolby Digital 7.1 [8ch]

But the "Default" #1 stream isn't always the 'best' audio — in the current example, the #2 stream is TrueHD Atmos 7.1 (less lossy, more info).

So, I'd like to rename movies w/ 'best' (highest bitrate, most channels) stream:
1917 118mins [18.0 Mbps] (1920x800) Dolby TrueHD with Atmos 7.1 [8ch]

But there's a piece missing for me, between def/script and CLI, and I can't find the answer (tho I know it's obvious.)
User avatar
rednoah
The Source
Posts: 22923
Joined: 16 Nov 2011, 08:59
Location: Taipei
Contact:

Re: Some questions regarding renaming

Post by rednoah »

1.
I strongly recommend prototyping your custom format in the Format Editor GUI first, and only copy & pasting it into your command-line call once you have thoroughly tested it in the GUI.

Use the @file syntax for reading command-line arguments from external text files, because you'll want to avoid the command-line argument parser.


2.
As far as custom formats are concerned, the language is vanilla Groovy, with some additional variables and functions predefined by FileBot:
viewtopic.php?t=10824


These two links should be all you need, though the Groovy manual will probably assume that you already have some Java programming expertise:
rednoah wrote: 20 May 2019, 07:16 FileBot uses the Groovy language for format expressions, filter expressions and execute expressions:
https://groovy-lang.org/single-page-documentation.html

The most important top-level bindings are documented here:
https://www.filebot.net/naming.html
:idea: Please read the FAQ and How to Request Help.
kim
Power User
Posts: 1251
Joined: 15 May 2014, 16:17

Re: Some questions regarding renaming

Post by kim »

How you use format files:

Code: Select all

filebot -rename -r %MovieFolder% --output %ScapedFolder% --log all --log-file custom.log --action move --conflict skip -non-strict --def ut_label=Movie --def movieFormat=@"movieFormat.groovy"
save as e.g. movieFormat.groovy

Code: Select all

{ny}{'/' + n}{' ' + minutes + 'mins'}{' [' + mbps + ']'}{' (' + resolution + ')'}{'/' + n}
{
	def useChFilter = false
	def filter = { [it.codec, it.ch, it.objects, it.lang] }

	def codecList =
	[
	'MP3' : 'MP3',
	'PCM' : 'PCM',
	'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 oneStream = { it.collect{ filter(it) }*.minus(null).unique().flatten().join(' ') }
	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) }{null}, 'frameRate' : any{ au.FrameRate.toDouble() * ch.toDouble() }{null},
		'objects' : any{ '[' + au['NumberOfDynamicObjects'] + ' Objs]' }{null}, 'lang' : any{ au.'LanguageString3'.upperInitial() }{null} ]
		return audioStreams
	}

	def addToList = audioStreams.codec.findAll{ it.contains('Add to') }.unique().sort()
	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 })

	' ' + any{addToList}{bestBitRate}{preferredStream}{defaultStream}
	}{'NO_AUDIO'}
}
{'/' + ny}{' @' + mbps}{' ' + tags.upper()}
btw: ALWAYS test in the GUI ;)
User avatar
rednoah
The Source
Posts: 22923
Joined: 16 Nov 2011, 08:59
Location: Taipei
Contact:

Re: Some questions regarding renaming

Post by rednoah »

Note that --def movieFormat is a script option specific to the amc script and thus has no effect on filebot -rename calls.

You can specify a format file like so:

Code: Select all

--format /path/to/file.groovy
:idea: Please read the FAQ and How to Request Help.
nartana
Posts: 35
Joined: 02 May 2019, 22:28

Re: Some questions regarding renaming

Post by nartana »

Thank you for all of this, Kim and rednoah. I've got this script/call working successfully. It's really great. Renaming entire library this weekend.

I'd like to customize rednoah's "mi.groovy" script to output the same "best" audio stream codec+channels as Kim's code does for my renames.

Below is my version of Kim's code:

Code: Select all

{any{"$collection/($y) $n"}{"$n ($y)"}}{'/' + n}{' (' + minutes + ' mins) '}{resolution}{' ['}{mbps}{']'}

{
	def useChFilter = false
	def filter = { [it.codec, it.ch] }
	def ignore = ".nfo"
	def codecList =
	[
	'MP3' : 'MP3',
	'PCM' : 'PCM',
	'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 oneStream = { it.collect{ filter(it) }*.minus(null).unique().flatten().join(' ') }
	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) }{null}, 'frameRate' : any{ au.FrameRate.toDouble() * ch.toDouble() }{null},
		'objects' : any{ '[' + au['NumberOfDynamicObjects'] + ' Objects]' }{null}]
		return audioStreams
	}

	def addToList = audioStreams.codec.findAll{ it.contains('Add to') }.unique().sort()
	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 })
	' ' + any{addToList}{bestBitRate}{preferredStream}{defaultStream}
	}{'NO_AUDIO'}
}
	{'/' + ny}{' ' + tags.upper()}



If I add those definitions to the mi.groovy script, would I call any specific attributes (e.g...)

Code: Select all

def model = [
	'Name': 'fn',
	'Container': 'cf',
	'Resolution': 'resolution',
	'Video Codec': 'vc',
	'Video Format': 'vf',
	'Audio Codec': 'ac',
	'Audio Channels': 'channels',
	'Audio Languages': 'audioLanguages',
	'Subtitle Languages': 'textLanguages',
	'Duration': 'hours',
	'File Size': 'megabytes',
	'Path': 'f.canonicalPath',
	'Original Name': 'original',
	'Extended Attributes': 'json'
]
If this type of customizing (of mi.groovy) is a whole thing onto itself, no worries — you've done so much already, to help me get over this hurdle with renaming. (Thank you.)
nartana
Posts: 35
Joined: 02 May 2019, 22:28

Re: Some questions regarding renaming

Post by nartana »

To close out this question (above) re. <mi.groovy> parameters — I'll assume there's no way to customize the "generate database report" Groovy function (to make it report audio info based on the <useChFilter> definitions in Kim's script.) And I'll assume the reports generated by <mi.groovy> will become increasingly accurate as MediaInfo continues to publish version updates. I see there's a new version (20.3) of MediaInfo available now, so I'll try to generate a report with standard {af} and {aco} parameters, and I'll see how it changes over time.

Thanks again.
nartana
Posts: 35
Joined: 02 May 2019, 22:28

Re: Some questions regarding renaming

Post by nartana »

FYI, I updated the MediaInfo plugin (OSX) and used the local version of Filebot to create a report for my media database (which exists on a server) — I wanted to share the results, as I added some parameters in a custom <mi.groovy> script. Specifically, the existing "AC" column (standard in mi.groovy) vs. the custom "ACO" column, which reported codec names more similar to those generated by Kim's "def = useChFilter" script that I use for renaming.

Here's a side-by-side sample (screenshot, below) of several dozen rows of movie files, in the custom <mi.groovy> script I made:

Image
nartana
Posts: 35
Joined: 02 May 2019, 22:28

Re: Some questions regarding renaming

Post by nartana »

FWIW follow-up.

I generated a <mi.groovy> report from a directory of "Series" media files. The screenshot below represents attributes generated by {ac} (left column, below) and {aco} (right column, below).

Image

The {aco} values are closer to the attributes generated by Kim's "def = useChFilter" script. (Apples and oranges, I know.)

My <fn:sysinfo> report (below) shows MediaInfo v.19.09. My MAS version is v.20.03. (Apples and oranges, again, I know.)

Before geneating the <mi.groovy> report, I had updated homebrew and reinstalled the Filebot package via Terminal.

Code: Select all

Wilsonian@Wilsonians-Mac-mini ~ % filebot -script fn:sysinfo
FileBot 4.9.0 (r7234)
JNA Native: 6.1.0
MediaInfo: 19.09
7-Zip-JBinding: 9.20
Chromaprint: 1.4.4
Extended Attributes: OK
Unicode Filesystem: OK
Script Bundle: 2020-03-16 (r625)
Groovy: 3.0.2
JRE: OpenJDK Runtime Environment 13.0.2
JVM: 64-bit OpenJDK 64-Bit Server VM
CPU/MEM: 4 Core / 2.1 GB Max Memory / 51 MB Used Memory
OS: Mac OS X (x86_64)
HW: Darwin Wilsonians-Mac-mini.local 19.4.0 Darwin Kernel Version 19.4.0: Wed Mar  4 22:28:40 PST 2020; root:xnu-6153.101.6~15/RELEASE_X86_64 x86_64
Again, this is all "FWIW" info. Unless my MediaInfo version issue is a PICNIC error (that's my only IT joke) — if there's something I should be doing, to update <libmediainfo.dylib> to v.20, I'm happy to correct that and re-generate <mi.groovy> reports. In case the newest MediaInfo library produces further variances between attributes generated by {ac} and {aco}.
User avatar
rednoah
The Source
Posts: 22923
Joined: 16 Nov 2011, 08:59
Location: Taipei
Contact:

Re: Some questions regarding renaming

Post by rednoah »

{aco} does seem generally informativ though. I'll add that as a column in mi.groovy by default.
:idea: Please read the FAQ and How to Request Help.
Post Reply