Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.tinymediamanager.scraper.kodi.AbstractKodiMetadataProvider Maven / Gradle / Ivy
/*
* Copyright 2012 - 2015 Manuel Laggner
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.tinymediamanager.scraper.kodi;
import java.io.ByteArrayInputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tinymediamanager.scraper.MediaArtwork;
import org.tinymediamanager.scraper.MediaArtwork.MediaArtworkType;
import org.tinymediamanager.scraper.MediaCastMember;
import org.tinymediamanager.scraper.MediaGenres;
import org.tinymediamanager.scraper.MediaMetadata;
import org.tinymediamanager.scraper.MediaProviderInfo;
import org.tinymediamanager.scraper.MediaScrapeOptions;
import org.tinymediamanager.scraper.MediaSearchOptions;
import org.tinymediamanager.scraper.MediaSearchResult;
import org.tinymediamanager.scraper.mediaprovider.IMediaProvider;
import org.tinymediamanager.scraper.util.DOMUtils;
import org.tinymediamanager.scraper.util.MetadataUtil;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
/**
* The abstract class for the main Kodi plugin parsing logic
*
* @author Manuel Laggner
*/
public abstract class AbstractKodiMetadataProvider implements IMediaProvider {
private static final Logger LOGGER = LoggerFactory.getLogger(AbstractKodiMetadataProvider.class);
private final DocumentBuilderFactory factory;
protected MediaProviderInfo providerInfo;
public KodiScraper scraper;
public AbstractKodiMetadataProvider(KodiScraper scraper) {
KodiScraperParser parser = new KodiScraperParser();
try {
scraper = parser.parseScraper(scraper, KodiUtil.commonXmls);
}
catch (Exception e) {
LOGGER.error("Failed to Load Kodi Scraper: " + scraper);
throw new RuntimeException("Failed to Load Kodi Scraper: " + scraper, e);
}
this.scraper = scraper;
this.providerInfo = new MediaProviderInfo(scraper.id, "Kodi: " + scraper.name + " - " + scraper.version, scraper.description,
scraper.logoUrl == null ? AbstractKodiMetadataProvider.class.getResource("/kodi_tv_png") : scraper.logoUrl);
factory = DocumentBuilderFactory.newInstance();
}
@Override
public MediaProviderInfo getProviderInfo() {
return providerInfo;
}
protected List _search(MediaSearchOptions options) throws Exception {
List l = new ArrayList();
String arg = options.get(MediaSearchOptions.SearchParam.QUERY);
// cannot search without any title/query
if (StringUtils.isBlank(arg)) {
return l;
}
// Kodi wants title and year separated, so let's do that
String args[] = parseTitle(arg);
String title = args[0];
String year = options.get(MediaSearchOptions.SearchParam.YEAR);
KodiAddonProcessor processor = new KodiAddonProcessor(scraper);
KodiUrl url = processor.getSearchUrl(title, year);
String xmlString = processor.getSearchResults(url);
LOGGER.trace("========= BEGIN Kodi Scraper Search Xml Results: Url: " + url);
LOGGER.trace(xmlString);
LOGGER.trace("========= End Kodi Scraper Search Xml Results: Url: " + url);
Document xml = parseXmlString(xmlString);
NodeList nl = xml.getElementsByTagName("entity");
for (int i = 0; i < nl.getLength(); i++) {
try {
Element el = (Element) nl.item(i);
NodeList titleList = el.getElementsByTagName("title");
String t = titleList.item(0).getTextContent();
NodeList yearList = el.getElementsByTagName("year");
String y = yearList.item(0).getTextContent();
NodeList urlList = el.getElementsByTagName("url");
KodiUrl u = new KodiUrl((Element) urlList.item(0));
MediaSearchResult sr = new MediaSearchResult(providerInfo.getId());
String id = DOMUtils.getElementValue(el, "id");
sr.setId(id);
sr.setUrl(u.toExternalForm());
sr.setProviderId(providerInfo.getId());
if (u.toExternalForm().indexOf("imdb") != -1) {
sr.setIMDBId(id);
}
// String v[] = ParserUtils.parseTitle(t);
// sr.setTitle(v[0]);
// sr.setYear(v[1]);
sr.setTitle(t);
sr.setYear(y);
sr.setScore(MetadataUtil.calculateScore(arg, t));
l.add(sr);
}
catch (Exception e) {
LOGGER.error("Error process an xml node! Ignoring it from the search results.");
}
}
Collections.sort(l);
Collections.reverse(l);
return l;
}
protected MediaMetadata _getMetadata(MediaScrapeOptions options) throws Exception {
MediaMetadata md = new MediaMetadata(providerInfo.getId());
MediaSearchResult result = options.getResult();
if (result.getIMDBId() != null && result.getIMDBId().contains("tt")) {
md.storeMetadata(MediaMetadata.IMDB, result.getIMDBId());
}
KodiAddonProcessor processor = new KodiAddonProcessor(scraper);
String xmlDetails = processor.getDetails(new KodiUrl(result.getUrl()), result.getId());
// workaround: replace problematic sequences
xmlDetails = xmlDetails.replace(" ", " ");
processXmlContent(xmlDetails, md, result);
// try to parse an imdb id from the url
if (!StringUtils.isEmpty(result.getUrl()) && md.getId(MediaMetadata.IMDB) != null) {
md.setId(MediaMetadata.IMDB, parseIMDBID(result.getUrl()));
}
return md;
}
private String parseIMDBID(String url) {
if (url == null)
return null;
Pattern p = Pattern.compile("/(tt[0-9]+)/");
Matcher m = p.matcher(url);
if (m.find()) {
return m.group(1);
}
return "";
}
protected Document parseXmlString(String xml) throws Exception {
DocumentBuilder parser = factory.newDocumentBuilder();
// xml = Utils.replaceAcutesHTML(xml);
xml = StringEscapeUtils.unescapeHtml4(xml);
xml = StringEscapeUtils.unescapeXml(xml);
xml = StringEscapeUtils.unescapeXml(xml);
xml = StringEscapeUtils.unescapeXml(xml);
xml = xml.replaceAll("\\&", "\\&");
// xml = StringEscapeUtils.escapeXml(xml);
Document doc = null;
for (String charset : new String[] { "UTF-8", "ISO-8859-1", "US-ASCII" }) {
try {
doc = parser.parse(new ByteArrayInputStream(xml.getBytes(charset)));
break;
}
catch (Throwable t) {
LOGGER.error("Failed to parse xml using charset: " + charset + " - " + t.getMessage());
System.out.println("*********** BEGIN");
System.out.println(xml);
System.out.println("*********** END");
}
}
if (doc == null) {
LOGGER.error("Unabled to parse xml string");
LOGGER.error(xml);
throw new Exception("Unable to parse xml!");
}
return doc;
}
protected void addMetadata(MediaMetadata md, Element details) {
LOGGER.debug("Processing node....");
NodeList subDetails = details.getElementsByTagName("details");
String title = getInfoFromScraperFunctionOrBase("title", details, subDetails);
if (StringUtils.isNotBlank(title)) {
md.storeMetadata(MediaMetadata.TITLE, title);
}
String originalTitle = getInfoFromScraperFunctionOrBase("originaltitle", details, subDetails);
if (StringUtils.isNotBlank(originalTitle)) {
md.storeMetadata(MediaMetadata.ORIGINAL_TITLE, originalTitle);
}
String plot = getInfoFromScraperFunctionOrBase("plot", details, subDetails);
if (StringUtils.isNotBlank(plot)) {
md.storeMetadata(MediaMetadata.PLOT, plot);
}
String year = getInfoFromScraperFunctionOrBase("year", details, subDetails);
if (StringUtils.isNotBlank(year)) {
md.storeMetadata(MediaMetadata.YEAR, year);
}
String tagline = getInfoFromScraperFunctionOrBase("tagline", details, subDetails);
if (StringUtils.isNotBlank(tagline)) {
md.storeMetadata(MediaMetadata.TAGLINE, tagline);
}
String set = getInfoFromScraperFunctionOrBase("set", details, subDetails);
if (StringUtils.isNotBlank(set)) {
md.storeMetadata(MediaMetadata.COLLECTION_NAME, set);
}
String runtime = getInfoFromScraperFunctionOrBase("runtime", details, subDetails);
if (StringUtils.isNotBlank(runtime)) {
try {
md.storeMetadata(MediaMetadata.RUNTIME, Integer.parseInt(runtime));
}
catch (NumberFormatException ignored) {
}
}
// fanarts
NodeList nl = details.getElementsByTagName("fanart");
for (int i = 0; i < nl.getLength(); i++) {
Element fanart = (Element) nl.item(i);
String url = fanart.getAttribute("url");
NodeList thumbs = fanart.getElementsByTagName("thumb");
if (thumbs != null && thumbs.getLength() > 0) {
processMediaArt(md, MediaArtworkType.BACKGROUND, "Backgrounds", thumbs, url);
}
else {
if (!StringUtils.isEmpty(url)) {
processMediaArt(md, MediaArtworkType.BACKGROUND, "Background", url);
}
}
}
// thumbs
nl = details.getElementsByTagName("thumb");
for (int i = 0; i < nl.getLength(); i++) {
Element poster = (Element) nl.item(i);
if (poster.getParentNode().getNodeName() == "details")
processMediaArt(md, MediaArtworkType.POSTER, "Poster", poster, null);
}
// actors
nl = details.getElementsByTagName("actor");
for (int i = 0; i < nl.getLength(); i++) {
Element actor = (Element) nl.item(i);
MediaCastMember cm = new MediaCastMember();
cm.setType(MediaCastMember.CastType.ACTOR);
cm.setName(DOMUtils.getElementValue(actor, "name"));
cm.setCharacter(DOMUtils.getElementValue(actor, "role"));
md.addCastMember(cm);
}
// directors
nl = details.getElementsByTagName("director");
for (int i = 0; i < nl.getLength(); i++) {
Element el = (Element) nl.item(i);
MediaCastMember cm = new MediaCastMember();
cm.setType(MediaCastMember.CastType.DIRECTOR);
cm.setName(StringUtils.trim(el.getTextContent()));
LOGGER.debug("Adding Director: " + cm.getName());
cm.setPart("Director");
md.addCastMember(cm);
}
// credits
nl = details.getElementsByTagName("credits");
for (int i = 0; i < nl.getLength(); i++) {
Element el = (Element) nl.item(i);
MediaCastMember cm = new MediaCastMember();
cm.setType(MediaCastMember.CastType.WRITER);
cm.setName(StringUtils.trim(el.getTextContent()));
cm.setPart("Writer");
md.addCastMember(cm);
}
// genres
nl = details.getElementsByTagName("genre");
for (int i = 0; i < nl.getLength(); i++) {
Element el = (Element) nl.item(i);
MediaGenres genre = MediaGenres.getGenre(StringUtils.trim(el.getTextContent()));
if (genre != null) {
md.addGenre(genre);
}
}
}
/**
* first search the tag in any (from a scraperfunction), then in
* base
*
* @param tag
* the tag to search for
* @param subDetails
* a node list
* @return the found info or an empty String
*/
private String getInfoFromScraperFunctionOrBase(String tag, Element details, NodeList subDetails) {
String info = "";
for (int i = 0; i < subDetails.getLength(); i++) {
Element subDetail = (Element) subDetails.item(i);
NodeList nl = subDetail.getElementsByTagName(tag);
for (int j = 0; j < nl.getLength();) {
Element el = (Element) nl.item(j);
info = el.getTextContent();
break;
}
if (info != null)
break;
}
if (StringUtils.isBlank(info)) {
info = DOMUtils.getElementValue(details, tag);
}
return info;
}
private void processMediaArt(MediaMetadata md, MediaArtworkType type, String label, NodeList els, String baseUrl) {
for (int i = 0; i < els.getLength(); i++) {
Element e = (Element) els.item(i);
String image = e.getTextContent();
if (image != null)
image = image.trim();
if (baseUrl != null) {
baseUrl = baseUrl.trim();
image = baseUrl + image;
}
processMediaArt(md, type, label, image);
}
}
private void processMediaArt(MediaMetadata md, MediaArtworkType type, String label, Element e, String baseUrl) {
String image = e.getTextContent();
if (image != null)
image = image.trim();
if (baseUrl != null) {
baseUrl = baseUrl.trim();
image = baseUrl + image;
}
processMediaArt(md, type, label, image);
}
private void processMediaArt(MediaMetadata md, MediaArtworkType type, String label, String image) {
MediaArtwork ma = new MediaArtwork();
ma.setType(type);
md.addMediaArt(ma);
}
/**
* return a 2 element array. 0 = title; 1=date
*
* parses the title in the format Title YEAR or Title (YEAR)
*
* @param title
* the title
* @return the string[]
*/
private String[] parseTitle(String title) {
String v[] = { "", "" };
if (title == null)
return v;
Pattern p = Pattern.compile("(.*)\\s+\\(?([0-9]{4})\\)?", Pattern.CASE_INSENSITIVE);
Matcher m = p.matcher(title);
if (m.find()) {
v[0] = m.group(1);
v[1] = m.group(2);
}
else {
v[0] = title;
}
return v;
}
protected abstract void processXmlContent(String xmlDetails, MediaMetadata md, MediaSearchResult result) throws Exception;
}