org.tinymediamanager.thirdparty.VSMeta Maven / Gradle / Ivy
The newest version!
package org.tinymediamanager.thirdparty;
import java.io.IOException;
import java.io.StringReader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tinymediamanager.core.Constants;
import org.tinymediamanager.core.MediaFileType;
import org.tinymediamanager.core.entities.Person;
import org.tinymediamanager.core.entities.Rating;
import org.tinymediamanager.core.movie.MovieList;
import org.tinymediamanager.core.movie.entities.Movie;
import org.tinymediamanager.core.movie.entities.MovieSet;
import org.tinymediamanager.core.tvshow.entities.TvShowEpisode;
import org.tinymediamanager.scraper.entities.Certification;
import org.tinymediamanager.scraper.entities.MediaArtwork;
import org.tinymediamanager.scraper.entities.MediaArtwork.MediaArtworkType;
import org.tinymediamanager.scraper.entities.MediaCastMember;
import org.tinymediamanager.scraper.entities.MediaCastMember.CastType;
import org.tinymediamanager.scraper.entities.MediaGenres;
import com.google.gson.stream.JsonReader;
/**
* Class to parse Synology .VSMETA files as additional nfo source
*
* @author Myron Boyle
*
*/
public class VSMeta {
private static final Logger LOGGER = LoggerFactory.getLogger(VSMeta.class);
private HashMap ids = new HashMap<>(0);
private String title1 = ""; // show/movie
private String title2 = ""; // season?
private String title3 = ""; // episode/movie tagline
private String description = "";
private String json = "";
private MovieSet movieSet = null;
private float rating = 0.0f;
private String year = "";
/** yyyy-mm-dd **/
private String date = "";
private Certification certification = Certification.UNKNOWN;
private List artworks = new ArrayList<>(0);
private List genres = new ArrayList<>(0);
private List cast = new ArrayList<>(0);
// global array & position
private byte[] fileArray = new byte[0];
private int currentpos;
/**
* tries to parse a .VSMETA file
*
* @param file
*/
public void parseFile(Path file) {
try {
fileArray = Files.readAllBytes(file);
if (fileArray.length < 30) {
LOGGER.warn("SYNO: Invalid file", file);
return;
}
LOGGER.debug("SYNO: found valid .vsmeta - try to parse metadata...");
int maxLength = fileArray.length > 5000 ? 5000 : fileArray.length - 1;
for (currentpos = 0; currentpos < maxLength; currentpos++) {
int b = fileArray[currentpos] & 0xff; // unsigned int
// =================================================
// get the values from VSMETA file
// =================================================
String ret = "";
switch (b) {
case 0x12: // Show/Movie Title
case 0x1A: // Season Title
case 0x22: // Episode Title/MoviePlot
case 0x32: // first aired / release
case 0x42: // desc
case 0x4A: // json
case 0x5A: // certification
ret = parseLengthString();
break;
case 0x52: // length of cast+genre array
ret = parseLengthString();
parseCaseGenre(ret.getBytes());
break;
case 0x28: // year
currentpos++;
int length = getLength(fileArray[currentpos], fileArray[currentpos + 1]);
if (length > 127) {
currentpos++;
}
ret = String.valueOf(length);
break;
case 0x60:
// TODO: base64 decode images and write to file system
// here comes the graphics base64 decoding - step out here
currentpos = maxLength + 1; // haa-haa
break;
default:
LOGGER.trace("*** skip " + currentpos);
break;
}
// =================================================
// set the value into correct object
// =================================================
switch (b) {
case 0x12:// show/movie title
title1 = ret;
break;
case 0x1A:// season title
title2 = ret;
break;
case 0x22: // episode title / movie plot
title3 = ret;
break;
case 0x28: // year
year = ret;
break;
case 0x32: // first aired
date = ret;
break;
case 0x42: // desc
description = ret;
break;
case 0x4A: // json
json = ret;
break;
case 0x5A: // certification
certification = Certification.findCertification(ret);
break;
default:
break;
}
} // end loop
fileArray = null;
// =================================================
parseJSON();
// =================================================
}
catch (Exception e) {
LOGGER.warn("SYNO: Error parsing file", e);
}
}
/**
* parses a length prefixed String, and forwards the counter to the end
*
* @return
*/
private String parseLengthString() {
String ret = "";
int length = 0;
LOGGER.trace("SYNO: Pos: " + currentpos + " Byt: 0x" + Integer.toHexString(fileArray[currentpos]));
currentpos++;
length = getLength(fileArray[currentpos], fileArray[currentpos + 1]);
LOGGER.trace("SYNO: Len: " + length);
currentpos++;
if (length > 127) {
// if 2 bytes, then skip an additional byte
currentpos++;
}
ret = new String(Arrays.copyOfRange(fileArray, currentpos, currentpos + length));
if ("null".equals(ret)) {
ret = "";
}
currentpos += length - 1;
LOGGER.trace("SYNO: " + ret);
return ret;
}
private void parseCaseGenre(byte[] array) {
MediaCastMember mcm = null;
int length = 0;
String ret = "";
for (int i = 0; i < array.length - 1; i++) {
int b = array[i] & 0xff; // unsigned int
LOGGER.trace("SYNO: Pos: " + currentpos + " Byt: 0x" + Integer.toHexString(fileArray[currentpos]));
i++;
length = getLength(array[i], array[i + 1]);
LOGGER.trace("SYNO: Len: " + length);
i++;
if (length > 127) {
i++;
}
ret = new String(Arrays.copyOfRange(array, i, i + length));
i += length - 1;
LOGGER.trace("SYNO: " + ret);
switch (b) {
case 0x0A:
mcm = new MediaCastMember(CastType.ACTOR);
mcm.setName(ret);
cast.add(mcm);
break;
case 0x12:
mcm = new MediaCastMember(CastType.DIRECTOR);
mcm.setName(ret);
cast.add(mcm);
break;
case 0x22:
mcm = new MediaCastMember(CastType.WRITER);
mcm.setName(ret);
cast.add(mcm);
break;
case 0x1A: // Genre
genres.add(MediaGenres.getGenre(ret));
break;
default:
break;
}
}
}
private void parseJSON() {
if (StringUtils.isBlank(json)) {
return;
}
try {
// parse JSON
LOGGER.trace("SYNO: try to parse additional JSON info...");
JsonReader reader = new JsonReader(new StringReader(json));
reader.beginObject();
while (reader.hasNext()) {
String name = reader.nextName();
if (name.startsWith("com.synology")) {
reader.beginObject();
while (reader.hasNext()) {
name = reader.nextName();
if (name.equals("backdrop")) {
reader.beginArray();
while (reader.hasNext()) {
String value = reader.nextString();
LOGGER.trace("SYNO: found backdrop: " + value);
MediaArtwork ma = new MediaArtwork("com.synology", MediaArtworkType.BACKGROUND);
ma.setDefaultUrl(value);
artworks.add(ma);
}
reader.endArray();
}
else if (name.equals("poster")) {
reader.beginArray();
while (reader.hasNext()) {
String value = reader.nextString();
LOGGER.trace("SYNO: found poster: " + value);
MediaArtwork ma = new MediaArtwork("com.synology", MediaArtworkType.POSTER);
ma.setDefaultUrl(value);
artworks.add(ma);
}
reader.endArray();
}
else if (name.equals("rating")) {
// read array
reader.beginObject();
while (reader.hasNext()) {
String key = reader.nextName();
String value = reader.nextString();
LOGGER.trace("SYNO: found rating: " + key + " - " + value);
try {
float f = Float.parseFloat(value);
rating = f;
}
catch (NumberFormatException ignored) {
}
}
reader.endObject();
}
else if (name.equals("reference")) {
// read array
reader.beginObject();
while (reader.hasNext()) {
String key = reader.nextName();
String value = reader.nextString();
LOGGER.trace("SYNO: found ID: " + key + " = " + value);
switch (key) {
case "imdb":
ids.put(Constants.IMDB, value);
break;
case "thetvdb":
ids.put(Constants.TVDB, value);
break;
case "themoviedb":
try {
int t = Integer.parseInt(value);
ids.put(Constants.TMDB, t);
}
catch (NumberFormatException ignored) {
}
break;
default:
break;
}
}
reader.endObject();
}
else if (name.equals("collection_id")) {
// read array
reader.beginObject();
while (reader.hasNext()) {
String key = reader.nextName();
String value = reader.nextString();
LOGGER.trace("SYNO: found SetID: " + key + " = " + value);
switch (key) {
case "themoviedb":
try {
int t = Integer.parseInt(value);
movieSet = MovieList.getInstance().getMovieSet(title1 + "_col", t);
}
catch (NumberFormatException ignored) {
}
break;
default:
break;
}
}
reader.endObject();
}
else {
reader.skipValue(); // avoid some unhandled events
}
}
}
else {
reader.skipValue(); // avoid some unhandled events
}
reader.endObject();
}
reader.endObject();
reader.close();
}
catch (IOException e) {
LOGGER.warn("Could not parse Synology VSMETA file: ", e);
}
}
public Movie getMovie() {
Movie m = new Movie();
m.setIds(ids);
m.setTitle(title1);
m.setTagline(title3);
m.setPlot(description);
m.setReleaseDate(date);
try {
m.setYear(Integer.parseInt(year));
}
catch (Exception e) {
m.setYear(0);
}
if (rating > 0) {
Rating r = new Rating(Rating.NFO, rating);
m.setRating(r);
}
m.setCertification(certification);
if (movieSet != null) {
m.setMovieSet(movieSet);
}
for (MediaArtwork ma : artworks) {
m.setArtworkUrl(ma.getDefaultUrl(), MediaFileType.getMediaFileType(ma.getType()));
}
for (MediaGenres g : genres) {
m.addGenre(g);
}
for (MediaCastMember mcm : cast) {
switch (mcm.getType()) {
case ACTOR:
m.addActor(new Person(mcm));
break;
case DIRECTOR:
m.addDirector(new Person(mcm));
break;
case WRITER:
m.addDirector(new Person(mcm));
break;
default:
break;
}
}
return m;
}
public TvShowEpisode getTvShowEpisode() {
TvShowEpisode ep = new TvShowEpisode();
ep.setIds(ids);
ep.setTitle(title3);
ep.setPlot(description);
ep.setFirstAired(date);
try {
ep.setYear(Integer.parseInt(year));
}
catch (Exception e) {
ep.setYear(0);
}
if (rating > 0) {
Rating r = new Rating(Rating.NFO, rating);
ep.setRating(r);
}
// tv.setCertification(certification);
for (MediaArtwork ma : artworks) {
ep.setArtworkUrl(ma.getDefaultUrl(), MediaFileType.getMediaFileType(ma.getType()));
}
for (MediaCastMember mcm : cast) {
switch (mcm.getType()) {
case ACTOR:
ep.addActor(new Person(mcm));
break;
case DIRECTOR:
ep.addDirector(new Person(mcm));
break;
case WRITER:
ep.addWriter(new Person(mcm));
break;
default:
break;
}
}
return ep;
}
/**
* gets the length of 1|2 byte sequence in LittleEndianFormat
*
* @param by1
* byte1
* @param by2
* byte2 or 0
* @return
*/
private int getLength(byte by1, byte by2) {
int length = 0;
int b1 = by1 & 0xFF;
int b2 = by2 & 0xFF;
if (b2 > 0 && b2 < 20) { // just to check a valid length a bit
// hopefully 2 bytes length
length = b1 + (b2 - 1) * 128;
}
else {
// 1 byte length
length = b1;
}
return length;
}
@Override
public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy