Adding new plex metadata scanner support

This commit is contained in:
Jason House
2020-08-31 18:39:32 +09:00
parent a0c29efb51
commit 32b04178d4
10 changed files with 253 additions and 54 deletions
@@ -58,13 +58,17 @@ public final class Movie implements Comparable<Movie> {
@JsonProperty("poster_url")
private final String posterUrl;
@Nullable
private final String imdbId;
private String imdbId;
@Nullable
private final String language;
@Nullable
private final String overview;
@NotNull
private final List<MovieFromCollection> moviesInCollection;
@NotNull
private final Integer ratingKey;
@NotNull
private final String key;
@Nullable
private String collection;
@NotNull
@@ -73,7 +77,8 @@ public final class Movie implements Comparable<Movie> {
private Integer tvdbId;
private Movie(@NotNull String name, @NotNull Integer year, @Nullable String posterUrl, @Nullable String collection, @NotNull Integer collectionId, @NotNull Integer tvdbId,
@Nullable String imdbId, @Nullable String language, @Nullable String overview, @NotNull List<MovieFromCollection> moviesInCollection) {
@Nullable String imdbId, @Nullable String language, @Nullable String overview, @NotNull List<MovieFromCollection> moviesInCollection, @NotNull Integer ratingKey,
@NotNull String key) {
this.name = name;
this.nameWithoutBadCharacters = name.replaceAll("[<>`~\\[\\]()*&^%$#@!|{}.,?\\-_=+:;]", "");
this.year = year;
@@ -85,6 +90,8 @@ public final class Movie implements Comparable<Movie> {
this.language = language;
this.overview = overview;
this.moviesInCollection = moviesInCollection;
this.ratingKey = ratingKey;
this.key = key;
}
public @NotNull Integer getCollectionId() {
@@ -103,6 +110,10 @@ public final class Movie implements Comparable<Movie> {
this.tvdbId = tvdbId;
}
public void setImdbId(@Nullable String imdbId) {
this.imdbId = imdbId;
}
@NotNull
public String getName() {
return name;
@@ -146,6 +157,14 @@ public final class Movie implements Comparable<Movie> {
return nameWithoutBadCharacters;
}
public Integer getRatingKey() {
return ratingKey;
}
public String getKey() {
return key;
}
@Override
public boolean equals(Object o) {
if (this == o) {
@@ -190,10 +209,12 @@ public final class Movie implements Comparable<Movie> {
", imdbId='" + imdbId + '\'' +
", language='" + language + '\'' +
", overview='" + overview + '\'' +
", moviesInCollection=" + moviesInCollection +
", collection='" + collection + '\'' +
", collectionId=" + collectionId +
", tvdbId=" + tvdbId +
", moviesInCollection=" + moviesInCollection +
", ratingKey=" + ratingKey +
", key='" + key + '\'' +
'}';
}
@@ -203,26 +224,39 @@ public final class Movie implements Comparable<Movie> {
public static class Builder {
@NotNull
private final String name;
private final int year;
@NotNull
private String posterUrl;
@NotNull
private String collection;
private int collectionId;
private int tvdbId;
@NotNull
private String imdbId;
@NotNull
private String language;
@NotNull
private String overview;
@NotNull
private List<MovieFromCollection> moviesInCollection;
@NotNull
private Integer ratingKey;
@NotNull
private String key;
public Builder(@NotNull String name, @NotNull Integer year) {
this.name = name;
this.year = year;
@@ -234,50 +268,72 @@ public final class Movie implements Comparable<Movie> {
this.language = "en";
this.overview = "";
this.moviesInCollection = new ArrayList<>();
this.ratingKey = -1;
this.key = "";
}
public Movie build() {
return new Movie(name, year, posterUrl, collection, collectionId, tvdbId, imdbId, language, overview, moviesInCollection);
return new Movie(name, year, posterUrl, collection, collectionId, tvdbId, imdbId, language, overview, moviesInCollection, ratingKey, key);
}
public Builder setPosterUrl(String posterUrl) {
@NotNull
public Builder setPosterUrl(@NotNull String posterUrl) {
this.posterUrl = posterUrl;
return this;
}
public Builder setCollection(String collection) {
@NotNull
public Builder setCollection(@NotNull String collection) {
this.collection = collection;
return this;
}
@NotNull
public Builder setCollectionId(int collectionId) {
this.collectionId = collectionId;
return this;
}
@NotNull
public Builder setTvdbId(int tvdbId) {
this.tvdbId = tvdbId;
return this;
}
public Builder setImdbId(String imdbId) {
@NotNull
public Builder setImdbId(@NotNull String imdbId) {
this.imdbId = imdbId;
return this;
}
public Builder setLanguage(String language) {
@NotNull
public Builder setLanguage(@NotNull String language) {
this.language = language;
return this;
}
public Builder setOverview(String overview) {
@NotNull
public Builder setOverview(@NotNull String overview) {
this.overview = overview;
return this;
}
public Builder setMoviesInCollection(List<MovieFromCollection> moviesInCollection) {
@NotNull
public Builder setMoviesInCollection(@NotNull List<MovieFromCollection> moviesInCollection) {
this.moviesInCollection = moviesInCollection;
return this;
}
@NotNull
public Builder setRatingKey(@NotNull Integer ratingKey) {
this.ratingKey = ratingKey;
return this;
}
@NotNull
public Builder setKey(@NotNull String key) {
this.key = key;
return this;
}
}
}
@@ -10,6 +10,8 @@
package com.jasonhhouse.gaps;
import com.jasonhhouse.gaps.properties.PlexProperties;
import com.jasonhhouse.plex.libs.PlexLibrary;
import com.jasonhhouse.plex.video.MediaContainer;
import java.util.List;
import java.util.Map;
@@ -43,4 +45,9 @@ public interface PlexQuery {
@NotNull MediaContainer findAllPlexVideos(@NotNull String url);
void findAllMovieIds(@NotNull List<Movie> movies, @NotNull PlexServer plexServer, @NotNull PlexLibrary plexLibrary);
@NotNull PlexServer getPlexServerFromMachineIdentifier(@NotNull PlexProperties plexProperties,@NotNull String machineIdentifier) throws IllegalArgumentException;
@NotNull PlexLibrary getPlexLibraryFromKey(@NotNull PlexServer plexServer,@NotNull Integer key) throws IllegalArgumentException;
}
@@ -128,4 +128,5 @@ public class GapsUrlGenerator implements UrlGenerator {
.addQueryParameter(PLEX_TOKEN, plexServer.getPlexToken())
.build();
}
}
@@ -116,6 +116,7 @@ public class SearchGapsTask implements Runnable {
HttpUrl url = gapsUrlGenerator.generatePlexLibraryUrl(plexServer, plexLibrary);
try {
List<Movie> ownedMovies = plexQuery.findAllPlexMovies(generateOwnedMovieMap(plexProperties), url);
plexQuery.findAllMovieIds(ownedMovies, plexServer, plexLibrary);
ioService.writeOwnedMoviesToFile(ownedMovies, plexServer.getMachineIdentifier(), plexLibrary.getKey());
notificationService.plexLibraryScanSuccessful(plexServer, plexLibrary);
} catch (ResponseStatusException e) {
@@ -73,4 +73,5 @@ public interface UrlGenerator {
@Nullable HttpUrl generatePlexLibraryUrl(@NotNull PlexServer plexServer,@NotNull PlexLibrary plexLibrary);
@NotNull HttpUrl generatePlexMetadataUrl(@NotNull PlexServer plexServer, @NotNull PlexLibrary plexLibrary,@NotNull Integer ratingKey);
}
@@ -13,12 +13,15 @@ package com.jasonhhouse.gaps.controller;
import com.jasonhhouse.gaps.Movie;
import com.jasonhhouse.gaps.Pair;
import com.jasonhhouse.gaps.PlexQuery;
import com.jasonhhouse.gaps.PlexServer;
import com.jasonhhouse.gaps.properties.PlexProperties;
import com.jasonhhouse.gaps.service.IoService;
import com.jasonhhouse.plex.libs.PlexLibrary;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -55,6 +58,9 @@ public class PlexMovieListController {
Map<Pair<String, Integer>, Movie> previousMovies = generateOwnedMovieMap(plexProperties, everyMovie);
String url = generatePlexMovieUrl(plexProperties, machineIdentifier, key);
List<Movie> ownedMovies = plexQuery.findAllPlexMovies(previousMovies, url);
PlexServer plexServer = plexQuery.getPlexServerFromMachineIdentifier(plexProperties, machineIdentifier);
PlexLibrary plexLibrary = plexQuery.getPlexLibraryFromKey(plexServer, key);
plexQuery.findAllMovieIds(ownedMovies, plexServer, plexLibrary);
//Update Owned Movies
ioService.writeOwnedMoviesToFile(ownedMovies, machineIdentifier, key);
@@ -16,6 +16,7 @@ import com.jasonhhouse.gaps.Payload;
import com.jasonhhouse.gaps.PlexQuery;
import com.jasonhhouse.gaps.PlexServer;
import com.jasonhhouse.gaps.UrlGenerator;
import com.jasonhhouse.gaps.properties.PlexProperties;
import com.jasonhhouse.plex.libs.MediaContainer;
import com.jasonhhouse.plex.libs.PlexLibrary;
import java.io.ByteArrayInputStream;
@@ -24,7 +25,6 @@ import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@@ -264,6 +264,114 @@ public class PlexQueryImpl implements PlexQuery {
return mediaContainer;
}
@Override
public void findAllMovieIds(@NotNull List<Movie> movies, @NotNull PlexServer plexServer, @NotNull PlexLibrary plexLibrary) {
LOGGER.info("findAllMovieIds( {}, {} )", plexServer, plexLibrary);
if(plexLibrary.getScanner().equals("Plex Movie Scanner")) {
LOGGER.info("PlexLibrary {} uses old scanner", plexLibrary.getTitle());
return;
}
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(180, TimeUnit.SECONDS)
.writeTimeout(180, TimeUnit.SECONDS)
.readTimeout(180, TimeUnit.SECONDS)
.build();
for (Movie movie : movies) {
if (movie.getRatingKey() == -1) {
LOGGER.info("No key found for the movie {}", movie.getName());
continue;
}
HttpUrl httpUrl = urlGenerator.generatePlexMetadataUrl(plexServer, plexLibrary, movie.getRatingKey());
Request request = new Request.Builder()
.url(httpUrl)
.build();
try (Response response = client.newCall(request).execute()) {
String body = response.body() != null ? response.body().string() : null;
if (StringUtils.isBlank(body)) {
LOGGER.error("Body returned empty from Plex for the movie {}", movie.getName());
continue;
}
InputStream fileIS = new ByteArrayInputStream(body.getBytes(StandardCharsets.UTF_8));
DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = builderFactory.newDocumentBuilder();
Document xmlDocument = builder.parse(fileIS);
XPath xPath = XPathFactory.newInstance().newXPath();
String expression = "/MediaContainer/Video/Guid";
NodeList nodeList = (NodeList) xPath.compile(expression).evaluate(xmlDocument, XPathConstants.NODESET);
if (nodeList.getLength() == 0) {
LOGGER.warn("No guids found in url: {}", httpUrl);
continue;
}
for (int i = 0; i < nodeList.getLength(); i++) {
Node node = nodeList.item(i);
Node nodeTitle = node.getAttributes().getNamedItem("id");
if (nodeTitle == null) {
LOGGER.error("Missing id from Guid element in Plex");
continue;
}
//Files can't have : so need to remove to find matches correctly
String urlId = nodeTitle.getNodeValue();
String id = urlId.replaceAll("[A-Za-z]+://", "");
if (urlId.contains("imdb")) {
movie.setImdbId(id);
} else if (urlId.contains("tmdb")) {
movie.setTvdbId(Integer.parseInt(id));
} else {
LOGGER.warn("Can't find ID to match {}", urlId);
}
}
} catch (IOException e) {
String reason = String.format("Error connecting to Plex to get Movie list: %s", httpUrl);
LOGGER.error(reason, e);
throw new ResponseStatusException(HttpStatus.NOT_FOUND, reason, e);
} catch (ParserConfigurationException | XPathExpressionException | SAXException e) {
String reason = String.format("Error parsing XML from Plex: %s", httpUrl);
LOGGER.error(reason, e);
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, reason, e);
}
}
}
@Override
public @NotNull PlexServer getPlexServerFromMachineIdentifier(@NotNull PlexProperties plexProperties, @NotNull String machineIdentifier) throws IllegalArgumentException{
LOGGER.info("generatePlexUrl( {} )", machineIdentifier);
for(PlexServer plexServer : plexProperties.getPlexServers()) {
if(plexServer.getMachineIdentifier().equals(machineIdentifier)) {
return plexServer;
}
}
throw new IllegalArgumentException(String.format("No PlexServer matching machineIdentifier %s found", machineIdentifier));
}
@Override
public @NotNull PlexLibrary getPlexLibraryFromKey(@NotNull PlexServer plexServer,@NotNull Integer key) throws IllegalArgumentException {
for(PlexLibrary plexLibrary : plexServer.getPlexLibraries()) {
if(plexLibrary.getKey().equals(key)) {
return plexLibrary;
}
}
throw new IllegalArgumentException(String.format("No PlexLibrary matching key %s found", key));
}
@Override
public @NotNull List<Movie> findAllPlexMovies(@NotNull Map<Pair<String, Integer>, Movie> previousMovies, @NotNull String url) {
LOGGER.info("findAllPlexMovies()");
@@ -297,7 +405,6 @@ public class PlexQueryImpl implements PlexQuery {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, reason);
}
InputStream fileIS = new ByteArrayInputStream(body.getBytes(StandardCharsets.UTF_8));
DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = builderFactory.newDocumentBuilder();
@@ -330,9 +437,17 @@ public class PlexQueryImpl implements PlexQuery {
}
int year = Integer.parseInt(node.getAttributes().getNamedItem("year").getNodeValue());
String guid = "";
Integer tmdbId = -1;
String imdbId = "";
if (node.getAttributes().getNamedItem("guid") != null) {
guid = node.getAttributes().getNamedItem("guid").getNodeValue();
String guid = node.getAttributes().getNamedItem("guid").getNodeValue();
if (guid.contains("com.plexapp.agents.themoviedb")) {
guid = guid.replaceAll("[A-Za-z\\.]+://", "");
tmdbId = Integer.valueOf(guid.substring(0, guid.indexOf('?')));
} else if (guid.contains("com.plexapp.agents.imdb")) {
guid = guid.replaceAll("[A-Za-z\\.]+://", "");
imdbId = guid.substring(0, guid.indexOf('?'));
}
}
String thumbnail = "";
@@ -345,23 +460,17 @@ public class PlexQueryImpl implements PlexQuery {
summary = node.getAttributes().getNamedItem("summary").getNodeValue();
}
Movie movie;
if (guid.contains("com.plexapp.agents.themoviedb")) {
//ToDo
//Find out what it looks like in TMDB
//language = ??
guid = guid.substring(guid.indexOf(ID_IDX_START) + ID_IDX_START.length(), guid.indexOf(ID_IDX_END));
movie = getOrCreateOwnedMovie(previousMovies, title, year, thumbnail, Integer.parseInt(guid), null, null, -1, null, summary);
} else if (guid.contains("com.plexapp.agents.imdb://")) {
String language = guid.substring(guid.indexOf("?lang=") + "?lang=".length());
language = new Locale(language, "").getDisplayLanguage();
guid = guid.substring(guid.indexOf(ID_IDX_START) + ID_IDX_START.length(), guid.indexOf(ID_IDX_END));
movie = getOrCreateOwnedMovie(previousMovies, title, year, thumbnail, -1, guid, language, -1, null, summary);
} else {
LOGGER.warn("Cannot handle guid value of {}", guid);
movie = getOrCreateOwnedMovie(previousMovies, title, year, thumbnail, -1, null, null, -1, null, summary);
String key = "";
if (node.getAttributes().getNamedItem("key") != null) {
key = node.getAttributes().getNamedItem("key").getNodeValue();
}
Integer ratingKey = -1;
if (node.getAttributes().getNamedItem("ratingKey") != null) {
ratingKey = Integer.valueOf(node.getAttributes().getNamedItem("ratingKey").getNodeValue());
}
Movie movie = getOrCreateOwnedMovie(previousMovies, title, year, tmdbId, imdbId, thumbnail, summary, ratingKey, key);
ownedMovies.add(movie);
}
LOGGER.info("{} movies found in plex", ownedMovies.size());
@@ -442,9 +551,17 @@ public class PlexQueryImpl implements PlexQuery {
}
int year = Integer.parseInt(node.getAttributes().getNamedItem("year").getNodeValue());
String guid = "";
Integer tmdbId = -1;
String imdbId = "";
if (node.getAttributes().getNamedItem("guid") != null) {
guid = node.getAttributes().getNamedItem("guid").getNodeValue();
String guid = node.getAttributes().getNamedItem("guid").getNodeValue();
if (guid.contains("com.plexapp.agents.themoviedb")) {
guid = guid.replaceAll("[A-Za-z\\.]+://", "");
tmdbId = Integer.valueOf(guid.substring(0, guid.indexOf('?')));
} else if (guid.contains("com.plexapp.agents.imdb")) {
guid = guid.replaceAll("[A-Za-z\\.]+://", "");
imdbId = guid.substring(0, guid.indexOf('?'));
}
}
String thumbnail = "";
@@ -457,23 +574,17 @@ public class PlexQueryImpl implements PlexQuery {
summary = node.getAttributes().getNamedItem("summary").getNodeValue();
}
Movie movie;
if (guid.contains("com.plexapp.agents.themoviedb")) {
//ToDo
//Find out what it looks like in TMDB
//language = ??
guid = guid.substring(guid.indexOf(ID_IDX_START) + ID_IDX_START.length(), guid.indexOf(ID_IDX_END));
movie = getOrCreateOwnedMovie(previousMovies, title, year, thumbnail, Integer.parseInt(guid), null, null, -1, null, summary);
} else if (guid.contains("com.plexapp.agents.imdb://")) {
String language = guid.substring(guid.indexOf("?lang=") + "?lang=".length());
language = new Locale(language, "").getDisplayLanguage();
guid = guid.substring(guid.indexOf(ID_IDX_START) + ID_IDX_START.length(), guid.indexOf(ID_IDX_END));
movie = getOrCreateOwnedMovie(previousMovies, title, year, thumbnail, -1, guid, language, -1, null, summary);
} else {
LOGGER.warn("Cannot handle guid value of {}", guid);
movie = getOrCreateOwnedMovie(previousMovies, title, year, thumbnail, -1, null, null, -1, null, summary);
String key = "";
if (node.getAttributes().getNamedItem("key") != null) {
key = node.getAttributes().getNamedItem("key").getNodeValue();
}
Integer ratingKey = -1;
if (node.getAttributes().getNamedItem("ratingKey") != null) {
ratingKey = Integer.valueOf(node.getAttributes().getNamedItem("ratingKey").getNodeValue());
}
Movie movie = getOrCreateOwnedMovie(previousMovies, title, year, tmdbId, imdbId, thumbnail, summary, ratingKey, key);
ownedMovies.add(movie);
}
LOGGER.info("{} movies found in plex", ownedMovies.size());
@@ -496,19 +607,29 @@ public class PlexQueryImpl implements PlexQuery {
return ownedMovies;
}
private Movie getOrCreateOwnedMovie(Map<Pair<String, Integer>, Movie> previousMovies, String title, int year, String thumbnail, int tvdbId, String imdbId, String language, int collection, String collectionName, String summary) {
private Movie getOrCreateOwnedMovie(Map<Pair<String, Integer>, Movie> previousMovies, @NotNull String title, int year, @NotNull Integer tmdbId, @NotNull String imdbId, @NotNull String thumbnail, @NotNull String summary, @NotNull Integer ratingKey, @NotNull String key) {
Pair<String, Integer> moviePair = new Pair<>(title, year);
if (previousMovies.containsKey(moviePair)) {
return previousMovies.get(moviePair);
Movie previousMovie = previousMovies.get(moviePair);
return new Movie.Builder(title, year)
.setPosterUrl(thumbnail)
.setOverview(summary)
.setKey(key)
.setRatingKey(ratingKey)
.setImdbId(previousMovie.getImdbId())
.setCollection(previousMovie.getCollection())
.setLanguage(previousMovie.getLanguage())
.setTvdbId(previousMovie.getTvdbId())
.setCollectionId(previousMovie.getCollectionId())
.build();
} else {
return new Movie.Builder(title, year)
.setPosterUrl(thumbnail)
.setTvdbId(tvdbId)
.setImdbId(imdbId)
.setLanguage(language)
.setCollectionId(collection)
.setCollection(collectionName)
.setOverview(summary)
.setKey(key)
.setRatingKey(ratingKey)
.setTvdbId(tmdbId)
.setImdbId(imdbId)
.build();
}
}
@@ -78,6 +78,11 @@
<img src="/images/final-2.svg" alt="Gaps Logo" style="width:50%;height:50%;" class="center">
<h3 class="top-margin">Updates</h3>
<h4 class="top-margin text-primary">v0.7.0</h4>
<ul class="text-muted">
<li>Added Support for the new <a href="https://forums.plex.tv/t/new-plex-media-server-movie-scanner-and-agent-preview/593269" target="_blank">Plex Media Scanner</a></li>
</ul>
<h4 class="top-margin text-primary">v0.6.1</h4>
<ul class="text-muted">
<li>Added Native Windows Java Support</li>
View File
+2 -1
View File
@@ -12,7 +12,8 @@ else
if [ $ENABLE_LOGIN == true ]; then
java -jar -Dspring.profiles.active=no-ssl $JAR_FILE
else
java -jar -Dspring.profiles.active=no-ssl-no-login $JAR_FILE
#java -jar -Dspring.profiles.active=no-ssl-no-login $JAR_FILE
java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005 -jar -Dspring.profiles.active=no-ssl-no-login $JAR_FILE
fi
fi