* utorrent integration: + fancy notification mails + force movie/series/anime + basic anime support (no auto-detection, only if forced)
This commit is contained in:
parent
14e4b86344
commit
0cb56f905d
|
@ -95,8 +95,19 @@ import groovy.text.GStringTemplateEngine
|
||||||
import net.sourceforge.filebot.format.PropertyBindings
|
import net.sourceforge.filebot.format.PropertyBindings
|
||||||
import net.sourceforge.filebot.format.UndefinedObject
|
import net.sourceforge.filebot.format.UndefinedObject
|
||||||
|
|
||||||
Object.metaClass.applyXmlTemplate = { template -> new XmlTemplateEngine("\t", false).createTemplate(template).make(new PropertyBindings(delegate, new UndefinedObject(""))).toString() }
|
Object.metaClass.applyXml = { template -> new XmlTemplateEngine("\t", false).createTemplate(template).make(new PropertyBindings(delegate, new UndefinedObject(""))).toString() }
|
||||||
Object.metaClass.applyTextTemplate = { template -> new GStringTemplateEngine().createTemplate(template).make(new PropertyBindings(delegate, new UndefinedObject(""))).toString() }
|
Object.metaClass.applyText = { template -> new GStringTemplateEngine().createTemplate(template).make(new PropertyBindings(delegate, new UndefinedObject(""))).toString() }
|
||||||
|
|
||||||
|
|
||||||
|
// MarkupBuilder helper
|
||||||
|
import groovy.xml.MarkupBuilder
|
||||||
|
|
||||||
|
def XML(bc) {
|
||||||
|
def out = new StringWriter()
|
||||||
|
def xmb = new MarkupBuilder(out)
|
||||||
|
bc.rehydrate(bc.delegate, xmb, xmb).call() // call closure in MarkupBuilder context
|
||||||
|
return out.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Shell helper
|
// Shell helper
|
||||||
|
@ -156,7 +167,7 @@ List.metaClass.watch = { c -> createWatchService(c, delegate, true) }
|
||||||
def getRenameLog(complete = false) {
|
def getRenameLog(complete = false) {
|
||||||
def spooler = net.sourceforge.filebot.HistorySpooler.getInstance()
|
def spooler = net.sourceforge.filebot.HistorySpooler.getInstance()
|
||||||
def history = complete ? spooler.completeHistory : spooler.sessionHistory
|
def history = complete ? spooler.completeHistory : spooler.sessionHistory
|
||||||
return history.sequences*.elements.flatten().collectEntries{ [new File(it.dir, it.from), new File(it.dir, it.to)] }
|
return history.sequences*.elements.flatten().collectEntries{ [new File(it.dir, it.from), new File(it.to).isAbsolute() ? new File(it.to) : new File(it.dir, it.to)] }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Season / Episode helpers
|
// Season / Episode helpers
|
||||||
|
|
|
@ -18,7 +18,7 @@ def sshexec(param) {
|
||||||
* e.g.
|
* e.g.
|
||||||
* mail(mailhost:'smtp.gmail.com', mailport:'587', ssl:'no', enableStartTLS:'yes', user:'rednoah@gmail.com', password:'correcthorsebatterystaple', from:'rednoah@gmail.com', to:'someone@gmail.com', subject:'Hello Ant World', message:'Dear Ant, ...')
|
* mail(mailhost:'smtp.gmail.com', mailport:'587', ssl:'no', enableStartTLS:'yes', user:'rednoah@gmail.com', password:'correcthorsebatterystaple', from:'rednoah@gmail.com', to:'someone@gmail.com', subject:'Hello Ant World', message:'Dear Ant, ...')
|
||||||
*/
|
*/
|
||||||
def mail(param) {
|
def sendmail(param) {
|
||||||
def sender = param.remove('from')
|
def sender = param.remove('from')
|
||||||
def recipient = param.remove('to')
|
def recipient = param.remove('to')
|
||||||
|
|
||||||
|
@ -35,12 +35,12 @@ def mail(param) {
|
||||||
* e.g.
|
* e.g.
|
||||||
* gmail(subject:'Hello Ant World', message:'Dear Ant, ...', to:'someone@gmail.com', user:'rednoah', password:'correcthorsebatterystaple')
|
* gmail(subject:'Hello Ant World', message:'Dear Ant, ...', to:'someone@gmail.com', user:'rednoah', password:'correcthorsebatterystaple')
|
||||||
*/
|
*/
|
||||||
def gmail(param) {
|
def sendGmail(param) {
|
||||||
param << [mailhost:'smtp.gmail.com', mailport:'587', ssl:'no', enableStartTLS:'yes']
|
param << [mailhost:'smtp.gmail.com', mailport:'587', ssl:'no', enableStartTLS:'yes']
|
||||||
param << [user:param.username ? param.remove('username') + "@gmail.com" : param.user]
|
param << [user:param.username ? param.remove('username') + '@gmail.com' : param.user]
|
||||||
param << [from: param.from ?: param.user]
|
param << [from: param.from ?: param.user]
|
||||||
|
|
||||||
mail(param)
|
sendmail(param)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -66,7 +66,7 @@ def fetchSeriesFanart(outputFile, series, type, season, locale) {
|
||||||
|
|
||||||
def fetchSeriesNfo(outputFile, series, locale) {
|
def fetchSeriesNfo(outputFile, series, locale) {
|
||||||
def info = TheTVDB.getSeriesInfo(series, locale)
|
def info = TheTVDB.getSeriesInfo(series, locale)
|
||||||
info.applyXmlTemplate('''<tvshow xmlns:gsp='http://groovy.codehaus.org/2005/gsp'>
|
info.applyXml('''<tvshow xmlns:gsp='http://groovy.codehaus.org/2005/gsp'>
|
||||||
<title>$name</title>
|
<title>$name</title>
|
||||||
<year>$firstAired.year</year>
|
<year>$firstAired.year</year>
|
||||||
<rating>$rating</rating>
|
<rating>$rating</rating>
|
||||||
|
@ -188,7 +188,7 @@ def createFileInfoXml(file) {
|
||||||
}
|
}
|
||||||
|
|
||||||
def fetchMovieNfo(outputFile, movieInfo, movieFile) {
|
def fetchMovieNfo(outputFile, movieInfo, movieFile) {
|
||||||
movieInfo.applyXmlTemplate('''<movie xmlns:gsp='http://groovy.codehaus.org/2005/gsp'>
|
movieInfo.applyXml('''<movie xmlns:gsp='http://groovy.codehaus.org/2005/gsp'>
|
||||||
<title>$name</title>
|
<title>$name</title>
|
||||||
<originaltitle>$originalName</originaltitle>
|
<originaltitle>$originalName</originaltitle>
|
||||||
<set>$collection</set>
|
<set>$collection</set>
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
// filebot -script "fn:utorrent-postprocess" --output "X:/media" --action copy --conflict override --def subtitles=true artwork=true xbmc=localhost plex=10.0.0.3 "ut_dir=%D" "ut_file=%F" "ut_kind=%K" "ut_label=%L" "ut_state=%S"
|
// filebot -script "fn:utorrent-postprocess" --output "X:/media" --action copy --conflict override --def subtitles=true artwork=true xbmc=localhost plex=10.0.0.1 gmail=username:password "ut_dir=%D" "ut_file=%F" "ut_kind=%K" "ut_title=%N" "ut_label=%L" "ut_state=%S"
|
||||||
def input = []
|
def input = []
|
||||||
def failOnError = _args.conflict == 'fail'
|
def failOnError = _args.conflict == 'fail'
|
||||||
|
|
||||||
// print input parameters
|
// print input parameters
|
||||||
_args.bindings?.each{ println "Parameter: $it.key = $it.value" }
|
_args.bindings?.each{ _log.finest("Parameter: $it.key = $it.value") }
|
||||||
|
|
||||||
// disable enable features as specified via --def parameters
|
// disable enable features as specified via --def parameters
|
||||||
def subtitles = tryQuietly{ subtitles.toBoolean() }
|
def subtitles = tryQuietly{ subtitles.toBoolean() }
|
||||||
|
@ -13,11 +13,31 @@ def artwork = tryQuietly{ artwork.toBoolean() }
|
||||||
def xbmc = tryQuietly{ xbmc.split(/[\s,|]+/) }
|
def xbmc = tryQuietly{ xbmc.split(/[\s,|]+/) }
|
||||||
def plex = tryQuietly{ plex.split(/[\s,|]+/) }
|
def plex = tryQuietly{ plex.split(/[\s,|]+/) }
|
||||||
|
|
||||||
|
// email notifications
|
||||||
|
def gmail = tryQuietly{ gmail.split(':', 2) }
|
||||||
|
|
||||||
|
// force movie/series/anime logic
|
||||||
|
def forceMovie(f) {
|
||||||
|
tryQuietly{ ut_label } =~ /^(?i:Movie|Couch.Potato)/
|
||||||
|
}
|
||||||
|
|
||||||
|
def forceSeries(f) {
|
||||||
|
parseEpisodeNumber(f) || parseDate(f) || tryQuietly{ ut_label } =~ /^(?i:TV)/
|
||||||
|
}
|
||||||
|
|
||||||
|
def forceAnime(f) {
|
||||||
|
tryQuietly{ ut_label } =~ /^(?i:Anime)/
|
||||||
|
}
|
||||||
|
|
||||||
|
def forceIgnore(f) {
|
||||||
|
tryQuietly{ ut_label } =~ /^(?i:Music|Ebook|other)/
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// collect input fileset as specified by the given --def parameters
|
// collect input fileset as specified by the given --def parameters
|
||||||
if (args.empty) {
|
if (args.empty) {
|
||||||
// assume we're called with utorrent parameters
|
// assume we're called with utorrent parameters
|
||||||
if (ut_kind == "single") {
|
if (ut_kind == 'single') {
|
||||||
input += new File(ut_dir, ut_file) // single-file torrent
|
input += new File(ut_dir, ut_file) // single-file torrent
|
||||||
} else {
|
} else {
|
||||||
input += new File(ut_dir).getFiles() // multi-file torrent
|
input += new File(ut_dir).getFiles() // multi-file torrent
|
||||||
|
@ -37,15 +57,26 @@ input = input.findAll{ it.isVideo() || it.isSubtitle() }
|
||||||
input = input.findAll{ !(it.path =~ /\b(?i:sample|trailer|extras|deleted.scenes|music.video|scrapbook)\b/) }
|
input = input.findAll{ !(it.path =~ /\b(?i:sample|trailer|extras|deleted.scenes|music.video|scrapbook)\b/) }
|
||||||
|
|
||||||
// print input fileset
|
// print input fileset
|
||||||
input.each{ println "Input: $it" }
|
input.each{ f -> _log.finest("Input: $f") }
|
||||||
|
|
||||||
// artwork/nfo utility
|
// artwork/nfo utility
|
||||||
include("lib/htpc")
|
include("lib/htpc")
|
||||||
|
|
||||||
// group episodes/movies and rename according to XBMC standards
|
// group episodes/movies and rename according to XBMC standards
|
||||||
def groups = input.groupBy{ f ->
|
def groups = input.groupBy{ f ->
|
||||||
|
// skip auto-detection if possible
|
||||||
|
if (forceIgnore(f))
|
||||||
|
return []
|
||||||
|
if (forceMovie(f))
|
||||||
|
return [mov: detectMovie(f, false)]
|
||||||
|
if (forceSeries(f))
|
||||||
|
return [tvs: detectSeriesName(f)]
|
||||||
|
if (forceAnime(f))
|
||||||
|
return [anime: detectSeriesName(f)]
|
||||||
|
|
||||||
|
|
||||||
def tvs = detectSeriesName(f)
|
def tvs = detectSeriesName(f)
|
||||||
def mov = (parseEpisodeNumber(f) || parseDate(f)) ? null : detectMovie(f, false) // skip movie detection if we can already tell it's an episode
|
def mov = detectMovie(f, false)
|
||||||
println "$f.name [series: $tvs, movie: $mov]"
|
println "$f.name [series: $tvs, movie: $mov]"
|
||||||
|
|
||||||
// DECIDE EPISODE VS MOVIE (IF NOT CLEAR)
|
// DECIDE EPISODE VS MOVIE (IF NOT CLEAR)
|
||||||
|
@ -71,9 +102,13 @@ def groups = input.groupBy{ f ->
|
||||||
throw new Exception("Media detection failed")
|
throw new Exception("Media detection failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
return [tvs:tvs, mov:mov]
|
return [tvs: tvs, mov: mov, anime: null]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// log movie/series/anime detection results
|
||||||
|
groups.each{ group -> _log.finest("Group: $group") }
|
||||||
|
|
||||||
|
// process each batch
|
||||||
groups.each{ group, files ->
|
groups.each{ group, files ->
|
||||||
// fetch subtitles
|
// fetch subtitles
|
||||||
if (subtitles) {
|
if (subtitles) {
|
||||||
|
@ -81,28 +116,31 @@ groups.each{ group, files ->
|
||||||
}
|
}
|
||||||
|
|
||||||
// EPISODE MODE
|
// EPISODE MODE
|
||||||
if (group.tvs && !group.mov) {
|
if ((group.tvs || group.anime) && !group.mov) {
|
||||||
def dest = rename(file:files, format:'TV Shows/{n}/{episode.special ? "Special" : "Season "+s}/{n} - {episode.special ? "S00E"+special.pad(2) : s00e00} - {t}', db:'TheTVDB')
|
// choose series / anime config
|
||||||
|
def config = group.tvs ? [name: group.tvs, format:'TV Shows/{n}/{episode.special ? "Special" : "Season "+s}/{n} - {episode.special ? "S00E"+special.pad(2) : s00e00} - {t}', db:'TheTVDB']
|
||||||
|
: [name: group.anime, format:'Anime/{n}/{n} - {e.pad(2)} - {t}', db:'AniDB']
|
||||||
|
def dest = rename(file: files, format: config.format, db: config.db)
|
||||||
if (dest && artwork) {
|
if (dest && artwork) {
|
||||||
dest.mapByFolder().each{ dir, fs ->
|
dest.mapByFolder().each{ dir, fs ->
|
||||||
println "Fetching artwork for $dir from TheTVDB"
|
println "Fetching artwork for $dir from TheTVDB"
|
||||||
def sxe = fs.findResult{ eps -> parseEpisodeNumber(eps) }
|
def sxe = fs.findResult{ eps -> parseEpisodeNumber(eps) }
|
||||||
def options = TheTVDB.search(group.tvs)
|
def options = TheTVDB.search(config.name)
|
||||||
if (options.isEmpty()) {
|
if (options.isEmpty()) {
|
||||||
println "TV Series not found: $group.tvs"
|
println "TV Series not found: $config.name"
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
options = options.sortBySimilarity(group.tvs, { opt -> opt.name })
|
options = options.sortBySimilarity(config.name, { s -> s.name })
|
||||||
fetchSeriesArtworkAndNfo(dir.dir, dir, options[0], sxe && sxe.season > 0 ? sxe.season : 1)
|
fetchSeriesArtworkAndNfo(dir.dir, dir, options[0], sxe && sxe.season > 0 ? sxe.season : 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (dest == null && failOnError) {
|
if (dest == null && failOnError) {
|
||||||
throw new Exception("Failed to rename series: $group.tvs")
|
throw new Exception("Failed to rename series: $config.name")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MOVIE MODE
|
// MOVIE MODE
|
||||||
if (group.mov && !group.tvs) {
|
if (group.mov && !group.tvs && !group.anime) {
|
||||||
def dest = rename(file:files, format:'Movies/{n} ({y})/{n} ({y}){" CD$pi"}{".$lang"}', db:'TheMovieDB')
|
def dest = rename(file:files, format:'Movies/{n} ({y})/{n} ({y}){" CD$pi"}{".$lang"}', db:'TheMovieDB')
|
||||||
if (dest && artwork) {
|
if (dest && artwork) {
|
||||||
dest.mapByFolder().each{ dir, fs ->
|
dest.mapByFolder().each{ dir, fs ->
|
||||||
|
@ -128,3 +166,39 @@ plex?.each{
|
||||||
println "Notify Plex: $it"
|
println "Notify Plex: $it"
|
||||||
refreshPlexLibrary(it)
|
refreshPlexLibrary(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// send status email
|
||||||
|
if (gmail && getRenameLog().size() > 0) {
|
||||||
|
// ant/mail utility
|
||||||
|
include('lib/ant')
|
||||||
|
|
||||||
|
// send html mail
|
||||||
|
def renameLog = getRenameLog()
|
||||||
|
|
||||||
|
sendGmail(
|
||||||
|
subject: '[FileBot] ' + ut_title,
|
||||||
|
message: XML {
|
||||||
|
html {
|
||||||
|
body {
|
||||||
|
p("FileBot finished processing ${ut_title} (${renameLog.size()} files).");
|
||||||
|
hr(); table {
|
||||||
|
th("Parameter"); th("Value")
|
||||||
|
_args.bindings.findAll{ param -> param.key =~ /^ut_/ }.each{ param ->
|
||||||
|
tr { [param.key, param.value].each{ td(it)} }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hr(); table {
|
||||||
|
th("Original Name"); th("New Name"); th("New Location")
|
||||||
|
renameLog.each{ from, to ->
|
||||||
|
tr { [from.name, to.name, to.parent].each{ cell -> td { code(cell) } } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hr(); small("// Generated by ${net.sourceforge.filebot.Settings.applicationIdentifier} on ${new Date().dateString} at ${new Date().timeString}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
messagemimetype: "text/html",
|
||||||
|
to: tryQuietly{ gmail2 } ?: gmail[0] =~ /@/ ? gmail[0] : gmail[0] + '@gmail.com',
|
||||||
|
user: gmail[0], password: gmail[1]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue