Add readOnly config option, Imptove metadata inheritance
All checks were successful
Build and push container / Build-Docker-Container (push) Successful in 5m29s

This commit is contained in:
MrLetsplay 2025-02-17 23:03:03 +01:00
parent a6dd5bd6f7
commit 92b83d240d
Signed by: mr
SSH Key Fingerprint: SHA256:92jBH80vpXyaZHjaIl47pjRq+Yt7XGTArqQg1V7hSqg
6 changed files with 94 additions and 48 deletions

View File

@ -9,6 +9,9 @@ public class Config implements JSONConvertible {
@JSONValue
private String libraryPath;
@JSONValue
private boolean readOnly;
@JSONValue
private String cachePath;
@ -19,6 +22,10 @@ public class Config implements JSONConvertible {
return libraryPath;
}
public boolean isReadOnly() {
return readOnly;
}
public String getCachePath() {
return cachePath;
}
@ -26,6 +33,7 @@ public class Config implements JSONConvertible {
public static Config createDefault() {
Config config = new Config();
config.libraryPath = "library";
config.readOnly = false;
config.cachePath = "cache";
return config;
}

View File

@ -81,7 +81,7 @@ public class VideoBase {
}
private static void loadLibrary() {
library = Library.load(Path.of(config.getLibraryPath()));
library = Library.load(Path.of(config.getLibraryPath()), config.isReadOnly());
}
}

View File

@ -25,13 +25,15 @@ public class Library {
VIDEO_METADATA_SUFFIX = ".meta.json";
private Path path;
private boolean readOnly;
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(videos, "videos");
this.path = path;
this.readOnly = readOnly;
this.videos = videos;
}
@ -39,6 +41,10 @@ public class Library {
return path;
}
public boolean isReadOnly() {
return readOnly;
}
public List<Video> getVideos() {
return videos;
}
@ -78,8 +84,10 @@ public class Library {
}
public boolean updateMetadata(Video video, VideoMetadata metadata) {
if(readOnly) return false;
VideoMetadata oldMetadata = video.getMetadata();
VideoMetadata newMetadata = VideoMetadata.inherit(oldMetadata, metadata);
VideoMetadata newMetadata = VideoMetadata.inherit(oldMetadata.getParent(), metadata);
Path videoPath = video.getPath().toAbsolutePath();
try {
@ -94,28 +102,32 @@ public class Library {
public boolean updateMetadata(String videoId, VideoMetadata metadata) {
Video video = findVideoById(videoId);
if(video == null) return false;
updateMetadata(video, metadata);
return true;
return updateMetadata(video, metadata);
}
public static Library load(Path path) {
public static Library load(Path path, boolean readOnly) {
List<Video> videos = new ArrayList<>();
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) {
if(!Files.isDirectory(path)) return;
LibraryMetadata libraryMeta = null;
VideoMetadata defaultMetadata = parentDefaultMetadata;
LibraryMetadata libraryMetadata = null;
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");
if(Files.isRegularFile(metaPath)) {
try {
JSONObject obj = new JSONObject(Files.readString(metaPath));
libraryMeta = JSONConverter.decodeObject(obj, LibraryMetadata.class);
defaultMetadata = VideoMetadata.inherit(defaultMetadata, libraryMeta.getDefault());
libraryMetadata = JSONConverter.decodeObject(obj, LibraryMetadata.class);
defaultMetadata = VideoMetadata.inherit(defaultMetadata, libraryMetadata.getDefault());
}catch(IOException | JSONParseException | ClassCastException e) {
VideoBase.LOGGER.warn("Failed to parse metadata at " + path, e);
}
@ -141,27 +153,26 @@ public class Library {
continue;
}
VideoMetadata inferredMetadata = new VideoMetadata(new JSONObject(Map.of(
VideoMetadata.FIELD_TITLE, fileNameNoExt,
VideoMetadata.FIELD_SERIES, path.getFileName().toString()
VideoMetadata inferredVideoMetadata = new VideoMetadata(new JSONObject(Map.of(
VideoMetadata.FIELD_TITLE, fileNameNoExt
)));
VideoMetadata videoMeta = VideoMetadata.inherit(defaultMetadata, inferredMetadata);
VideoMetadata videoMetadata = VideoMetadata.inherit(defaultMetadata, inferredVideoMetadata);
Path videoMetaPath = path.resolve(subPath.getFileName().toString() + VIDEO_METADATA_SUFFIX);
if(Files.isRegularFile(videoMetaPath)) {
try {
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) {
VideoBase.LOGGER.warn("Failed to parse metadata at " + path, e);
}
}else if(libraryMeta != null && libraryMeta.getOverrides().containsKey(fileName)) {
videoMeta = VideoMetadata.inherit(videoMeta, libraryMeta.getOverrides().get(fileName));
}else if(libraryMetadata != null && libraryMetadata.getOverrides().containsKey(fileName)) {
videoMetadata = VideoMetadata.inherit(videoMetadata, libraryMetadata.getOverrides().get(fileName));
}
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) {
VideoBase.LOGGER.warn("Failed to load folder at " + path, e);

View File

@ -1,7 +1,6 @@
package me.mrletsplay.videobase.library;
import java.util.Map;
import java.util.Objects;
import me.mrletsplay.mrcore.json.JSONObject;
import me.mrletsplay.mrcore.json.JSONType;
@ -38,36 +37,61 @@ public class VideoMetadata implements JSONConvertible {
.optional(FIELD_AUTHOR, JSONType.STRING)
.optional(FIELD_INDEX, JSONType.INTEGER);
private VideoMetadata parent;
private JSONObject metadata;
private JSONObject cachedEffectiveMetadata;
@JSONConstructor
private VideoMetadata() {}
public VideoMetadata(JSONObject metadata) {
Objects.requireNonNull(metadata, "metadata");
this.metadata = metadata;
this.metadata = metadata == null ? new JSONObject() : metadata;
}
public String getSeries() {
return metadata.optString(FIELD_SERIES).orElse(null);
return getEffective().optString(FIELD_SERIES).orElse(null);
}
public String getTitle() {
return metadata.optString(FIELD_TITLE).orElse(null);
return getEffective().optString(FIELD_TITLE).orElse(null);
}
public String getAuthor() {
return metadata.optString(FIELD_AUTHOR).orElse(null);
return getEffective().optString(FIELD_AUTHOR).orElse(null);
}
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() {
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
public void preDeserialize(JSONObject object) {
this.metadata = object;
@ -86,24 +110,9 @@ public class VideoMetadata implements JSONConvertible {
}
public static VideoMetadata inherit(VideoMetadata parent, VideoMetadata child) {
if(parent == null) return child;
if(child == null) return parent;
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);
VideoMetadata meta = new VideoMetadata(child == null ? null : child.getRaw());
meta.parent = parent;
return meta;
}
}

View File

@ -98,19 +98,31 @@ public class LibraryAPI implements EndpointCollection {
}
@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);
if(video == null) {
ctx.respond(HttpStatusCodes.NOT_FOUND_404, new TextResponse("Not found"));
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)
public void updateVideoMetadata(HttpRequestContext ctx, @RequestParameter("video") String videoId) {
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);
if(video == null) {
ctx.respond(HttpStatusCodes.NOT_FOUND_404, new TextResponse("Not found"));
@ -129,7 +141,11 @@ public class LibraryAPI implements EndpointCollection {
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()));
}
@ -185,7 +201,7 @@ public class LibraryAPI implements EndpointCollection {
private JSONObject videoToJSON(Video video) {
JSONObject v = new JSONObject();
v.put("id", video.getId());
v.put("metadata", video.getMetadata().getRaw());
v.put("metadata", video.getMetadata().getEffective());
return v;
}

View File

@ -68,6 +68,8 @@ public class ThumbnailCreator {
}
BufferedImage thumbnail = ImageIO.read(new ByteArrayInputStream(bOut.toByteArray()));
if(thumbnail == null) return null;
try(OutputStream out = Files.newOutputStream(thumbnailFile)) {
ImageIO.write(thumbnail, "PNG", out);
}