Make all config options except libraryPath optional, Add in-memory thumbnail cache, Add excludePaths & .nomedia to exclude paths
All checks were successful
Build and push container / Build-Docker-Container (push) Successful in 4m44s
All checks were successful
Build and push container / Build-Docker-Container (push) Successful in 4m44s
This commit is contained in:
parent
a14ecb8050
commit
2187519abc
@ -1,11 +1,18 @@
|
||||
package me.mrletsplay.videobase;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import me.mrletsplay.mrcore.json.JSONType;
|
||||
import me.mrletsplay.mrcore.json.converter.JSONConstructor;
|
||||
import me.mrletsplay.mrcore.json.converter.JSONConvertible;
|
||||
import me.mrletsplay.mrcore.json.converter.JSONListType;
|
||||
import me.mrletsplay.mrcore.json.converter.JSONValue;
|
||||
|
||||
public class Config implements JSONConvertible {
|
||||
|
||||
public static final List<String> DEFAULT_FILE_TYPES = List.of("mp4", "mpeg", "mkv", "flv", "avi", "webm");
|
||||
|
||||
@JSONValue
|
||||
private String libraryPath;
|
||||
|
||||
@ -15,6 +22,10 @@ public class Config implements JSONConvertible {
|
||||
@JSONValue
|
||||
private String cachePath;
|
||||
|
||||
@JSONValue
|
||||
@JSONListType(JSONType.STRING)
|
||||
private List<String> includeFileTypes;
|
||||
|
||||
@JSONConstructor
|
||||
private Config() {}
|
||||
|
||||
@ -30,11 +41,20 @@ public class Config implements JSONConvertible {
|
||||
return cachePath;
|
||||
}
|
||||
|
||||
public List<String> getIncludeFileTypes() {
|
||||
return includeFileTypes == null ? DEFAULT_FILE_TYPES : includeFileTypes;
|
||||
}
|
||||
|
||||
public void validate() {
|
||||
Objects.requireNonNull(libraryPath, "libraryPath");
|
||||
}
|
||||
|
||||
public static Config createDefault() {
|
||||
Config config = new Config();
|
||||
config.libraryPath = "library";
|
||||
config.readOnly = false;
|
||||
config.cachePath = "cache";
|
||||
config.includeFileTypes = DEFAULT_FILE_TYPES;
|
||||
return config;
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,6 @@ import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@ -20,6 +19,7 @@ import me.mrletsplay.simplehttpserver.http.cors.CorsConfiguration;
|
||||
import me.mrletsplay.simplehttpserver.http.server.HttpServer;
|
||||
import me.mrletsplay.videobase.library.Library;
|
||||
import me.mrletsplay.videobase.rest.LibraryAPI;
|
||||
import me.mrletsplay.videobase.util.ThumbnailCreator;
|
||||
|
||||
public class VideoBase {
|
||||
|
||||
@ -48,6 +48,7 @@ public class VideoBase {
|
||||
}
|
||||
|
||||
config = JSONConverter.decodeObject(new JSONObject(Files.readString(configPath, StandardCharsets.UTF_8)), Config.class);
|
||||
config.validate();
|
||||
} catch (IOException e) {
|
||||
LOGGER.error("Failed to load config", e);
|
||||
System.exit(1);
|
||||
@ -82,8 +83,8 @@ public class VideoBase {
|
||||
}
|
||||
|
||||
private static void loadLibrary() {
|
||||
Objects.requireNonNull(config.getLibraryPath(), "libraryPath");
|
||||
library = Library.load(Path.of(config.getLibraryPath()), config.isReadOnly());
|
||||
ThumbnailCreator.clearCache();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -113,6 +113,10 @@ public class Library {
|
||||
|
||||
private static void load(Path rootPath, Path path, List<Video> videos, VideoMetadata parentDefaultMetadata) {
|
||||
if(!Files.isDirectory(path)) return;
|
||||
if(Files.isRegularFile(path.resolve(".nomedia"))) {
|
||||
VideoBase.LOGGER.debug("Ignoring folder at " + path + ": .nomedia file exists");
|
||||
return;
|
||||
}
|
||||
|
||||
LibraryMetadata libraryMetadata = null;
|
||||
|
||||
@ -138,21 +142,31 @@ public class Library {
|
||||
.filter(p -> path.equals(p.getParent()))
|
||||
.sorted(Comparator.comparing(p -> p.getFileName().toString()))
|
||||
.collect(Collectors.toList())) {
|
||||
String fileName = subPath.getFileName().toString();
|
||||
String fileNameNoExt = fileName;
|
||||
if(fileNameNoExt.contains(".")) {
|
||||
fileNameNoExt = fileNameNoExt.substring(0, fileNameNoExt.lastIndexOf('.'));
|
||||
if(Files.isDirectory(subPath)) {
|
||||
if(libraryMetadata != null && libraryMetadata.getExcludePaths().contains(subPath.getFileName().toString())) {
|
||||
VideoBase.LOGGER.debug("Ignoring folder at " + subPath + ": Listed in excludedPaths");
|
||||
continue;
|
||||
}
|
||||
|
||||
if(Files.isDirectory(subPath)) {
|
||||
load(rootPath, subPath, videos, defaultMetadata);
|
||||
continue;
|
||||
}
|
||||
|
||||
String fileName = subPath.getFileName().toString();
|
||||
if(fileName.equals(METADATA_NAME) || fileName.endsWith(VIDEO_METADATA_SUFFIX)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!VideoBase.getConfig().getIncludeFileTypes().stream().anyMatch(ext -> fileName.endsWith("." + ext))) {
|
||||
VideoBase.LOGGER.debug("Ignoring file at " + subPath + ": No valid file extension");
|
||||
continue;
|
||||
}
|
||||
|
||||
String fileNameNoExt = fileName;
|
||||
if(fileNameNoExt.contains(".")) {
|
||||
fileNameNoExt = fileNameNoExt.substring(0, fileNameNoExt.lastIndexOf('.'));
|
||||
}
|
||||
|
||||
VideoMetadata inferredVideoMetadata = new VideoMetadata(new JSONObject(Map.of(
|
||||
VideoMetadata.FIELD_TITLE, fileNameNoExt
|
||||
)));
|
||||
|
@ -1,12 +1,15 @@
|
||||
package me.mrletsplay.videobase.library;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import me.mrletsplay.mrcore.json.JSONObject;
|
||||
import me.mrletsplay.mrcore.json.JSONType;
|
||||
import me.mrletsplay.mrcore.json.converter.JSONConstructor;
|
||||
import me.mrletsplay.mrcore.json.converter.JSONConverter;
|
||||
import me.mrletsplay.mrcore.json.converter.JSONConvertible;
|
||||
import me.mrletsplay.mrcore.json.converter.JSONListType;
|
||||
import me.mrletsplay.mrcore.json.converter.JSONValue;
|
||||
|
||||
public class LibraryMetadata implements JSONConvertible {
|
||||
@ -14,6 +17,10 @@ public class LibraryMetadata implements JSONConvertible {
|
||||
@JSONValue("default")
|
||||
private VideoMetadata defaultMetadata;
|
||||
|
||||
@JSONValue
|
||||
@JSONListType(JSONType.STRING)
|
||||
private List<String> excludePaths;
|
||||
|
||||
private Map<String, VideoMetadata> overrides;
|
||||
|
||||
@JSONConstructor
|
||||
@ -25,6 +32,10 @@ public class LibraryMetadata implements JSONConvertible {
|
||||
return defaultMetadata;
|
||||
}
|
||||
|
||||
public List<String> getExcludePaths() {
|
||||
return excludePaths;
|
||||
}
|
||||
|
||||
public Map<String, VideoMetadata> getOverrides() {
|
||||
return overrides;
|
||||
}
|
||||
|
@ -189,9 +189,10 @@ public class LibraryAPI implements EndpointCollection {
|
||||
|
||||
try {
|
||||
long length = Files.size(videoPath);
|
||||
String contentType = Files.probeContentType(videoPath);
|
||||
InputStream in = Files.newInputStream(videoPath);
|
||||
ctx.getServerHeader().setCompressionEnabled(false);
|
||||
ctx.getServerHeader().setContent(MimeType.of("video/mpeg"), in, length);
|
||||
ctx.getServerHeader().setContent(MimeType.of(contentType == null ? "video/mpeg" : contentType), in, length);
|
||||
} catch (IOException e) {
|
||||
ctx.respond(HttpStatusCodes.INTERNAL_SERVER_ERROR_500, new TextResponse("Failed to load video"));
|
||||
return;
|
||||
|
@ -8,6 +8,8 @@ import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
@ -17,20 +19,38 @@ import me.mrletsplay.videobase.library.Video;
|
||||
|
||||
public class ThumbnailCreator {
|
||||
|
||||
private static Map<String, BufferedImage> cachedThumbnails = new HashMap<>();
|
||||
|
||||
public static void clearCache() {
|
||||
cachedThumbnails.clear();
|
||||
}
|
||||
|
||||
public static BufferedImage createThumbnail(Video video) {
|
||||
Path cacheDir = Path.of(VideoBase.getConfig().getCachePath()).resolve("thumbnails").toAbsolutePath();
|
||||
Path thumbnailFile = cacheDir.resolve(video.getId() + ".png");
|
||||
if(cachedThumbnails.containsKey(video.getId())) {
|
||||
return cachedThumbnails.get(video.getId());
|
||||
}
|
||||
|
||||
String cachePath = VideoBase.getConfig().getCachePath();
|
||||
Path thumbnailFile = null;
|
||||
|
||||
if(cachePath != null) {
|
||||
thumbnailFile = Path.of(cachePath).resolve("thumbnails").resolve(video.getId() + ".png").toAbsolutePath();
|
||||
|
||||
try {
|
||||
Files.createDirectories(cacheDir);
|
||||
if(Files.exists(thumbnailFile)) {
|
||||
try(InputStream in = Files.newInputStream(thumbnailFile)) {
|
||||
return ImageIO.read(in);
|
||||
BufferedImage thumbnail = ImageIO.read(in);
|
||||
if(thumbnail == null) return null;
|
||||
|
||||
cachedThumbnails.put(video.getId(), thumbnail);
|
||||
return thumbnail;
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
VideoBase.LOGGER.warn("Failed to load thumbnail", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
ProcessBuilder ffmpegBuilder = new ProcessBuilder("ffmpeg",
|
||||
"-i", video.getPath().toAbsolutePath().toString(),
|
||||
@ -70,10 +90,14 @@ public class ThumbnailCreator {
|
||||
BufferedImage thumbnail = ImageIO.read(new ByteArrayInputStream(bOut.toByteArray()));
|
||||
if(thumbnail == null) return null;
|
||||
|
||||
if(thumbnailFile != null) {
|
||||
Files.createDirectories(thumbnailFile.getParent());
|
||||
try(OutputStream out = Files.newOutputStream(thumbnailFile)) {
|
||||
ImageIO.write(thumbnail, "PNG", out);
|
||||
}
|
||||
}
|
||||
|
||||
cachedThumbnails.put(video.getId(), thumbnail);
|
||||
return thumbnail;
|
||||
} catch (InterruptedException | IOException e) {
|
||||
VideoBase.LOGGER.warn("Failed to create thumbnail", e);
|
||||
|
Loading…
x
Reference in New Issue
Block a user