Add readOnly config option, Imptove metadata inheritance
All checks were successful
Build and push container / Build-Docker-Container (push) Successful in 5m29s
All checks were successful
Build and push container / Build-Docker-Container (push) Successful in 5m29s
This commit is contained in:
parent
a6dd5bd6f7
commit
92b83d240d
@ -9,6 +9,9 @@ public class Config implements JSONConvertible {
|
|||||||
@JSONValue
|
@JSONValue
|
||||||
private String libraryPath;
|
private String libraryPath;
|
||||||
|
|
||||||
|
@JSONValue
|
||||||
|
private boolean readOnly;
|
||||||
|
|
||||||
@JSONValue
|
@JSONValue
|
||||||
private String cachePath;
|
private String cachePath;
|
||||||
|
|
||||||
@ -19,6 +22,10 @@ public class Config implements JSONConvertible {
|
|||||||
return libraryPath;
|
return libraryPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isReadOnly() {
|
||||||
|
return readOnly;
|
||||||
|
}
|
||||||
|
|
||||||
public String getCachePath() {
|
public String getCachePath() {
|
||||||
return cachePath;
|
return cachePath;
|
||||||
}
|
}
|
||||||
@ -26,6 +33,7 @@ public class Config implements JSONConvertible {
|
|||||||
public static Config createDefault() {
|
public static Config createDefault() {
|
||||||
Config config = new Config();
|
Config config = new Config();
|
||||||
config.libraryPath = "library";
|
config.libraryPath = "library";
|
||||||
|
config.readOnly = false;
|
||||||
config.cachePath = "cache";
|
config.cachePath = "cache";
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
@ -81,7 +81,7 @@ public class VideoBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void loadLibrary() {
|
private static void loadLibrary() {
|
||||||
library = Library.load(Path.of(config.getLibraryPath()));
|
library = Library.load(Path.of(config.getLibraryPath()), config.isReadOnly());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -25,13 +25,15 @@ public class Library {
|
|||||||
VIDEO_METADATA_SUFFIX = ".meta.json";
|
VIDEO_METADATA_SUFFIX = ".meta.json";
|
||||||
|
|
||||||
private Path path;
|
private Path path;
|
||||||
|
private boolean readOnly;
|
||||||
private List<Video> videos;
|
private List<Video> videos;
|
||||||
|
|
||||||
public Library(Path path, List<Video> videos) {
|
public Library(Path path, boolean readOnly, List<Video> videos) {
|
||||||
Objects.requireNonNull(path, "path");
|
Objects.requireNonNull(path, "path");
|
||||||
Objects.requireNonNull(videos, "videos");
|
Objects.requireNonNull(videos, "videos");
|
||||||
|
|
||||||
this.path = path;
|
this.path = path;
|
||||||
|
this.readOnly = readOnly;
|
||||||
this.videos = videos;
|
this.videos = videos;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,6 +41,10 @@ public class Library {
|
|||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isReadOnly() {
|
||||||
|
return readOnly;
|
||||||
|
}
|
||||||
|
|
||||||
public List<Video> getVideos() {
|
public List<Video> getVideos() {
|
||||||
return videos;
|
return videos;
|
||||||
}
|
}
|
||||||
@ -78,8 +84,10 @@ public class Library {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean updateMetadata(Video video, VideoMetadata metadata) {
|
public boolean updateMetadata(Video video, VideoMetadata metadata) {
|
||||||
|
if(readOnly) return false;
|
||||||
|
|
||||||
VideoMetadata oldMetadata = video.getMetadata();
|
VideoMetadata oldMetadata = video.getMetadata();
|
||||||
VideoMetadata newMetadata = VideoMetadata.inherit(oldMetadata, metadata);
|
VideoMetadata newMetadata = VideoMetadata.inherit(oldMetadata.getParent(), metadata);
|
||||||
|
|
||||||
Path videoPath = video.getPath().toAbsolutePath();
|
Path videoPath = video.getPath().toAbsolutePath();
|
||||||
try {
|
try {
|
||||||
@ -94,28 +102,32 @@ public class Library {
|
|||||||
public boolean updateMetadata(String videoId, VideoMetadata metadata) {
|
public boolean updateMetadata(String videoId, VideoMetadata metadata) {
|
||||||
Video video = findVideoById(videoId);
|
Video video = findVideoById(videoId);
|
||||||
if(video == null) return false;
|
if(video == null) return false;
|
||||||
updateMetadata(video, metadata);
|
return updateMetadata(video, metadata);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Library load(Path path) {
|
public static Library load(Path path, boolean readOnly) {
|
||||||
List<Video> videos = new ArrayList<>();
|
List<Video> videos = new ArrayList<>();
|
||||||
load(path, path, videos, VideoMetadata.DEFAULT_METADATA);
|
load(path, path, videos, VideoMetadata.DEFAULT_METADATA);
|
||||||
return new Library(path, videos);
|
return new Library(path, readOnly, videos);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void load(Path rootPath, Path path, List<Video> videos, VideoMetadata parentDefaultMetadata) {
|
private static void load(Path rootPath, Path path, List<Video> videos, VideoMetadata parentDefaultMetadata) {
|
||||||
if(!Files.isDirectory(path)) return;
|
if(!Files.isDirectory(path)) return;
|
||||||
|
|
||||||
LibraryMetadata libraryMeta = null;
|
LibraryMetadata libraryMetadata = null;
|
||||||
VideoMetadata defaultMetadata = parentDefaultMetadata;
|
|
||||||
|
VideoMetadata inferredMetadata = new VideoMetadata(new JSONObject(Map.of(
|
||||||
|
VideoMetadata.FIELD_SERIES, path.getFileName().toString()
|
||||||
|
)));
|
||||||
|
|
||||||
|
VideoMetadata defaultMetadata = VideoMetadata.inherit(parentDefaultMetadata, inferredMetadata);
|
||||||
|
|
||||||
Path metaPath = path.resolve("meta.json");
|
Path metaPath = path.resolve("meta.json");
|
||||||
if(Files.isRegularFile(metaPath)) {
|
if(Files.isRegularFile(metaPath)) {
|
||||||
try {
|
try {
|
||||||
JSONObject obj = new JSONObject(Files.readString(metaPath));
|
JSONObject obj = new JSONObject(Files.readString(metaPath));
|
||||||
libraryMeta = JSONConverter.decodeObject(obj, LibraryMetadata.class);
|
libraryMetadata = JSONConverter.decodeObject(obj, LibraryMetadata.class);
|
||||||
defaultMetadata = VideoMetadata.inherit(defaultMetadata, libraryMeta.getDefault());
|
defaultMetadata = VideoMetadata.inherit(defaultMetadata, libraryMetadata.getDefault());
|
||||||
}catch(IOException | JSONParseException | ClassCastException e) {
|
}catch(IOException | JSONParseException | ClassCastException e) {
|
||||||
VideoBase.LOGGER.warn("Failed to parse metadata at " + path, e);
|
VideoBase.LOGGER.warn("Failed to parse metadata at " + path, e);
|
||||||
}
|
}
|
||||||
@ -141,27 +153,26 @@ public class Library {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
VideoMetadata inferredMetadata = new VideoMetadata(new JSONObject(Map.of(
|
VideoMetadata inferredVideoMetadata = new VideoMetadata(new JSONObject(Map.of(
|
||||||
VideoMetadata.FIELD_TITLE, fileNameNoExt,
|
VideoMetadata.FIELD_TITLE, fileNameNoExt
|
||||||
VideoMetadata.FIELD_SERIES, path.getFileName().toString()
|
|
||||||
)));
|
)));
|
||||||
|
|
||||||
VideoMetadata videoMeta = VideoMetadata.inherit(defaultMetadata, inferredMetadata);
|
VideoMetadata videoMetadata = VideoMetadata.inherit(defaultMetadata, inferredVideoMetadata);
|
||||||
|
|
||||||
Path videoMetaPath = path.resolve(subPath.getFileName().toString() + VIDEO_METADATA_SUFFIX);
|
Path videoMetaPath = path.resolve(subPath.getFileName().toString() + VIDEO_METADATA_SUFFIX);
|
||||||
if(Files.isRegularFile(videoMetaPath)) {
|
if(Files.isRegularFile(videoMetaPath)) {
|
||||||
try {
|
try {
|
||||||
JSONObject obj = new JSONObject(Files.readString(videoMetaPath));
|
JSONObject obj = new JSONObject(Files.readString(videoMetaPath));
|
||||||
videoMeta = VideoMetadata.inherit(videoMeta, JSONConverter.decodeObject(obj, VideoMetadata.class));
|
videoMetadata = VideoMetadata.inherit(videoMetadata, JSONConverter.decodeObject(obj, VideoMetadata.class));
|
||||||
}catch(IOException | JSONParseException | ClassCastException e) {
|
}catch(IOException | JSONParseException | ClassCastException e) {
|
||||||
VideoBase.LOGGER.warn("Failed to parse metadata at " + path, e);
|
VideoBase.LOGGER.warn("Failed to parse metadata at " + path, e);
|
||||||
}
|
}
|
||||||
}else if(libraryMeta != null && libraryMeta.getOverrides().containsKey(fileName)) {
|
}else if(libraryMetadata != null && libraryMetadata.getOverrides().containsKey(fileName)) {
|
||||||
videoMeta = VideoMetadata.inherit(videoMeta, libraryMeta.getOverrides().get(fileName));
|
videoMetadata = VideoMetadata.inherit(videoMetadata, libraryMetadata.getOverrides().get(fileName));
|
||||||
}
|
}
|
||||||
|
|
||||||
String id = Hash.hash(rootPath.relativize(subPath).toString());
|
String id = Hash.hash(rootPath.relativize(subPath).toString());
|
||||||
videos.add(new Video(subPath, id, videoMeta == null ? VideoMetadata.DEFAULT_METADATA : videoMeta));
|
videos.add(new Video(subPath, id, videoMetadata == null ? VideoMetadata.DEFAULT_METADATA : videoMetadata));
|
||||||
}
|
}
|
||||||
}catch(IOException e) {
|
}catch(IOException e) {
|
||||||
VideoBase.LOGGER.warn("Failed to load folder at " + path, e);
|
VideoBase.LOGGER.warn("Failed to load folder at " + path, e);
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package me.mrletsplay.videobase.library;
|
package me.mrletsplay.videobase.library;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
import me.mrletsplay.mrcore.json.JSONObject;
|
import me.mrletsplay.mrcore.json.JSONObject;
|
||||||
import me.mrletsplay.mrcore.json.JSONType;
|
import me.mrletsplay.mrcore.json.JSONType;
|
||||||
@ -38,36 +37,61 @@ public class VideoMetadata implements JSONConvertible {
|
|||||||
.optional(FIELD_AUTHOR, JSONType.STRING)
|
.optional(FIELD_AUTHOR, JSONType.STRING)
|
||||||
.optional(FIELD_INDEX, JSONType.INTEGER);
|
.optional(FIELD_INDEX, JSONType.INTEGER);
|
||||||
|
|
||||||
|
private VideoMetadata parent;
|
||||||
private JSONObject metadata;
|
private JSONObject metadata;
|
||||||
|
private JSONObject cachedEffectiveMetadata;
|
||||||
|
|
||||||
@JSONConstructor
|
@JSONConstructor
|
||||||
private VideoMetadata() {}
|
private VideoMetadata() {}
|
||||||
|
|
||||||
public VideoMetadata(JSONObject metadata) {
|
public VideoMetadata(JSONObject metadata) {
|
||||||
Objects.requireNonNull(metadata, "metadata");
|
this.metadata = metadata == null ? new JSONObject() : metadata;
|
||||||
this.metadata = metadata;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getSeries() {
|
public String getSeries() {
|
||||||
return metadata.optString(FIELD_SERIES).orElse(null);
|
return getEffective().optString(FIELD_SERIES).orElse(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getTitle() {
|
public String getTitle() {
|
||||||
return metadata.optString(FIELD_TITLE).orElse(null);
|
return getEffective().optString(FIELD_TITLE).orElse(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getAuthor() {
|
public String getAuthor() {
|
||||||
return metadata.optString(FIELD_AUTHOR).orElse(null);
|
return getEffective().optString(FIELD_AUTHOR).orElse(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Integer getIndex() {
|
public Integer getIndex() {
|
||||||
return metadata.optInt(FIELD_INDEX).orElse(null);
|
return getEffective().optInt(FIELD_INDEX).orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public VideoMetadata getParent() {
|
||||||
|
return parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
public JSONObject getRaw() {
|
public JSONObject getRaw() {
|
||||||
return new JSONObject(metadata);
|
return new JSONObject(metadata);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public JSONObject getEffective() {
|
||||||
|
if(cachedEffectiveMetadata != null) return cachedEffectiveMetadata;
|
||||||
|
|
||||||
|
JSONObject meta = new JSONObject(parent == null ? null : parent.getEffective());
|
||||||
|
for(String key : metadata.keys()) {
|
||||||
|
Object value = metadata.get(key);
|
||||||
|
if(value == null) { // Setting to null resets to default value if available, otherwise removes attribute
|
||||||
|
value = DEFAULT_METADATA.metadata.opt(key).orElse(null);
|
||||||
|
if(value == null) {
|
||||||
|
meta.remove(key);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
meta.set(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return cachedEffectiveMetadata = meta;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void preDeserialize(JSONObject object) {
|
public void preDeserialize(JSONObject object) {
|
||||||
this.metadata = object;
|
this.metadata = object;
|
||||||
@ -86,24 +110,9 @@ public class VideoMetadata implements JSONConvertible {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static VideoMetadata inherit(VideoMetadata parent, VideoMetadata child) {
|
public static VideoMetadata inherit(VideoMetadata parent, VideoMetadata child) {
|
||||||
if(parent == null) return child;
|
VideoMetadata meta = new VideoMetadata(child == null ? null : child.getRaw());
|
||||||
if(child == null) return parent;
|
meta.parent = parent;
|
||||||
|
return meta;
|
||||||
JSONObject meta = new JSONObject(parent == null ? null : parent.metadata);
|
|
||||||
for(String key : child.metadata.keys()) {
|
|
||||||
Object value = child.metadata.get(key);
|
|
||||||
if(value == null) { // Setting to null resets to default value if available, otherwise removes attribute
|
|
||||||
value = DEFAULT_METADATA.metadata.opt(key).orElse(null);
|
|
||||||
if(value == null) {
|
|
||||||
meta.remove(key);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
meta.set(key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new VideoMetadata(meta);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -98,19 +98,31 @@ public class LibraryAPI implements EndpointCollection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Endpoint(method = HttpRequestMethod.GET, path = "/video/{video}/metadata", pathPattern = true)
|
@Endpoint(method = HttpRequestMethod.GET, path = "/video/{video}/metadata", pathPattern = true)
|
||||||
public void getMetadata(HttpRequestContext ctx, @RequestParameter("video") String videoId) {
|
public void getVideoMetadata(HttpRequestContext ctx, @RequestParameter("video") String videoId) {
|
||||||
Video video = VideoBase.getLibrary().findVideoById(videoId);
|
Video video = VideoBase.getLibrary().findVideoById(videoId);
|
||||||
if(video == null) {
|
if(video == null) {
|
||||||
ctx.respond(HttpStatusCodes.NOT_FOUND_404, new TextResponse("Not found"));
|
ctx.respond(HttpStatusCodes.NOT_FOUND_404, new TextResponse("Not found"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.respond(HttpStatusCodes.OK_200, new JsonResponse(video.getMetadata().getRaw()));
|
VideoMetadata videoMetadata = video.getMetadata();
|
||||||
|
|
||||||
|
JSONObject meta = new JSONObject();
|
||||||
|
meta.put("raw", videoMetadata.getRaw());
|
||||||
|
meta.put("inherited", videoMetadata.getParent() == null ? new JSONObject() : videoMetadata.getParent().getEffective());
|
||||||
|
meta.put("effective", videoMetadata.getEffective());
|
||||||
|
|
||||||
|
ctx.respond(HttpStatusCodes.OK_200, new JsonResponse(meta));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Endpoint(method = HttpRequestMethod.PUT, path = "/video/{video}/metadata", pathPattern = true)
|
@Endpoint(method = HttpRequestMethod.PUT, path = "/video/{video}/metadata", pathPattern = true)
|
||||||
public void updateVideoMetadata(HttpRequestContext ctx, @RequestParameter("video") String videoId) {
|
public void updateVideoMetadata(HttpRequestContext ctx, @RequestParameter("video") String videoId) {
|
||||||
Library library = VideoBase.getLibrary();
|
Library library = VideoBase.getLibrary();
|
||||||
|
if(library.isReadOnly()) {
|
||||||
|
ctx.respond(HttpStatusCodes.ACCESS_DENIED_403, new TextResponse("Library is read-only"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Video video = VideoBase.getLibrary().findVideoById(videoId);
|
Video video = VideoBase.getLibrary().findVideoById(videoId);
|
||||||
if(video == null) {
|
if(video == null) {
|
||||||
ctx.respond(HttpStatusCodes.NOT_FOUND_404, new TextResponse("Not found"));
|
ctx.respond(HttpStatusCodes.NOT_FOUND_404, new TextResponse("Not found"));
|
||||||
@ -129,7 +141,11 @@ public class LibraryAPI implements EndpointCollection {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
library.updateMetadata(video, new VideoMetadata(metadata));
|
if(!library.updateMetadata(video, new VideoMetadata(metadata))) {
|
||||||
|
ctx.respond(HttpStatusCodes.INTERNAL_SERVER_ERROR_500, new TextResponse("Failed to update metadata"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
ctx.respond(HttpStatusCodes.OK_200, new JsonResponse(video.getMetadata().getRaw()));
|
ctx.respond(HttpStatusCodes.OK_200, new JsonResponse(video.getMetadata().getRaw()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -185,7 +201,7 @@ public class LibraryAPI implements EndpointCollection {
|
|||||||
private JSONObject videoToJSON(Video video) {
|
private JSONObject videoToJSON(Video video) {
|
||||||
JSONObject v = new JSONObject();
|
JSONObject v = new JSONObject();
|
||||||
v.put("id", video.getId());
|
v.put("id", video.getId());
|
||||||
v.put("metadata", video.getMetadata().getRaw());
|
v.put("metadata", video.getMetadata().getEffective());
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,6 +68,8 @@ public class ThumbnailCreator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
BufferedImage thumbnail = ImageIO.read(new ByteArrayInputStream(bOut.toByteArray()));
|
BufferedImage thumbnail = ImageIO.read(new ByteArrayInputStream(bOut.toByteArray()));
|
||||||
|
if(thumbnail == null) return null;
|
||||||
|
|
||||||
try(OutputStream out = Files.newOutputStream(thumbnailFile)) {
|
try(OutputStream out = Files.newOutputStream(thumbnailFile)) {
|
||||||
ImageIO.write(thumbnail, "PNG", out);
|
ImageIO.write(thumbnail, "PNG", out);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user