Advertisement
Guest User

test.groovy

a guest
Feb 26th, 2018
59
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Groovy 9.39 KB | None | 0 0
  1. #!/usr/bin/env filebot -script
  2. // log input parameters
  3. log.fine("Run script [$_args.script] at [$now]")
  4. _def.each{ n, v -> log.finest('Parameter: ' + [n, n =~ /plex|kodi|pushover|pushbullet|mail|myepisodes/ ? '*****' : v].join(' = ')) }
  5. args.withIndex().each{ f, i -> if (f.exists()) { log.finest "Argument[$i]: $f" } else { log.warning "Argument[$i]: File does not exist: $f" } }
  6.  
  7.  
  8. // initialize variables
  9. failOnError = _args.conflict.equalsIgnoreCase('fail')
  10. testRun = _args.action.equalsIgnoreCase('test')
  11.  
  12. // --output folder must be a valid folder
  13. outputFolder = tryLogCatch{ any{ _args.output }{ '.' }.toFile().getCanonicalFile() }
  14.  
  15. // enable/disable features as specified via --def parameters
  16. unsorted  = tryQuietly{ unsorted.toBoolean() }
  17. music     = tryQuietly{ music.toBoolean() }
  18. subtitles = tryQuietly{ subtitles.split(/\W+/) as List }
  19. artwork   = tryQuietly{ artwork.toBoolean() && !testRun }
  20. extras    = tryQuietly{ extras.toBoolean() }
  21. clean     = tryQuietly{ clean.toBoolean() }
  22. exec      = tryQuietly{ exec.toString() }
  23.  
  24. // array of kodi/plex/emby hosts
  25. kodi = tryQuietly{ any{kodi}{xbmc}.split(/[ ,;|]+/)*.split(/:(?=\d+$)/).collect{ it.length >= 2 ? [host: it[0], port: it[1] as int] : [host: it[0]] } }
  26. plex = tryQuietly{ plex.split(/[ ,;|]+/)*.split(/:/).collect{ it.length >= 2 ? [host: it[0], token: it[1]] : [host: it[0]] } }
  27. emby = tryQuietly{ emby.split(/[ ,;|]+/)*.split(/:/).collect{ it.length >= 2 ? [host: it[0], token: it[1]] : [host: it[0]] } }
  28.  
  29. // extra options, myepisodes updates and email notifications
  30. extractFolder      = tryQuietly{ extractFolder as File }
  31. skipExtract        = tryQuietly{ skipExtract.toBoolean() }
  32. deleteAfterExtract = tryQuietly{ deleteAfterExtract.toBoolean() }
  33. excludeList        = tryQuietly{ def f = excludeList as File; f.isAbsolute() ? f : outputFolder.resolve(f.path) }
  34. myepisodes         = tryQuietly{ myepisodes.split(':', 2) as List }
  35. gmail              = tryQuietly{ gmail.split(':', 2) as List }
  36. mail               = tryQuietly{ mail.split(':', 5) as List }
  37. pushover           = tryQuietly{ pushover.split(':', 2) as List }
  38. pushbullet         = tryQuietly{ pushbullet.toString() }
  39. storeReport        = tryQuietly{ storeReport.toBoolean() }
  40. reportError        = tryQuietly{ reportError.toBoolean() }
  41.  
  42. // user-defined filters
  43. label       = any{ ut_label }{ null }
  44. ignore      = any{ ignore }{ null }
  45. minFileSize = any{ minFileSize.toLong() }{ 50 * 1000L * 1000L }
  46. minLengthMS = any{ minLengthMS.toLong() }{ 10 * 60 * 1000L }
  47.  
  48. // series/anime/movie format expressions
  49. seriesFormat   = any{ seriesFormat   }{ '{plex}' }
  50. animeFormat    = any{ animeFormat    }{ '{plex}' }
  51. movieFormat    = any{ movieFormat    }{ '{plex}' }
  52. musicFormat    = any{ musicFormat    }{ '{plex}' }
  53. unsortedFormat = any{ unsortedFormat }{ 'Unsorted/{file.structurePathTail}' }
  54.  
  55.  
  56.  
  57. // force Movie / TV Series / Anime behaviour
  58. def forceMovie(f) {
  59.     label =~ /^(?i:Movie|Film|Concert|UFC)/ || f.dir.listPath().any{ it.name ==~ /(?i:Movies|Movie)/ } || f.isMovie() || any{ (f.isVideo() || f.isSubtitle()) && !forceSeries(f) && getMediaInfo(f, '{minutes}').toInteger() >= 100 }{ false }
  60. }
  61.  
  62. def forceSeries(f) {
  63.     label =~ /^(?i:TV|Show|Series|Documentary)/ || f.dir.listPath().any{ it.name ==~ /(?i:TV.Shows|TV.Series)/ } || f.path =~ /(?<=\b|_)(?i:tv[sp]-|Season\D?\d{1,2}|\d{4}.S\d{2})(?=\b|_)/ || parseEpisodeNumber(f.path, true) || parseDate(f.path)
  64. }
  65.  
  66. def forceAnime(f) {
  67.     label =~ /^(?i:Anime)/ || f.dir.listPath().any{ it.name ==~ /(?i:Anime)/ } || ((f.isVideo() || f.isSubtitle()) && (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 }))
  68. }
  69.  
  70. def forceAudio(f) {
  71.     label =~ /^(?i:audio|music|music.video)/ || (f.isAudio() && !f.isVideo())
  72. }
  73.  
  74. def forceIgnore(f) {
  75.     label =~ /^(?i:games|ebook|other|ignore)/
  76. }
  77.  
  78. // include artwork/nfo, pushover/pushbullet and ant utilities as required
  79. if (artwork || kodi || plex || emby) { include('lib/htpc') }
  80. if (pushover || pushbullet ) { include('lib/web') }
  81.  
  82.  
  83.  
  84. def fail(message) {
  85.     die(message)
  86. }
  87.  
  88. // check input parameters
  89. def ut = _def.findAll{ k, v -> k.startsWith('ut_') }.collectEntries{ k, v ->
  90.     if (v ==~ /[%$]\p{Alnum}|\p{Punct}+/) {
  91.         log.warning "Bad $k value: $v"
  92.         v = null
  93.     }
  94.     return [k.substring(3), v ? v : null]
  95. }
  96.  
  97.  
  98.  
  99. roots = args
  100.  
  101. if (args.size() == 0) {
  102.     // assume we're called with utorrent parameters (account for older and newer versions of uTorrents)
  103.     if (ut.kind == 'single' || (ut.kind != 'multi' && ut.dir && ut.file)) {
  104.         roots = [new File(ut.dir, ut.file).getCanonicalFile()] // single-file torrent
  105.     } else {
  106.         roots = [new File(ut.dir).getCanonicalFile()] // multi-file torrent
  107.     }
  108. }
  109.  
  110.  
  111. // helper function to work with the structure relative path rather than the whole absolute path
  112. def relativeInputPath(f) {
  113.     def r = roots.find{ r -> f.path.startsWith(r.path) && r.isDirectory() && f.isFile() }
  114.     if (r != null) {
  115.         return f.path.substring(r.path.length() + 1)
  116.     }
  117.     return f.name
  118. }
  119.  
  120.  
  121.  
  122. // define and load exclude list (e.g. to make sure files are only processed once)
  123. excludePathSet = new FileSet()
  124.  
  125. if (excludeList) {
  126.     if (excludeList.exists()) {
  127.         try {
  128.             excludePathSet.load(excludeList)
  129.         } catch(Exception e) {
  130.             fail "Failed to load excludeList: $e"
  131.         }
  132.         log.fine "Use excludes: $excludeList (${excludePathSet.size()})"
  133.     } else {
  134.         log.fine "Use excludes: $excludeList"
  135.         if ((!excludeList.parentFile.isDirectory() && !excludeList.parentFile.mkdirs()) || (!excludeList.isFile() && !excludeList.createNewFile())) {
  136.             fail "Failed to create excludeList: $excludeList"
  137.         }
  138.     }
  139. }
  140.  
  141.  
  142.  
  143. extractedArchives = []
  144. temporaryFiles = []
  145.  
  146. def extract(f) {
  147.     def folder = new File(extractFolder ?: f.dir, f.nameWithoutExtension)
  148.     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) ?: []
  149.  
  150.     extractedArchives += f
  151.     temporaryFiles += folder
  152.     temporaryFiles += files
  153.  
  154.     return files
  155. }
  156.  
  157.  
  158. def acceptFile(f) {
  159.     if (f.isHidden()) {
  160.         log.finest "Ignore hidden: $f"
  161.         return false
  162.     }
  163.  
  164.     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/) {
  165.         log.finest "Ignore system path: $f"
  166.         return false
  167.     }
  168.  
  169.     if (f.name =~ /(?<=\b|_)(?i:Sample|Trailer|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|_)/) {
  170.         log.finest "Ignore extra: $f"
  171.         return false
  172.     }
  173.  
  174.     // ignore if the user-defined ignore pattern matches
  175.     if (f.path.findMatch(ignore)) {
  176.         log.finest "Ignore pattern: $f"
  177.         return false
  178.     }
  179.  
  180.     // ignore archives that are on the exclude path list
  181.     if (excludePathSet.contains(f)) {
  182.         return false
  183.     }
  184.  
  185.     // accept folders right away and skip file sanity checks
  186.     if (f.isDirectory()) {
  187.         return true
  188.     }
  189.  
  190.     // accept archives if the extract feature is enabled
  191.     if (f.isArchive() || f.hasExtension('001')) {
  192.         return !skipExtract
  193.     }
  194.  
  195.     // ignore iso images that do not contain a video disk structure
  196.     if (f.hasExtension('iso') && !f.isDisk()) {
  197.         log.fine "Ignore disk image: $f"
  198.         return false
  199.     }
  200.  
  201.     // ignore small video files
  202.     if (minFileSize > 0 && f.isVideo() && f.length() < minFileSize) {
  203.         log.fine "Skip small video file: $f"
  204.         return false
  205.     }
  206.  
  207.     // ignore short videos
  208.     if (minLengthMS > 0 && f.isVideo() && any{ getMediaInfo(f, '{minutes}').toLong() * 60 * 1000L < minLengthMS }{ false /* default if MediaInfo fails */ }) {
  209.         log.fine "Skip short video: $f"
  210.         return false
  211.     }
  212.  
  213.     // ignore subtitle files without matching video file in the same or parent folder
  214.     if (f.isSubtitle() && ![f, f.dir].findResults{ it.dir }.any{ it.listFiles{ it.isVideo() && f.isDerived(it) }}) {
  215.         log.fine "Ignore orphaned subtitles: $f"
  216.         return false   
  217.     }
  218.  
  219.     // process only media files (accept audio files only if music mode is enabled)
  220.     return f.isVideo() || f.isSubtitle() || (music && f.isAudio())
  221. }
  222.  
  223.  
  224. // 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)
  225. def resolveInput(f) {
  226.     // resolve folder recursively, except disk folders
  227.     if (f.isDirectory()) {
  228.         if (f.isDisk()) {
  229.             return f
  230.         }
  231.         return f.listFiles{ acceptFile(it) }.collect{ resolveInput(it) }
  232.     }
  233.  
  234.     if (f.isArchive() || f.hasExtension('001')) {
  235.         return extract(f).findAll{ acceptFile(it) }.collect{ resolveInput(it) }
  236.     }
  237.  
  238.     return f
  239. }
  240.  
  241.  
  242. // flatten nested file structure
  243. def input = roots.findAll{ acceptFile(it) }.flatten{ resolveInput(it) }.toSorted()
  244.  
  245. // update exclude list with all input that will be processed during this run
  246. if (excludeList && !testRun) {
  247.     excludePathSet.append(excludeList, extractedArchives, input)
  248. }
  249.  
  250.  
  251. // print exclude and input sets for logging
  252. input.each{ log.fine "Input: $it" }
  253.  
  254.  
  255.  
  256. // early abort if there is nothing to do
  257. if (input.size() == 0) {
  258.     log.warning "No files selected for processing"
  259.     return
  260. }
  261.  
  262.  
  263. if (exec) {
  264.     input.collect{ getMediaInfo(it, exec) }.unique().each{ command ->
  265.         //log.fine "Execute: $command"
  266.         execute(command)
  267.     }
  268. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement