Extract Jellyfin thumbnails as a module
This commit is contained in:
@@ -0,0 +1,125 @@
|
|||||||
|
package sh.rhiobet.lalafin.api.thumbnail;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URLDecoder;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import javax.enterprise.context.ApplicationScoped;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import org.eclipse.microprofile.config.inject.ConfigProperty;
|
||||||
|
import org.im4java.core.ConvertCmd;
|
||||||
|
import org.im4java.core.IM4JavaException;
|
||||||
|
import org.im4java.core.IMOperation;
|
||||||
|
import sh.rhiobet.lalafin.api.configuration.FileApiConfiguration;
|
||||||
|
import sh.rhiobet.lalafin.api.model.FileInfo;
|
||||||
|
import sh.rhiobet.lalafin.api.model.FileInfoBase;
|
||||||
|
import sh.rhiobet.lalafin.api.model.FolderInfo;
|
||||||
|
|
||||||
|
@ApplicationScoped
|
||||||
|
public class JellyfinThumbnailGenerator implements ThumbnailGenerator {
|
||||||
|
|
||||||
|
@ConfigProperty(name = "api.thumbnail.jellyfin.paths")
|
||||||
|
Optional<List<String>> paths;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
FileApiConfiguration fileApiConfiguration;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canHandle(FileInfoBase fileInfoBase) {
|
||||||
|
if (paths.isPresent()) {
|
||||||
|
String filePath = URLDecoder.decode(fileInfoBase.directUrl, StandardCharsets.UTF_8);
|
||||||
|
for (String path : paths.get()) {
|
||||||
|
if (filePath.toString().startsWith("/file" + path)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean generateThumbnail(FileInfoBase fileInfoBase, Path thumbPath) {
|
||||||
|
if (fileInfoBase instanceof FileInfo) {
|
||||||
|
return this.generateThumbnailForFile((FileInfo) fileInfoBase, thumbPath);
|
||||||
|
} else if (fileInfoBase instanceof FolderInfo) {
|
||||||
|
return this.generateThumbnailForFolder((FolderInfo) fileInfoBase, thumbPath);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean generateThumbnailForFile(FileInfo fileInfo, Path thumbPath) {
|
||||||
|
Path filePath = Paths.get(fileApiConfiguration.directory()
|
||||||
|
+ URLDecoder.decode(fileInfo.directUrl, StandardCharsets.UTF_8));
|
||||||
|
|
||||||
|
Path imgPath = filePath.resolveSibling(
|
||||||
|
filePath.getFileName().toString().replaceAll("\\.[^.]*$", "-thumb.jpg"));
|
||||||
|
if (Files.exists(imgPath)) {
|
||||||
|
try {
|
||||||
|
thumbPath.getParent().toFile().mkdirs();
|
||||||
|
|
||||||
|
ConvertCmd convert = new ConvertCmd();
|
||||||
|
IMOperation op = new IMOperation();
|
||||||
|
op.addImage(imgPath.toString());
|
||||||
|
op.resize(200, null);
|
||||||
|
op.addImage(thumbPath.toString());
|
||||||
|
convert.run(op);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (IOException | InterruptedException | IM4JavaException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean generateThumbnailForFolder(FolderInfo folderInfo, Path thumbPath) {
|
||||||
|
Path folderPath = Paths.get(fileApiConfiguration.directory()
|
||||||
|
+ URLDecoder.decode(folderInfo.directUrl, StandardCharsets.UTF_8));
|
||||||
|
|
||||||
|
try {
|
||||||
|
Optional<Path> imgPath;
|
||||||
|
|
||||||
|
Path posterPath = folderPath.resolve("poster.jpg");
|
||||||
|
if (Files.exists(posterPath)) {
|
||||||
|
// Check for series poster
|
||||||
|
imgPath = Optional.of(posterPath);
|
||||||
|
} else if (folderPath.getFileName().toString().startsWith("Season ")
|
||||||
|
|| (folderPath.getFileName().toString().equals("Specials"))) {
|
||||||
|
// Season folder
|
||||||
|
String folderName = folderPath.getFileName().toString();
|
||||||
|
if (folderName.startsWith("Season ")) {
|
||||||
|
String seasonNumber =
|
||||||
|
String.format("%02d", Integer.parseInt(folderName.substring(7)));
|
||||||
|
imgPath = Optional
|
||||||
|
.of(folderPath.resolveSibling("season" + seasonNumber + "-poster.jpg"));
|
||||||
|
} else {
|
||||||
|
imgPath = Optional.of(folderPath.resolveSibling("season-specials-poster.jpg"));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
imgPath = Optional.empty();
|
||||||
|
}
|
||||||
|
if (imgPath.isPresent()) {
|
||||||
|
thumbPath.getParent().toFile().mkdirs();
|
||||||
|
|
||||||
|
ConvertCmd convert = new ConvertCmd();
|
||||||
|
IMOperation op = new IMOperation();
|
||||||
|
op.addImage(imgPath.get().toString());
|
||||||
|
op.resize(200, null);
|
||||||
|
op.addImage(thumbPath.toString());
|
||||||
|
convert.run(op);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch (IOException | InterruptedException | IM4JavaException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package sh.rhiobet.lalafin.api.thumbnail;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import sh.rhiobet.lalafin.api.model.FileInfoBase;
|
||||||
|
|
||||||
|
public interface ThumbnailGenerator {
|
||||||
|
|
||||||
|
boolean canHandle(FileInfoBase fileInfoBase);
|
||||||
|
|
||||||
|
boolean generateThumbnail(FileInfoBase fileInfoBase, Path thumbPath);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@ import java.util.Optional;
|
|||||||
import java.util.zip.ZipEntry;
|
import java.util.zip.ZipEntry;
|
||||||
import java.util.zip.ZipFile;
|
import java.util.zip.ZipFile;
|
||||||
import javax.enterprise.context.ApplicationScoped;
|
import javax.enterprise.context.ApplicationScoped;
|
||||||
|
import javax.enterprise.inject.Instance;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import org.im4java.core.ConvertCmd;
|
import org.im4java.core.ConvertCmd;
|
||||||
import org.im4java.core.IM4JavaException;
|
import org.im4java.core.IM4JavaException;
|
||||||
@@ -19,19 +20,26 @@ import sh.rhiobet.lalafin.api.configuration.FileApiConfiguration;
|
|||||||
import sh.rhiobet.lalafin.api.model.FileInfo;
|
import sh.rhiobet.lalafin.api.model.FileInfo;
|
||||||
import sh.rhiobet.lalafin.api.model.FileInfoBase;
|
import sh.rhiobet.lalafin.api.model.FileInfoBase;
|
||||||
import sh.rhiobet.lalafin.api.model.FolderInfo;
|
import sh.rhiobet.lalafin.api.model.FolderInfo;
|
||||||
|
import sh.rhiobet.lalafin.api.thumbnail.ThumbnailGenerator;
|
||||||
|
|
||||||
@ApplicationScoped
|
@ApplicationScoped
|
||||||
public class ThumbnailService {
|
public class ThumbnailService {
|
||||||
@Inject
|
@Inject
|
||||||
FileApiConfiguration fileApiConfiguration;
|
FileApiConfiguration fileApiConfiguration;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
Instance<ThumbnailGenerator> generators;
|
||||||
|
|
||||||
public boolean generateThumbnail(FileInfoBase fileInfoBase, Path thumbPath) {
|
public boolean generateThumbnail(FileInfoBase fileInfoBase, Path thumbPath) {
|
||||||
|
for (ThumbnailGenerator generator : generators) {
|
||||||
|
if (generator.canHandle(fileInfoBase)) {
|
||||||
|
return generator.generateThumbnail(fileInfoBase, thumbPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (fileInfoBase instanceof FileInfo) {
|
if (fileInfoBase instanceof FileInfo) {
|
||||||
FileInfo fileInfo = (FileInfo) fileInfoBase;
|
FileInfo fileInfo = (FileInfo) fileInfoBase;
|
||||||
if (fileInfo.filename.endsWith(".zip")) {
|
if (fileInfo.filename.endsWith(".zip")) {
|
||||||
return this.generateThumbnailFromZip(fileInfo, thumbPath);
|
return this.generateThumbnailFromZip(fileInfo, thumbPath);
|
||||||
} else {
|
|
||||||
return this.generateThumbnailForFile(fileInfo, thumbPath);
|
|
||||||
}
|
}
|
||||||
} else if (fileInfoBase instanceof FolderInfo) {
|
} else if (fileInfoBase instanceof FolderInfo) {
|
||||||
return this.generateThumbnailForFolder((FolderInfo) fileInfoBase, thumbPath);
|
return this.generateThumbnailForFolder((FolderInfo) fileInfoBase, thumbPath);
|
||||||
@@ -39,32 +47,6 @@ public class ThumbnailService {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean generateThumbnailForFile(FileInfo fileInfo, Path thumbPath) {
|
|
||||||
Path filePath = Paths.get(fileApiConfiguration.directory()
|
|
||||||
+ URLDecoder.decode(fileInfo.directUrl, StandardCharsets.UTF_8));
|
|
||||||
|
|
||||||
Path imgPath = filePath.resolveSibling(
|
|
||||||
filePath.getFileName().toString().replaceAll("\\.[^.]*$", "-thumb.jpg"));
|
|
||||||
if (Files.exists(imgPath)) {
|
|
||||||
try {
|
|
||||||
thumbPath.getParent().toFile().mkdirs();
|
|
||||||
|
|
||||||
ConvertCmd convert = new ConvertCmd();
|
|
||||||
IMOperation op = new IMOperation();
|
|
||||||
op.addImage(imgPath.toString());
|
|
||||||
op.resize(200, null);
|
|
||||||
op.addImage(thumbPath.toString());
|
|
||||||
convert.run(op);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
} catch (IOException | InterruptedException | IM4JavaException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean generateThumbnailFromZip(FileInfo fileInfo, Path thumbPath) {
|
public boolean generateThumbnailFromZip(FileInfo fileInfo, Path thumbPath) {
|
||||||
Path zipPath = Paths.get(fileApiConfiguration.directory()
|
Path zipPath = Paths.get(fileApiConfiguration.directory()
|
||||||
+ URLDecoder.decode(fileInfo.directUrl, StandardCharsets.UTF_8));
|
+ URLDecoder.decode(fileInfo.directUrl, StandardCharsets.UTF_8));
|
||||||
@@ -105,30 +87,10 @@ public class ThumbnailService {
|
|||||||
+ URLDecoder.decode(folderInfo.directUrl, StandardCharsets.UTF_8));
|
+ URLDecoder.decode(folderInfo.directUrl, StandardCharsets.UTF_8));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Optional<Path> imgPath;
|
Optional<Path> imgPath = Files.list(folderPath).sorted()
|
||||||
|
.filter(p -> !Files.isDirectory(p)
|
||||||
Path posterPath = folderPath.resolve("poster.jpg");
|
&& (p.toString().endsWith(".png") || p.toString().endsWith(".jpg")))
|
||||||
if (Files.exists(posterPath)) {
|
.findFirst();
|
||||||
// Check for series poster
|
|
||||||
imgPath = Optional.of(posterPath);
|
|
||||||
} else if (folderPath.getFileName().toString().startsWith("Season ")
|
|
||||||
|| (folderPath.getFileName().toString().equals("Specials"))) {
|
|
||||||
// Season folder
|
|
||||||
String folderName = folderPath.getFileName().toString();
|
|
||||||
if (folderName.startsWith("Season ")) {
|
|
||||||
String seasonNumber =
|
|
||||||
String.format("%02d", Integer.parseInt(folderName.substring(7)));
|
|
||||||
imgPath = Optional
|
|
||||||
.of(folderPath.resolveSibling("season" + seasonNumber + "-poster.jpg"));
|
|
||||||
} else {
|
|
||||||
imgPath = Optional.of(folderPath.resolveSibling("season-specials-poster.jpg"));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
imgPath = Files.list(folderPath).sorted()
|
|
||||||
.filter(p -> !Files.isDirectory(p)
|
|
||||||
&& (p.toString().endsWith(".png") || p.toString().endsWith(".jpg")))
|
|
||||||
.findFirst();
|
|
||||||
}
|
|
||||||
if (imgPath.isPresent()) {
|
if (imgPath.isPresent()) {
|
||||||
thumbPath.getParent().toFile().mkdirs();
|
thumbPath.getParent().toFile().mkdirs();
|
||||||
|
|
||||||
|
|||||||
@@ -30,4 +30,8 @@ api:
|
|||||||
- path: / # Root corresonds to the endpoint /file/
|
- path: / # Root corresonds to the endpoint /file/
|
||||||
roles: {} # Only users with these roles will have access to this route (empty = ALL)
|
roles: {} # Only users with these roles will have access to this route (empty = ALL)
|
||||||
folder:
|
folder:
|
||||||
tokens: {} # List of tokens to make some routes available trhough the public folders API
|
tokens: {} # List of tokens to make some routes available trhough the public folders API
|
||||||
|
|
||||||
|
# thumbnail:
|
||||||
|
# jellyfin:
|
||||||
|
# paths: {}
|
||||||
Reference in New Issue
Block a user