[DOCS] Custom Post-Processing Scripts

Running FileBot from the console, Groovy scripting, shell scripts, etc
Post Reply
User avatar
rednoah
The Source
Posts: 24345
Joined: 16 Nov 2011, 08:59
Location: Taipei
Contact:

[DOCS] Custom Post-Processing Scripts

Post by rednoah »

Add Post-Processing Scripts via Rename Settings

You can add custom post-processing scripts via Icon Renaming SettingsIcon Post ProcessIcon New Script.

ScreenshotScreenshotScreenshot


:idea: Use Right-ClickRun or CTRL+R to do a test run and check debug output:

ScreenshotScreenshot




Add Post-Processing Scripts via the --apply option from Terminal

--apply can be added to -rename, -find and -mediainfo commands to run your custom code on newly renamed files or previously renamed files.

Shell: Select all

--apply '{ source, target, metadata -> println "$source | $target | $metadata" }'

Shell: Select all

--apply /path/to/apply.groovy




Some Examples






e.g. Run Command

Run Command after processing:

Groovy: Select all

system 'say', '-v', 'Trinoids', 'Rename Complete'
Run Command after processing and pass along all target file paths via $@ at once:

Groovy: Select all

system '/path/to/script.sh', *args
Run Command for each newly processed file, passing the source file path and target file path as $1 and $2 respectively:

Groovy: Select all

{ source, target ->
	system '/path/to/script.sh', source, target
}

e.g. Refresh Plex

Refresh Plex via a HTTP request with X-Plex-Token authentication:

Groovy: Select all

def host = '127.0.0.1'
def auth = 'YOUR_PLEX_TOKEN'

curl "http://${host}:32400/library/sections/all/refresh?X-Plex-Token=${auth}"

e.g. Refresh Plex (Partial Scan)

Refresh Plex via dedicated HTTP requests for each library and target folder path:

Groovy: Select all

def host = '127.0.0.1'
def auth = 'YOUR_PLEX_TOKEN'

def sections = "http://${host}:32400/library/sections"
def header = ['X-Plex-Token': auth]

// request library sections
def libraryRoot = [:].withDefault{ [] }

curl(header, sections).'Directory'.'Location'.collect{ location ->
	def key = location.'..'.'@key'.text()
	def path = location.'@path'.text()
	def root = path.split(/[\\\/]/).last()
	libraryRoot[root] += [key: key, path: path]
}


// guess remote file path
def requests = [] as Set

args.collect{ f -> f.dir.path.split(/[\\\/]/).tail() }.unique().each{ components ->
	components.eachWithIndex{ c, i ->
		libraryRoot[c].each{ r ->
			if (i < components.size() - 1) {
				requests += [section: r.key, path: [r.path, *components[i+1..-1]].join('/')]
			} else {
				requests += [section: r.key, path: r.path]
			}
		}
	}
}


// send refresh requests
requests.each{ r ->
	curl(header, "${sections}/${r.section}/refresh".toURL(path: r.path))
}

e.g. Refresh Emby / Jellyfin

Refresh Emby / Jellyfin via a HTTP request:

Groovy: Select all

def host = '127.0.0.1'
def auth = 'YOUR_API_KEY'

curl "http://${host}:8096/Library/Refresh?api_key=${auth}", [:]

e.g. Refresh Kodi

Refresh Kodi via a HTTP request:

Groovy: Select all

def host = '127.0.0.1'
def port = 8080

curl "http://${host}:${port}/jsonrpc", [jsonrpc: '2.0', method: 'VideoLibrary.Scan', id: 1]

e.g. Refresh Radarr

Refresh Radarr via HTTP requests:

Groovy: Select all

def host = '127.0.0.1'
def port = 8310
def auth = 'YOUR_API_KEY'

def ids = model.findAll{ it.type =~ /Movie/ }.findResults{ it.tmdbId } as Set

ids.each{ id ->
	def r = curl "http://${host}:${port}/api/v3/movie?tmdbId=${id}", 'X-Api-Key': auth
	r.each{ m ->
		curl "http://${host}:${port}/api/v3/command", [name: 'rescanMovie', movieId: m.id], 'X-Api-Key': auth
	}
}

e.g. Refresh Sonarr

Refresh Sonarr via HTTP requests:

Groovy: Select all

def host = '127.0.0.1'
def port = 8989
def auth = 'YOUR_API_KEY'

def ids = model.findAll{ it.type =~ /Episode/ }.findResults{ it.tvdbId } as Set

ids.each{ id ->
	def r = curl "http://${host}:${port}/api/v3/series?tvdbId=${id}", 'X-Api-Key': auth
	r.each{ s ->
		curl "http://${host}:${port}/api/v3/command", [name: 'rescanSeries', seriesId: s.id], 'X-Api-Key': auth
	}
}

e.g. Notify via Discord

Send alerts to a Discord channel via Server Settings ➔ Integrations ➔ Webhooks:

Groovy: Select all

{ source, target ->
	curl 'https://discord.com/api/webhooks/YOUR_WEBHOOK', [content: """```[${action}] from [${source.name}] to [${target.name}]```"""]
}

e.g. Notify via Mattermost

Send alerts to a Mattermost channel via Incoming Webhooks:

Groovy: Select all

{ source, target ->
	curl 'http://YOUR_SITE/hooks/YOUR_WEBHOOK', [text: """```[${action}] from [${source.name}] to [${target.name}]```"""]
}

e.g. Notify via ntfy

Send messages to a ntfy.sh topic:

Groovy: Select all

{ source, target ->
	curl 'https://ntfy.sh/YOUR_TOPIC', """[${action}] from [${source.name}] to [${target.name}]""", 'X-Priority':'low', 'X-Tags':'white_check_mark'
}

e.g. Notify via Pushover

Send Pushover notifications:
Screenshot

Groovy: Select all

submit 'https://api.pushover.net/1/messages.json', [
	token: '<YOUR APPLICATION TOKEN>',
	user: '<YOUR USER KEY>',
	html: 1,
	title: 'FileBot has processed ' + model.size() + ' file(s)',
	message: XML{
		model.each{ source, target, metadata -> 
			p{
				b(type.match('Episode': '📺 ', 'Movie': '🎬 ')){
					a(href: type.match('Episode': "https://www.themoviedb.org/tv/$id", 'Movie': "https://www.themoviedb.org/movie/$id"), target.nameWithoutExtension)
				}
				br(); span("☆ $rating • $vf • $acf • $bytes • $hours")
				br(); span(source.name)
			}
		}
	}.removeAll(/\R/)
]

e.g. Add YouTube trailer link

Create a trailer.url (i.e. InternetShortcut file) that links to the trailer on YouTube in each movie folder:

Groovy: Select all

{ source, target ->
	def extra = movie.extras.find{ it.type == /Trailer/ && it.site == /YouTube/ }
	if (extra) {
		INI(target.dir / 'trailer.url') {
			InternetShortcut {
				URL "https://www.youtube.com/watch?v=${extra.key}"
			}
		}
	}
}

e.g. Add subtitles in multiple languages

Fetch normal subtitles (excluding forced and HI subtitles) in multiple languages via Exact Search if possible or Fuzzy Search if necessary, but only if the file at hand doesn't already have embedded subtitles:

Groovy: Select all

{ source, target ->
	if (!textLanguages) {
		getSubtitles target, 'en', 'de', 'fr', strict: false, filter: { !it.forced && !it.HI }
	}
}

e.g. Move subtitle files to a dedicated subs folder

Move subtitle files to a dedicated subs/ folder:

Groovy: Select all

{ source, target ->
	if (target.video) {
		target.dir.listFiles().each{
			if (it.subtitle && it.isDerived(target)) {
				it.move(target.dir / 'subs' / it.name)
			}
		}		
	}
}

e.g. Delete duplicates

Delete logical duplicates that are of lower quality from the target folder if the newly added file is of better quality:

Groovy: Select all

{ source, target, metadata ->
	target.dir.listFiles{ it.video && it != target && it.metadata == metadata }.each{
		if (target.isBetter(it)) {
			trash(it)
		} else {
			reveal(it)
		}
	}
}

e.g. Delete clutter files

Delete *.txt files and *.nfo files from the source folder:

Groovy: Select all

{ source, target ->
	trash source.dir.listFiles{ it.extension ==~ /txt|nfo/ }
}

e.g. Set xattr metadata on source files

Set xattr metadata on the source file as well:

Groovy: Select all

{ source, target, metadata ->
	source.metadata = metadata
}
Add .xattr folders for the source file as well:

Groovy: Select all

{ source, target, metadata ->
	json.saveAs(source.dir / '.xattr' / f.name / 'net.filebot.metadata')
}

e.g. Set Finder Comments

Run osascript to set the Finder Comments to the original file name:

Groovy: Select all

{ source, target ->
	// reveal file in Finder
	system 'open', '-R', target
	// set Finder Comment
	system 'osascript', '-e', """
		tell app "Finder"
			set f to (POSIX file "$target" as alias)
			set c to "$original"
			set comment of f to c
		end tell
	"""
}

e.g. Set Media Title

Run mkvpropedit or AtomicParsley to set the media title property to the original file name:

Groovy: Select all

{ source, target ->
	if (ext == /mkv/) {
		system 'mkvpropedit', target, '--edit', 'info', '--set', "title=$original"
	}
	if (ext == /mp4/) {
		system 'AtomicParsley', target, '--title', original, '--overWrite'
	}
}
Run the Write Embedded Tags script to write embedded tags and embedded artwork:

Groovy: Select all

system 'filebot', '-script', 'fn:tags', *args

e.g. Hello World

The Hello World example that will get you started with any possible custom use case:

Groovy: Select all

// RUN ONCE
{
	println "args = $args"          // args ... list of target files
	println "model.n = $model.n"    // model.* ... any binding for all matches
	println "model.f = $model.f"    // ⋮
	println "action = $action"      // action ... move, copy, etc
}

// RUN ONCE FOR EACH FILE
{ source, target, metadata ->
	println "source = $source"      // source ... source file
	println "target = $target"      // target ... target file
	println "metadata = $metadata"  // metadata ... movie, episode, etc
	println "n = $n" 	            // * ... any binding for this match
	println "type = $type"          // ⋮
	println "f = $f"                // ⋮
	println "ext = $ext"            // ⋮
}


Additional Examples and Use Cases

:idea: Please read the FAQ and How to Request Help.
User avatar
rednoah
The Source
Posts: 24345
Joined: 16 Nov 2011, 08:59
Location: Taipei
Contact:

How about sharing your post-processing scripts?

Post by rednoah »

⭑⭑ Please share your scripts, or ideas for scripts, so that we may grow the list of example post-processing scripts ready for copy & paste. ⭑⭑
:idea: Please read the FAQ and How to Request Help.
Post Reply