New binding: {plex} => built-in Plex format defaults for Episode/Movie/Music objects
This commit is contained in:
parent
963fb62172
commit
36a02ff457
|
@ -45,6 +45,7 @@ import net.filebot.Settings;
|
||||||
import net.filebot.Settings.ApplicationFolder;
|
import net.filebot.Settings.ApplicationFolder;
|
||||||
import net.filebot.WebServices;
|
import net.filebot.WebServices;
|
||||||
import net.filebot.hash.HashType;
|
import net.filebot.hash.HashType;
|
||||||
|
import net.filebot.media.NamingStandard;
|
||||||
import net.filebot.mediainfo.MediaInfo;
|
import net.filebot.mediainfo.MediaInfo;
|
||||||
import net.filebot.mediainfo.MediaInfo.StreamKind;
|
import net.filebot.mediainfo.MediaInfo.StreamKind;
|
||||||
import net.filebot.mediainfo.MediaInfoException;
|
import net.filebot.mediainfo.MediaInfoException;
|
||||||
|
@ -914,6 +915,17 @@ public class MediaBindingBean {
|
||||||
return 1 + identityIndexOf(duplicates, getInfoObject());
|
return 1 + identityIndexOf(duplicates, getInfoObject());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Define("plex")
|
||||||
|
public File getPlexStandardPath() throws Exception {
|
||||||
|
String path = NamingStandard.Plex.getPath(infoObject);
|
||||||
|
try {
|
||||||
|
path = path.concat(getSubtitleTags());
|
||||||
|
} catch (Exception e) {
|
||||||
|
// ignore => no language tags
|
||||||
|
}
|
||||||
|
return new File(path);
|
||||||
|
}
|
||||||
|
|
||||||
@Define("self")
|
@Define("self")
|
||||||
public AssociativeScriptObject getSelf() {
|
public AssociativeScriptObject getSelf() {
|
||||||
return createBindingObject(mediaFile, infoObject, context);
|
return createBindingObject(mediaFile, infoObject, context);
|
||||||
|
|
|
@ -0,0 +1,93 @@
|
||||||
|
package net.filebot.media;
|
||||||
|
|
||||||
|
import static java.util.Arrays.*;
|
||||||
|
import static java.util.stream.Collectors.*;
|
||||||
|
import static net.filebot.WebServices.*;
|
||||||
|
import static net.filebot.similarity.Normalization.*;
|
||||||
|
import static net.filebot.util.FileUtilities.*;
|
||||||
|
import static net.filebot.web.EpisodeFormat.*;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import net.filebot.web.AudioTrack;
|
||||||
|
import net.filebot.web.Episode;
|
||||||
|
import net.filebot.web.EpisodeFormat;
|
||||||
|
import net.filebot.web.Movie;
|
||||||
|
import net.filebot.web.MoviePart;
|
||||||
|
import net.filebot.web.MultiEpisode;
|
||||||
|
|
||||||
|
public enum NamingStandard {
|
||||||
|
|
||||||
|
Plex;
|
||||||
|
|
||||||
|
public String getPath(Object o) {
|
||||||
|
if (o instanceof Episode)
|
||||||
|
return getPath((Episode) o);
|
||||||
|
if (o instanceof Movie)
|
||||||
|
return getPath((Movie) o);
|
||||||
|
if (o instanceof AudioTrack)
|
||||||
|
return getPath((AudioTrack) o);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPath(Episode e) {
|
||||||
|
// enforce title length limit by default
|
||||||
|
String episodeTitle = truncateText(e instanceof MultiEpisode ? SeasonEpisode.formatMultiTitle(((MultiEpisode) e).getEpisodes()) : e.getTitle(), 150);
|
||||||
|
|
||||||
|
// Anime
|
||||||
|
if (isAnime(e)) {
|
||||||
|
String primaryTitle = e.getSeriesInfo().getName();
|
||||||
|
String episode = String.join(" - ", primaryTitle, EpisodeFormat.SeasonEpisode.formatSxE(e), episodeTitle);
|
||||||
|
return path("Anime", primaryTitle, episode);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TV Series
|
||||||
|
String episode = String.join(" - ", e.getSeriesName(), EpisodeFormat.SeasonEpisode.formatS00E00(e), episodeTitle);
|
||||||
|
String season = e.getSeason() == null ? null : e.getSpecial() == null ? String.format("Season %02d", e.getSeason()) : "Special";
|
||||||
|
return path("TV Shows", e.getSeriesName(), season, episode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPath(Movie m) {
|
||||||
|
// Movie
|
||||||
|
String name = m.getNameWithYear();
|
||||||
|
|
||||||
|
// Movie (multi-part)
|
||||||
|
if (m instanceof MoviePart) {
|
||||||
|
name = String.format("%s CD%d", name, ((MoviePart) m).getPartIndex());
|
||||||
|
}
|
||||||
|
|
||||||
|
return path("Movies", m.getNameWithYear(), name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPath(AudioTrack a) {
|
||||||
|
// Music
|
||||||
|
String name = String.join(" - ", a.getArtist(), first(a.getTrackTitle(), a.getTitle()));
|
||||||
|
|
||||||
|
// prepend track number
|
||||||
|
if (a.getTrack() != null) {
|
||||||
|
name = String.format("%02d. %s", a.getTrack(), name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return path("Music", first(a.getAlbumArtist(), a.getArtist()), a.getAlbum(), name);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String path(String... name) {
|
||||||
|
return stream(name).filter(Objects::nonNull).map(s -> {
|
||||||
|
s = replacePathSeparators(s, " ");
|
||||||
|
s = replaceSpace(s, " ");
|
||||||
|
s = normalizeQuotationMarks(s);
|
||||||
|
s = trimTrailingPunctuation(s);
|
||||||
|
return s;
|
||||||
|
}).collect(joining("/"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String first(String... options) {
|
||||||
|
return stream(options).filter(Objects::nonNull).findFirst().get();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isAnime(Episode e) {
|
||||||
|
return AniDB.getIdentifier().equals(e.getSeriesInfo().getDatabase());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -14,6 +14,7 @@ public class Normalization {
|
||||||
|
|
||||||
private static final Pattern[] brackets = new Pattern[] { compile("\\([^\\(]*\\)"), compile("\\[[^\\[]*\\]"), compile("\\{[^\\{]*\\}") };
|
private static final Pattern[] brackets = new Pattern[] { compile("\\([^\\(]*\\)"), compile("\\[[^\\[]*\\]"), compile("\\{[^\\{]*\\}") };
|
||||||
private static final Pattern trailingParentheses = compile("(?<!^)[(]([^)]*)[)]$");
|
private static final Pattern trailingParentheses = compile("(?<!^)[(]([^)]*)[)]$");
|
||||||
|
private static final Pattern trailingPunctuation = compile("[!?.]+$");
|
||||||
|
|
||||||
private static final Pattern checksum = compile("[\\(\\[]\\p{XDigit}{8}[\\]\\)]");
|
private static final Pattern checksum = compile("[\\(\\[]\\p{XDigit}{8}[\\]\\)]");
|
||||||
|
|
||||||
|
@ -29,6 +30,10 @@ public class Normalization {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String trimTrailingPunctuation(String name) {
|
||||||
|
return trailingPunctuation.matcher(name).replaceAll("").trim();
|
||||||
|
}
|
||||||
|
|
||||||
public static String normalizePunctuation(String name) {
|
public static String normalizePunctuation(String name) {
|
||||||
// remove/normalize special characters
|
// remove/normalize special characters
|
||||||
name = apostrophe.matcher(name).replaceAll("");
|
name = apostrophe.matcher(name).replaceAll("");
|
||||||
|
|
|
@ -2,4 +2,4 @@
|
||||||
parameter.exclude: ^StreamKind|^UniqueID|^StreamOrder|^ID|Count$
|
parameter.exclude: ^StreamKind|^UniqueID|^StreamOrder|^ID|Count$
|
||||||
|
|
||||||
# preview expressions (keys are tagged so they can be sorted alphabetically)
|
# preview expressions (keys are tagged so they can be sorted alphabetically)
|
||||||
expressions: n,y,s,e,es,sxe,s00e00,t,d,startdate,absolute,special,episode,series,primaryTitle,alias,movie,tmdbid,imdbid,pi,pn,lang,subt,az,music,album,artist,albumArtist,actors,director,collection,genre,genres,languages,certification,rating,vc,ac,cf,vf,hpi,af,channels,resolution,dim,ws,sdhd,source,tags,s3d,group,original,fn,ext,mediaType,file,file.name,folder,folder.name,gigabytes,crc32,info,info.runtime,info.status,omdb.rating,omdb.votes,localize.German.title,age,duration,seconds,minutes,media,media.title,media.overallBitRateString,video,video.codecID,video.frameRate,video.displayAspectRatioString,video.scanType,audio,audio.bitRateString,audio.language,audios,audios.language,text,text.codecInfo,text.language,texts,texts.language
|
expressions: n,y,s,e,es,sxe,s00e00,t,d,startdate,absolute,special,episode,series,primaryTitle,alias,movie,tmdbid,imdbid,pi,pn,lang,subt,plex,az,music,album,artist,albumArtist,actors,director,collection,genre,genres,languages,certification,rating,vc,ac,cf,vf,hpi,af,channels,resolution,dim,ws,sdhd,source,tags,s3d,group,original,fn,ext,mediaType,file,file.name,folder,folder.name,gigabytes,crc32,info,info.runtime,info.status,omdb.rating,omdb.votes,localize.German.title,age,duration,seconds,minutes,media,media.title,media.overallBitRateString,video,video.codecID,video.frameRate,video.displayAspectRatioString,video.scanType,audio,audio.bitRateString,audio.language,audios,audios.language,text,text.codecInfo,text.language,texts,texts.language
|
||||||
|
|
|
@ -270,7 +270,7 @@ public class FormatDialog extends JDialog {
|
||||||
|
|
||||||
// initialize window properties
|
// initialize window properties
|
||||||
setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
|
setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
|
||||||
setMinimumSize(new Dimension(650, 470));
|
setMinimumSize(new Dimension(650, 500));
|
||||||
|
|
||||||
// initialize data
|
// initialize data
|
||||||
setState(initMode, lockOnBinding != null ? lockOnBinding : restoreSample(initMode), lockOnBinding != null);
|
setState(initMode, lockOnBinding != null ? lockOnBinding : restoreSample(initMode), lockOnBinding != null);
|
||||||
|
|
|
@ -19,6 +19,8 @@ episode.example[2]: {n} [{airdate}] {t}
|
||||||
episode.example[3]: {n.space('.').lower()}.{s}{e.pad(2)}
|
episode.example[3]: {n.space('.').lower()}.{s}{e.pad(2)}
|
||||||
# organize folder structure
|
# organize folder structure
|
||||||
episode.example[4]: {n}/{'Season '+s}/{n} - {s00e00} - {t}
|
episode.example[4]: {n}/{'Season '+s}/{n} - {s00e00} - {t}
|
||||||
|
# plex standard
|
||||||
|
episode.example[5]: {home}/Media/{plex}
|
||||||
|
|
||||||
# simple name/year
|
# simple name/year
|
||||||
movie.example[0]: {n} ({y}){' CD'+pi}{subt}
|
movie.example[0]: {n} ({y}){' CD'+pi}{subt}
|
||||||
|
@ -30,6 +32,8 @@ movie.example[2]: {n} {[y, certification, rating]}
|
||||||
movie.example[3]: {n.space('.')}.{y}{'.'+source}.{vc}
|
movie.example[3]: {n.space('.')}.{y}{'.'+source}.{vc}
|
||||||
# organize folder structure
|
# organize folder structure
|
||||||
movie.example[4]: {n} ({y})/{n} ({y}){' CD'+pi}
|
movie.example[4]: {n} ({y})/{n} ({y}){' CD'+pi}
|
||||||
|
# plex standard
|
||||||
|
movie.example[5]: {home}/Media/{plex}
|
||||||
|
|
||||||
# simple artist - title
|
# simple artist - title
|
||||||
music.example[0]: {artist} - {t}
|
music.example[0]: {artist} - {t}
|
||||||
|
@ -41,6 +45,8 @@ music.example[2]: {n} - {t} {[audio.SamplingRateString]}
|
||||||
music.example[3]: {pi.pad(2)} {n} - {t} {[af, audio.BitRate]}
|
music.example[3]: {pi.pad(2)} {n} - {t} {[af, audio.BitRate]}
|
||||||
# organize folder structure
|
# organize folder structure
|
||||||
music.example[4]: {n}/{album+'/'}{pi.pad(2)+'. '} {t}
|
music.example[4]: {n}/{album+'/'}{pi.pad(2)+'. '} {t}
|
||||||
|
# plex standard
|
||||||
|
music.example[5]: {home}/Media/{plex}
|
||||||
|
|
||||||
# simple filename without extension
|
# simple filename without extension
|
||||||
file.example[0]: {i.pad(3)} - {fn}
|
file.example[0]: {i.pad(3)} - {fn}
|
||||||
|
|
|
@ -188,6 +188,11 @@
|
||||||
<td>season / episode numbers</td>
|
<td>season / episode numbers</td>
|
||||||
<td>S01E01</td>
|
<td>S01E01</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>plex</td>
|
||||||
|
<td><a href="https://support.plex.tv/hc/en-us/sections/200059498-Naming-and-Organizing-TV-Shows" target="_blank">Plex Naming Standard</a></td>
|
||||||
|
<td><path></td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>imdbid</td>
|
<td>imdbid</td>
|
||||||
<td>imdb id</td>
|
<td>imdb id</td>
|
||||||
|
|
Loading…
Reference in New Issue