Where do I find all the documentation?

All about user-defined episode / movie / file name format expressions
User avatar
Wolfie
Posts: 128
Joined: 27 Oct 2015, 02:59

Where do I find all the documentation?

Post by Wolfie »

Okay, been trying to figure this out to no avail (obviously)...

I have a subfolder where all "small movies" go into. Basically, anything with an actual run time of under an hour (60 minutes), so that things like TV specials or just one-off shows/etc can automatically go into there. The problem is when there is a movie that is broken into multiple parts and one or more of those parts are under 60 minutes. I've tried a few different ways that either don't work at all, or work one way but not the other. Here are a couple of examples of what I've tried...

Code: Select all

{if (minutes < 60 && ! pi) (code)}
{if (minutes < 60 && (any{pi}{y} != {y})) (code)}
The overall code for the entire naming is much bigger, but that's the one area I'm having an issue with. I've tried comparing 'pi' to null/undefined, negating it, so on. I more or less need a to test if it's undefined as well as it having a value. I'm sure there's a way to do it that I don't know of, since there appears to be many 'hidden' expressions/functions available that don't seem to be all on one page. (The 'expressions' help page lists some common ones but there are a lot of things not listed on that page.)

So, how can I get a true result for 'pi' being undefined and where can I find out about more functions that are seemingly like easter eggs to find and use?
User avatar
Wolfie
Posts: 128
Joined: 27 Oct 2015, 02:59

Re: Trouble with an 'if' condition (minutes/parts)

Post by Wolfie »

Okay managed to figure it out though it's odd that other similar attempts failed.

Code: Select all

{if ((any{pn}{"X"} == "X" && minutes < 60)) (code)}
User avatar
rednoah
The Source
Posts: 23936
Joined: 16 Nov 2011, 08:59
Location: Taipei
Contact:

Re: Trouble with an 'if' condition (minutes/parts)

Post by rednoah »

1.
{minutes} is based on the duration MediaInfo of a given file. Multi-Part movies are tricky, but let's just assume that {pi} is working and that it's only defined for multi-part movies, and throw an exception when it's not defined.

@see viewtopic.php?f=5&t=1895


2.
The "correct" solution is probably one that combines all the {minutes} for all model objects that are the same movie:

Code: Select all

{model.findAll{it.id == id}.minutes.sum()}
You could also use the movie runtime (from TheMovieDB, not based on file content) which is probably what you want here:

Code: Select all

{info.runtime}

3.
The built-in help should contain everything you need:
Image

You can click the source link to see the code (and thus every single binding and it's implementation):
http://www.filebot.net/naming.html
:idea: Please read the FAQ and How to Request Help.
User avatar
Wolfie
Posts: 128
Joined: 27 Oct 2015, 02:59

Re: Trouble with an 'if' condition (minutes/parts)

Post by Wolfie »

rednoah wrote:1.
{minutes} is based on the duration MediaInfo of a given file. Multi-Part movies are tricky, but let's just assume that {pi} is working and that it's only defined for multi-part movies, and throw an exception when it's not defined.

@see viewtopic.php?f=5&t=1895
That's actually why I prefer to use 'minutes,' as it gives the actual duration of the video itself vs durations that may include time for commercials. I've seen ones where the reported time is 60 minutes but the time of the video is like 45. I'm fairly certain that the 15 minute difference is for the commercials shown when it originally aired on TV.

Is there a way to compare for an exception? In this instance, I managed to figure something out using the any{}{} function, but checking using an 'if' might be useful at times too.

rednoah wrote:2.
The "correct" solution is probably one that combines all the {minutes} for all model objects that are the same movie:

Code: Select all

{model.findAll{it.id == id}.minutes.sum()}
You could also use the movie runtime (from TheMovieDB, not based on file content) which is probably what you want here:

Code: Select all

{info.runtime}
I tried using 'info.runtime' but for some reason, it wouldn't let me compare when an integer and when I tried with a string, it was unreliable.


Thanks for the reply. I'll check out the links you provided.
User avatar
Wolfie
Posts: 128
Joined: 27 Oct 2015, 02:59

Re: Trouble with an 'if' condition (minutes/parts)

Post by Wolfie »

rednoah wrote:1.
{minutes} is based on the duration MediaInfo of a given file. Multi-Part movies are tricky, but let's just assume that {pi} is working and that it's only defined for multi-part movies, and throw an exception when it's not defined.

@see viewtopic.php?f=5&t=1895
Just to clarify, {pi} is working when there is a number. The issue was in finding a way to check if there wasn't a number.

rednoah wrote:3.
The built-in help should contain everything you need:
Image

You can click the source link to see the code (and thus every single binding and it's implementation):
http://www.filebot.net/naming.html
Both of those links I had already seen. The problem is, the 'naming.html' one doesn't show use of the any{}{} function and neither show the use of if-then-else. There are also many other functions not shown that apparently can be used. So was hoping there was a link to somewhere with a more comprehension list to look at.
User avatar
rednoah
The Source
Posts: 23936
Joined: 16 Nov 2011, 08:59
Location: Taipei
Contact:

Re: Trouble with an 'if' condition (minutes/parts)

Post by rednoah »

1.
You don't need to check, because pi itself will unwind the expression if it's undefined:

Code: Select all

{'CD'+pi}

Code: Select all

{pi; 'This is a multi-part movie'}

Code: Select all

{any{' CD'+pi}{' (Single File Movie)'}}
With any you can avoid most of the if-then-else bloat. But if you really want "value of pi or 0 if pi is undefined", then any{pi}{0} will do that.

e.g.

Code: Select all

any {code that uses pi and fails if pi is undefined} {code for when pi is undefined} {code for the next fallback} ...

2.
The any and allOf functions are documented here. Then there's csv and readLines and that's pretty much it. There's a link to the source at the bottom.

The rest is just Groovy: Syntax, Operators, Closures, Semantics and of course the entire Java API plus Groovy Enhancements if you need a complete Object/Method reference.

That's why it's best to copy & paste examples and play with it. The comprehensive list of 10.000+ classes and 100.000+ methods is just a bit overwhelming when you just need String.replace(target, replacement). ;)
:idea: Please read the FAQ and How to Request Help.
User avatar
Wolfie
Posts: 128
Joined: 27 Oct 2015, 02:59

Re: Trouble with an 'if' condition (minutes/parts)

Post by Wolfie »

What about 'File()'? I see where someone used that and would like to read up on it. Where would that be at?
User avatar
rednoah
The Source
Posts: 23936
Joined: 16 Nov 2011, 08:59
Location: Taipei
Contact:

Re: Trouble with an 'if' condition (minutes/parts)

Post by rednoah »

This one is probably about the File class of the Java API:
https://docs.oracle.com/javase/8/docs/a ... /File.html

e.g. get current working directory:

Code: Select all

new File('.').getCanonicalPath()
e.g. if you want to check if a path exists you might create a File object for the given path so you can then call the File.exists() method. I don't think there's any good use case for new File(...) in FileBot format expressions though.

Note that {file} will also give you a File object (and not a String object).
:idea: Please read the FAQ and How to Request Help.
User avatar
Wolfie
Posts: 128
Joined: 27 Oct 2015, 02:59

Re: Trouble with an 'if' condition (minutes/parts)

Post by Wolfie »

Oh and what about things similar to {home}? Is there a list of defined values (such as {home} and others) in one of those links?
User avatar
rednoah
The Source
Posts: 23936
Joined: 16 Nov 2011, 08:59
Location: Taipei
Contact:

Re: Trouble with an 'if' condition (minutes/parts)

Post by rednoah »

This code is about reading CSV files.

Groovy defines File.splitEachLine which is great for reading CSV files. SyAccursed knows Groovy so he just did it the Groovy way.

FileBot has the csv(String path) function built-in:

Code: Select all

csv('/path/to/table.csv')
I recommend doing it the easy way. But if you know Java/Groovy you can do it a million different ways. ;)

@see viewtopic.php?f=5&t=2#p12156
:idea: Please read the FAQ and How to Request Help.
User avatar
rednoah
The Source
Posts: 23936
Joined: 16 Nov 2011, 08:59
Location: Taipei
Contact:

Re: Trouble with an 'if' condition (minutes/parts)

Post by rednoah »

{home} is part of the built-in examples. Works the same as any of the other object/mediainfo bindings, except it's always the same regardless of what object/file match you're formatting.

{self} will self-reference the binding object, so the default String representation will show you all binding names.

If you click source you will find the implementation details of all bindings:
https://github.com/filebot/filebot/blob ... .java#L897


PS: Note that these 1000+ character formats are extreme overkill and nobody (including the original author) is expected to understand how it works and what it does exactly.
:idea: Please read the FAQ and How to Request Help.
User avatar
Wolfie
Posts: 128
Joined: 27 Oct 2015, 02:59

Re: Where do I find all the documentation?

Post by Wolfie »

Here is what I have so far and it seems to be working fine... (Watch it blow up in my face...)

Code: Select all

~path to...~/Movies/{def x,cleanName=n.replaceAll(/:/, ' -').replaceAll(/\?/, '');new File('P:/collections.txt').splitEachLine(':'){if (it[0].matches(/.*${imdbid}.*/)) x=it[1]};A:{any{if (any{collection}{x}) ("_ COLLECTIONS _/${any{collection}{x}}/${y} - ${cleanName}/${cleanName}")}{if (any{pn}{"X"} == "X" && minutes < 60) ("_ MINI MOVIES _/${cleanName} (${y})/${cleanName}")}{"${az}/${cleanName} (${y})/${cleanName}"}}} ({y}){'.CD'+pi}{'.'+minutes+'m'}{'.'+certification}{'.'+resolution}{'.'+source}{'.'+crc32}
In the 'collections.txt' file, I have imdbid's seperated by ;'s and at the end of a list, a colon (:) and the collection name. Helps when there are movies that are part of a collection but not registering as such (not blaming filebot for that, that's user contributed data). I have the James Bond collection on BluRay (got it for a good deal from Amazon) and almost none of the movies are part of a James Bond Collection.

Maybe I missed it, but is there an existing 'name' value that is already cleaned for file name usage? I turn the :'s into -'s and chop off ?'s and will add other illegal characters I come across. However, would feel better if there was a pre-cleansed name or a function to cleanse with an option to override certain characters... Something like this:

Code: Select all

n.cleanse() <-- default
n.cleanse(":","-") <-- substitute :'s with -'s
n.cleanse(":?","_-_") <-- substitute :'s and ?'s with _-_'s
Except for optional substitutions, all characters considered 'illegal' by the filesystem get removed completely.

If something like this already exists... what is it? :D
User avatar
rednoah
The Source
Posts: 23936
Joined: 16 Nov 2011, 08:59
Location: Taipei
Contact:

Re: Where do I find all the documentation?

Post by rednoah »

1.
WTF? My eyes are bleeding... :shock:

I'd keep my collections.csv file in a simple Key;Value format:

Code: Select all

tt0499549;My Avatar Collection
The rest can be simplified (and made readable):

Code: Select all

{any{collection}
    {csv('X:/collections.csv')[imdbid]}
    {if (minutes < 60 && type != 'MoviePart') 'Mini Movies'} 
    {az}
}
/{ny}/{plex.name}
* {fn} doesn't do filename validation, but {plex.name} does
* No {y} {n} special case for collections (just don't do it, it looks terrible, even if it sorts nicely :P)


EDIT:
On second thought, if you want a collections folder and do "Collections/" + null value you'll get "Collectionsnull" which breaks our any cascade. Using String.concat(String) instead of String.plus(Object) will do the trick and make things break when they ought to break.

This might be more close to your spec:

Code: Select all

{any{'Collections/'.concat(collection)}
    {'Collections/'.concat(csv('X:/collections.csv')[imdbid])}
    ...
}

2.
FileBot will force valid filenames anyway on rename, but if you want to do it in the format you can:

Code: Select all

{n.validateFileName()}
String.tr() should be perfect for any simple character-by-character replacement.
:idea: Please read the FAQ and How to Request Help.
User avatar
Wolfie
Posts: 128
Joined: 27 Oct 2015, 02:59

Re: Where do I find all the documentation?

Post by Wolfie »

rednoah wrote:1.
WTF? My eyes are bleeding... :shock:

I'd keep my collections.csv file in a simple Key;Value format:

Code: Select all

tt0499549;My Avatar Collection
The rest can be simplified (and made readable):

Code: Select all

{any{collection}
    {csv('X:/collections.csv')[imdbid]}
    {if (minutes < 60 && type != 'MoviePart') 'Mini Movies'} 
    {az}
}
/{ny}/{plex.name}
Will that handle a line like this?

Code: Select all

tt0055928;tt0057076;tt0058150;tt0059800;tt0062512;tt0064757:James Bond Collection
(With the ;'s changed to something else and the : changed to a ; instead.)

To me, it looks better to have multiple titles grouped together to a collection. Still using a few lines to do it, but it's all neatly packed together instead of a bunch of lines with one title to the same collection. If there's a way to have it make a hit using the same line above (again changing the use of :'s and ;'s) then that would prove to be easier.


rednoah wrote: * {fn} doesn't do filename validation, but {plex.name} does
* No {y} {n} special case for collections (just don't do it, it looks terrible, even if it sorts nicely :P)
For non collection folder names, it does the "{n} {y}" style. Inside of a collection, I like it to be sorted by year released, since that is *usually* the sequence of events or the best way to watch them, etc. It also doesn't look terrible to me, since the year is a fixed length, it actual name of the movies still line up just fine.

rednoah wrote: EDIT:
On second thought, if you want a collections folder and do "Collections/" + null value you'll get "Collectionsnull" which breaks our any cascade. Using String.concat(String) instead of String.plus(Object) will do the trick and make things break when they ought to break.

This might be more close to your spec:

Code: Select all

{any{'Collections/'.concat(collection)}
    {'Collections/'.concat(csv('X:/collections.csv')[imdbid])}
    ...
}
Would this have the same effect?

Code: Select all

{'Collections/'.concat(any{collection}{csv('X:/collections.csv')[imdbid]})}
rednoah wrote:2.
FileBot will force valid filenames anyway on rename, but if you want to do it in the format you can:

Code: Select all

{n.validateFileName()}
String.tr() should be perfect for any simple character-by-character replacement.
I tried testing .tr() but it wouldn't work for me. However, doing {n.replaceAll(/[:]/,' -').validateFileName()} does and I think gives slightly better control. I can make the desired changes before it does it's cleansing. Much appreciated.

Don't suppose there's some sort of a secret editor that gives more viewing space and let's you use multiple lines, etc...? :D
User avatar
Wolfie
Posts: 128
Joined: 27 Oct 2015, 02:59

Re: Where do I find all the documentation?

Post by Wolfie »

Did come across something that I wish I could say is a bug, but arguably it's not. {info.runtime} sometimes returns values as strings vs integers. I've seen before where sometimes it will have a value and "min" as well, depending on the data source, which is why I say it's arguably not a bug. Noticed it happening again but {info.runtime} would display something (vs being null) and so I added .toInteger() to it and it started working.
User avatar
rednoah
The Source
Posts: 23936
Joined: 16 Nov 2011, 08:59
Location: Taipei
Contact:

Re: Where do I find all the documentation?

Post by rednoah »

1.
Your csv line format will not work. The built-in csv function expects simple Key;Value lines and return a Map<String, String> object.


2.
If none of the Closures passed to any() yield any results, then it'll return null and concat will fail with an NPE, which is what we want here.


3.
tr is about replacing characters. 1 char can only be replaced by 1 other char. Use the replace method for simple String replace:

Code: Select all

n.replace(':', ' -')
I recommend using the built-in colon method:

Code: Select all

n.colon(' - ')
(this will take care of French-style colons with Thin Space)
:idea: Please read the FAQ and How to Request Help.
User avatar
rednoah
The Source
Posts: 23936
Joined: 16 Nov 2011, 08:59
Location: Taipei
Contact:

Re: Where do I find all the documentation?

Post by rednoah »

info.runtime is a String value of whatever the database says. TheMovieDB should give you nice numbers (if somebody has entered the data) but with OMDb all bets are off and you might get String values like "90 min" or "1h 30min" etc.

info.runtime.toInteger() will give you a number, or an exception if it's null or not a number.
:idea: Please read the FAQ and How to Request Help.
User avatar
Wolfie
Posts: 128
Joined: 27 Oct 2015, 02:59

Re: Where do I find all the documentation?

Post by Wolfie »

If there was a way to do a regex search with csv, then that would definitely be better than what I posted.

With .tr(), when I have .tr('','') then it shows up fine, but if I put anything in either (or both), even just one character in each, then the information just disappears.

Will switch to the .colon() expression, would make it a bit cleaner, especially since it's the only item I can think of at the moment that I'd want changed like that anyways.

So info.runtime isn't too reliable I take it? Is there any sort of conversion available to convert it from "1h 30min" to "90min" which could then get changed to "90"? Or is that not an option?

Doing my best to come up with something that will rarely need any tweaking, despite what I might throw at it. If a part of a movie is encountered, even if it is an only file and thus, doesn't get a part number assigned, would be preferable for it to still be sorted properly (and reliably), but sounds like I might have to accept some chance of it not working as desired. (No fault to filebot, of course.)
User avatar
rednoah
The Source
Posts: 23936
Joined: 16 Nov 2011, 08:59
Location: Taipei
Contact:

Re: Where do I find all the documentation?

Post by rednoah »

1.
If I had to parse a text file format like yours, I'd do it like this:

Code: Select all

{readLines('/path/to/file').find{ it =~ imdbid }.after(':')}
2.
String.tr() doesn't seem to work because FileBot somehow interferes with the Groovy runtime. :o

3.
There is really no reason not to use TheMovieDB anymore. The API is stable and serves good data. The same cannot be said of OMDb (and weird inconsistent runtime values are just one example).

I'd sort all movies with known runtime > 60 into the az folders, and default everything else into the shitty shorts folder. It's either gonna be short movies, or shitty movies where nobody has bothered entering runtime info yet. :lol:
:idea: Please read the FAQ and How to Request Help.
User avatar
Wolfie
Posts: 128
Joined: 27 Oct 2015, 02:59

Re: Where do I find all the documentation?

Post by Wolfie »

1. Very nice. Odd though...
Using that directly within concat works, but if I set it to a variable first and then try to use that, it fails. Of course, since the value is only needed once, it being inside of concat gets the job done. The variable gets the value, because if I remove the concat part and use the variable directly, it appears. Some sort of a bug perhaps?

2. Okay so it's not just me. Phew.

3. I use TMDB mostly, but there are a few things that OMDb has that TMDB doesn't (as far as matching content goes). I think I've come across two or three items where OMDB had a match that TMDB didn't.

Also, they're not necessarily 'shitty' shorts. Just some stuff that might be a one-time thing and the popular thing to do is to label it as a movie (since it certainly doesn't qualify as a TV series). But if it's 60 min or under, then to me, that's not really a movie. A movie is generally over an hour and while there are some things that aren't movies that go over an hour, this at least catches most of those.

Using that 'collections' file, I can more easily group stuff together, multiple items per line, making it much nicer to deal with. I appreciate that solution, it's great!

Just to mention this, I use the P drive as my Plex system drive. Within Plex itself, I redirected the database and other files to P (for Plex obviously), which makes it easier if I have to do a reinstall, also no worries of system drive filling up and crashing the computer, etc. Still doing a little reorganizing, but goal is to have M being the media drive (and using Windows Storage Spaces, a drive that can continue to expand in size when needed).

One issue I'm noticing, and it seems to be random, but when picking different files to test the expressions on, some will load up (download) the meta data right away. Others, nothing, even after repeated attempts. Then after awhile, it might work. If I try a different file, that has a chance of working, but certain ones just continue to fail no matter what. So I doubt I'm being blocked for some reason, but it working for some and not for others tells me that the API is working fine. So I'm baffled. Any ideas? (I've already cleared the cache.)
User avatar
Wolfie
Posts: 128
Joined: 27 Oct 2015, 02:59

Re: Where do I find all the documentation?

Post by Wolfie »

Oh and here's what I have now, in case you're wondering or if anyone else is hoping to try new things...

Code: Select all

...path.to.media.../Movies/{def cleanName=n.colon(" -").validateFileName();A:{any{"_ COLLECTIONS _/".concat(any{MC:{readLines('P:/collections.txt').find{ it =~ imdbid }.after(':')}}{collection})+"/${y} - ${cleanName}"}{if (minutes <= 60 && info.runtime.toInteger() < 75) ("_ MINI MOVIES _/${cleanName} (${y})")}{"${az}/${cleanName} (${y})"}+"/${cleanName} (${y})"}} {'.CD'+pi}{'.'+minutes+'m'}{'.'+certification}{'.'+resolution}{'.'+source}{'.'+crc32}
User avatar
rednoah
The Source
Posts: 23936
Joined: 16 Nov 2011, 08:59
Location: Taipei
Contact:

Re: Where do I find all the documentation?

Post by rednoah »

1.
Did you mean something like this? Works fine as far as I can tell:

Code: Select all

{def x = readLines('/path/to/file').find{ it =~ imdbid }.after(':'); 'Collections/'.concat(x)}
:shock: You are the first person to ever use block labels in format expressions... I completely forgot Java/Groovy even had those:

Code: Select all

MC:{readLines('P:/collections.txt').find{ it =~ imdbid }.after(':')}
@see http://groovy-lang.org/semantics.html#_ ... statements


3.
Do you have any logs or examples? You can make FileBot dump all the network responses to the log if you enabled it with the Developer Options / Java System Properties so you can see what exactly the database is sending back.
:idea: Please read the FAQ and How to Request Help.
User avatar
Wolfie
Posts: 128
Joined: 27 Oct 2015, 02:59

Re: Where do I find all the documentation?

Post by Wolfie »

1. When I would try it, it wouldn't take. The 'def' would be before the A: block, which could have something to do with it.

2. I was sort of forced into it. Without them, it fusses at me with an error message and tells me to use them.

3. Will have to make a log.
User avatar
Wolfie
Posts: 128
Joined: 27 Oct 2015, 02:59

Re: Where do I find all the documentation?

Post by Wolfie »

Okay, enabled the options in the filebot.l4j.ini file, cleared the cache, then ran it.

Loaded it up, it showed the check for the latest version. Went to 'edit format' and it showed the response (from omdb?) for the file it had marked as the guinea pig. When I clicked the button to choose a new file, a lot of scrolling happened, but with information on the same file. When I pick a file that failed previously, nothing happens. When I pick another file (all while remaining in the "movie bindings" dialog box), data would scroll. So, seems as though certain files just aren't getting a response (or aren't sending requests).

It works just fine when trying to do matches for renaming, which is where it truly counts. It's just the 'bindings' area that it's being weird.
Post Reply