Page 1 of 2

Point to external file in GUI ?

Posted: 23 May 2019, 22:13
by stephen147
Would this:

Code: Select all

DriveLetter:\Path\{@"external_format.ext"}
or this:

Code: Select all

{@"external_format.ext"}
currently, be possible at the moment?


E.G. for formatFile

Code: Select all

{@"Z:\Movies & TV\New Downloads\_FileBot Bindings\Format_Movie.groovy"}
E.G. for just path/formatFile

Code: Select all

[*]{@"Z:\Movies & TV\New Downloads\_FileBot Bindings\Format_Movie.groovy"}

Re: Point to external file in GUI ?

Posted: 24 May 2019, 03:13
by rednoah
Unfortunately, the format engine itself currently does not support any kind of @file syntax.

Re: Point to external file in GUI ?

Posted: 24 May 2019, 08:50
by devster
But wouldn't this be possible with normal Groovy code?
Like new File("path/to/file") or similar?

Re: Point to external file in GUI ?

Posted: 24 May 2019, 09:54
by rednoah
I'm not aware of any way that would allow you to execute code from a String within the current script context (i.e. with the same bindings and everything). Though I wouldn't be surprised if there was a way to somehow make it work without any changes to the current code base. :lol:



EDIT:

It's so straight-forward and self-explanatory, I almost feel stupid for not knowing this was working all along. :lol:

Code: Select all

{evaluate('plex.name')}

Code: Select all

{evaluate('/path/to/file.groovy' as File)}
:arrow: http://docs.groovy-lang.org/latest/html ... cript.html

:!: r6394 is required though, since older revisions wouldn't allow dynamic script execution.

Re: Point to external file in GUI ?

Posted: 24 May 2019, 11:09
by devster
Leaving security implications aside, could this be the basis for format templating?

Code: Select all

{evaluate('/path/to/video.groovy' as File)}
{evaluate('/path/to/audio.groovy' as File)}

Re: Point to external file in GUI ?

Posted: 24 May 2019, 11:14
by rednoah
Yep, you can pass in String or File arguments to evaluate() so you can do pretty much anything, dynamically.

e.g. these two can be considered equivalent:

Code: Select all

{plex}

Code: Select all

{evaluate('plex')}

Re: Point to external file in GUI ?

Posted: 24 May 2019, 14:16
by stephen147

Code: Select all

{evaluate('Z:/Movies & TV/New Downloads/_FileBot/Format_Movie.groovy' as File)}
Doesn't work this code in the Format_Movie.groovy file.

Code: Select all

{
  //////////////////////////////////////////////
  // MOVIE BINDING
  //////////////////////////////////////////////
  // Posted here: https://www.filebot.net/forums/viewtopic.php?f=4&t=10766&p=43842#p43842
  //////////////////////////////////////////////
  def space = call{' '};
  // Root Directory
  def dir_root = 'Z:\\Movies & TV\\'+
  call{hd.matches(/(?i)SD/) ? '1. SD\\' : ' '}+
  call{hd.matches(/(?i)HD/) ? '2. HD\\' : ' '}+
  call{hd.matches(/(?i)UHD/) ? '3. UHD\\' : ' '};
  // Main Title e.g.
  // 1408 (2007) (Director's Cut) 1080p HD Blu-ray non-HDR x264 DTS5.1ch_SiNNERS
  // 300 (2007) 720p HD BRRip non-HDR x265 DTS 5.1ch_ESiR
  // Deadpool (2016) 2160p UHD WEB-DL non-HDR AVC DTS-HD MA 7.1ch_DDR
  // Deadpool 2 (2018) (Super Duper Cut) 2160p UHD Blu-ray REMUX HDR10bit ATEME TrueHD Atmos 13Obj 7.1ch_EPSiLON
  def main_title = call{n}+
  space + '(' + call{y} + ')'+
  space + call{fn.matches(/(?i).+\b25th.+?anniv.+/) ? '(25th Anniv. Edition)' : ' '}+
  space + call{fn.matches(/(?i).+\b\(limited\b.*?\).+/) ? '(Limited Edition)' : ' '}+
  space + call{fn.matches(/(?i).+\b\(uncut\b.*?\).+/) ? '(Uncut)' : ' '}+
  space + call{fn.matches(/(?i).+\bcollector.+?s.+?edition\b.+/) ? '(Collector\'s Edition)' : ' '}+
  space + call{fn.matches(/(?i).+\bdirect.+?cut\b.+/) ? '(Director\'s Cut)' : ' '}+
  space + call{fn.matches(/(?i).+\bextended.+?\b.+/) ? '(Extended)' : ' '}+
  space + call{fn.matches(/(?i).+\bextended.+?edit\b.+/) ? '(Extended Edition)' : ' '}+
  space + call{fn.matches(/(?i).+\bimax\b.+/) ? '(IMAX Edition)' : ' '}+
  space + call{fn.matches(/(?i).+\blimited\b.+/) ? '(Limited)' : ' '}+
  space + call{fn.matches(/(?i).+\bremastered\b.+/) ? '(Remastered)' : ' '}+
  space + call{fn.matches(/(?i).+\bsuper.+duper.+cut\b.+/) ? '(Super Duper Cut)' : ' '}+
  space + call{fn.matches(/(?i).+\btheatrical\b.+/) ? '(Theatrical)' : ' '}+
  space + call{fn.matches(/(?i).+\bunrated\b.+/) ? '(Unrated)' : ' '}+
  space + call{any{fn.match(/\([^\()+?[^\d]+?\)\s*/)} {' '}{' '}}+
  space + call{self.vf ? self.vf : self.hpi}+
  space + call{hd}+
  space + call{source.matches(/(?i)blu.*ray/) ? 'Blu-ray' : {source} ?: 'WEB-DL'}+
  space + call{fn.matches(/(?i).+\bremux\b.+/) ? 'REMUX' : ' '}+
  // Only calls {hdr} if it's not SD else non-HDR
  space + call{if (hd =~ 'HD') {any{hdr + "-" + bitdepth + 'bit'}{'non-HDR'}}}+
  space + call{vc}+
  space +
  // Call audio
  // Thread here where I got the base code from: https://www.filebot.net/forums/viewtopic.php?f=5&t=5285
  call {
    def mCFP =
    [
    'AAC LC SBR PS' : 'AAC',
    'AAC LC SBR' : 'AAC',
    'AAC LC' : 'AAC',
    'AC 3 Dep' : 'E-AC3',
    'AC 3' : 'AC3',
    'DTS 96 24' : 'DTS 96-24',
    'DTS ES XBR' : 'DTS-HD HRA',
    'DTS ES XLL' : 'DTS-HD MA',
    'DTS ES XXCH XBR' : 'DTS-HD HRA',
    'DTS ES XXCH XLL' : 'DTS-HD MA',
    'DTS ES XXCH' : 'DTS-ES',
    'DTS ES' : 'DTS-ES',
    'DTS XBR' : 'DTS-HD HRA',
    'DTS XLL X' : 'DTS X',
    'DTS XLL' : 'DTS-HD MA',
    'DTS' : 'DTS',
    'E AC 3 JOC' : 'EAC3 Atmos',
    'E AC 3' : 'EAC3',
    'MLP FBA 16 ch' : 'TrueHD Atmos',
    'MLP FBA' : 'TrueHD',
    'MP3' : 'MP3',
    'MPEG Audio' : 'MP2',
    'PCM' : 'PCM'
    ];
    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/,'5.1').replaceAll(/8/,'7.1') };
    def audioCollection = audio.collect
    { au ->
      def channels = any{ channelClean(au['ChannelPositionsString2'])}{ channelClean(au['ChannelsOriginal'])}{ channelClean(au['Channels']) };
      def dynChannel = {au['NumberOfDynamicObjects'] + 'Obj'};
      def ch = channels.tokenize('\\/').take(3)*.toDouble().inject(0, { a, b -> a + b }).findAll { it > 0 }.max().toString() + 'ch';
      def codec = audioClean(any{ au['CodecID/Hint'] }{ au['Format'] });
      def format_profile = { ( au['Format_AdditionalFeatures'] != null) ? audioClean(au['Format_AdditionalFeatures']) : '' };
      def combined = allOf{codec}{format_profile}.join(' ');
      def stream = allOf { mCFP.get(combined, 'UNKNOWN_FORMAT--'+combined+'--') } { dynChannel } { ch };
    };
    return audioCollection[0].join( ' ' )
    } +
    '_'+
    // Group
    call{ any{ fn.match(/(?<=[_-])[^\s_-]+?$/)} {group} {'_NA'} };
    // Language
    def lang = call{ any{'.'+subt} + {fn.matches(/(?i).+sdh.+/) ? '_SDH' : ''} {any{fn.match(/(?i)\(foreignpartsonly\)/)} '' } };
    // Extension
    // def ext = call{'.'+ext};
    // call all the bindings to create the result
    // call(dir_root) +
    (call(main_title).replace(':', ';').replace('*', '') + call(lang)).replaceAll(/null/,'')
  }
:!: This is the error

Code: Select all

Expression yields empty value: startup failed:
Z:\Movies & TV\New Downloads\_FileBot\Format_Movie_Test.groovy: 1: Ambiguous expression could be either a parameterless closure expression or an isolated open code block;
   solution: Add an explicit closure parameter list, e.g. {it -> ...}, or force it to be treated as an open block by giving it a label, e.g. L:{...} @ line 1, column 1.

Re: Point to external file in GUI ?

Posted: 24 May 2019, 15:40
by rednoah
Keep in mind that we're evaluating Groovy code, not FileBot Format Expression code.

:arrow: You need to get rid of the outer {...} because Groovy code only starts within the outer {...}.

YES:

Code: Select all

plex
NO:

Code: Select all

{plex}
By evaluating the Groovy code {plex} and not plex, you're effectively doing this, which of course doesn't work:

Code: Select all

{{plex}}

Re: Point to external file in GUI ?

Posted: 24 May 2019, 17:43
by stephen147
It is possible on a basic level

This works:

Code: Select all

{call(evaluate('Z:/Movies & TV/New Downloads/_FileBot/Format_Movie_Test.groovy' as File))}
With groovy file:

Code: Select all

  (
  {n}
  )
Just need to get it working with my more complex format.

:?: Would it be even possible before I torture myself? :lol:

:!: BUT, there's a lot of call() fn inside that code. Not sure how this will fair!

SCRAP that last thing.

I want the external file to be interchangeable between the GUI and the CLI.

Re: Point to external file in GUI ?

Posted: 24 May 2019, 18:06
by rednoah
I sense that you still don't truly understand the difference between FileBot Format Expression Code and Groovy Code.


1.
This is a FileBot Format Expression:

Code: Select all

A/{"Groovy Code " +1}/{"Groovy Code " +2}
It consists of 4 parts:
* Literal: A/
* Code: "Groovy Code " +1
* Literal: /
* Code: "Groovy Code " +2


2.
Here's example for Groovy Code:

Code: Select all

"Groovy Code " +1
Simple code like accessing a variable is no more or less Groovy Code:

Code: Select all

n
Notably, {...} is used in the Groovy Language for code blocks and closures.


3.
Consider a FileBot Format Expression such as this:

Code: Select all

{allOf{x}{y}{z}}
The outer {...} is FileBot Format Expression logic, and whatever is inside is Groovy logic, i.e. completely different even though it looks similar:

Code: Select all

allOf{x}{y}{z}
Looking at the Groovy code, it's just a more elegant way of writing this:

Code: Select all

Closure functionX = { x }
Closure functionY = { y }
Closure functionZ = { z }
allOf(functionX, functionY, functionZ)


EDIT:

:!: It does seem that global functions like allOf(Closure...) and call(Closure) are not available in the context sub-scripts. IDK. Not ideal. Probably not easy to fix scripts that rely on those.

Adding this to the top of your script should do the trick:

Code: Select all

import static net.filebot.format.ExpressionFormatFunctions.*

EDIT 2:

Fixed with r6395.

Re: Point to external file in GUI ?

Posted: 24 May 2019, 18:33
by devster
On MacOS the fix (import) seems to work, r6224.

Re: Point to external file in GUI ?

Posted: 24 May 2019, 18:36
by devster
@stephen147
try the below:

Code: Select all

import static net.filebot.format.ExpressionFormatFunctions.*

	//////////////////////////////////////////////
	// MOVIE BINDING
	//////////////////////////////////////////////
	// Posted here: https://www.filebot.net/forums/viewtopic.php?f=4&t=10766&p=43842#p43842
	//////////////////////////////////////////////
	String space = ' '
	// Root Directory
	def dir_root = allOf
		{ 'Z:\\Movies & TV\\' }
		{ if (hd.matches(/(?i)SD/)) '1. SD\\' }
		{ if (hd.matches(/(?i)HD/)) '2. HD\\' }
		{ if (hd.matches(/(?i)UHD/)) '3. UHD\\' }
		.join(' ')
	// Main Title e.g.
	// 1408 (2007) (Director's Cut) 1080p HD Blu-ray non-HDR x264 DTS5.1ch_SiNNERS
	// 300 (2007) 720p HD BRRip non-HDR x265 DTS 5.1ch_ESiR
	// Deadpool (2016) 2160p UHD WEB-DL non-HDR AVC DTS-HD MA 7.1ch_DDR
	// Deadpool 2 (2018) (Super Duper Cut) 2160p UHD Blu-ray REMUX HDR10bit ATEME TrueHD Atmos 13Obj 7.1ch_EPSiLON
	def main_title = allOf
		{n}
		{ '(' + y + ')' }
		{ fn.matches(/(?i).+\b25th.+?anniv.+/) '(25th Anniv. Edition)' }
		{ fn.matches(/(?i).+\b\(limited\b.*?\).+/) '(Limited Edition)' }
		{ fn.matches(/(?i).+\b\(uncut\b.*?\).+/) '(Uncut)' }
		{ fn.matches(/(?i).+\bcollector.+?s.+?edition\b.+/) '(Collector\'s Edition)' }
		{ fn.matches(/(?i).+\bdirect.+?cut\b.+/) '(Director\'s Cut)' }
		{ fn.matches(/(?i).+\bextended.+?\b.+/) '(Extended)' }
		{ fn.matches(/(?i).+\bextended.+?edit\b.+/) '(Extended Edition)' }
		{ fn.matches(/(?i).+\bimax\b.+/) '(IMAX Edition)' }
		{ fn.matches(/(?i).+\blimited\b.+/) '(Limited)' }
		{ fn.matches(/(?i).+\bremastered\b.+/) '(Remastered)' }
		{ fn.matches(/(?i).+\bsuper.+duper.+cut\b.+/) '(Super Duper Cut)' }
		{ fn.matches(/(?i).+\btheatrical\b.+/) '(Theatrical)' }
		{ fn.matches(/(?i).+\bunrated\b.+/) '(Unrated)' }
		{ any{fn.match(/\([^\()+?[^\d]+?\)\s*/)}{'  '}}
		{ self.vf ? self.vf : self.hpi }
		{ hd }
		{ source.matches(/(?i)blu.*ray/) ? 'Blu-ray' : {source} ?: 'WEB-DL'}
		{ fn.matches(/(?i).+\bremux\b.+/) 'REMUX' }
		// Only calls {hdr} if it's not SD else non-HDR
		{ if (hd =~ 'HD') any{ hdr + "-" + bitdepth + 'bit'}{'non-HDR'}}
		{ vc }
		// Call audio
		// Thread here where I got the base code from: https://www.filebot.net/forums/viewtopic.php?f=5&t=5285
		{
			def mCFP =
			[
				'AAC LC SBR PS' : 'AAC',
				'AAC LC SBR' : 'AAC',
				'AAC LC' : 'AAC',
				'AC 3 Dep' : 'E-AC3',
				'AC 3' : 'AC3',
				'DTS 96 24' : 'DTS 96-24',
				'DTS ES XBR' : 'DTS-HD HRA',
				'DTS ES XLL' : 'DTS-HD MA',
				'DTS ES XXCH XBR' : 'DTS-HD HRA',
				'DTS ES XXCH XLL' : 'DTS-HD MA',
				'DTS ES XXCH' : 'DTS-ES',
				'DTS ES' : 'DTS-ES',
				'DTS XBR' : 'DTS-HD HRA',
				'DTS XLL X' : 'DTS X',
				'DTS XLL' : 'DTS-HD MA',
				'DTS' : 'DTS',
				'E AC 3 JOC' : 'EAC3 Atmos',
				'E AC 3' : 'EAC3',
				'MLP FBA 16 ch' : 'TrueHD Atmos',
				'MLP FBA' : 'TrueHD',
				'MP3' : 'MP3',
				'MPEG Audio' : 'MP2',
				'PCM' : 'PCM'
			]
			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/,'5.1').replaceAll(/8/,'7.1') }
			def audioCollection = audio.collect{ au ->
				def channels = any{ channelClean(au['ChannelPositionsString2'])}{ channelClean(au['ChannelsOriginal'])}{ channelClean(au['Channels']) }
				def dynChannel = {au['NumberOfDynamicObjects'] + 'Obj'}
				def ch = channels.tokenize('\\/').take(3)*.toDouble().inject(0, { a, b -> a + b }).findAll { it > 0 }.max().toString() + 'ch'
				def codec = audioClean(any{ au['CodecID/Hint'] }{ au['Format'] })
				def format_profile = { ( au['Format_AdditionalFeatures'] != null) ? audioClean(au['Format_AdditionalFeatures']) : '' }
				def combined = allOf{codec}{format_profile}.join(' ')
				def stream = allOf{ mCFP.get(combined, 'UNKNOWN_FORMAT--'+combined+'--') }{ dynChannel }{ ch }
			}
			audioCollection[0].join( ' ' )
		}
		{ '_' }
		// Group
		{ any{ fn.match(/(?<=[_-])[^\s_-]+?$/)} {group} {'_NA'} }
		.join(space)
	// Language
	def lang = any
		{'.' + subt + fn.matches(/(?i).+sdh.+/) ? '_SDH' : ''}
		{any{fn.match(/(?i)\(foreignpartsonly\)/)} '' }
	allOf{main_title.replace(':', ';').replace('*', '')}{lang}.join('').replaceAll(/null/,'')

Re: Point to external file in GUI ?

Posted: 24 May 2019, 18:56
by stephen147
devster wrote: 24 May 2019, 18:36 @stephen147
Thanks for this. That's great but I was looking for a way to use my existing code and just call a function to parse the text of it into the GUI.

Seeing as it both works in the CLI and GUI I thought it would be easy enough to make work via my first post here.

Re: Point to external file in GUI ?

Posted: 24 May 2019, 19:03
by devster
the script I posted is just a conversion of yours without some call{}. In my mac it works both in GUI and CLI using

Code: Select all

{evaluate("file.groovy" as File)}

Re: Point to external file in GUI ?

Posted: 24 May 2019, 19:12
by stephen147
Sorry, I don't think you understand.

My format code viewtopic.php?p=44253#p44242 can be pasted directly into the GUI and it also works using the CLI.

What I was after was a solution to point to that file in the GUI.

Maybe @rednoah miss understood also.

Re: Point to external file in GUI ?

Posted: 24 May 2019, 19:34
by rednoah
1.
The GUI indeed does indeed not support just pointing to an external file. This has not changed.


2.
We have discovered a new way to load Groovy code within Groovy expressions, which closely related to what you're asking for, but not exactly that.

GUI:

Code: Select all

{evaluate('file.groovy' as File)}
CLI:

Code: Select all

--format "{evaluate('file.groovy' as File)}"
So we have a format, which itself loads and evaluates code from a file. So you can't just give the GUI a file path to use as format, but you can paste a format which further delegates to some file. So you can achieve your goal of having a single script shared by both CLI and GUI.

Re: Point to external file in GUI ?

Posted: 24 May 2019, 20:18
by stephen147
1. Right so... as long as we're clear now.

Perhaps some sort of new addition for my use-case ?

No worries if not.

Re: Point to external file in GUI ?

Posted: 24 May 2019, 20:46
by rednoah
Not planned for now. There's already too many ways to do almost same thing via different angles.

Re: Point to external file in GUI ?

Posted: 24 May 2019, 21:11
by stephen147
Okay. If you do find the time to allow some basic switches like --format in the GUI then that would be great (if possible).

Re: Point to external file in GUI ?

Posted: 25 May 2019, 04:25
by rednoah
1.
stephen147 wrote: 24 May 2019, 21:11 Okay. If you do find the time to allow some basic switches like --format in the GUI then that would be great (if possible).
I don't understand. The Edit Format is the equivalent of the --format option.


2.
If you just want a script that modifies the FileBot GUI settings before running the GUI, then you can write a script for that:

Code: Select all

_def.each{ k, v -> 
  java.util.prefs.Preferences.userRoot().node('net/filebot/ui/rename').put(k, v) 
}
Usage:

Code: Select all

filebot -script /path/to/PutPreferences.groovy --def rename.format.episode=@/path/to/SeriesFormat.groovy rename.format.movie=@/path/to/MovieFormat.groovy
Test:

Code: Select all

filebot -script fn:preferences

3.
What so unsatisfactory about this solution though?
viewtopic.php?f=8&t=10837&p=44261#p44256

Just copy these simple formats that just delegate to your file in both CLI and GUI, and henceforth you never ever need to look at anything but your file. Since you're already using a single-expression-just-groovy-code style format, it'll work perfectly for you.

Re: Point to external file in GUI ?

Posted: 25 May 2019, 15:46
by stephen147
2. It's not updating the GUI. I've not had it running while running this below:

Code: Select all

filebot -script /path/to/PutPreferences.groovy --def rename.format.episode=@/path/to/SeriesFormat.groovy rename.format.movie=@/path/to/MovieFormat.groovy
OUTPUT:

Code: Select all

Z:\Movies & TV\New Downloads\_FileBot>filebot -script fn:preferences
Print User Preference Node: /
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE preferences SYSTEM "http://java.sun.com/dtd/preferences.dtd">
<preferences EXTERNAL_XML_VERSION="1.0">
  <root type="user">
    <map/>
    <node name="net">
      <map/>
      <node name="filebot">
        <map>
          <entry key="getting.started" value="1"/>
          <entry key="dialog.open.folder" value="Z:\Movies & TV\3. UHD"/>
        </map>
        <node name="ui">
          <map>
            <entry key="window.width" value="2115"/>
            <entry key="window.height" value="755"/>
            <entry key="window.x" value="155"/>
            <entry key="window.y" value="2"/>
            <entry key="panel.selected" value="0"/>
          </map>
          <node name="rename">
            <map>
              <entry key="rename.format.movie" value="{
  //////////////////////////////////////////////
  // MOVIE BINDING
  //////////////////////////////////////////////
  // Posted here: https://www.filebot.net/forums/viewtopic.php?f=4&t=10766&p=43842#p43842
  //////////////////////////////////////////////
  def space = call{' '};
  // Root Directory
  def dir_root = 'Z:\\Movies & TV\\'+
  call{hd.matches(/(?i)SD/) ? '1. SD\\' : ' '}+
  call{hd.matches(/(?i)HD/) ? '2. HD\\' : ' '}+
  call{hd.matches(/(?i)UHD/) ? '3. UHD\\' : ' '};
  // Main Title e.g.
  // 1408 (2007) (Director's Cut) 1080p HD Blu-ray non-HDR x264 DTS5.1ch_SiNNERS
  // 300 (2007) 720p HD BRRip non-HDR x265 DTS 5.1ch_ESiR
  // Deadpool (2016) 2160p UHD WEB-DL non-HDR AVC DTS-HD MA 7.1ch_DDR
  // Deadpool 2 (2018) (Super Duper Cut) 2160p UHD Blu-ray REMUX HDR10bit ATEME TrueHD Atmos 13Obj 7.1ch_EPSiLON
  def main_title = call{n}+
  space + '(' + call{y} + ')'+
  space + call{fn.matches(/(?i).+\b25th.+?anniv.+/) ? '(25th Anniv. Edition)' : ' '}+
  space + call{fn.matches(/(?i).+\b\(limited\b.*?\).+/) ? '(Limited Edition)' : ' '}+
  space + call{fn.matches(/(?i).+\b\(uncut\b.*?\).+/) ? '(Uncut)' : ' '}+
  space + call{fn.matches(/(?i).+\bcollector.+?s.+?edition\b.+/) ? '(Collector\'s Edition)' : ' '}+
  space + call{fn.matches(/(?i).+\bdirect.+?cut\b.+/) ? '(Director\'s Cut)' : ' '}+
  space + call{fn.matches(/(?i).+\bextended.+?\b.+/) ? '(Extended)' : ' '}+
  space + call{fn.matches(/(?i).+\bextended.+?edit\b.+/) ? '(Extended Edition)' : ' '}+
  space + call{fn.matches(/(?i).+\bimax\b.+/) ? '(IMAX Edition)' : ' '}+
  space + call{fn.matches(/(?i).+\blimited\b.+/) ? '(Limited)' : ' '}+
  space + call{fn.matches(/(?i).+\bremastered\b.+/) ? '(Remastered)' : ' '}+
  space + call{fn.matches(/(?i).+\bsuper.+duper.+cut\b.+/) ? '(Super Duper Cut)' : ' '}+
  space + call{fn.matches(/(?i).+\btheatrical\b.+/) ? '(Theatrical)' : ' '}+
  space + call{fn.matches(/(?i).+\bunrated\b.+/) ? '(Unrated)' : ' '}+
  space + call{any{fn.match(/\([^\()+?[^\d]+?\)\s*/)} {' '}{' '}}+
  space + call{self.vf ? self.vf : self.hpi}+
  space + call{hd}+
  space + call{source.matches(/(?i)blu.*ray/) ? 'Blu-ray' : {source} ?: 'WEB-DL'}+
  space + call{fn.matches(/(?i).+\bremux\b.+/) ? 'REMUX' : ' '}+
  // Only calls {hdr} if it's not SD else non-HDR
  space + call{if (hd =~ 'HD') {any{hdr + "-" + bitdepth + 'bit'}{'non-HDR'}}}+
  space + call{vc}+
  space +
  // Call audio
  // Thread here where I got the base code from: https://www.filebot.net/forums/viewtopic.php?f=5&t=5285
  call {
    def mCFP =
    [
    'AAC LC SBR PS' : 'AAC',
    'AAC LC SBR' : 'AAC',
    'AAC LC' : 'AAC',
    'AC 3 Dep' : 'E-AC3',
    'AC 3' : 'AC3',
    'DTS 96 24' : 'DTS 96-24',
    'DTS ES XBR' : 'DTS-HD HRA',
    'DTS ES XLL' : 'DTS-HD MA',
    'DTS ES XXCH XBR' : 'DTS-HD HRA',
    'DTS ES XXCH XLL' : 'DTS-HD MA',
    'DTS ES XXCH' : 'DTS-ES',
    'DTS ES' : 'DTS-ES',
    'DTS XBR' : 'DTS-HD HRA',
    'DTS XLL X' : 'DTS X',
    'DTS XLL' : 'DTS-HD MA',
    'DTS' : 'DTS',
    'E AC 3 JOC' : 'EAC3 Atmos',
    'E AC 3' : 'EAC3',
    'MLP FBA 16 ch' : 'TrueHD Atmos',
    'MLP FBA' : 'TrueHD',
    'MP3' : 'MP3',
    'MPEG Audio' : 'MP2',
    'PCM' : 'PCM'
    ];
    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/,'5.1').replaceAll(/8/,'7.1') };
    def audioCollection = audio.collect
    { au ->
      def channels = any{ channelClean(au['ChannelPositionsString2'])}{ channelClean(au['ChannelsOriginal'])}{ channelClean(au['Channels']) };
      def dynChannel = {au['NumberOfDynamicObjects'] + 'Obj'};
      def ch = channels.tokenize('\\/').take(3)*.toDouble().inject(0, { a, b -> a + b }).findAll { it > 0 }.max().toString() + 'ch';
      def codec = audioClean(any{ au['CodecID/Hint'] }{ au['Format'] });
      def format_profile = { ( au['Format_AdditionalFeatures'] != null) ? audioClean(au['Format_AdditionalFeatures']) : '' };
      def combined = allOf{codec}{format_profile}.join(' ');
      def stream = allOf { mCFP.get(combined, 'UNKNOWN_FORMAT--'+combined+'--') } { dynChannel } { ch };
    };
    return audioCollection[0].join( ' ' )
    } +
    '_'+
    // Group
    call{ any{ fn.match(/(?<=[_-])[^\s_-]+?$/)} {group} {'_NA'} };
    // Language
    def lang = call{ any{'.'+subt} + {fn.matches(/(?i).+sdh.+/) ? '_SDH' : ''} {any{fn.match(/(?i)\(foreignpartsonly\)/)} '' } };
    // Extension
    // def ext = call{'.'+ext};
    // call all the bindings to create the result
    // call(dir_root) +
    (call(main_title).replace(':', ';').replace('*', '') + call(lang)).replaceAll(/null/,'')
  }"/>
              <entry key="rename.last.format.state" value="Movie"/>
              <entry key="dialog.select.width" value="979"/>
              <entry key="dialog.select.height" value="380"/>
              <entry key="dialog.select.repeat" value="false"/>
              <entry key="rename.format.episode" value="//////////////////////////////////////////////
// TV BINDING
//////////////////////////////////////////////
Z:/Movies & TV/TV/{n.replace(':','-').replaceAll(/[\/:*?"<>|]/,' - ')}/{n.replace(':','-').replaceAll(/[\/:*?"<>|]/,' - ')}
({any{self.d}{'0000-00-00'}})
[{info.network}]
{episode.special ? 'S00E'+special.pad(2) : s00e00}
{t.after(/^[.]+/).replace(':','-').replaceAll(/[\/:*?"<>|]/,' - ')}
{self.vf ? self.vf + "" : self.hpi}
{fn.matches(/(?i).+\bAMZN\b.+/) ? "AMZN" : ""}
{"${self.source ?: 'NA'}"}
{ac}
{audio[0].channels.replaceAll(/2/, "2ch").replaceAll(/6/, "5.1ch")}    {vc}_{any{"$group"}{fn.match(/(?<=[_-])[^\s_-]+?$/)}{'NA'}.replaceAll(/[-_\[\]]\s*|\.\w{3}$/, "")}{any{'.'+lang}{lang}}{any{fn.match(/(?i)sdh.+?/)('')}{fn.match(/(?i).*sdh.*/)('_SDH')}}{any{fn.match(/(?i)\(foreignpartsonly\)/)}{''}}{"."+ext}"/>
            </map>
            <node name="format.recent.movie">
              <map>
                <entry key="0" value="{evaluate('Z:/Movies & TV/New Downloads/_FileBot/Format_Movie_Test.groovy' as File)}"/>
                <entry key="1" value="{
  //////////////////////////////////////////////
  // MOVIE BINDING
  //////////////////////////////////////////////
  // Posted here: https://www.filebot.net/forums/viewtopic.php?f=4&t=10766&p=43842#p43842
  //////////////////////////////////////////////
  def space = call{' '};
  // Root Directory
  def dir_root = 'Z:\\Movies & TV\\'+
  call{hd.matches(/(?i)SD/) ? '1. SD\\' : ' '}+
  call{hd.matches(/(?i)HD/) ? '2. HD\\' : ' '}+
  call{hd.matches(/(?i)UHD/) ? '3. UHD\\' : ' '};
  // Main Title e.g.
  // 1408 (2007) (Director's Cut) 1080p HD Blu-ray non-HDR x264 DTS5.1ch_SiNNERS
  // 300 (2007) 720p HD BRRip non-HDR x265 DTS 5.1ch_ESiR
  // Deadpool (2016) 2160p UHD WEB-DL non-HDR AVC DTS-HD MA 7.1ch_DDR
  // Deadpool 2 (2018) (Super Duper Cut) 2160p UHD Blu-ray REMUX HDR10bit ATEME TrueHD Atmos 13Obj 7.1ch_EPSiLON
  def main_title = call{n}+
  space + '(' + call{y} + ')'+
  space + call{fn.matches(/(?i).+\b25th.+?anniv.+/) ? '(25th Anniv. Edition)' : ' '}+
  space + call{fn.matches(/(?i).+\b\(limited\b.*?\).+/) ? '(Limited Edition)' : ' '}+
  space + call{fn.matches(/(?i).+\b\(uncut\b.*?\).+/) ? '(Uncut)' : ' '}+
  space + call{fn.matches(/(?i).+\bcollector.+?s.+?edition\b.+/) ? '(Collector\'s Edition)' : ' '}+
  space + call{fn.matches(/(?i).+\bdirect.+?cut\b.+/) ? '(Director\'s Cut)' : ' '}+
  space + call{fn.matches(/(?i).+\bextended.+?\b.+/) ? '(Extended)' : ' '}+
  space + call{fn.matches(/(?i).+\bextended.+?edit\b.+/) ? '(Extended Edition)' : ' '}+
  space + call{fn.matches(/(?i).+\bimax\b.+/) ? '(IMAX Edition)' : ' '}+
  space + call{fn.matches(/(?i).+\blimited\b.+/) ? '(Limited)' : ' '}+
  space + call{fn.matches(/(?i).+\bremastered\b.+/) ? '(Remastered)' : ' '}+
  space + call{fn.matches(/(?i).+\bsuper.+duper.+cut\b.+/) ? '(Super Duper Cut)' : ' '}+
  space + call{fn.matches(/(?i).+\btheatrical\b.+/) ? '(Theatrical)' : ' '}+
  space + call{fn.matches(/(?i).+\bunrated\b.+/) ? '(Unrated)' : ' '}+
  space + call{any{fn.match(/\([^\()+?[^\d]+?\)\s*/)} {' '}{' '}}+
  space + call{self.vf ? self.vf : self.hpi}+
  space + call{hd}+
  space + call{source.matches(/(?i)blu.*ray/) ? 'Blu-ray' : {source} ?: 'WEB-DL'}+
  space + call{fn.matches(/(?i).+\bremux\b.+/) ? 'REMUX' : ' '}+
  // Only calls {hdr} if it's not SD else non-HDR
  space + call{if (hd =~ 'HD') {any{hdr + "-" + bitdepth + 'bit'}{'non-HDR'}}}+
  space + call{vc}+
  space +
  // Call audio
  // Thread here where I got the base code from: https://www.filebot.net/forums/viewtopic.php?f=5&t=5285
  call {
    def mCFP =
    [
    'AAC LC SBR PS' : 'AAC',
    'AAC LC SBR' : 'AAC',
    'AAC LC' : 'AAC',
    'AC 3 Dep' : 'E-AC3',
    'AC 3' : 'AC3',
    'DTS 96 24' : 'DTS 96-24',
    'DTS ES XBR' : 'DTS-HD HRA',
    'DTS ES XLL' : 'DTS-HD MA',
    'DTS ES XXCH XBR' : 'DTS-HD HRA',
    'DTS ES XXCH XLL' : 'DTS-HD MA',
    'DTS ES XXCH' : 'DTS-ES',
    'DTS ES' : 'DTS-ES',
    'DTS XBR' : 'DTS-HD HRA',
    'DTS XLL X' : 'DTS X',
    'DTS XLL' : 'DTS-HD MA',
    'DTS' : 'DTS',
    'E AC 3 JOC' : 'EAC3 Atmos',
    'E AC 3' : 'EAC3',
    'MLP FBA 16 ch' : 'TrueHD Atmos',
    'MLP FBA' : 'TrueHD',
    'MP3' : 'MP3',
    'MPEG Audio' : 'MP2',
    'PCM' : 'PCM'
    ];
    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/,'5.1').replaceAll(/8/,'7.1') };
    def audioCollection = audio.collect
    { au ->
      def channels = any{ channelClean(au['ChannelPositionsString2'])}{ channelClean(au['ChannelsOriginal'])}{ channelClean(au['Channels']) };
      def dynChannel = {au['NumberOfDynamicObjects'] + 'Obj'};
      def ch = channels.tokenize('\\/').take(3)*.toDouble().inject(0, { a, b -> a + b }).findAll { it > 0 }.max().toString() + 'ch';
      def codec = audioClean(any{ au['CodecID/Hint'] }{ au['Format'] });
      def format_profile = { ( au['Format_AdditionalFeatures'] != null) ? audioClean(au['Format_AdditionalFeatures']) : '' };
      def combined = allOf{codec}{format_profile}.join(' ');
      def stream = allOf { mCFP.get(combined, 'UNKNOWN_FORMAT--'+combined+'--') } { dynChannel } { ch };
    };
    return audioCollection[0].join( ' ' )
    } +
    '_'+
    // Group
    call{ any{ fn.match(/(?<=[_-])[^\s_-]+?$/)} {group} {'_NA'} };
    // Language
    def lang = call{ any{'.'+subt} + {fn.matches(/(?i).+sdh.+/) ? '_SDH' : ''} {any{fn.match(/(?i)\(foreignpartsonly\)/)} '' } };
    // Extension
    // def ext = call{'.'+ext};
    // call all the bindings to create the result
    // call(dir_root) +
    (call(main_title).replace(':', ';').replace('*', '') + call(lang)).replaceAll(/null/,'')
  }"/>
                <entry key="2" value="{evaluate('Z:/Movies & TV/New Downloads/_FileBot/Format_Movie.groovy' as File)}"/>
                <entry key="3" value="{evaluate('Z:/Movies & TV/New Downloads/_FileBot Bindings/Format_Movie.groovy' as File)}"/>
                <entry key="4" value="{
	//////////////////////////////////////////////
	// MOVIE BINDING
	//////////////////////////////////////////////
	// Posted here: https://www.filebot.net/forums/viewtopic.php?f=4&t=10766&p=43842#p43842
	//////////////////////////////////////////////
	def space = call{' '};
	// Root Directory
	def dir_root = 'Z:\\Movies & TV\\'+
	call{hd.matches(/(?i)SD/) ? '1. SD\\' : ' '}+
	call{hd.matches(/(?i)HD/) ? '2. HD\\' : ' '}+
	call{hd.matches(/(?i)UHD/) ? '3. UHD\\' : ' '};
	// Main Title e.g.
	// 1408 (2007) (Director's Cut) 1080p HD Blu-ray non-HDR x264 DTS5.1ch_SiNNERS
	// 300 (2007) 720p HD BRRip non-HDR x265 DTS 5.1ch_ESiR
	// Deadpool (2016) 2160p UHD WEB-DL non-HDR AVC DTS-HD MA 7.1ch_DDR
	// Deadpool 2 (2018) (Super Duper Cut) 2160p UHD Blu-ray REMUX HDR10bit ATEME TrueHD Atmos 13Obj 7.1ch_EPSiLON
	def main_title = call{n}+
	space + '(' + call{y} + ')'+
	space + call{fn.matches(/(?i).+\b25th.+?anniv.+/) ? '(25th Anniv. Edition)' : ' '}+
	space + call{fn.matches(/(?i).+\b\(limited\b.*?\).+/) ? '(Limited Edition)' : ' '}+
	space + call{fn.matches(/(?i).+\b\(uncut\b.*?\).+/) ? '(Uncut)' : ' '}+
	space + call{fn.matches(/(?i).+\bcollector.+?s.+?edition\b.+/) ? '(Collector\'s Edition)' : ' '}+
	space + call{fn.matches(/(?i).+\bdirect.+?cut\b.+/) ? '(Director\'s Cut)' : ' '}+
	space + call{fn.matches(/(?i).+\bextended.+?\b.+/) ? '(Extended)' : ' '}+
	space + call{fn.matches(/(?i).+\bextended.+?edit\b.+/) ? '(Extended Edition)' : ' '}+
	space + call{fn.matches(/(?i).+\bimax\b.+/) ? '(IMAX Edition)' : ' '}+
	space + call{fn.matches(/(?i).+\blimited\b.+/) ? '(Limited)' : ' '}+
	space + call{fn.matches(/(?i).+\bremastered\b.+/) ? '(Remastered)' : ' '}+
	space + call{fn.matches(/(?i).+\bsuper.+duper.+cut\b.+/) ? '(Super Duper Cut)' : ' '}+
	space + call{fn.matches(/(?i).+\btheatrical\b.+/) ? '(Theatrical)' : ' '}+
	space + call{fn.matches(/(?i).+\bunrated\b.+/) ? '(Unrated)' : ' '}+
	space + call{any{fn.match(/\([^\()+?[^\d]+?\)\s*/)} {' '}{' '}}+
	space + call{self.vf ? self.vf : self.hpi}+
	space + call{hd}+
	space + call{source.matches(/(?i)blu.*ray/) ? 'Blu-ray' : {source} ?: 'WEB-DL'}+
	space + call{fn.matches(/(?i).+\bremux\b.+/) ? 'REMUX' : ' '}+
	// Only calls {hdr} if it's not SD else non-HDR
	space + call{if (hd =~ 'HD') {any{hdr + "-" + bitdepth + 'bit'}{'non-HDR'}}}+
	space + call{vc}+
	space +
	// Call audio
	// Thread here where I got the base code: https://www.filebot.net/forums/viewtopic.php?f=5&t=5285
	call {
		def mCFP =
		[
		'AAC LC SBR PS' : 'AAC',
		'AAC LC SBR' : 'AAC',
		'AAC LC' : 'AAC',
		'AC 3 Dep' : 'E-AC3',
		'AC 3' : 'AC3',
		'DTS 96 24' : 'DTS 96-24',
		'DTS ES XBR' : 'DTS-HD HRA',
		'DTS ES XLL' : 'DTS-HD MA',
		'DTS ES XXCH XBR' : 'DTS-HD HRA',
		'DTS ES XXCH XLL' : 'DTS-HD MA',
		'DTS ES XXCH' : 'DTS-ES',
		'DTS ES' : 'DTS-ES',
		'DTS XBR' : 'DTS-HD HRA',
		'DTS XLL X' : 'DTS X',
		'DTS XLL' : 'DTS-HD MA',
		'DTS' : 'DTS',
		'E AC 3 JOC' : 'EAC3 Atmos',
		'E AC 3' : 'EAC3',
		'MLP FBA 16 ch' : 'TrueHD Atmos',
		'MLP FBA' : 'TrueHD',
		'MP3' : 'MP3',
		'MPEG Audio' : 'MP2',
		'PCM' : 'PCM'
		];
		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/,'5.1').replaceAll(/8/,'7.1') };
		def audioCollection = audio.collect
		{ au ->
			def channels = any{ channelClean(au['ChannelPositionsString2'])}{ channelClean(au['ChannelsOriginal'])}{ channelClean(au['Channels']) };
			def dynChannel = {au['NumberOfDynamicObjects'] + 'Obj'};
			def ch = channels.tokenize('\\/').take(3)*.toDouble().inject(0, { a, b -> a + b }).findAll { it > 0 }.max().toString() + 'ch';
			def codec = audioClean(any{ au['CodecID/Hint'] }{ au['Format'] });
			def format_profile = { ( au['Format_AdditionalFeatures'] != null) ? audioClean(au['Format_AdditionalFeatures']) : '' };
			def combined = allOf{codec}{format_profile}.join(' ');
			def stream = allOf { mCFP.get(combined, 'UNKNOWN_FORMAT--'+combined+'--') } { dynChannel } { ch };
		};
		return audioCollection[0].join( ' ' )
		} +
		'_'+
		// Group
		call{ any{ fn.match(/(?<=[_-])[^\s_-]+?$/)} {group} {'_NA'} };
		// Language
		def lang = call{ any{'.'+subt} + {fn.matches(/(?i).+sdh.+/) ? '_SDH' : ''} {any{fn.match(/(?i)\(foreignpartsonly\)/)} '' } };
		// Extension
		// def ext = call{'.'+ext};
		// call all the bindings to create the result
		// call(dir_root) +
		(call(main_title).replace(':', ';').replace('*', '') + call(lang)).replaceAll(/null/,'')
	}"/>
                <entry key="5" value="{
	//////////////////////////////////////////////
	// MOVIE BINDING
	//////////////////////////////////////////////
	// Posted here: https://www.filebot.net/forums/viewtopic.php?f=4&t=10766&p=43842#p43842
	//////////////////////////////////////////////
	def space = call{' '};
	// Root Directory
	def dir_root = 'Z:\\Movies & TV\\'+
	call{hd.matches(/(?i)SD/) ? '1. SD\\' : ' '}+
	call{hd.matches(/(?i)HD/) ? '2. HD\\' : ' '}+
	call{hd.matches(/(?i)UHD/) ? '3. UHD\\' : ' '};
	// Main Title e.g.
	// 1408 (2007) (Director's Cut) 1080p HD Blu-ray non-HDR x264 DTS5.1ch_SiNNERS
	// 300 (2007) 720p HD BRRip non-HDR x265 DTS 5.1ch_ESiR
	// Deadpool (2016) 2160p UHD WEB-DL non-HDR AVC DTS-HD MA 7.1ch_DDR
	// Deadpool 2 (2018) (Super Duper Cut) 2160p UHD Blu-ray REMUX HDR10bit ATEME TrueHD Atmos 13Obj 7.1ch_EPSiLON
	def main_title = call{n}+
	space + '(' + call{y} + ')'+
	space + call{fn.matches(/(?i).+\b25th.+?anniv.+/) ? '(25th Anniv. Edition)' : ' '}+
	space + call{fn.matches(/(?i).+\b\(limited\b.*?\).+/) ? '(Limited Edition)' : ' '}+
	space + call{fn.matches(/(?i).+\b\(uncut\b.*?\).+/) ? '(Uncut)' : ' '}+
	space + call{fn.matches(/(?i).+\bcollector.+?s.+?edition\b.+/) ? '(Collector\'s Edition)' : ' '}+
	space + call{fn.matches(/(?i).+\bdirect.+?cut\b.+/) ? '(Director\'s Cut)' : ' '}+
	space + call{fn.matches(/(?i).+\bextended.+?\b.+/) ? '(Extended)' : ' '}+
	space + call{fn.matches(/(?i).+\bextended.+?edit\b.+/) ? '(Extended Edition)' : ' '}+
	space + call{fn.matches(/(?i).+\bimax\b.+/) ? '(IMAX Edition)' : ' '}+
	space + call{fn.matches(/(?i).+\blimited\b.+/) ? '(Limited)' : ' '}+
	space + call{fn.matches(/(?i).+\bremastered\b.+/) ? '(Remastered)' : ' '}+
	space + call{fn.matches(/(?i).+\bsuper.+duper.+cut\b.+/) ? '(Super Duper Cut)' : ' '}+
	space + call{fn.matches(/(?i).+\btheatrical\b.+/) ? '(Theatrical)' : ' '}+
	space + call{fn.matches(/(?i).+\bunrated\b.+/) ? '(Unrated)' : ' '}+
	space + call{any{fn.match(/\([^\()+?[^\d]+?\)\s*/)} {' '}{' '}}+
	space + call{self.vf ? self.vf : self.hpi}+
	space + call{hd}+
	space + call{source.matches(/(?i)blu.*ray/) ? 'Blu-ray' : {source} ?: 'WEB-DL'}+
	space + call{fn.matches(/(?i).+\bremux\b.+/) ? 'REMUX' : ' '}+
	// Only calls {hdr} if it's not SD else non-HDR
	space + call{if (hd =~ 'HD') {any{hdr + "-" + bitdepth + 'bit'}{'non-HDR'}}}+
	space + call{vc}+
	space +
	// Call audio
	// Thread here where I got the base code: https://www.filebot.net/forums/viewtopic.php?f=5&t=5285
	call {
		def mCFP =
		[
		'AAC LC SBR PS' : 'AAC',
		'AAC LC SBR' : 'AAC',
		'AAC LC' : 'AAC',
		'AC 3 Dep' : 'E-AC3',
		'AC 3' : 'AC3',
		'DTS 96 24' : 'DTS 96-24',
		'DTS ES XBR' : 'DTS-HD HRA',
		'DTS ES XLL' : 'DTS-HD MA',
		'DTS ES XXCH XBR' : 'DTS-HD HRA',
		'DTS ES XXCH XLL' : 'DTS-HD MA',
		'DTS ES XXCH' : 'DTS-ES',
		'DTS ES' : 'DTS-ES',
		'DTS XBR' : 'DTS-HD HRA',
		'DTS XLL X' : 'DTS X',
		'DTS XLL' : 'DTS-HD MA',
		'DTS' : 'DTS',
		'E AC 3 JOC' : 'EAC3 Atmos',
		'E AC 3' : 'EAC3',
		'MLP FBA 16 ch' : 'TrueHD Atmos',
		'MLP FBA' : 'TrueHD',
		'MP3' : 'MP3',
		'MPEG Audio' : 'MP2',
		'PCM' : 'PCM'
		];
		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/,'5.1').replaceAll(/8/,'7.1') };
		def audioCollection = audio.collect
		{ au ->
			def channels = any{ channelClean(au['ChannelPositionsString2'])}{ channelClean(au['ChannelsOriginal'])}{ channelClean(au['Channels']) };
			def dynChannel = {au['NumberOfDynamicObjects'] + 'Obj'};
			def ch = channels.tokenize('\\/').take(3)*.toDouble().inject(0, { a, b -> a + b }).findAll { it > 0 }.max().toString() + 'ch';
			def codec = audioClean(any{ au['CodecID/Hint'] }{ au['Format'] });
			def format_profile = { ( au['Format_AdditionalFeatures'] != null) ? audioClean(au['Format_AdditionalFeatures']) : '' };
			def combined = allOf{codec}{format_profile}.join(' ');
			def stream = allOf { mCFP.get(combined, 'UNKNOWN_FORMAT--'+combined+'--') } { dynChannel } { ch };
		};
		return audioCollection[0].join( ' ' )
		} +
		'_'+
		// Group
		call{ any{ fn.match(/(?<=[_-])[^\s_-]+?$/)} {group} {'_NA'} };
		// Language
		def lang = call{ any{'.'+subt} + {fn.matches(/(?i).+sdh.+/) ? '_SDH' : ''} {any{fn.match(/(?i)\(foreignpartsonly\)/)} '' } };
		// Extension
		// def ext = call{'.'+ext};
		// Call all the bindings to create the result
		call(dir_root) + (call(main_title).replace(':', ';').replace('*', '') + call(lang)).replaceAll(/null/,'')
	}"/>
                <entry key="6" value="{
	//////////////////////////////////////////////
	// MOVIE BINDING
	//////////////////////////////////////////////
	// Posted here: https://www.filebot.net/forums/viewtopic.php?f=4&t=10766&p=43842#p43842
	//////////////////////////////////////////////
	def space = call{' '};
	// Root Directory
	def dir_root = 'Z:/Movies & TV/'+
	call{hd.matches(/(?i)SD/) ? '1. SD/' : ' '}+
	call{hd.matches(/(?i)HD/) ? '2. HD/' : ' '}+
	call{hd.matches(/(?i)UHD/) ? '3. UHD/' : ' '};
	// Main Title e.g.
	// 1408 (2007) (Director's Cut) 1080p HD Blu-ray non-HDR x264 DTS5.1ch_SiNNERS
	// 300 (2007) 720p HD BRRip non-HDR x265 DTS 5.1ch_ESiR
	// Deadpool (2016) 2160p UHD WEB-DL non-HDR AVC DTS-HD MA 7.1ch_DDR
	// Deadpool 2 (2018) (Super Duper Cut) 2160p UHD Blu-ray REMUX HDR10bit ATEME TrueHD Atmos 13Obj 7.1ch_EPSiLON
	def main_title = call{n}+
	space + '(' + call{y} + ')'+
	space + call{fn.matches(/(?i).+\b25th.+?anniv.+/) ? '(25th Anniv. Edition)' : ' '}+
	space + call{fn.matches(/(?i).+\b\(limited\b.*?\).+/) ? '(Limited Edition)' : ' '}+
	space + call{fn.matches(/(?i).+\b\(uncut\b.*?\).+/) ? '(Uncut)' : ' '}+
	space + call{fn.matches(/(?i).+\bcollector.+?s.+?edition\b.+/) ? '(Collector\'s Edition)' : ' '}+
	space + call{fn.matches(/(?i).+\bdirect.+?cut\b.+/) ? '(Director\'s Cut)' : ' '}+
	space + call{fn.matches(/(?i).+\bextended.+?\b.+/) ? '(Extended)' : ' '}+
	space + call{fn.matches(/(?i).+\bextended.+?edit\b.+/) ? '(Extended Edition)' : ' '}+
	space + call{fn.matches(/(?i).+\bimax\b.+/) ? '(IMAX Edition)' : ' '}+
	space + call{fn.matches(/(?i).+\blimited\b.+/) ? '(Limited)' : ' '}+
	space + call{fn.matches(/(?i).+\bremastered\b.+/) ? '(Remastered)' : ' '}+
	space + call{fn.matches(/(?i).+\bsuper.+duper.+cut\b.+/) ? '(Super Duper Cut)' : ' '}+
	space + call{fn.matches(/(?i).+\btheatrical\b.+/) ? '(Theatrical)' : ' '}+
	space + call{fn.matches(/(?i).+\bunrated\b.+/) ? '(Unrated)' : ' '}+
	space + call{any{fn.match(/\([^\()+?[^\d]+?\)\s*/)} {' '}{' '}}+
	space + call{self.vf ? self.vf : self.hpi}+
	space + call{hd}+
	space + call{source.matches(/(?i)blu.*ray/) ? 'Blu-ray' : {source} ?: 'WEB-DL'}+
	space + call{fn.matches(/(?i).+\bremux\b.+/) ? 'REMUX' : ' '}+
	// Only calls {hdr} if it's not SD else non-HDR
	space + call{if (hd =~ 'HD') {any{hdr + "-" + bitdepth + 'bit'}{'non-HDR'}}}+
	space + call{vc}+
	space +
	// Call audio
	// Thread here where I got the base code: https://www.filebot.net/forums/viewtopic.php?f=5&t=5285
	call {
		def mCFP =
		[
		'AAC LC SBR PS' : 'AAC',
		'AAC LC SBR' : 'AAC',
		'AAC LC' : 'AAC',
		'AC 3 Dep' : 'E-AC3',
		'AC 3' : 'AC3',
		'DTS 96 24' : 'DTS 96-24',
		'DTS ES XBR' : 'DTS-HD HRA',
		'DTS ES XLL' : 'DTS-HD MA',
		'DTS ES XXCH XBR' : 'DTS-HD HRA',
		'DTS ES XXCH XLL' : 'DTS-HD MA',
		'DTS ES XXCH' : 'DTS-ES',
		'DTS ES' : 'DTS-ES',
		'DTS XBR' : 'DTS-HD HRA',
		'DTS XLL X' : 'DTS X',
		'DTS XLL' : 'DTS-HD MA',
		'DTS' : 'DTS',
		'E AC 3 JOC' : 'EAC3 Atmos',
		'E AC 3' : 'EAC3',
		'MLP FBA 16 ch' : 'TrueHD Atmos',
		'MLP FBA' : 'TrueHD',
		'MP3' : 'MP3',
		'MPEG Audio' : 'MP2',
		'PCM' : 'PCM'
		];
		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/,'5.1').replaceAll(/8/,'7.1') };
		def audioCollection = audio.collect
		{ au ->
			def channels = any{ channelClean(au['ChannelPositionsString2'])}{ channelClean(au['ChannelsOriginal'])}{ channelClean(au['Channels']) };
			def dynChannel = {au['NumberOfDynamicObjects'] + 'Obj'};
			def ch = channels.tokenize('\\/').take(3)*.toDouble().inject(0, { a, b -> a + b }).findAll { it > 0 }.max().toString() + 'ch';
			def codec = audioClean(any{ au['CodecID/Hint'] }{ au['Format'] });
			def format_profile = { ( au['Format_AdditionalFeatures'] != null) ? audioClean(au['Format_AdditionalFeatures']) : '' };
			def combined = allOf{codec}{format_profile}.join(' ');
			def stream = allOf { mCFP.get(combined, 'UNKNOWN_FORMAT--'+combined+'--') } { dynChannel } { ch };
		};
		return audioCollection[0].join( ' ' )
		} +
		'_'+
		// Group
		call{ any{ fn.match(/(?<=[_-])[^\s_-]+?$/)} {group} {'_NA'} };
		// Language
		def lang = call{ any{'.'+subt} + {fn.matches(/(?i).+sdh.+/) ? '_SDH' : ''} {any{fn.match(/(?i)\(foreignpartsonly\)/)} '' } };
		// Extension
		// def ext = call{'.'+ext};
		// Call all the bindings to create the result
		call(dir_root) + (call(main_title).replace(':', ';').replace('*', '') + call(lang)).replaceAll(/null/,'')
	}"/>
                <entry key="7" value="{
	//////////////////////////////////////////////
	// MOVIE BINDING
	//////////////////////////////////////////////
	// Posted here: https://www.filebot.net/forums/viewtopic.php?f=4&t=10766&p=43842#p43842
	//////////////////////////////////////////////
	def space = call{' '};
	// Root Directory
	def dir_root = 'Z:/Movies & TV/'+
	call{hd.matches(/(?i)SD/) ? '1. SD/' : ' '}+
	call{hd.matches(/(?i)HD/) ? '2. HD/' : ' '}+
	call{hd.matches(/(?i)UHD/) ? '3. UHD/' : ' '};
	// Main Title e.g.
	// 1408 (2007) (Director's Cut) 1080p HD Blu-ray non-HDR x264 DTS5.1ch_SiNNERS
	// 300 (2007) 720p HD BRRip non-HDR x265 DTS 5.1ch_ESiR
	// Deadpool (2016) 2160p UHD WEB-DL non-HDR AVC DTS-HD MA 7.1ch_DDR
	// Deadpool 2 (2018) (Super Duper Cut) 2160p UHD Blu-ray REMUX HDR10bit ATEME TrueHD Atmos 13Obj 7.1ch_EPSiLON
	def main_title = call{n}+
	space + '(' + call{y} + ')'+
	space + call{fn.matches(/(?i).+\b25th.+?anniv.+/) ? '(25th Anniv. Edition)' : ' '}+
	space + call{fn.matches(/(?i).+\b\(limited\b.*?\).+/) ? '(Limited Edition)' : ' '}+
	space + call{fn.matches(/(?i).+\b\(uncut\b.*?\).+/) ? '(Uncut)' : ' '}+
	space + call{fn.matches(/(?i).+\bcollector.+?s.+?edition\b.+/) ? '(Collector\'s Edition)' : ' '}+
	space + call{fn.matches(/(?i).+\bdirect.+?cut\b.+/) ? '(Director\'s Cut)' : ' '}+
	space + call{fn.matches(/(?i).+\bextended.+?\b.+/) ? '(Extended)' : ' '}+
	space + call{fn.matches(/(?i).+\bextended.+?edit\b.+/) ? '(Extended Edition)' : ' '}+
	space + call{fn.matches(/(?i).+\bimax\b.+/) ? '(IMAX Edition)' : ' '}+
	space + call{fn.matches(/(?i).+\blimited\b.+/) ? '(Limited)' : ' '}+
	space + call{fn.matches(/(?i).+\bremastered\b.+/) ? '(Remastered)' : ' '}+
	space + call{fn.matches(/(?i).+\bsuper.+duper.+cut\b.+/) ? '(Super Duper Cut)' : ' '}+
	space + call{fn.matches(/(?i).+\btheatrical\b.+/) ? '(Theatrical)' : ' '}+
	space + call{fn.matches(/(?i).+\bunrated\b.+/) ? '(Unrated)' : ' '}+
	space + call{any{fn.match(/\([^\()+?[^\d]+?\)\s*/)} {' '}{' '}}+
	space + call{self.vf ? self.vf : self.hpi}+
	space + call{hd}+
	space + call{source.matches(/(?i)blu.*ray/) ? 'Blu-ray' : {source} ?: 'WEB-DL'}+
	space + call{fn.matches(/(?i).+\bremux\b.+/) ? 'REMUX' : ' '}+
	// Only calls {hdr} if it's not SD else non-HDR
	space + call{if (hd =~ 'HD') {any{hdr + "-" + bitdepth + 'bit'}{'non-HDR'}}}+
	space + call{vc}+
	space +
	// Call audio
	// Thread here where I got the base code: https://www.filebot.net/forums/viewtopic.php?f=5&t=5285
	call {
		def mCFP =
		[
		'AAC LC SBR PS' : 'AAC',
		'AAC LC SBR' : 'AAC',
		'AAC LC' : 'AAC',
		'AC 3 Dep' : 'E-AC3',
		'AC 3' : 'AC3',
		'DTS 96 24' : 'DTS 96-24',
		'DTS ES XBR' : 'DTS-HD HRA',
		'DTS ES XLL' : 'DTS-HD MA',
		'DTS ES XXCH XBR' : 'DTS-HD HRA',
		'DTS ES XXCH XLL' : 'DTS-HD MA',
		'DTS ES XXCH' : 'DTS-ES',
		'DTS ES' : 'DTS-ES',
		'DTS XBR' : 'DTS-HD HRA',
		'DTS XLL X' : 'DTS X',
		'DTS XLL' : 'DTS-HD MA',
		'DTS' : 'DTS',
		'E AC 3 JOC' : 'EAC3 Atmos',
		'E AC 3' : 'EAC3',
		'MLP FBA 16 ch' : 'TrueHD Atmos',
		'MLP FBA' : 'TrueHD',
		'MP3' : 'MP3',
		'MPEG Audio' : 'MP2',
		'PCM' : 'PCM'
		];
		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/,'5.1').replaceAll(/8/,'7.1') };
		def audioCollection = audio.collect
		{ au ->
			def channels = any{ channelClean(au['ChannelPositionsString2'])}{ channelClean(au['ChannelsOriginal'])}{ channelClean(au['Channels']) };
			def dynChannel = {au['NumberOfDynamicObjects'] + 'Obj'};
			def ch = channels.tokenize('\\/').take(3)*.toDouble().inject(0, { a, b -> a + b }).findAll { it > 0 }.max().toString() + 'ch';
			def codec = audioClean(any{ au['CodecID/Hint'] }{ au['Format'] });
			def format_profile = { ( au['Format_AdditionalFeatures'] != null) ? audioClean(au['Format_AdditionalFeatures']) : '' };
			def combined = allOf{codec}{format_profile}.join(' ');
			def stream = allOf { mCFP.get(combined, 'UNKNOWN_FORMAT--'+combined+'--') } { dynChannel } { ch };
		};
		return audioCollection[0].join( ' ' )
		} +
		'_'+
		// Group
		call{ any{ fn.match(/(?<=[_-])[^\s_-]+?$/)} {group} {'_NA'} };
		// Language
		def lang = call{ any{'.'+subt} + {fn.matches(/(?i).+sdh.+/) ? '_SDH' : ''} {any{fn.match(/(?i)\(foreignpartsonly\)/)} '' } };
		// Extension
		// def ext = call{'.'+ext};
		// Call all the bindings to create the result
		(call(main_title).replace(':', ';').replace('*', '') + call(lang)).replaceAll(/null/,'')
	}"/>
              </map>
            </node>
            <node name="presets">
              <map>
                <entry key="My Preset" value="{"@type":"net.filebot.ui.rename.Preset","name":"My Preset","path":"Z:\\Movies & TV\\3. UHD","includes":"minutes > 5 && fn != /(?i)extras/","format":"{home}/Movies/{n} {y}","database":"TheMovieDB::TV","sortOrder":"DVD","matchMode":"Opportunistic","language":"en","action":"HARDLINK"}"/>
              </map>
            </node>
          </node>
        </node>
      </node>
    </node>
  </root>
</preferences>

Done ?(?????)?

Z:\Movies & TV\New Downloads\_FileBot>

Re: Point to external file in GUI ?

Posted: 25 May 2019, 18:49
by rednoah
stephen147 wrote: 25 May 2019, 15:46 It's not updating the GUI.
Did you test by matching files and letting it run the format, or did you test by clicking on Edit Format? The former works. The latter does not. Since we're just setting internal settings without going through the usual steps.

Re: Point to external file in GUI ?

Posted: 25 May 2019, 19:03
by stephen147
I see. I went to edit format first. Without running a match.

Thanks for this. I've made a batch file to update my portable build and install one. Will post for others once I get to my PC.

Re: Point to external file in GUI ?

Posted: 26 May 2019, 13:19
by stephen147
Just checked, that worked. Batch file below I've written to update my 2 versions at the same time. (Installed & portable)

Code: Select all

@echo off
color 5F
mode con:cols=50 lines=20
goto START

::::::::::::::::::::::::::::::::::::::::::::::::

:: Read this thread first before using: https://www.filebot.net/forums/viewtopic.php?f=8&t=10837

:: VER 01 - 26-05-2019 - First release.

:: WHAT IT DOES:
:: Sets the default formats for Series and Movies without needing to paste anything to the GUI using your external file/s.
:: The 'edit format' box in the GUI will not get updated using this method, but will still work fine when you click match in the GUI,
:: as this is an internal method of updating the preferences.

:: This will work for filebot installed and portable versions.

:: Save this to Preferences.groovy without the ::'s at the start.
:: _def.each{ k, v ->
::   java.util.prefs.Preferences.userRoot().node('net/filebot/ui/rename').put(k, v)
:: }

:: The FILEBOT_PREFERENCES variable below will be that path. Set it to your location.
:: CHANGE ALL OTHER PATHS TO SUIT!
:: Save this as a .bat file to your system.
:: Double click it to update filebots internal format method. (It takes around 15 seconds to complete)

::::::::::::::::::::::::::::::::::::::::::::::::

:START
set "FOLDER_ROOT=Z:/Movies & TV/New Downloads/_FileBot"
set "GROOVY_ANIME=Format_Anime.groovy"
set "GROOVY_MOVIE=Format_Movie.groovy"
set "GROOVY_TV=Format_TV.groovy"
set "FILEBOT_PORTABLE=%FOLDER_ROOT%/filebot.exe"
set "FILEBOT_PREFERENCES=%FOLDER_ROOT%/Preferences.groovy"

::::::::::::::::::::::::::::::::::::::::::::::::

title FileBot GUI Format Update Wizward by stephen147 [%~nx0]
echo [%~nx0]
echo --------------------------------------------
echo:
echo          Hello '%username%'
echo:
echo --------------------------------------------
echo:
goto FILEBOT_PORTABLE

::::::::::::::::::::::::::::::::::::::::::::::::

:FILEBOT_PORTABLE
"%FILEBOT_PORTABLE%" -script "%FILEBOT_PREFERENCES%" --def rename.format.episode=@"%FOLDER_ROOT%/%GROOVY_TV%" rename.format.movie=@"%FOLDER_ROOT%/%GROOVY_MOVIE%"
goto FILEBOT_INSTALL

:FILEBOT_INSTALL
filebot -script "%FILEBOT_PREFERENCES%" --def rename.format.episode=@"%FOLDER_ROOT%/%GROOVY_TV%" rename.format.movie=@"%FOLDER_ROOT%/%GROOVY_MOVIE%"
goto PROCESS_COMPLETE

::::::::::::::::::::::::::::::::::::::::::::::::

:PROCESS_COMPLETE
echo:
echo:--------------------------------------------
color 0E
echo:
echo:   Process complete !
echo:
echo:--------------------------------------------
echo 
set /a WAITTIME_INPUT = 5
ping 192.0.2.0 -n 1 -w %WAITTIME_INPUT%000 >nul
rem @pause
EXIT

Re: Point to external file in GUI ?

Posted: 28 May 2019, 05:39
by rednoah
So I've looked into possible side effects, and no format ever posted here in the forums starts with @ or ends with groovy, so I think it's pretty safe to add (although a bit of undocumented black magic). :lol:

You can now just paste something like this into the Format Editor, and it'll work:

Code: Select all

@/path/to/file.groovy
Changes to the file will not be reflected in real-time though. You'll have to either restart FileBot or open the Format Editor and confirm to apply any changes if you're actively changing the file.