Improve thumbnail creation
All checks were successful
Build and push container / Build-Docker-Container (push) Successful in 5m44s

This commit is contained in:
MrLetsplay 2025-02-19 23:32:14 +01:00
parent a45b809a99
commit 444e42c091
Signed by: mr
SSH Key Fingerprint: SHA256:92jBH80vpXyaZHjaIl47pjRq+Yt7XGTArqQg1V7hSqg
3 changed files with 32 additions and 25 deletions

View File

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

View File

@ -157,7 +157,7 @@ public class LibraryAPI implements EndpointCollection {
return;
}
BufferedImage thumbnail = ThumbnailCreator.createThumbnail(video);
BufferedImage thumbnail = ThumbnailCreator.getThumbnail(video);
if(thumbnail == null) {
ctx.respond(HttpStatusCodes.NOT_FOUND_404, new TextResponse("No thumbnail found"));
return;

View File

@ -8,8 +8,10 @@ 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.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import javax.imageio.ImageIO;
@ -19,33 +21,34 @@ import me.mrletsplay.videobase.library.Video;
public class ThumbnailCreator {
private static Map<String, BufferedImage> cachedThumbnails = new HashMap<>();
private static Map<String, BufferedImage> cachedThumbnails = new ConcurrentHashMap<>();
private static ExecutorService executorService = Executors.newFixedThreadPool(10);
public static void clearCache() {
cachedThumbnails.clear();
}
public static void createThumbnails() {
for(Video v : VideoBase.getLibrary().getVideos()) {
executorService.submit(() -> createThumbnail(v));
}
}
public static BufferedImage createThumbnail(Video video) {
if(cachedThumbnails.containsKey(video.getId())) {
return cachedThumbnails.get(video.getId());
}
String cachePath = VideoBase.getConfig().getCachePath();
Path thumbnailFile = null;
Path thumbnailFile = cachePath == null ? null : Path.of(cachePath).resolve("thumbnails").resolve(video.getId() + ".png").toAbsolutePath();
if(cachePath != null) {
thumbnailFile = Path.of(cachePath).resolve("thumbnails").resolve(video.getId() + ".png").toAbsolutePath();
if(thumbnailFile != null && Files.isRegularFile(thumbnailFile)) {
try(InputStream in = Files.newInputStream(thumbnailFile)) {
BufferedImage thumbnail = ImageIO.read(in);
if(thumbnail == null) return null;
try {
if(Files.exists(thumbnailFile)) {
try(InputStream in = Files.newInputStream(thumbnailFile)) {
BufferedImage thumbnail = ImageIO.read(in);
if(thumbnail == null) return null;
cachedThumbnails.put(video.getId(), thumbnail);
return thumbnail;
}
}
cachedThumbnails.put(video.getId(), thumbnail);
return thumbnail;
} catch (IOException e) {
VideoBase.LOGGER.warn("Failed to load thumbnail", e);
return null;
@ -54,8 +57,8 @@ public class ThumbnailCreator {
ProcessBuilder ffmpegBuilder = new ProcessBuilder("ffmpeg",
"-i", video.getPath().toAbsolutePath().toString(),
"-vf", "thumbnail",
"-vf", "scale=960:540:force_original_aspect_ratio=decrease,pad=960:540:-1:-1:color=black",
"-r", "0.5",
"-vf", "thumbnail,scale=960:540:force_original_aspect_ratio=decrease,pad=960:540:-1:-1:color=black",
"-frames:v", "1",
"-f", "image2pipe",
"-vcodec", "png",
@ -69,18 +72,18 @@ public class ThumbnailCreator {
ByteArrayOutputStream bOut = new ByteArrayOutputStream();
byte[] buf = new byte[1024];
int len;
while(System.currentTimeMillis() - start < 1000
&& (len = ffmpeg.getInputStream().read(buf)) > 0) {
while(System.currentTimeMillis() - start < 60000
&& (len = ffmpeg.getInputStream().read(buf)) != -1) {
bOut.write(buf, 0, len);
}
ffmpeg.waitFor(10, TimeUnit.SECONDS);
ffmpeg.waitFor(60, TimeUnit.SECONDS);
if(ffmpeg.isAlive()) {
VideoBase.LOGGER.warn("ffmpeg didn't exit after 10 seconds, destroying");
VideoBase.LOGGER.warn("Video " + video.getId() + ": ffmpeg didn't exit after 60 seconds, destroying");
ffmpeg.destroy();
if(!ffmpeg.waitFor(10, TimeUnit.SECONDS)) {
VideoBase.LOGGER.warn("ffmpeg didn't exit 10 seconds after destroying, destroying forcibly");
if(!ffmpeg.waitFor(60, TimeUnit.SECONDS)) {
VideoBase.LOGGER.warn("Video " + video.getId() + ": ffmpeg didn't exit 60 seconds after destroying, destroying forcibly");
ffmpeg.destroyForcibly();
}
@ -97,14 +100,17 @@ public class ThumbnailCreator {
}
}
VideoBase.LOGGER.debug("Created thumbnail for video at " + video.getPath());
cachedThumbnails.put(video.getId(), thumbnail);
return thumbnail;
} catch (InterruptedException | IOException e) {
VideoBase.LOGGER.warn("Failed to create thumbnail", e);
return null;
}
}
public static BufferedImage getThumbnail(Video video) {
return cachedThumbnails.get(video.getId());
}
}