"But I'm not inclined to implement (and maintain) support for per-episode nfo/artwork files."
I can help with that
htpc:
Code: Select all
def fetchSeriesNfo(outputFile, i, override, locale, nfotype, tvshowFile, newSxE, SameNameAsTVshowThumb, EpisodeOrder, nSeason) {
def ActorsJson = TheTVDB.requestJson("series/${i.id}/actors", locale, Cache.ONE_MONTH)
def xml = XML {
if (nfotype == 'tvshow') {
log.fine "FROM htpc fetchSeriesNfo tvshow - Generate Series NFO: $i.name [$i.id]"
tvshow {
//ALL(i.dump()) // {localize.english.SeriesInfo.dump()}
title(i.name) // {localize.english.SeriesInfo.name}
sorttitle([i.name, i.startDate as String].findAll{ it?.length() > 0 }.findResults{ it.sortName('$2') }.join('::'))
rating(i.rating) // {localize.english.SeriesInfo.rating}
votes(i.ratingCount) // {localize.english.SeriesInfo.ratingCount}
year(i.startDate?.year)
airsday(i.airsDayOfWeek)
airstime(i.airsTime)
plot(i.overview)
runtime(i.runtime)
thumb(aspect:"banner", i.bannerUrl)
// thumb(aspect:"poster", i.posterUrl)
// thumb(aspect:"fanart", i.fanartUrl)
mpaa(i.certification)
episodeguide {
url(cache:"${i.id}.xml", "http://www.thetvdb.com/api/1D62F2F90030C444/series/${i.id}/all/${locale.language}.zip")
}
id(i.id) // {localize.english.SeriesInfo.id}
i.genres.each{ // {localize.english.SeriesInfo.genres}
genre(it)
}
premiered(i.startDate)
status(i.status) // {localize.english.SeriesInfo.status}
studio(i.network) // {localize.english.SeriesInfo.network}
ActorsJson.data.each{ a ->
actor {
name((a.name).toString().trim())
role(a.role)
order(a.sortOrder)
if (a.image != '') {
thumb("https://www.thetvdb.com/banners/${a.image}")
}
}
}
imdb(id:i.imdbId, "http://www.imdb.com/title/" + i.imdbId)
tvdb(id:i.id, "http://www.thetvdb.com/?tab=series&id=${i.id}")
}
}
if (nfotype == 'SameNameAsTVshow') {
def mi = tryLogCatch{ tvshowFile ? MediaInfo.snapshot(tvshowFile) : null }
log.fine "FROM htpc fetchSeriesNfo SameNameAsTVshow - EpisodeOrder: ${EpisodeOrder}"
def eInfo = ''
def EpisodesJson = []
// --order [Airdate, Absolute, DVD] : Episode order
if (newSxE =~ 'Special'){
log.fine "FROM htpc fetchSeriesNfo Special - episodeID: $newSxE"
eInfo = net.filebot.WebServices.TheTVDB.getEpisodeList(i.id, SortOrder.Airdate, locale).find{it =~ newSxE}
log.fine "FROM htpc fetchSeriesNfo SameNameAsTVshow - eInfo: $eInfo"
EpisodesJson = TheTVDB.requestJson("series/${i.id}/episodes/query?airedSeason=${nSeason}&airedEpisode=${eInfo.special}", locale, Cache.ONE_WEEK)
}
else if (EpisodeOrder == 'DVD'){
eInfo = net.filebot.WebServices.TheTVDB.getEpisodeList(i.id, SortOrder.DVD, locale).find{it =~ newSxE}
log.fine "FROM htpc fetchSeriesNfo SameNameAsTVshow - eInfo: $eInfo"
if (eInfo.season != '' && eInfo.episode != ''){
EpisodesJson = TheTVDB.requestJson("series/${i.id}/episodes/query?dvdSeason=${eInfo.season}&dvdEpisode=${eInfo.episode}", locale, Cache.ONE_WEEK)
}
}
else {
eInfo = net.filebot.WebServices.TheTVDB.getEpisodeList(i.id, SortOrder.Airdate, locale).find{it =~ newSxE}
log.fine "FROM htpc fetchSeriesNfo SameNameAsTVshow - eInfo: $eInfo"
EpisodesJson = TheTVDB.requestJson("series/${i.id}/episodes/query?airedSeason=${eInfo.season}&airedEpisode=${eInfo.episode}", locale, Cache.ONE_WEEK)
}
// log.fine "FROM htpc fetchSeriesNfo - EpisodesJson: $EpisodesJson"
def nyeID = EpisodesJson.data.id.join('')
log.fine "FROM htpc fetchSeriesNfo - EpisodeID: $nyeID"
def EpisodeJson = TheTVDB.requestJson("episodes/${nyeID}", locale, Cache.ONE_WEEK)
log.fine "FROM htpc fetchSeriesNfo SameNameAsTVshow - Generate Series NFO: $i.name [$newSxE]"
def EpisodePicUrl = ''
def data = EpisodeJson.data
if (data.filename != ''){
EpisodePicUrl = new URL ("https://www.thetvdb.com/banners/${data.filename}")
EpisodePicUrl.saveAs(SameNameAsTVshowThumb)
log.fine "FROM htpc fetchSeriesNfo - EpisodePicUrl: $EpisodePicUrl"
}
episodedetails {
title(data.episodeName)
showtitle(i.name)
rating(data.siteRating)
season(data.airedSeason)
episode(data.airedEpisodeNumber)
if (data.dvdSeason != null && data.dvdEpisodeNumber != null){
seasondvd(data.dvdSeason)
episodedvd(data.dvdEpisodeNumber)
}
uniqueid(data.id)
votes(data.siteRatingCount)
if (data.overview != '' && data.overview != null) {
plot((data.overview).trim())
}
runtime(i.runtime)
// yo(mi.values()[0]['Duration'].join(''))
// thumb("https://www.thetvdb.com/banners/${data.filename}")
if (EpisodePicUrl != ''){
thumb(EpisodePicUrl)
}
mpaa(i.certification)
i.genres.each{
genre(it)
}
if (data.writers != '') {
(data.writers).each{
credits(it)
}
}
if (data.director != '') {
(data.director).tokenize('|').each{ d ->
director(d)
}
}
premiered(i.startDate)
aired(data.firstAired)
studio(i.network)
(data.guestStars).each{ g ->
actor {
name(g)
role("Guest star")
}
}
ActorsJson.data.each{ a ->
actor {
name((a.name).toString().trim())
role(a.role)
order(a.sortOrder)
if (a.image != '') {
thumb("https://www.thetvdb.com/banners/${a.image}")
}
}
}
fileinfo {
streamdetails {
mi?.each { kind, streams ->
def section = kind.toString().toLowerCase()
streams.each { s ->
if (section == 'general') {
general {
if (s.'FileName' != null) {
filename((s.'FileName').trim())
}
if (s.'FileExtension' != null) {
fileextension((s.'FileExtension').trim())
}
if (s.'FileSize' != null) {
filesize((s.'FileSize').trim())
}
if (s.'Encoded_Date' != null) {
encoded((s.'Encoded_Date').trim())
}
if (s.'Duration/String3' != null) {
durationinhmsms((s.'Duration/String3').trim())
}
if (s.'OverallBitRate/String' != null) {
overallbitrate((s.'OverallBitRate/String').replaceAll(/ /, '').replaceAll(/Kbps/, '').trim())
}
}
}
if (section == 'video') {
video {
def CodecList = [s.'CodecID/Hint' != null ? s.'CodecID/Hint' : null, s.'CodecID' != null ? (s.'CodecID'.contains('avc1') ? s.'CodecID' : null) : null, s.'InternetMediaType' != null ? (s.'InternetMediaType'.contains('video/H264') ? 'h264' : null) : null, s.'Encoded_Library_Name' != null ? s.'Encoded_Library_Name' : null, s.'Format'].findResult{it}
codec((CodecList).replaceAll(/\s\w{3,}/).space('').trim())
if (s.'Duration' != null) {
BigDecimal durationinMS = new BigDecimal(s.'Duration')
durationinseconds(Math.round((durationinMS)/1000))
durationinminutes(Math.round((durationinMS)/60000))
}
if (s.'DisplayAspectRatio' != null) {
aspect((s.'DisplayAspectRatio').trim())
}
if (s.'Width' != null) {
width((s.'Width').trim())
}
if (s.'Height' != null) {
height((s.'Height').trim())
}
if (s.'ScanType' != null) {
scantype((s.'ScanType').trim())
}
if (s.'BitRate/String' != null) {
bitrate((s.'BitRate/String').replaceAll(/ /, '').replaceAll(/Kbps/, '').trim())
}
if (s.'FrameRate' != null) {
framerate((s.'FrameRate').trim())
}
}
}
if (section == 'audio') {
audio {
if (s.'CodecID/Hint' ?: s.'Format' != null) {
codec((s.'CodecID/Hint' ?: s.'Codec'.contains('DTS-HD') ? s.'Codec' : s.'Format').replaceAll(/\p{Punct}/, '').trim())
if (s.'Format_Profile' != null){
codecformatprofile((s.'Format_Profile').replaceAll(/\/ Core/, '').replaceAll(/ /, '_').trim())
}
}
if (s.'Language/String3' != null) {
language((s.'Language/String3').trim())
}
if (s.'Language/String' != null) {
longlanguage((s.'Language/String').trim())
}
if (s.'Channel(s)' != null) {
if (s.'Channel(s)_Original' != null){
channels((s.'Channel(s)_Original').trim())
}
else {channels((s.'Channel(s)')*.split(' / ')*.max().max())}
}
if (s.'BitRate/String' != null) {
bitrate((s.'BitRate/String').replaceAll(/\s|Unknown \/|\/ Unknown|[A-Z]\w+/, '').split('/')*.toBigDecimal().max())
}
}
}
if (section == 'text') {
subtitle {
language(s.'Language/String3')
if (s.'Language/String' != null) {
longlanguage((s.'Language/String').replaceAll(/ /, '').trim())
}
if (s.'Format' != null) {
format((s.'Format').trim())
}
}
}
}
}
}
}
if (data.imdbId != ''){
imdb(id:data.imdbId, "https://www.imdb.com/title/${data.imdbId}")
}
tvdb(id:data.id, "https://www.thetvdb.com/?tab=episode&id=${data.id}")
}
}
}
xml.saveAs(outputFile)
}
htpc:
Code: Select all
def fetchSeriesArtworkAndNfo(seriesDir, seasonDir, seriesId, season, override = false, locale = _args.locale, tvshowFile = null, episodeNumber, sxe, EpisodeOrder = _args.order) {
log.fine "FROM htpc fetchSeriesArtworkAndNfo - seasonDir: [$seasonDir]"
log.fine "FROM htpc fetchSeriesArtworkAndNfo - season: [$season] / episodeNumber: [$episodeNumber] / sxe: [$sxe]"
log.fine "FROM htpc fetchSeriesArtworkAndNfo - tvshowFile: [$tvshowFile]"
tryLogCatch {
// fetch nfo
def seriesInfo = TheTVDB.getSeriesInfo(seriesId, locale)
// log.fine "FROM htpc fetchSeriesArtworkAndNfo - seriesInfo: [$seriesInfo]"
if (season == 0){
sxe = episodeNumber
}
def String fname = tvshowFile.name
def String SameNameAsTVshow = fname.nameWithoutExtension+'.nfo'
log.fine "FROM htpc fetchSeriesArtworkAndNfo - SameNameAsTVshow: [$SameNameAsTVshow]"
def EpisodePicExt = '-thumb.jpg'
def String SameNameAsTVshowT = tvshowFile.nameWithoutExtension+EpisodePicExt
def SameNameAsTVshowThumb = seasonDir.resolve(SameNameAsTVshowT)
// log.fine "FROM htpc fetchSeriesArtworkAndNfo - SameNameAsTVshowThumb: [$SameNameAsTVshowThumb]"
// fetchSeriesNfo(seasonDir.resolve(SameNameAsTVshow), seriesInfo, override, locale, 'SameNameAsTVshow', f, e, SameNameAsTVshowThumb, EpisodeOrder)
fetchSeriesNfo(seasonDir.resolve(SameNameAsTVshow), seriesInfo, override, locale, 'SameNameAsTVshow', tvshowFile, sxe, SameNameAsTVshowThumb, EpisodeOrder, season)
// };
// e.g season13-poster.jpg
def String seasonnumber = season.pad(2)
def seasonposter = "season$seasonnumber-poster.jpg"
// fetch season banners
if (seasonDir != seriesDir) {
fetchSeriesBanner(seasonDir.resolve('folder.jpg'), seriesId, 'season', 'season', season, override, locale)
fetchSeriesBanner(seasonDir.resolve('banner.jpg'), seriesId, 'seasonwide', 'seasonwide', season, override, locale)
// folder image (resuse series poster if possible)
copyIfPossible(seasonDir.resolve('folder.jpg'), seriesDir.resolve(seasonposter))
}
fetchSeriesNfo(seriesDir.resolve('tvshow.nfo'), seriesInfo, override, locale, 'tvshow', tvshowFile, sxe, null, EpisodeOrder, season)
// fetch series banner, fanart, posters, etc
['680x1000', null].findResult{ fetchSeriesBanner(seriesDir.resolve('folder.jpg'), seriesId, 'poster', it, null, override, locale) }
['graphical', null].findResult{ fetchSeriesBanner(seriesDir.resolve('banner.jpg'), seriesId, 'series', it, null, override, locale) }
// fetch highest resolution fanart
['1920x1080', '1280x720', null].findResult{ fetchSeriesBanner(seriesDir.resolve('fanart.jpg'), seriesId, 'fanart', it, null, override, locale) }
// fetch fanart
//['hdclearart', 'clearart'].findResult{ type -> fetchSeriesFanart(seriesDir.resolve('clearart.png'), seriesId, type, null, override, locale) }
//['hdtvlogo', 'clearlogo'].findResult{ type -> fetchSeriesFanart(seriesDir.resolve('logo.png'), seriesId, type, null, override, locale) }
//fetchSeriesFanart(seriesDir.resolve('landscape.jpg'), seriesId, 'tvthumb', null, override, locale)
// fetch season fanart
// if (seasonDir != seriesDir) {
// fetchSeriesFanart(seasonDir.resolve('landscape.jpg'), seriesId, 'seasonthumb', season, override, locale)
// }
// folder image (resuse series poster if possible)
//copyIfPossible(seriesDir.resolve('folder.jpg'), seriesDir.resolve('season-all-poster.jpg'))
}
}
amc:
Code: Select all
// EPISODE MODE
if ((group.tvs || group.anime) && !group.mov) {
// choose series / anime config
def config = group.tvs ? [name:group.tvs, format:format.tvs, db:'TheTVDB'] : [name:group.anime, format:format.anime, db:'AniDB']
def dest = rename(file: files, format: config.format, db: config.db)
if (dest != null) {
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.findResults{ it }.findAll{ it.metadata.seriesInfo.database == 'TheTVDB' }.collect{ [metadata: it.metadata, name: it.metadata.seriesName, season: it.metadata.special ? 0 : it.metadata.season, id: it.metadata.seriesInfo.id, episode: it.metadata.special ? 'Special '+it.metadata.special : it.metadata.episode, sxe: it.metadata.special ? 0 + 'x' + it.metadata.special.pad(2) : it.metadata.season + 'x' + it.metadata.episode.pad(2), tvshowFile: it]}.unique().each{
log.fine "Fetching series artwork for [$it.name / Season $it.season] to [$dir]"
fetchSeriesArtworkAndNfo(hasSeasonFolder ? dir.parentFile : dir, dir, it.id, it.season, override, _args.locale, it.tvshowFile, it.episode, it.sxe, _args.order)
}
}
}
} else if (failOnError) {
fail("Failed to rename series: $config.name")
} else {
unsortedFiles += files
}
}