Skip to content

Commit 7a6d9a1

Browse files
2 parents 1008bc1 + 262bb84 commit 7a6d9a1

36 files changed

+1393
-651
lines changed

.gitignore

+4-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,4 @@
1-
build
1+
build
2+
3+
# Ignore Mac DS_Store files
4+
.DS_Store

README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ and much more powerful. The provided functions are the same as
77
in the CLI and parameter usage is also exactly the same.
88

99
## Links
10-
* [Usage and Examples](https://www.filebot.net/forums/viewtopic.php?f=4&t=5)
11-
* [AMC Script Manual](https://www.filebot.net/forums/viewtopic.php?f=4&t=215)
10+
* [Usage and Examples](https://www.filebot.net/forums/viewtopic.php?t=5)
11+
* [AMC Script Manual](https://www.filebot.net/amc.html)
1212

1313
## Clone
1414
If you want to run scripts from a local file or if you want to make your own modifications just clone the repository into your local filesystem:

amc.groovy

+151-82
Large diffs are not rendered by default.

artwork.groovy

100644100755
+69-24
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,94 @@
11
#!/usr/bin/env filebot -script
22

33

4-
def fetchMovieArtwork(file, movie, language) {
5-
fetch movie.getArtwork('posters', language)
6-
fetch movie.getArtwork('backdrops', null)
4+
def fetchMovieArtwork(movie, language, movieFolder) {
5+
fetchArtwork(movie, 'posters', language, movieFolder / 'poster.jpg')
6+
fetchArtwork(movie, 'backdrops', null, movieFolder / 'backdrop.jpg')
77
}
88

99

10-
def fetchSeriesArtwork(file, series, language) {
11-
switch(series.database) {
12-
case ~/TheTVDB/:
13-
fetch series.getArtwork('poster', language)
14-
fetch series.getArtwork('series', language)
15-
fetch series.getArtwork('season', language)
16-
fetch series.getArtwork('seasonwide', language)
17-
break
18-
case ~/TheMovieDB/:
19-
fetch series.getArtwork('posters', language)
20-
fetch series.getArtwork('backdrops', null)
21-
break
22-
default:
23-
fetch series.getArtwork()
24-
break
10+
def fetchSeriesArtwork(series, language, seriesFolder, seasonFolder) {
11+
if (series.database == 'TheTVDB') {
12+
fetchArtwork(series, 'poster', language, seriesFolder / 'poster.jpg')
13+
fetchArtwork(series, 'series', language, seriesFolder / 'series.jpg')
14+
fetchArtwork(series, 'fanart', language, seriesFolder / 'fanart.jpg')
15+
fetchArtwork(series, 'season', language, seasonFolder / 'season.jpg')
16+
fetchArtwork(series, 'seasonwide', language, seasonFolder / 'seasonwide.jpg')
17+
return
18+
}
19+
20+
if (series.database == 'TheMovieDB::TV') {
21+
fetchArtwork(series, 'posters', language, seriesFolder / 'poster.jpg')
22+
fetchArtwork(series, 'backdrops', null, seriesFolder / 'backdrop.jpg')
23+
return
2524
}
25+
26+
log.warning "Artwork not supported: $series"
27+
return
2628
}
2729

2830

29-
def fetch(artwork) {
30-
artwork.eachWithIndex{ a, i ->
31-
log.fine "$i: $a"
31+
def fetchEpisodeArtwork(episode, episodeFile) {
32+
def thumbnailFile = episodeFile.dir / episodeFile.nameWithoutExtension + '.jpg'
33+
if (thumbnailFile.exists()) {
34+
log.finest "[SKIP] Artwork already exists: $thumbnailFile"
35+
return
36+
}
37+
38+
def i = episode.info
39+
if (i == null) {
40+
log.warning "Artwork not supported: $episode.seriesInfo"
41+
return
42+
}
43+
if (i.image == null) {
44+
log.warning "Artwork not found: $episode [$thumbnailFile]"
45+
return
46+
}
47+
48+
log.fine "Fetch $i.image [$thumbnailFile]"
49+
try {
50+
i.image.saveAs(thumbnailFile)
51+
} catch(e) {
52+
log.severe "Failed to fetch artwork: $e.message"
3253
}
3354
}
3455

3556

57+
def fetchArtwork(object, category, language, file) {
58+
if (file.exists()) {
59+
log.finest "[SKIP] Artwork already exists: $file"
60+
return
61+
}
62+
63+
def artwork = object.getArtwork(category, language)
64+
if (artwork == null || artwork.empty) {
65+
log.warning "Artwork not found: $object [$file]"
66+
return
67+
}
68+
69+
def a = artwork[0]
70+
log.fine "Fetch $a [$file]"
71+
try {
72+
a.url.saveAs(file)
73+
} catch(e) {
74+
log.severe "Failed to fetch artwork: $e.message"
75+
}
76+
}
77+
78+
79+
80+
3681
args.getFiles{ it.video }.each{ f ->
3782
def m = f.metadata
38-
3983
switch(m) {
4084
case Movie:
4185
log.finest "[MOVIE] $m [$f]"
42-
fetchMovieArtwork(f, m, m.language)
86+
fetchMovieArtwork(m, m.language, f.dir)
4387
break
4488
case Episode:
4589
log.finest "[EPISODE] $m [$f]"
46-
fetchSeriesArtwork(f, m.seriesInfo, m.seriesInfo.language)
90+
fetchSeriesArtwork(m.seriesInfo, m.seriesInfo.language, f.dir.dir, f.dir)
91+
fetchEpisodeArtwork(m, f)
4792
break;
4893
default:
4994
log.finest "[XATTR NOT FOUND] $f"

artwork.tmdb.groovy

100644100755
+5-5
Original file line numberDiff line numberDiff line change
@@ -12,25 +12,25 @@ include('lib/htpc')
1212

1313
args.eachMediaFolder{ dir ->
1414
// fetch only missing artwork by default
15-
if (dir.hasFile{it.name == 'movie.nfo'} && dir.hasFile{it.name == 'poster.jpg'} && dir.hasFile{it.name == 'fanart.jpg'}) {
16-
log.finest "Skipping $dir"
15+
if (dir.hasFile{ it.name == 'poster.jpg' }) {
16+
log.finest "Skip [$dir] because [poster.jpg] already exists"
1717
return
1818
}
1919

2020
def videos = dir.listFiles{ it.isVideo() }
2121
def query = _args.query
22-
def locale = any{ _args.language.locale }{ Locale.ENGLISH }
22+
def locale = _args.language.locale
2323
def options = []
2424

2525
if (query) {
2626
// manual search & sort by relevance
27-
options = TheMovieDB.searchMovie(query, locale).sortBySimilarity(query, { it.name })
27+
options = TheMovieDB.searchMovie(query, locale).sortBySimilarity(query){ it.name }
2828
} else if (videos.size() > 0) {
2929
// run movie auto-detection for video files
3030
options = MediaDetection.detectMovie(videos[0], TheMovieDB, locale, true)
3131
}
3232

33-
if (options.isEmpty()) {
33+
if (!options) {
3434
log.warning "$dir ${videos.name} => movie not found"
3535
return
3636
}

artwork.tvdb.groovy

100644100755
+26-17
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#!/usr/bin/env filebot -script
22

33

4-
// sanity checks
4+
// require at least 1 input folder
55
if (args.size() == 0) {
66
die "Illegal usage: no input"
77
}
@@ -12,48 +12,57 @@ include('lib/htpc')
1212

1313
args.eachMediaFolder{ dir ->
1414
// fetch only missing artwork by default
15-
if (dir.hasFile{it.name == 'banner.jpg'}) {
16-
log.finest "Skipping $dir"
15+
if (dir.hasFile{ it.name == 'poster.jpg' }) {
16+
log.finest "Skip [$dir] because [poster.jpg] already exists"
1717
return
1818
}
1919

2020
def videos = dir.listFiles{ it.isVideo() }
21-
def query = _args.query ?: detectSeriesName(videos)
2221
def sxe = videos.findResult{ parseEpisodeNumber(it) }
23-
def locale = any{ _args.language.locale }{ Locale.ENGLISH }
22+
def locale = _args.language.locale
2423

25-
if (query == null) {
24+
// use --q option value
25+
def query = _args.query
26+
// use series detection
27+
if (!query) {
28+
def s = detectSeries(videos)
29+
if (s) {
30+
query = (s.getExternalId('TheTVDB') as String) ?: s.name
31+
}
32+
}
33+
// use series folder name
34+
if (!query) {
2635
query = dir.dir.hasFile{ it.name =~ /Season/ && it.isDirectory() } ? dir.dir.name : dir.name
2736
}
2837

29-
log.finest "$dir => Search by $query"
30-
def options = TheTVDB.search(query, locale)
38+
log.finest "$dir => Lookup by $query"
39+
def options = TheTVDB.lookup(query, locale)
3140
if (options.isEmpty()) {
3241
log.warning "TV Series not found: $query"
3342
return
3443
}
3544

3645
// sort by relevance
37-
options = options.sortBySimilarity(query, { it.name })
46+
options = options.sortBySimilarity(query){ it.name }
3847

3948
// auto-select series
40-
def series = options[0]
49+
def id = options.first()
4150

4251
// maybe require user input
4352
if (options.size() > 1 && _args.strict) {
44-
series = showInputDialog(options, query, 'Select TV Series')
53+
id = showInputDialog(options, query, 'Select TV Series')
4554
}
4655

47-
if (series == null) {
56+
if (id == null) {
4857
return null
4958
}
5059

5160
// auto-detect structure
52-
def seriesDir = [dir.dir, dir].sortBySimilarity(series.name, { it.name })[0]
61+
def seriesInfo = TheTVDB.getSeriesInfo(id, locale)
62+
63+
def seriesDir = [dir.dir, dir].sortBySimilarity(seriesInfo.name){ it.name }.first()
5364
def season = sxe && sxe.season > 0 ? sxe.season : 1
5465

55-
log.fine "$dir => $series"
56-
tryLogCatch {
57-
fetchSeriesArtworkAndNfo(seriesDir, dir, series.id, season, false, locale)
58-
}
66+
log.fine "$dir => $seriesInfo.name [$seriesInfo]"
67+
fetchSeriesArtworkAndNfo(seriesDir, dir, seriesInfo, season, false, locale)
5968
}

chkall.groovy

-11
This file was deleted.

cleaner.groovy

+40-12
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44
def deleteRootFolder = any{ root.toBoolean() }{ false }
55

66
def ignore = any{ ignore }{ /extrathumbs/ }
7-
def exts = any{ exts }{ /jpg|jpeg|png|gif|ico|nfo|info|xml|htm|html|log|m3u|cue|ffp|srt|sub|idx|smi|sup|md5|sfv|txt|rtf|url|db|dna|exe|log|tgmd|json|data|ignore|srv|srr|nzb|vbs|ini|vsmeta/ }
8-
def terms = any{ terms }{ /sample|trailer|extras|deleted.scenes|music.video|scrapbook|DS_Store/ }
7+
def exts = any{ exts }{ /jpg|jpeg|png|gif|ico|nfo|info|xml|htm|html|log|m3u|cue|ffp|srt|sub|idx|smi|sup|md5|sfv|txt|rtf|url|website|db|dna|exe|log|tgmd|json|data|ignore|srv|srr|nzb|vbs|ini|vsmeta/ }
8+
def terms = any{ terms }{ /sample|trailer|extras|deleted.scenes|music.video|scrapbook|DS_Store|eaDir/ }
99
def minsize = any{ minsize.toLong() }{ 20 * 1024 * 1024 }
10-
def maxsize = any{ maxsize.toLong() }{ 100 * 1024 * 1024 }
10+
def maxsize = any{ maxsize.toLong() }{ 120 * 1024 * 1024 }
1111

1212

1313
def testRun = _args.action.equalsIgnoreCase('test')
@@ -24,17 +24,17 @@ if (args.size() == 0) {
2424
// delete orphaned "clutter" files like nfo, jpg, etc and sample files
2525
def isClutter = { f ->
2626
// whitelist
27-
if (f.path.findMatch(ignore))
27+
if (f.path.findWordMatch(ignore))
2828
return false
2929

3030
// file is either too small to have meaning, or to large to be considered clutter
3131
def fsize = f.length()
3232

3333
// path contains blacklisted terms or extension is blacklisted
34-
if (f.extension ==~ exts && fsize < maxsize)
34+
if (f.extension?.findWordMatch(exts) && fsize < maxsize)
3535
return true
3636

37-
if (f.path.findMatch(/\b(/ + terms + /)\b/) && fsize < maxsize)
37+
if (f.path.findWordMatch(terms) && fsize < maxsize)
3838
return true
3939

4040
// NOTE: some smb filesystem implementations are buggy and known to incorrectly return filesize 0 for all files
@@ -58,15 +58,43 @@ def clean = { f ->
5858

5959

6060
// memoize media folder status for performance
61-
def hasMediaFiles = { dir -> dir.isDirectory() && dir.getFiles().find{ (it.isVideo() || it.isAudio()) && !isClutter(it) } }.memoize()
61+
def hasMediaFiles = { dir ->
62+
return dir.isDirectory() && dir.getFiles().any{ f -> (f.isVideo() || f.isAudio()) && !isClutter(f) }
63+
}.memoize()
64+
6265

6366
// delete clutter files in orphaned media folders
64-
args.getFiles{ isClutter(it) && !hasMediaFiles(it.dir) }.each{ clean(it) }
67+
args.files.each{ f ->
68+
// keep non-clutter files
69+
if (!isClutter(f)) {
70+
log.finest "Keep $f (not clutter)"
71+
return
72+
}
73+
74+
// keep sibling files of non-clutter files
75+
if (hasMediaFiles(f.dir)) {
76+
log.finest "Keep $f (parent folder contains media files)"
77+
return
78+
}
79+
80+
clean(f)
81+
}
82+
6583

6684
// delete empty folders but exclude given args
67-
args.getFolders().toSorted().reverse().each{
68-
if (it.isDirectory() && !it.hasFile{ it.isDirectory() || !isClutter(it) }) {
69-
if (deleteRootFolder || !args.contains(it))
70-
clean(it)
85+
args.folders.toSorted().reverse().each{ d ->
86+
// skip input folder
87+
if (!deleteRootFolder && args.contains(d)) {
88+
log.finest "Keep $d (root folder)"
89+
return
7190
}
91+
92+
// skip non-empty folder
93+
if (d.hasFile{ f -> f.isDirectory() || !isClutter(f) }) {
94+
log.finest "Keep $d (not empty)"
95+
return
96+
}
97+
98+
// delete folder
99+
clean(d)
72100
}

0 commit comments

Comments
 (0)