Erreur TheTvDB filebot 4.7.9-portable

Publié par [Admin] Bastien le

Depuis deux trois jours j’ai une erreur sur Filebot, erreur avec TheTvDB seulement, la cause ? L’api de TheTvDB n’est plus compatible avec la version de Filebot, ce qui entraine une erreur dans la lecture du fichier XML.

Pour corriger l’erreur il suffit de changer de fournisseur (MovieDB par exemple) mais cela ne fonctionne pas en dans le script.

Il suffit d’utiliser une alternative au script AMC.

Créer un fichier avec Notepad ++ par exemple, portant le nom de “amc_custom.groovy”, et coller ceci dedans puis sauvegarder.

#!/usr/bin/env filebot -script

import static groovy.json.StringEscapeUtils.*
import static net.filebot.web.OpenSubtitlesHasher.*

def formatType = []
_args.args.each{ n -> n =~ /=@/ ? formatType << n :'' }
def override = _args.conflict.equalsIgnoreCase('override')
def logFile = any{_args.logFile }{ 'No log file' }
def locale = any{ _args.language.locale }{ Locale.US }
def nfoOnly = tryQuietly{ nfoOnly.toBoolean() }
def tmdbTV = tryQuietly{ tmdbTV.toBoolean() }
def tvDB = tmdbTV ? 'TheMovieDB::TV' : 'TheTVDB'

// log input parameters
log.info("FROM custom amc - Run script [$_args.script] at [$now]")
log.info("FROM custom amc - Log file [$logFile]")
log.info "FROM custom amc - tvDB: [${tvDB}]"
log.info "FROM custom amc - EpisodeOrder: [${_args.order}]"
log.info("FROM custom amc - Parameter: Language = [$locale]")
log.info("FROM custom amc - Parameter: Override = [$override]")
_def.each{ n, v -> log.finest('FROM custom amc - Parameter: ' + [n, n =~ /plex|kodi|pushover|pushbullet|mail|myepisodes|.+Format/ ? n =~ /.+Format/ ? formatType : '*****' : v].join(' = ')) }
args.withIndex().each{ f, i -> if (f.exists()) { log.finest "FROM custom amc - Argument[$i]: $f" } else { log.warning "FROM custom amc - Argument[$i]: Does not exist: $f" } }

// initialize variables
failOnError = _args.conflict.equalsIgnoreCase('fail')
testRun = _args.action.equalsIgnoreCase('test')

// --output folder must be a valid folder
outputFolder = tryLogCatch{ any{ _args.output }{ '.' }.toFile().getCanonicalFile() }

// enable/disable features as specified via --def parameters
unsorted  = tryQuietly{ unsorted.toBoolean() }
music     = tryQuietly{ music.toBoolean() }
subtitles = tryQuietly{ subtitles.split(/\W+/) as List }
artwork   = tryQuietly{ artwork.toBoolean() && !testRun }
extras    = tryQuietly{ extras.toBoolean() }
clean     = tryQuietly{ clean.toBoolean() }
exec      = tryQuietly{ exec.toString() }

// array of kodi/plex/emby hosts
kodi = tryQuietly{ any{kodi}{xbmc}.split(/[ ,;|]+/)*.split(/:(?=\d+$)/).collect{ it.length >= 2 ? [host: it[0], port: it[1] as int] : [host: it[0]] } }
plex = tryQuietly{ plex.split(/[ ,;|]+/)*.split(/:/).collect{ it.length >= 2 ? [host: it[0], token: it[1]] : [host: it[0]] } }
emby = tryQuietly{ emby.split(/[ ,;|]+/)*.split(/:/).collect{ it.length >= 2 ? [host: it[0], token: it[1]] : [host: it[0]] } }

// extra options, myepisodes updates and email notifications
extractFolder      = tryQuietly{ extractFolder as File }
skipExtract        = tryQuietly{ skipExtract.toBoolean() }
deleteAfterExtract = tryQuietly{ deleteAfterExtract.toBoolean() }
excludeList        = tryQuietly{ def f = excludeList as File; f.isAbsolute() ? f : outputFolder.resolve(f.path) }
myepisodes         = tryQuietly{ myepisodes.split(':', 2) as List }
gmail              = tryQuietly{ gmail.split(':', 2) as List }
mail               = tryQuietly{ mail.split(':', 5) as List }
pushover           = tryQuietly{ pushover.split(':', 2) as List }
pushbullet         = tryQuietly{ pushbullet.toString() }
storeReport        = tryQuietly{ storeReport.toBoolean() }
reportError        = tryQuietly{ reportError.toBoolean() }

// user-defined filters
label       = any{ _args.mode }{ ut_label }{ null }
ignore      = any{ ignore }{ null }
minFileSize = any{ minFileSize.toLong() }{ 50 * 1000L * 1000L }
minLengthMS = any{ minLengthMS.toLong() }{ 10 * 60 * 1000L }

// series/anime/movie format expressions
seriesFormat   = any{ seriesFormat   }{ '{plex}' }
animeFormat    = any{ animeFormat    }{ '{plex}' }
movieFormat    = any{ movieFormat    }{ '{plex}' }
musicFormat    = any{ musicFormat    }{ '{plex}' }
unsortedFormat = any{ unsortedFormat }{ 'Unsorted/{file.structurePathTail}' }



// force Movie / TV Series / Anime behaviour
def forceMovie(f) {
	label =~ /^(?i:Movie|Film|Concert|UFC)/ || f.dir.listPath().any{ it.name ==~ /(?i:Movies|Movie)/ } || f.isMovie() || any{ f.isVideo() && !forceSeries(f) && getMediaInfo(f, '{minutes}').toInteger() >= 100 }{ false }
}

def forceSeries(f) {
	label =~ /^(?i:TV|Show|Series|Documentary)/ || f.dir.listPath().any{ it.name ==~ /(?i:TV.Shows|TV.Series|TV)/ } || f.path =~ /(?<=\b|_)(?i:tv[sp]-|Season\D?\d{1,2}|\d{4}.S\d{2})(?=\b|_)/ || parseEpisodeNumber(f.path, true) || parseDate(f.path)
}

def forceAnime(f) {
	label =~ /^(?i:Anime)/ || f.dir.listPath().any{ it.name ==~ /(?i:Anime)/ } || (f.isVideo() && (f.name =~ /[\(\[]\p{XDigit}{8}[\]\)]/ || any{ getMediaInfo(f, '{media.AudioLanguageList} {media.TextCodecList}').tokenize().containsAll(['Japanese', 'ASS']) && (parseEpisodeNumber(f.name, false) != null || getMediaInfo(f, '{minutes}').toInteger() < 60) }{ false }))
}

def forceAudio(f) {
	label =~ /^(?i:audio|music|music.video)/ || (f.isAudio() && !f.isVideo())
}

def forceIgnore(f) {
	label =~ /^(?i:games|ebook|other|ignore)/
}



// include artwork/nfo, pushover/pushbullet and ant utilities as required
// if (artwork || kodi || plex || emby) { include('lib/htpc') }
if (pushover || pushbullet ) { include('lib/web') }
if (gmail || mail) { include('lib/ant') }



// error reporting functions
def sendEmailReport(title, message, messagetype) {
	if (gmail) {
		sendGmail(
			subject: title, message: message, messagemimetype: messagetype,
			to: any{ mailto } { gmail[0].contains('@') ? gmail[0] : gmail[0] + '@gmail.com' },		// mail to self by default
			user: gmail[0].contains('@') ? gmail[0] : gmail[0] + '@gmail.com', password: gmail[1]
		)
	}
	if (mail) {
		sendmail(
			subject: title, message: message, messagemimetype: messagetype,
			mailhost: mail[0], mailport: mail[1], from: mail[2], to: mailto,
			user: mail[3], password: mail[4]
		)
	}
}

def fail(message) {
	if (reportError) {
		sendEmailReport('[FileBot] Failure', message as String, 'text/plain')
	}
	die(message)
}



// check input parameters
def ut = _def.findAll{ k, v -> k.startsWith('ut_') }.collectEntries{ k, v ->
	if (v ==~ /[%$]\p{Alnum}|\p{Punct}+/) {
		log.warning "FROM custom amc - Bad $k value: $v"
		v = null
	}
	return [k.substring(3), v ? v : null]
}



// sanity checks
if (outputFolder == null || !outputFolder.isDirectory()) {
	fail "Illegal usage: output folder must exist and must be a directory: $outputFolder"
}

if (ut.dir) {
	if (ut.state_allow && !(ut.state ==~ ut.state_allow)) {
		fail "Illegal state: $ut.state != $ut.state_allow"
	}
	if (args.size() > 0) {
		fail "Illegal usage: use either script parameters $ut or file arguments $args but not both"
	}
	if (ut.dir == '/') {
		fail "Illegal usage: No! Are you insane? You can't just pass in the entire filesystem. Think long and hard about what you just tried to do."
	}
	if (ut.dir.toFile() in outputFolder.listPath()) {
		fail "Illegal usage: output folder [$outputFolder] must be separate from input folder $ut"
	}
} else if (args.size() == 0) {
	fail "Illegal usage: no input"
} else if (args.any{ f -> f in outputFolder.listPath() }) {
	fail "Illegal usage: output folder [$outputFolder] must be separate from input arguments $args"
} else if (args.any{ f -> f in File.listRoots() }) {
	fail "Illegal usage: input $args must not include a filesystem root"
}



// collect input fileset as specified by the given --def parameters
roots = args

if (args.size() == 0) {
	// assume we're called with utorrent parameters (account for older and newer versions of uTorrents)
	if (ut.kind == 'single' || (ut.kind != 'multi' && ut.dir && ut.file)) {
		roots = [new File(ut.dir, ut.file).getCanonicalFile()] // single-file torrent
	} else {
		roots = [new File(ut.dir).getCanonicalFile()] // multi-file torrent
	}
}

// helper function to work with the structure relative path rather than the whole absolute path
def relativeInputPath(f) {
	def r = roots.find{ r -> f.path.startsWith(r.path) && r.isDirectory() && f.isFile() }
	if (r != null) {
		return f.path.substring(r.path.length() + 1)
	}
	return f.name
}



// define and load exclude list (e.g. to make sure files are only processed once)
excludePathSet = new FileSet()

if (excludeList) {
	if (excludeList.exists()) {
		try {
			excludePathSet.load(excludeList)
		} catch(Exception e) {
			fail "Failed to load excludeList: $e"
		}
		log.fine "FROM custom amc - Use excludes: $excludeList (${excludePathSet.size()})"
	} else {
		log.fine "FROM custom amc - Use excludes: $excludeList"
		if ((!excludeList.parentFile.isDirectory() && !excludeList.parentFile.mkdirs()) || (!excludeList.isFile() && !excludeList.createNewFile())) {
			fail "Failed to create excludeList: $excludeList"
		}
	}
}


extractedArchives = []
temporaryFiles = []

def extract(f) {
	def folder = new File(extractFolder ?: f.dir, f.nameWithoutExtension)
	def files = extract(file: f, output: folder.resolve(f.dir.name), conflict: 'auto', filter: { it.isArchive() || it.isVideo() || it.isSubtitle() || (music && it.isAudio()) }, forceExtractAll: true) ?: []

	extractedArchives += f
	temporaryFiles += folder
	temporaryFiles += files

	return files
}


def acceptFile(f) {
	if (f.isHidden()) {
		log.finest "FROM custom amc - Ignore hidden: $f"
		return false
	}

	if (f.isDirectory() && f.name ==~ /[.@].+|bin|initrd|opt|sbin|var|dev|lib|proc|sys|var.defaults|etc|lost.found|root|tmp|etc.defaults|mnt|run|usr|System.Volume.Information/) {
		log.finest "FROM custom amc - Ignore system path: $f"
		return false
	}

	if (f.name =~ /(?<=\b|_)(?i:Sample|Extras|Extra.Episodes|Bonus.Features|Music.Video|Scrapbook|Behind.the.Scenes|Extended.Scenes|Deleted.Scenes|Mini.Series|s\d{2}c\d{2}|S\d+EXTRA|\d+xEXTRA|NCED|NCOP|(OP|ED)\d+|Formula.1.\d{4})(?=\b|_)/) {
		log.finest "FROM custom amc - Ignore extra: $f"
		return false
	}

	// ignore if the user-defined ignore pattern matches
	if (f.path.findMatch(ignore)) {
		log.finest "FROM custom amc - Ignore pattern: $f"
		return false
	}

	// ignore archives that are on the exclude path list
	if (excludePathSet.contains(f)) {
		return false
	}

	// accept folders right away and skip file sanity checks
	if (f.isDirectory()) {
		return true
	}

	// accept archives if the extract feature is enabled
	if (f.isArchive() || f.hasExtension('001')) {
		return !skipExtract
	}

	// ignore iso images that do not contain a video disk structure
	if (f.hasExtension('iso') && !f.isDisk()) {
		log.fine "FROM custom amc - Ignore disk image: $f"
		return false
	}

	// ignore small video files
	if (minFileSize > 0 && f.isVideo() && f.length() < minFileSize) {
		log.fine "FROM custom amc - Skip small video file: $f"
		return false
	}

	// ignore short videos
	if (minLengthMS > 0 && f.isVideo() && any{ getMediaInfo(f, '{minutes}').toLong() * 60 * 1000L < minLengthMS }{ false /* default if MediaInfo fails */ }) {
		log.fine "FROM custom amc - Skip short video: $f"
		return false
	}

	// ignore subtitle files without matching video file in the same or parent folder
	if (f.isSubtitle() && ![f, f.dir].findResults{ it.dir }.any{ it.listFiles{ it.isVideo() && f.isDerived(it) }}) {
		log.fine "FROM custom amc - Ignore orphaned subtitles: $f"
		return false
	}

	// process only media files (accept audio files only if music mode is enabled)
	return f.isVideo() || f.isSubtitle() || (music && f.isAudio())
}


// specify how to resolve input folders, e.g. grab files from all folders except disk folders and already processed folders (i.e. folders with movie/tvshow nfo files)
def resolveInput(f) {
	// resolve folder recursively, except disk folders
	if (f.isDirectory()) {
		if (f.isDisk()) {
			return f
		}
		return f.listFiles{ acceptFile(it) }.collect{ resolveInput(it) }
	}

	if (f.isArchive() || f.hasExtension('001')) {
		return extract(f).findAll{ acceptFile(it) }.collect{ resolveInput(it) }
	}

	return f
}



// flatten nested file structure
def input = roots.findAll{ acceptFile(it) }.flatten{ resolveInput(it) }.toSorted()

// update exclude list with all input that will be processed during this run
if (excludeList && !testRun) {
	excludePathSet.append(excludeList, extractedArchives, input)
}

// print exclude and input sets for logging
input.each{ log.fine "FROM custom amc - Input: $it" }

// early abort if there is nothing to do
if (input.size() == 0) {
	log.warning "FROM custom amc - No files selected for processing"
	return
}


// group episodes/movies and rename according to Plex standards
def groups = input.groupBy{ f ->
	// print xattr metadata
	if (f.metadata) {
		log.finest "FROM custom amc groups - xattr: [$f.name] => [$f.metadata]"
	}

	// skip auto-detection if possible
	if (forceIgnore(f))
		return []
	if (music && forceAudio(f)) // process audio only if music mode is enabled
		return [music: f.dir.name]
	if (forceMovie(f))
		return [mov:   detectMovie(f, false)]
	if (forceSeries(f))
		return [tvs:   detectSeriesName(f) ?: detectSeriesName(input.findAll{ s -> f.dir == s.dir && s.isVideo() })]
	if (forceAnime(f))
		return [anime: detectAnimeName(f) ?: detectAnimeName(input.findAll{ s -> f.dir == s.dir && s.isVideo() })]


	def tvs = detectSeriesName(f)
	def mov = detectMovie(f, false)
	log.fine "FROM custom amc - $f.name [series: $tvs, movie: $mov]"

	// DECIDE EPISODE VS MOVIE (IF NOT CLEAR)
	if (tvs && mov) {
		log.fine "FROM custom amc groups - DECIDE EPISODE VS MOVIE (IF NOT CLEAR)"
		def norm = { s -> s.ascii().normalizePunctuation().lower().space(' ') }
		def dn = norm(guessMovieFolder(f)?.name ?: '')
		log.fine "FROM custom amc groups - DECIDE EPISODE VS MOVIE:  [$dn] [MediaDetection.java]"
		def fn = norm(f.nameWithoutExtension)
		def sn = norm(tvs)
		def mn = norm(mov.name)
		def my = mov.year as String

		// S00E00 | 2012.07.21 | One Piece 217 | Firefly - Serenity | [Taken 1, Taken 2, Taken 3, Taken 4, ..., Taken 10]
		def metrics = [
			[tvs: -1, mov:  0, fun: { mn == fn } ],
			[tvs: -1, mov:  0, fun: { mov.year >= 1950 && f.listPath().reverse().take(3).find{ it.name.contains(my) && parseEpisodeNumber(it.name.after(my), false) == null } } ],
			[tvs: -1, mov:  0, fun: { mn =~ sn && [dn, fn].find{ it =~ /\b(19|20)\d{2}\b/ && parseEpisodeNumber(it.after(/\b(19|20)\d{2}\b/), false) == null } } ],
			[tvs:  5, mov: -1, fun: { parseEpisodeNumber(fn, true) || parseDate(fn) } ],
			[tvs:  5, mov: -1, fun: { f.dir.listFiles{ it.isVideo() && (dn =~ sn || norm(it.name) =~ sn) && it.name =~ /\d{1,3}/}.findResults{ it.name.matchAll(/\d{1,3}/) as Set }.unique().size() >= 10 } ],
			[tvs:  1, mov: -1, fun: { fn.after(sn) ==~ /.{0,3}\s-\s.+/ && matchMovie(fn) == null } ],
			[tvs:  1, mov: -1, fun: { [dn, fn].find{ it =~ sn && matchMovie(it) == null } && (parseEpisodeNumber(stripReleaseInfo(fn.after(sn), false), false) || stripReleaseInfo(fn.after(sn), false) =~ /\D\d{1,2}\D{1,3}\d{1,2}\D/) && matchMovie(fn) == null } ],
			[tvs: -1, mov:  1, fun: { fn =~ /tt\d{7}/ } ],
			[tvs: -1, mov:  1, fun: { f.nameWithoutExtension ==~ /[\D\s_.]+/ } ],
			[tvs: -1, mov:  5, fun: { detectMovie(f, true) && [dn, fn].find{ it =~ /(19|20)\d{2}/ } != null } ],
			[tvs: -1, mov:  1, fun: { fn.contains(mn) && parseEpisodeNumber(fn.after(mn), false) == null } ],
			[tvs: -1, mov:  1, fun: { mn.getSimilarity(fn) >= 0.8 || [dn, fn].find{ it.findAll( ~/\d{4}/ ).findAll{ y -> [mov.year-1, mov.year, mov.year+1].contains(y.toInteger()) }.size() > 0 } != null } ],
			[tvs: -1, mov:  1, fun: { [dn, fn].find{ it =~ mn && !(it.after(mn) =~ /\b\d{1,3}\b/) && (it.getSimilarity(mn) > 0.2 + it.getSimilarity(sn)) } != null } ],
			[tvs: -1, mov:  1, fun: { detectMovie(f, false).aliasNames.find{ fn.contains(norm(it)) } } ]
		]

		def score = [tvs: 0, mov: 0]
		metrics.each{
			if (tvs && mov && it.fun()) {
				score.tvs += it.tvs
				score.mov += it.mov

				if (score.tvs >= 1 && score.mov <= -1) {
					log.fine "FROM custom amc - Exclude Movie: $mov"
					mov = null
				} else if (score.mov >= 1 && score.tvs <= -1) {
					log.fine "FROM custom amc - Exclude Series: $tvs"
					tvs = null
				}
			}
		}

	}

	// CHECK CONFLICT
	if (((mov && tvs) || (!mov && !tvs))) {
		if (failOnError) {
			fail 'Media detection failed'
		} else {
			log.fine "FROM custom amc - Unable to differentiate: [$f.name] => [$tvs] VS [$mov]"
			return [:]
		}
	}

	return [tvs: tvs, mov: mov]
}

// group entries by unique tvs/mov descriptor
groups = groups.groupBy{ group, files -> group.collectEntries{ type, query -> [type, query ? query.toString().ascii().normalizePunctuation().lower() : null] } }.collectEntries{ group, maps -> [group, maps.values().flatten()] }

// log movie/series/anime detection results
groups.each{ group, files -> log.finest "FROM custom amc Group: $group => ${files*.name}" }

// keep track of files that have been processed successfully
def destinationFiles = []

// keep track of unsorted files or files that could not be processed for some reason
def unsortedFiles = []

// process each batch
groups.each{ group, files ->
	// fetch subtitles (but not for anime)
	if (group.anime == null && subtitles != null && files.findAll{ it.isVideo() }.size() > 0) {
		subtitles.each{ languageCode ->
			def subtitleFiles = getMissingSubtitles(file: files, lang: languageCode, strict: true, output: 'srt', encoding: 'UTF-8', format: 'MATCH_VIDEO_ADD_LANGUAGE_TAG') ?: []
			files += subtitleFiles
			input += subtitleFiles // make sure subtitles are added to the exclude list and other post processing operations
			temporaryFiles += subtitleFiles // if downloaded for temporarily extraced files delete later
		}
	}

	// EPISODE MODE
	if ((group.tvs || group.anime) && !group.mov) {
		// choose series / anime

		def dest = group.tvs ? rename(file: files, format: seriesFormat, db: tvDB) : rename(file: files, format: animeFormat, db: 'AniDB')

		if (dest != null) {
			destinationFiles += dest

			if (artwork) {
				dest.mapByFolder().each{ dir, fs ->
					def hasSeasonFolder = any{ dir =~ /Specials|Season.\d+/ || dir.parentFile.structurePathTail.listPath().size() > 0 }{ false }	// MAY NOT WORK FOR CERTAIN FORMATS

					fs.each{epFile ->
						def tvInfo = epFile.metadata
						if (tvInfo instanceof MultiEpisode){
							log.fine "FROM custom amc Episode Mode artwork - FileName: ${epFile}"
							log.fine "FROM custom amc Episode Mode artwork - MultiEpisode: ${tvInfo} / Ids: ${tvInfo.episodes.id}"
							log.fine "FROM custom amc Episode Mode artwork - Fetching series artwork for [$tvInfo.seriesName] / Season: [${tvInfo.special ? 0 : tvInfo.season}] / Episodes: ${tvInfo.special ? tvInfo.episodes.special : tvInfo.episodes.episode} / Titles: $tvInfo.episodes.title to [$dir]"
							fetchSeriesArtworkAndNfo(hasSeasonFolder ? dir.parentFile : dir, dir, tvInfo.seriesInfo.id, tvInfo.special ? 0 : tvInfo.season, override, locale, epFile, tvInfo.special ? tvInfo.episodes.special : tvInfo.episodes.episode, tvInfo.toString().matchAll(/\d{1,2}x\d{2}/), _args.order, tvInfo.episodes ? tvInfo.episodes.id : tvInfo.id, extras, nfoOnly, tvDB)
						}
						else {
							log.fine "FROM custom amc Episode Mode artwork - FileName: [${epFile}]"
							log.fine "FROM custom amc Episode Mode artwork - Episode: [${tvInfo}] / Id: [${tvInfo.id}]"
							log.fine "FROM custom amc Episode Mode artwork - Fetching series artwork for [$tvInfo.seriesName] / Season: [${tvInfo.special ? 0 : tvInfo.season}] / Episode: [${tvInfo.special ? tvInfo.special : tvInfo.episode}] / Title: [$tvInfo.title] to [$dir]"
							fetchSeriesArtworkAndNfo(hasSeasonFolder ? dir.parentFile : dir, dir, tvInfo.seriesInfo.id, tvInfo.special ? 0 : tvInfo.season, override, locale, epFile, tvInfo.special ? tvInfo.special : tvInfo.episode, tvInfo.special ? 0 + 'x' + tvInfo.special.pad(2) : tvInfo.season + 'x' + tvInfo.episode.pad(2), _args.order, tvInfo.id, extras, nfoOnly, tvDB)
						}
					}

				}
			}
		} else if (failOnError) {
			fail "Failed to process group: $group"
		} else {
			unsortedFiles += files
		}
	}

	// MOVIE MODE
	else if (group.mov && !group.tvs && !group.anime) {
		def trailerFile = files.findAll{ it.getName().matches(/(?i:.+\-Trailer\.\w{3})/) }.findResults{ it }
		files = files.findAll{ !it.getName().matches(/(?i:.+\-Trailer\.\w{3})/) }.findResults{ it }
		if (trailerFile.size() != 0) {log.fine "FROM custom amc Movie Mode - trailerFile: $trailerFile"}
		def dest = rename(file: files, format: movieFormat, db: 'TheMovieDB')
		if (trailerFile && dest){
			trailerFile.each{
				def ext = it.toString().after(/-trailer/)
				def newTrailerName = dest.nameWithoutExtension.join()+"-trailer"+ext
				log.fine "FROM custom amc Movie Mode - Rename Trailer File: [${it.getName()}] to [${newTrailerName}]"
				log.fine "FROM custom amc Movie Mode - Moving Trailer File: [${newTrailerName}] to ${dest.parentFile}"
				it.renameTo(new File(dest.parentFile.join(), newTrailerName))
			}
		}

		if (dest != null) {
			destinationFiles += dest

			if (artwork) {
				dest.mapByFolder().each{ dir, fs ->
					def movieFiles = fs.findAll{ it.isVideo() || it.isDisk() }.sort().findResults{ it }
					log.fine "FROM custom amc Movie Mode artwork - movieFiles: $movieFiles"

					def movieFile = fs.findAll{ it.isVideo() || it.isDisk() }.sort{ it.length() }.reverse().findResult{ it }
					if (movieFile) {
						def movieInfo = movieFile.metadata
						log.fine "FROM custom amc Movie Mode - Fetching movie NFO and artwork for [$movieInfo] to [$dir]"
						fetchMovieArtworkAndNfo(dir, movieInfo, movieFile, extras, override, locale, movieFiles, nfoOnly)
					}
				}
			}
		} else if (failOnError) {
			fail "Failed to process group: $group"
		} else {
			unsortedFiles += files
		}
	}

	// MUSIC MODE
	else if (group.music) {
		def dest = rename(file: files, format: musicFormat, db: 'ID3')

		if (dest != null) {
			destinationFiles += dest
		} else if (failOnError) {
			fail "Failed to process group: $group"
		} else {
			unsortedFiles += files
		}
	}

	// UNSORTED
	else {
		unsortedFiles += files
	}
}


// ---------- POST PROCESSING ---------- //

// deal with remaining files that cannot be sorted automatically
if (unsorted) {
	if (unsortedFiles.size() > 0) {
		log.fine "FROM custom amc - Processing ${unsortedFiles.size()} unsorted files"

		def dest = rename(map: unsortedFiles.collectEntries{ original ->
			def destination = getMediaInfo(original, unsortedFormat) as File

			// sanity check user-defined unsorted format
			if (destination == null) {
				fail("Illegal usage: unsorted format must yield valid file path")
			}

			// resolve relative paths
			if (!destination.isAbsolute()) {
				destination = outputFolder.resolve(destination.path)
			}

			return [original, destination]
		})

		if (dest != null) {
			destinationFiles += dest
		}
	}
}

// run program on newly processed files
if (exec) {
	destinationFiles.collect{ getMediaInfo(it, exec) }.unique().each{ command ->
		log.fine "FROM custom amc - Execute: $command"
		execute(command)
	}
}


// ---------- REPORTING ---------- //


if (getRenameLog().size() > 0) {
	// messages used for kodi / plex / emby pushover notifications
	def getNotificationTitle = {
		def count = getRenameLog().count{ k, v -> !v.isSubtitle() }
		return "FileBot finished processing $count files"
	}.memoize()

	def getNotificationMessage = { prefix = '• ', postfix = '\n' ->
		return ut.title ?: (input.findAll{ !it.isSubtitle() } ?: input).collect{ relativeInputPath(it) as File }.root.nameWithoutExtension.unique().collect{ prefix + it }.join(postfix).trim()
	}.memoize()

	// make Kodi scan for new content and display notification message
	if (kodi) {
		kodi.each{ instance ->
			log.fine "FROM custom amc - Notify Kodi: $instance"
			tryLogCatch {
				showNotification(instance.host, instance.port ?: 8080, getNotificationTitle(), getNotificationMessage(), 'https://app.filebot.net/icon.png')
				scanVideoLibrary(instance.host, instance.port ?: 8080)
			}
		}
	}

	// make Plex scan for new content
	if (plex) {
		plex.each{ instance ->
			log.fine "FROM custom amc - Notify Plex: $instance"
			tryLogCatch {
				refreshPlexLibrary(instance.host, 32400, instance.token)
			}
		}
	}

	// make Emby scan for new content
	if (emby) {
		emby.each{ instance ->
			log.fine "FROM custom amc - Notify Emby: $instance"
			tryLogCatch {
				refreshEmbyLibrary(instance.host, 8096, instance.token)
			}
		}
	}

	// mark episodes as 'acquired'
	if (myepisodes) {
		log.fine 'FROM custom amc - Update MyEpisodes'
		tryLogCatch {
			executeScript('update-mes', [login:myepisodes.join(':'), addshows:true], getRenameLog().values())
		}
	}

	if (pushover) {
		log.fine 'FROM custom amc - Sending Pushover notification'
		tryLogCatch {
			Pushover(pushover[0], pushover[1] ?: 'wcckDz3oygHSU2SdIptvnHxJ92SQKK').send(getNotificationTitle(), getNotificationMessage())
		}
	}

	// messages used for email / pushbullet reports
	def getReportSubject = { getNotificationMessage('', ' | ') }
	def getReportTitle = { '[FileBot] ' + getReportSubject() }
	def getReportMessage = {
		def renameLog = getRenameLog()
		'''<!DOCTYPE html>\n''' + XML {
			html {
				head {
					meta(charset:'UTF-8')
					style('''
						p{font-family:Arial,Helvetica,sans-serif}
						p b{color:#07a}
						hr{border-style:dashed;border-width:1px 0 0 0;border-color:lightgray}
						small{color:#d3d3d3;font-size:xx-small;font-weight:normal;font-family:Arial,Helvetica,sans-serif}
						table a:link{color:#666;font-weight:bold;text-decoration:none}
						table a:visited{color:#999;font-weight:bold;text-decoration:none}
						table a:active,table a:hover{color:#bd5a35;text-decoration:underline}
						table{font-family:Arial,Helvetica,sans-serif;color:#666;background:#eaebec;margin:15px;border:#ccc 1px solid;border-radius:3px;box-shadow:0 1px 2px #d1d1d1}
						table th{padding:15px;border-top:1px solid #fafafa;border-bottom:1px solid #e0e0e0;background:#ededed}
						table th{text-align:center;padding-left:20px}
						table tr:first-child th:first-child{border-top-left-radius:3px}
						table tr:first-child th:last-child{border-top-right-radius:3px}
						table tr{text-align:left;padding-left:20px}
						table td:first-child{text-align:left;padding-left:20px;border-left:0}
						table td{padding:15px;border-top:1px solid #fff;border-bottom:1px solid #e0e0e0;border-left:1px solid #e0e0e0;background:#fafafa;white-space:nowrap}
						table tr.even td{background:#f6f6f6}
						table tr:last-child td{border-bottom:0}
						table tr:last-child td:first-child{border-bottom-left-radius:3px}
						table tr:last-child td:last-child{border-bottom-right-radius:3px}
						table tr:hover td{background:#f2f2f2}
					''')
					title(getReportTitle())
				}
				body {
					p {
						mkp.yield("FileBot finished processing ")
						b(getReportSubject())
						mkp.yield(" (${renameLog.size()} files).")
					}
					hr(); table {
						tr { th('Original Name'); th('New Name'); th('New Location') }
						renameLog.each{ from, to ->
							tr { [from.name, to.name, to.parent].each{ cell -> td(cell) } }
						}
					}
					hr(); small("// Generated by ${Settings.applicationIdentifier} on ${InetAddress.localHost.hostName} at ${now}")
				}
			}
		}
	}

	// store processing report
	if (storeReport) {
		def reportFolder = ApplicationFolder.AppData.resolve('reports')
		def reportName = [now.format(/[yyyy-MM-dd HH mm]/), getReportSubject().take(50)].join(' ').validateFileName().space('_')
		def reportFile = getReportMessage().saveAs(reportFolder.resolve(reportName + '.html'))
		log.finest "FROM custom amc - Saving report as ${reportFile}"
	}

	// send pushbullet report
	if (pushbullet) {
		log.fine 'FROM custom amc - Sending PushBullet report'
		tryLogCatch {
			PushBullet(pushbullet).sendFile(getNotificationTitle(), getReportMessage(), 'text/html', getNotificationMessage(), any{ mailto }{ null })
		}
	}

	// send email report
	if (gmail || mail) {
		tryLogCatch {
			sendEmailReport(getReportTitle(), getReportMessage(), 'text/html')
		}
	}
}


// ---------- CLEAN UP ---------- //

// clean up temporary files that may be left behind after extraction
if (deleteAfterExtract) {
	extractedArchives.each{ a ->
		log.finest "FROM custom amc - Delete archive $a"
		a.delete()
		a.dir.listFiles().toList().findAll{ v -> v.name.startsWith(a.nameWithoutExtension) && v.extension ==~ /r\d+/ }.each{ v ->
			log.finest "FROM custom amc - Delete archive volume $v"
			v.delete()
		}
	}
}

// clean empty folders, clutter files, etc after move
if (clean) {
	if (['DUPLICATE', 'COPY', 'HARDLINK'].any{ it.equalsIgnoreCase(_args.action) } && temporaryFiles.size() > 0) {
		log.fine 'FROM custom amc - Clean temporary extracted files'
		// delete extracted files
		temporaryFiles.findAll{ it.isFile() }.sort().each{
			log.finest "FROM custom amc - Delete $it"
			it.delete()
		}
		// delete remaining empty folders
		temporaryFiles.findAll{ it.isDirectory() }.sort().reverse().each{
			log.finest "FROM custom amc - Delete $it"
			if (it.getFiles().size() == 0) {
				it.deleteDir()
			}
		}
	}

	// deleting remaining files only makes sense after moving files
	if ('MOVE'.equalsIgnoreCase(_args.action)) {
		def cleanerInput = args.size() > 0 ? args : ut.kind == 'multi' && ut.dir ? [ut.dir as File] : []
		cleanerInput = cleanerInput.findAll{ f -> f.exists() }
		if (cleanerInput.size() > 0) {
			log.fine 'FROM custom amc - Clean clutter files and empty folders'
			executeScript('cleaner', args.size() == 0 ? [root:true, ignore: ignore] : [root:false, ignore: ignore], cleanerInput)
		}
	}
}


if (destinationFiles.size() == 0) {
	fail "FROM custom amc - Finished without processing any files"
}

Ensuite dans votre script filebot il vous suffit d’appeler le script AMC comme ceci :

-script C:/FileBot_4.7.9-portable/amc_custom.groovy

Pour pouvoir forcer le synchro sur MovieDB il faut rajouter ceci :

--def tmdbTV=y

Et voilà, maintenant votre script utilisera uniquement TheMovieDB pour rechercher les films et séries !


Poster un Commentaire

avatar

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.

  Souscrire  
Me notifier des