mirror of
https://github.com/mitb-archive/filebot
synced 2025-03-09 13:59:49 -04:00
Support Kodi Naming Standard (i.e. same as Plex, but with SxE and non-range based multi-episode formatting) via {kodi} binding
This commit is contained in:
parent
9eb07913b5
commit
6e8359913b
@ -55,7 +55,7 @@ import net.filebot.media.FFProbe;
|
|||||||
import net.filebot.media.ImageMetadata;
|
import net.filebot.media.ImageMetadata;
|
||||||
import net.filebot.media.LocalDatasource.PhotoFile;
|
import net.filebot.media.LocalDatasource.PhotoFile;
|
||||||
import net.filebot.media.MetaAttributes;
|
import net.filebot.media.MetaAttributes;
|
||||||
import net.filebot.media.PlexNamingStandard;
|
import net.filebot.media.NamingStandard;
|
||||||
import net.filebot.media.VideoFormat;
|
import net.filebot.media.VideoFormat;
|
||||||
import net.filebot.mediainfo.MediaInfo;
|
import net.filebot.mediainfo.MediaInfo;
|
||||||
import net.filebot.mediainfo.MediaInfo.StreamKind;
|
import net.filebot.mediainfo.MediaInfo.StreamKind;
|
||||||
@ -201,7 +201,7 @@ public class MediaBindingBean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// enforce title length limit by default
|
// enforce title length limit by default
|
||||||
return truncateText(t, PlexNamingStandard.TITLE_MAX_LENGTH);
|
return truncateText(t, NamingStandard.TITLE_MAX_LENGTH);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Define("d")
|
@Define("d")
|
||||||
@ -1074,7 +1074,18 @@ public class MediaBindingBean {
|
|||||||
|
|
||||||
@Define("plex")
|
@Define("plex")
|
||||||
public File getPlexStandardPath() throws Exception {
|
public File getPlexStandardPath() throws Exception {
|
||||||
String path = new PlexNamingStandard().getPath(infoObject);
|
String path = NamingStandard.PLEX.getPath(infoObject);
|
||||||
|
try {
|
||||||
|
path = path.concat(getSubtitleTags()); // NPE if {subt} is undefined
|
||||||
|
} catch (Exception e) {
|
||||||
|
// ignore => no language tags
|
||||||
|
}
|
||||||
|
return new File(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Define("kodi")
|
||||||
|
public File getKodiStandardPath() throws Exception {
|
||||||
|
String path = NamingStandard.KODI.getPath(infoObject);
|
||||||
try {
|
try {
|
||||||
path = path.concat(getSubtitleTags()); // NPE if {subt} is undefined
|
path = path.concat(getSubtitleTags()); // NPE if {subt} is undefined
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
@ -13,8 +13,32 @@ import net.filebot.web.Episode;
|
|||||||
import net.filebot.web.EpisodeFormat;
|
import net.filebot.web.EpisodeFormat;
|
||||||
import net.filebot.web.Movie;
|
import net.filebot.web.Movie;
|
||||||
import net.filebot.web.MoviePart;
|
import net.filebot.web.MoviePart;
|
||||||
|
import net.filebot.web.MultiEpisode;
|
||||||
|
|
||||||
public class PlexNamingStandard {
|
public class NamingStandard {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plex Naming Standard (i.e. Default Implementation)
|
||||||
|
*
|
||||||
|
* https://support.plex.tv/articles/categories/media-preparation/
|
||||||
|
*/
|
||||||
|
public static final NamingStandard PLEX = new NamingStandard();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kodi Naming Standard (i.e. same as Plex, but with SxE and non-range based multi-episode formatting)
|
||||||
|
*
|
||||||
|
* https://kodi.wiki/view/Naming_video_files
|
||||||
|
*/
|
||||||
|
public static final NamingStandard KODI = new NamingStandard() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String formatEpisodeNumbers(Episode e) {
|
||||||
|
if (e instanceof MultiEpisode) {
|
||||||
|
return EpisodeFormat.SeasonEpisode.formatMultiSxE((MultiEpisode) e);
|
||||||
|
}
|
||||||
|
return EpisodeFormat.SeasonEpisode.formatSxE(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
public String getPath(Object o) {
|
public String getPath(Object o) {
|
||||||
if (o instanceof Episode)
|
if (o instanceof Episode)
|
||||||
@ -133,7 +157,6 @@ public class PlexNamingStandard {
|
|||||||
// TV Series
|
// TV Series
|
||||||
return EpisodeFormat.SeasonEpisode.formatS00E00(e);
|
return EpisodeFormat.SeasonEpisode.formatS00E00(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String formatEpisodeTitle(Episode e) {
|
public String formatEpisodeTitle(Episode e) {
|
@ -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, sxe, s00e00, t, d, startdate, absolute, ny, es, sy, sc, di, dc, age, special, episode, series, primaryTitle, alias, movie, tmdbid, imdbid, pi, pn, lang, subt, plex, az, type, anime, regular, music, album, artist, albumArtist, actors, director, collection, genre, genres, languages, runtime, certification, rating, votes, vc, ac, cf, vf, hpi, aco, af, channels, resolution, dim, width, height, bitdepth, hdr, bitrate, kbps, mbps, khz, ws, hd, source, tags, s3d, group, original, info, info.network, info.status, info.productionCompanies, info.productionCountries, info.certifications, info.certifications.AU, info.certifications.DE, omdb.rating, omdb.votes, localize.deu.n, localize.deu.t, localize.zho.n, localize.zho.t, order.airdate.sxe, order.dvd.sxe, fn, ext, mediaType, mediaPath, file, file.name, folder, folder.name, mediaTitle, audioLanguages, textLanguages, duration, seconds, minutes, hours, bytes, megabytes, gigabytes, crc32, media.title, media.collection, media.season, media.part, media.partID, media.genre, media.contentType, media.description, media.lyrics, video[0].codecID, video[0].frameRate, video[0].displayAspectRatioString, video[0].scanType, audio.language, audio[0].bitRateString, audio[0].language, text.language, text[0].language, text[0].codecInfo, camera, camera.maker, camera.model, location, location.country
|
expressions: n, y, s, e, sxe, s00e00, t, d, startdate, absolute, ny, es, sy, sc, di, dc, age, special, episode, series, primaryTitle, alias, movie, tmdbid, imdbid, pi, pn, lang, subt, plex, plex.name, kodi, kodi.name, az, type, anime, regular, music, album, artist, albumArtist, actors, director, collection, genre, genres, languages, runtime, certification, rating, votes, vc, ac, cf, vf, hpi, aco, af, channels, resolution, dim, width, height, bitdepth, hdr, bitrate, kbps, mbps, khz, ws, hd, source, tags, s3d, group, original, info, info.network, info.status, info.productionCompanies, info.productionCountries, info.certifications, info.certifications.AU, info.certifications.DE, omdb.rating, omdb.votes, localize.deu.n, localize.deu.t, localize.zho.n, localize.zho.t, order.airdate.sxe, order.dvd.sxe, fn, ext, mediaType, mediaPath, file, file.name, folder, folder.name, mediaTitle, audioLanguages, textLanguages, duration, seconds, minutes, hours, bytes, megabytes, gigabytes, crc32, media.title, media.collection, media.season, media.part, media.partID, media.genre, media.contentType, media.description, media.lyrics, video[0].codecID, video[0].frameRate, video[0].displayAspectRatioString, video[0].scanType, audio.language, audio[0].bitRateString, audio[0].language, text.language, text[0].language, text[0].codecInfo, camera, camera.maker, camera.model, location, location.country
|
||||||
|
@ -119,6 +119,21 @@ public class EpisodeFormat extends Format {
|
|||||||
}).collect(joining(" - "));
|
}).collect(joining(" - "));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String formatMultiSxE(Iterable<Episode> episodes) {
|
||||||
|
return formatMultiNumbers(episodes, "%01dx", "%02d", "x");
|
||||||
|
}
|
||||||
|
|
||||||
|
public String formatMultiS00E00(Iterable<Episode> episodes) {
|
||||||
|
return formatMultiNumbers(episodes, "S%02d", "E%02d", "-");
|
||||||
|
}
|
||||||
|
|
||||||
|
public String formatMultiNumbers(Iterable<Episode> episodes, String seasonFormat, String episodeFormat, String delimiter) {
|
||||||
|
return getSeasonEpisodeNumbers(episodes).entrySet().stream().map(it -> {
|
||||||
|
String s = it.getKey() >= 0 ? String.format(seasonFormat, it.getKey()) : "";
|
||||||
|
return it.getValue().stream().distinct().map(i -> String.format(episodeFormat, i)).collect(joining(delimiter, s, ""));
|
||||||
|
}).collect(joining(" - "));
|
||||||
|
}
|
||||||
|
|
||||||
private SortedMap<Integer, SortedSet<Integer>> getSeasonEpisodeNumbers(Iterable<Episode> episodes) {
|
private SortedMap<Integer, SortedSet<Integer>> getSeasonEpisodeNumbers(Iterable<Episode> episodes) {
|
||||||
SortedMap<Integer, SortedSet<Integer>> n = new TreeMap<Integer, SortedSet<Integer>>();
|
SortedMap<Integer, SortedSet<Integer>> n = new TreeMap<Integer, SortedSet<Integer>>();
|
||||||
for (Episode it : episodes) {
|
for (Episode it : episodes) {
|
||||||
|
@ -5,9 +5,10 @@ import static java.util.Collections.*;
|
|||||||
import static java.util.stream.Collectors.*;
|
import static java.util.stream.Collectors.*;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class MultiEpisode extends Episode {
|
public class MultiEpisode extends Episode implements Iterable<Episode> {
|
||||||
|
|
||||||
protected Episode[] episodes;
|
protected Episode[] episodes;
|
||||||
|
|
||||||
@ -23,6 +24,11 @@ public class MultiEpisode extends Episode {
|
|||||||
this.episodes = episodes.toArray(new Episode[0]);
|
this.episodes = episodes.toArray(new Episode[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<Episode> iterator() {
|
||||||
|
return stream(episodes).iterator();
|
||||||
|
}
|
||||||
|
|
||||||
public List<Episode> getEpisodes() {
|
public List<Episode> getEpisodes() {
|
||||||
return unmodifiableList(asList(episodes));
|
return unmodifiableList(asList(episodes));
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user