diff --git a/models/defaults.go b/models/defaults.go index b13f22e..afba508 100644 --- a/models/defaults.go +++ b/models/defaults.go @@ -121,6 +121,9 @@ Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; verti {Name: "thumb_proxy_enabled", Value: "0", Type: "thumb"}, {Name: "thumb_proxy_policy", Value: "[]", Type: "thumb"}, {Name: "thumb_max_src_size", Value: "31457280", Type: "thumb"}, + {Name: "thumb_libraw_path", Value: "simple_dcraw", Type: "thumb"}, + {Name: "thumb_libraw_enabled", Value: "0", Type: "thumb"}, + {Name: "thumb_libraw_exts", Value: "arw,raf,dng", Type: "thumb"}, {Name: "pwa_small_icon", Value: "/static/img/favicon.ico", Type: "pwa"}, {Name: "pwa_medium_icon", Value: "/static/img/logo192.png", Type: "pwa"}, {Name: "pwa_large_icon", Value: "/static/img/logo512.png", Type: "pwa"}, diff --git a/pkg/filesystem/image.go b/pkg/filesystem/image.go index dc573dd..2563cc9 100644 --- a/pkg/filesystem/image.go +++ b/pkg/filesystem/image.go @@ -145,6 +145,7 @@ func (fs *FileSystem) generateThumbnail(ctx context.Context, file *model.File) e "thumb_vips_enabled", "thumb_ffmpeg_enabled", "thumb_libreoffice_enabled", + "thumb_libraw_enabled", )) if err != nil { _ = updateThumbStatus(file, model.ThumbStatusNotAvailable) diff --git a/pkg/thumb/libraw.go b/pkg/thumb/libraw.go new file mode 100644 index 0000000..ad168d9 --- /dev/null +++ b/pkg/thumb/libraw.go @@ -0,0 +1,93 @@ +package thumb + +import ( + "bytes" + "context" + "fmt" + "io" + "os" + "os/exec" + "path/filepath" + "strings" + + model "github.com/cloudreve/Cloudreve/v3/models" + "github.com/cloudreve/Cloudreve/v3/pkg/util" + "github.com/gofrs/uuid" +) + +func init() { + RegisterGenerator(&LibRawGenerator{}) +} + +type LibRawGenerator struct { + exts []string + lastRawExts string +} + +func (f *LibRawGenerator) Generate(ctx context.Context, file io.Reader, _ string, name string, options map[string]string) (*Result, error) { + const ( + thumbLibRawPath = "thumb_libraw_path" + thumbLibRawExt = "thumb_libraw_exts" + thumbTempPath = "temp_path" + ) + + opts := model.GetSettingByNames(thumbLibRawPath, thumbLibRawExt, thumbTempPath) + + if f.lastRawExts != opts[thumbLibRawExt] { + f.exts = strings.Split(opts[thumbLibRawExt], ",") + f.lastRawExts = opts[thumbLibRawExt] + } + + if !util.IsInExtensionList(f.exts, name) { + return nil, fmt.Errorf("unsupported image format: %w", ErrPassThrough) + } + + inputFilePath := filepath.Join( + util.RelativePath(opts[thumbTempPath]), + "thumb", + fmt.Sprintf("thumb_%s", uuid.Must(uuid.NewV4()).String()), + ) + defer func() { _ = os.Remove(inputFilePath) }() + + inputFile, err := util.CreatNestedFile(inputFilePath) + if err != nil { + return nil, fmt.Errorf("failed to create temp file: %w", err) + } + + if _, err = io.Copy(inputFile, file); err != nil { + _ = inputFile.Close() + return nil, fmt.Errorf("failed to write input file: %w", err) + } + _ = inputFile.Close() + + cmd := exec.CommandContext(ctx, opts[thumbLibRawPath], "-e", inputFilePath) + + var stdErr bytes.Buffer + cmd.Stderr = &stdErr + if err = cmd.Run(); err != nil { + util.Log().Warning("Failed to invoke LibRaw: %s", stdErr.String()) + return nil, fmt.Errorf("failed to invoke LibRaw: %w", err) + } + + outputFilePath := inputFilePath + ".thumb.jpg" + defer func() { _ = os.Remove(outputFilePath) }() + + // use builtin function + ff, err := os.OpenFile(outputFilePath, os.O_RDONLY, os.ModePerm) + if err != nil { + return nil, fmt.Errorf("failed to open temp file: %w", err) + } + defer func() { _ = ff.Close() }() + + return new(Builtin).Generate(ctx, ff, outputFilePath, filepath.Base(outputFilePath), options) +} + +func (f *LibRawGenerator) Priority() int { + return 250 +} + +func (f *LibRawGenerator) EnableFlag() string { + return "thumb_libraw_enabled" +} + +var _ Generator = (*LibRawGenerator)(nil) diff --git a/pkg/thumb/tester.go b/pkg/thumb/tester.go index 2dcc4be..1b9204f 100644 --- a/pkg/thumb/tester.go +++ b/pkg/thumb/tester.go @@ -23,6 +23,8 @@ func TestGenerator(ctx context.Context, name, executable string) (string, error) return testFfmpegGenerator(ctx, executable) case "libreOffice": return testLibreOfficeGenerator(ctx, executable) + case "libRaw": + return testLibRawGenerator(ctx, executable) default: return "", ErrUnknownGenerator } @@ -72,3 +74,18 @@ func testLibreOfficeGenerator(ctx context.Context, executable string) (string, e return output.String(), nil } + +func testLibRawGenerator(ctx context.Context, executable string) (string, error) { + cmd := exec.CommandContext(ctx, executable) + var output bytes.Buffer + cmd.Stdout = &output + if err := cmd.Run(); err != nil { + return "", fmt.Errorf("failed to invoke libraw executable: %w", err) + } + + if !strings.Contains(output.String(), "LibRaw") { + return "", ErrUnknownOutput + } + + return output.String(), nil +}