Sharing my TV shows expression

All about user-defined episode / movie format expressions
Post Reply
User avatar
Posts: 102
Joined: 27 Oct 2015, 02:59

Sharing my TV shows expression

Post by Wolfie » 09 May 2018, 10:17

Thought I would share one of my currently developed expressions. It's not the one I use, but it's the one that others might find useful features to use.

First, some background information...
I use P: as my Plex drive, meaning it contains the database, etc. It also have various junction links to folders on other drives, such as P:\_01 linking to the "Media" folder on the first drive containing my media collection. P:\_02 does the same thing but on the second drive, etc. I also have a P:\dev folder, where the same numbers (P:\dev\01) link to the root of the drive.

The benefits to doing this is that on Plex, I just add in folders P:\_01\TV, P:\_02\TV, etc. No need to assign drive letters, unless desired. Also, no need to remember drive letters, and adding another drive keeps scripting simple as well. Same with having the root of each drive linked in a \dev\ folder, in case it's needed.

Code: Select all

Starting off, this is rather simple. It looks at the original path of the file and if it matches P:\_## or P:\dev\## then it will keep it on that same path, otherwise it throws it to P:\_03 (which is currently the default for new material). If you go on a frenzy of renaming files with a new scheme, files will remain on the same drive without having to alter the expression each time.

For anyone wondering, I store newly creature media files in a different structure, so that I can verify that it works without issues in Plex, before fully integrating it. Thus the \dev\ use. Same structure, just a different root folder name. Also, the files don't get all the same fancy naming done to them, so if I have a few different copies, I can more easily identify and remove the culprit among a smaller group of files. After I'm sure everything is fine, I can FileBot them to their final homes.

Code: Select all

{any{concat(readLines('P:/series.txt').find{ it =~ /^${} / }.after(/^\d+\s+/)).before(/[:#\t\/*\\]/)}{"${ny.colon(' - ').validateFileName()} (${y})"}}
Most people won't use this, but it can be useful for people who want to custom name the folders of shows, especially if there are multiple shows with similar names, like Law & Order, CSI, Star Trek, etc. If you want to use this, you will need to create a file (you can edit the location and name, obviously). In the file, at the start of the line, enter in the ID from TheTVDB, followed by a space or tab, then the name you want to use for the show. For example, for Star Trek The Next Generation and Deep Space 9, these two lines would be in the file:
71470 Star Trek TNG
72073 Star Trek DS9

You can add a tab and then a comment after the name if you want to add any comments for personal use (will be ignored by filebot).
An example of the above being used:
Star Trek DS9/Season xx/
Star Trek TNG/Season xx/

If not used, then the original name will be used with the year appended to it:
Star Trek Deep Space Nine (1993)/Season xx
Star Trek The Next Generation (1987)/Season xx

This can also be modified to mix two different shows into one main folder, though I'm not sure why anyone would want to do that.

Code: Select all

{"/Season ${episode.special || episode.special == 0 ? '00':s.pad(2)}/${ny.colon(' - ').validateFileName()}"} ({y}) - {s00e00.lower()}
Generates the "Season ##" folder, adding a leading 0 for seasons less than 10 and using '00' for Specials.
After that the name of the show, the year the show started, and the season/episode numbers. The little extra work you see taking place is convert any colon's to a dash surrounded by spaces, and to validate the name for use. You'll also notice I'm using {ny} instead of {n}, which adds the year to the end of the name. I like to have a year added after the show's name for consistency and reference. Maybe I have OCD, but seeing some titles with a year and others without, it bugs me. (Some titles have the year attached even without the 'y' being used.)

Code: Select all

{if (n != "Tom and Jerry") airdate.format(' - yyyy-MM-dd') else airdate.format(' - (MMM dd)')}
Oddly enough, and no idea why, but when my Tom and Jerry collection has the full date, Plex gets all confused by it. No other show suffers from this that I've noticed. But since each season is also the year number, only need/want to know the month and day the episode originally aired.

Code: Select all

{" - ${t.replacePart(' (Part $1)').colon(' - ').replaceAll(/[*]/,'-').validateFileName()}"}{".[${crc32}]"}
Episode title cleaning as well as separating any reference to a part number, for easier view. CRC32 value in brackets so that files can be verified later on, if desired.

Other useful expressions...

Code: Select all

{ny.colon(' - ').validateFileName()} - {s00e00.lower()}{" - "+fn.after(s00e00).after(/\.| (- )?/)}
This will pretty up the name of the file with the show's name and year, followed by season/episode numbers, and then attach the original filename, starting after the s##e## part of the name (if there is anything left after that). Useful for when you want to retain the file information, such as resolution, encoding, group, etc. Sort of a compromise between the original filename, and having a more pleasant file name.

For movies...

Code: Select all

{any{"_ COLLECTIONS _/".concat(any{MC:{readLines('P:/collections.txt').find{ it =~ imdbid }.after(':').before(/[:#\t*\/\\]/)}}{collection.colon(" - ").validateFileName()})+"/${y} - ${n.colon(" - ").validateFileName()}"}}
This sorts movies that are associated with a collection (or a custom collection) into a folder titled... "_ COLLECTIONS _" as you might expect. It will then add into a folder structure consisting of the collection name, then folder using "(year) (movie name)" as the structure. Only drawback to this is that sometimes Plex doesn't figure out the proper name of the movie, since the year is first. As a personal preference, when I have some movies that are part of a series, I like them to be sorted by year when browsing the files, instead of trying to remember the order.

If you look closely, you'll see a bonus here, which allows you to create custom (or override existing) movie collections. Some movies are already associated with certain collections, but you may want to rename it or alter which collection a particular movie is saved to. With some work, this could also be made to exclude certain movies from being in a collection (file structure wise, not Plex wise). The format of the "collections.txt" file is rather simple:

tt0055928;tt0057076;tt0058150;tt0059800;tt0062512;:James Bond Collection

Mind you, the ;'s are really necessary, and could be replaced with spaces, commas, periods, middle fingers, etc, just not colons. The colon tells the expression where the name of the collection starts. For anyone wondering if there's a change of an false hit despite having proper IMDb ID's in the file, the ID's are all the same length, so if it finds a match, then it's the correct match (assuming you put the correct ID's in there). I have a BluRay set of James Bond movies, and using this allows me to ensure that files are always in a collection, even if an online database doesn't have it properly added. If I wanted to, I could rename it to "007 Collection" or "007 James Bond" or something else. (There are a few more lines in my file for the JB collection, the line shown is just an example.)

Questions, comments, jaws on floor from confusion?
Last edited by Wolfie on 10 May 2018, 11:19, edited 4 times in total.

Posts: 332
Joined: 06 Jun 2017, 22:56

Re: Sharing my TV shows expression

Post by devster » 10 May 2018, 08:50

The collections are a very nice touch, didn't think about that one. Does Plex automatically pick them up?

Also I see validateFileName() but not its definition, I can imagine it does filename cleaning?
I only work in black and sometimes very, very dark grey. (Batman)

User avatar
The Source
Posts: 16546
Joined: 16 Nov 2011, 08:59
Location: Taipei

Re: Sharing my TV shows expression

Post by rednoah » 10 May 2018, 09:57

String.validateFileName() is a FileBot-specific Groovy extension. It'll remove all special characters that aren't allowed in file names on Windows. FileBot will do that anyway, but if you do it in the format right away, then the FileBot GUI won't need prompt you about validating file names.
:idea: Please read the FAQ and How to Request Help.

User avatar
Posts: 102
Joined: 27 Oct 2015, 02:59

Re: Sharing my TV shows expression

Post by Wolfie » 10 May 2018, 09:58

validateFileName() cleanses out anything that would cause issues with content being used as a filename. I'm not sure if it accepts any arguments, such as what to replace invalid characters with, but it's quite useful when you want to manually control the process. Keep in mind that it will remove illegal characters, which is why I have the colon() option in there, replacing it with a dash surrounded by two spaces, so a show like "Law & Order: Special Victims Unit" will turn into "Law & Order - Special Victims Unit" instead of just dropping the colon altogether. Helps it to stand out better, IMO. You'll also notice for the title (episode title) that I have a replaceAll to change *'s to -'s as well, instead of just letting them get removed.

As for the collections, I just tested it and unfortunately, Plex ignores the filenames and uses the 'root' folder for the series name (and ignores subfolders). So at best, it can be used to rename the folder, which is still useful (instead of "Law & Order - Special Victims Unit", I could have it use "Law & Order - SVU" for example). I tested it with a couple of different Star Trek series I have, and when they are within a folder, even with a subfolder with a good naming mechanism, it groups them all together. It can still be used with movies though, as I have been doing that for awhile. Some movies are already labeled as being part of a collection, but for movies you feel belong together, but aren't being stored that way, you can use the above to make it happen. There's a slightly different expression for it that I use, so that you can have multiple IMDb titles on one line instead of having a ton of seemingly duplicate lines. (Thanks to rednoah for teaching me how to do it.)

Note: I altered the 'collections' section to make it into a way to customize the name of the folder of the series.

User avatar
Posts: 102
Joined: 27 Oct 2015, 02:59

Re: Sharing my TV shows expression

Post by Wolfie » 20 Nov 2018, 20:24

Updated in next post...
Last edited by Wolfie on 23 Nov 2018, 03:55, edited 1 time in total.

User avatar
Posts: 102
Joined: 27 Oct 2015, 02:59

Re: Sharing my TV shows expression

Post by Wolfie » 23 Nov 2018, 03:53

Another little update, with options that can be tweaked...

See below for information on using this code...

Code: Select all

   def drive="01", talkshows=true, preserve=false, smartTitles=true
   def vol="P:", drives="01,02,03".tokenize(',').join('|'), trim, release, post, tail
   def vc=any{vc.match(/^[hx]\.?26\d|hevc/).replaceAll(/[hx]\.?265/,'HEVC')}{}, year=any{y}{episodelist[0].airdate.year}{'0000'}
   def name=n.removeAll(/\s*\((00|19|20)\d\d\)\s*$/).colon(' - ').validateFileName(), show="${name} (${year})", ep=s00e00.lower()
   def az(text) {return t.lower().matchAll(/[a-z]/).join().match(strip(text).stripReleaseInfo().matchAll(/[a-z]/).join())?t:""}
   def releaseInfo(text) { return text.removeAll(/${text.stripReleaseInfo().replaceAll(/\s/,'.+')}/).removeAll(/(^[-. ]+|[-. ]+$)/) }
   def stripRelease(text) {return any{text.match(/(?i)${text.stripReleaseInfo().replaceAll(/ /,'.+')}/)}{text.stripReleaseInfo()}.removeAll(/(?<=\.| - )multi(?=\.|$)/)}
   def stripShow(text) { text.after(n.removeAll(/(?:\s?\(\d{4}\)\s?)?$/).replaceAll(/[, ]/,'.')).removeAll(/\.\[?[a-f\d]{8}\]?(?=\.|$)/)
        .after(/((s\d{1,3})?e\d{1,3}(-(s\d{1,3})?e?\d{1,3})?|\d{1,4}x\d{1,3}(-\d{1,3})?)[-. ]*/).after(/([-. ]+)?[(\[]?\d{4}[-.]\d{2}[-.]\d{2}[)\]]?([ .-]+)?/) }
   def strip(text) {text=stripShow(stripRelease(text))}
   def root="${vol}/_${any{file.path.match(/(?<=${vol}(?:\\dev)?\\_)${drives}(?=\\)/)}{drive}}/${any{file.path.match(/(?i)(\\WOLF\\)/)}{"/"}}"
   def path=[ tv: 'TV', folder: any{readLines('P:/collections.txt').find{ it =~ /^${}\s/ }.after(/^\d+\s+/).before(/[:#\t*]/)}{show},
              season: "Season ${(episode.special || episode.special == 0 ? '00':s.pad(2))}"]
   if (talkshows && any{genres.toString().match(/(?i)Talk ?Shows?/)}{file.path.match(/(?i)(\\(_ )?TALKSHOWS?\\)/)})
      { path['season']=airdate.format('yyyy-MM MMMM'); ep=airdate; path['tv']="TALKSHOWS" }

   trim=(preserve ? stripShow(fn) : (smartTitles ? any{az(fn)}{az(original)}{strip(fn)}{strip(original)}{t} : any{strip(fn)}{strip(original)} ))
   post=(preserve ? trim : allOf{allOf{trim.replaceAll(/\.{3,}/,'…')}{release.join('.')}.join('.')}{group}.join('-'))
   tail=allOf{".[${crc32}]"}{fn.find(/(?<=[. -])(cd|part)\s?\d/).upper()}{subt}.toString().match(/(?<=^\[).*(?=\]$)/).replaceAll(/, /,'.')
   path['name']=(allOf{show}{ep}{post}.join(' - ')+tail).colon(' - ').validateFileName()

   return allOf{root}{path.values().join('/')}.join('/')
This expression follows a structure of (drive letter):/_(drive number)/TV/(show name)/(Season)/(show name/episode info)
The (drive number) is really a junction link to another volume, so that I have one 'root' drive, with a top level folder that redirects to another drive. So even though everything (for me) is going to P:\, the _01, _02, _03 (etc) folders are going to different volumes. This lets me cut down on the number of drive letters being used, and makes it less to remember when it comes to drive letters (which letter is for which volume, etc). The above is for those who want to follow the same method of storage.

That said, here is how to use the expression.
vol = drive letter, with the colon. drive = default drive number. drives = list of drives available (separated by commas)
talkshows/preserve/smartTitles = true/false
I have talk shows placed in a different folder so that they don't clutter the "On Deck" of my Plex home page, but that can be toggled off by changing the "talkshows" value from 'true' to 'false.' Also, with 'talkshows' enabled, the season and episode information is formatted differently, using date info instead of the regular s00e00 structure.
In some instances, preserving the original title information (everything after the show's name, episode and/or date) is desired. Having 'preserve' set to true will try to filter out the first part of the file name so that it can be formatted, and keep everything else, including any release information (codec info, source, group, etc). It will still remove any CRC32 values it find (assuming it is somewhat properly formatted).
The 'smartTitles' option is sort of the flip of the preserve option. When this is enabled, it will try to compare the current filename (and then the original filename) to see if any part of it matches the retrieved 'Title' information. If it does, then it will replace it with the {t} (title) value, as a manner of 'upgrading' it. This is useful when wanting to keep the original name if it doesn't quite match what is retrieved from an online database, while allowing the script to update it if the original name will still be kept (somewhat). This is very useful when there is a show where the titles aren't matching up with a database because of the episodes not matching, while letting matching episodes get the title tweaked. If the smartTitle attempts fail, it falls to trying to retain a title from the current filename (or the original), followed by using the {t} value if all else fails.

I sometimes like to have certain content placed into a different subfolder (sort of a "middle man" folder), for items that I want to be scanned into a different library from the main one. That's where the 'wolf' value comes in. It can be ignored, or changed to fit ones needs.

If you want to use this as a file for filebot to call on when auto processing a torrent, then remove the line that starts off with "def root="

Other notes about the above expression... Unless 'preserve' is enabled, it will remove any release information (video, audio, group information) and append it after the title with {source} in uppercase, {vf}, {vc} as either x264 or HEVC (or will be blank), {ac} if it's no more than 5 characters (most audio codecs), along with -{group} if available. Also, it will always add ".[{crc32}]" to the end, along with any cd/part and subtitle information (if applicable).

Obviously, most people might not be able to use the expression "as-is," but hopefully some of it will give others some ideas on what to do with their own.

(Thanks to rednoah for his help in other discussions, it really helped a lot.)

Couple of things to look at that can be useful. fileBot already offers "stripReleaseInfo()" as a way of removing certain information from the filename or title. I couldn't find a function to do the opposite (so if it exists, please tell me rednoah!), so I created one. The "def releaseInfo()" line is it. It will return the release info that it finds, by removing the results of stripReleaseInfo().
stripShow() will try to remove the show's name, year, and episode information (as well as the air date if it's there) so that the rest of the filename remains. It's part of what makes the "preserve" option work.
stripRelease is just like stripReleaseInfo() except it will try to return everything as it was already formatted, instead of returning stuff filtered into spaces. (stripReleaseInfo() converting items to spaces is a good thing, but sometimes you want original content).

Post Reply