|
|
|
package thumb
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
model "github.com/cloudreve/Cloudreve/v3/models"
|
|
|
|
"github.com/cloudreve/Cloudreve/v3/pkg/util"
|
|
|
|
"io"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"reflect"
|
|
|
|
"sort"
|
|
|
|
"strconv"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Generator generates a thumbnail for a given reader.
|
|
|
|
type Generator interface {
|
|
|
|
// Generate generates a thumbnail for a given reader. Src is the original file path, only provided
|
|
|
|
// for local policy files.
|
|
|
|
Generate(ctx context.Context, file io.Reader, src string, name string, options map[string]string) (*Result, error)
|
|
|
|
|
|
|
|
// Priority of execution order, smaller value means higher priority.
|
|
|
|
Priority() int
|
|
|
|
|
|
|
|
// EnableFlag returns the setting name to enable this generator.
|
|
|
|
EnableFlag() string
|
|
|
|
}
|
|
|
|
|
|
|
|
type Result struct {
|
|
|
|
Path string
|
|
|
|
Continue bool
|
|
|
|
Cleanup []func()
|
|
|
|
}
|
|
|
|
|
|
|
|
type (
|
|
|
|
GeneratorType string
|
|
|
|
GeneratorList []Generator
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
Generators = GeneratorList{}
|
|
|
|
|
|
|
|
ErrPassThrough = errors.New("pass through")
|
|
|
|
ErrNotAvailable = fmt.Errorf("thumbnail not available: %w", ErrPassThrough)
|
|
|
|
)
|
|
|
|
|
|
|
|
func (g GeneratorList) Len() int {
|
|
|
|
return len(g)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (g GeneratorList) Less(i, j int) bool {
|
|
|
|
return g[i].Priority() < g[j].Priority()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (g GeneratorList) Swap(i, j int) {
|
|
|
|
g[i], g[j] = g[j], g[i]
|
|
|
|
}
|
|
|
|
|
|
|
|
// RegisterGenerator registers a thumbnail generator.
|
|
|
|
func RegisterGenerator(generator Generator) {
|
|
|
|
Generators = append(Generators, generator)
|
|
|
|
sort.Sort(Generators)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p GeneratorList) Generate(ctx context.Context, file io.Reader, src, name string, options map[string]string) (*Result, error) {
|
|
|
|
inputFile, inputSrc, inputName := file, src, name
|
|
|
|
for _, generator := range p {
|
|
|
|
if model.IsTrueVal(options[generator.EnableFlag()]) {
|
|
|
|
res, err := generator.Generate(ctx, inputFile, inputSrc, inputName, options)
|
|
|
|
if errors.Is(err, ErrPassThrough) {
|
|
|
|
util.Log().Debug("Failed to generate thumbnail using %s for %s: %s, passing through to next generator.", reflect.TypeOf(generator).String(), name, err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if res != nil && res.Continue {
|
|
|
|
util.Log().Debug("Generator %s for %s returned continue, passing through to next generator.", reflect.TypeOf(generator).String(), name)
|
|
|
|
|
|
|
|
// defer cleanup funcs
|
|
|
|
for _, cleanup := range res.Cleanup {
|
|
|
|
defer cleanup()
|
|
|
|
}
|
|
|
|
|
|
|
|
// prepare file reader for next generator
|
|
|
|
intermediate, err := os.Open(res.Path)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to open intermediate thumb file: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
defer intermediate.Close()
|
|
|
|
inputFile = intermediate
|
|
|
|
inputSrc = res.Path
|
|
|
|
inputName = filepath.Base(res.Path)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
return res, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil, ErrNotAvailable
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p GeneratorList) Priority() int {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p GeneratorList) EnableFlag() string {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
func thumbSize(options map[string]string) (uint, uint) {
|
|
|
|
w, h := uint(400), uint(300)
|
|
|
|
if wParsed, err := strconv.Atoi(options["thumb_width"]); err == nil {
|
|
|
|
w = uint(wParsed)
|
|
|
|
}
|
|
|
|
|
|
|
|
if hParsed, err := strconv.Atoi(options["thumb_height"]); err == nil {
|
|
|
|
h = uint(hParsed)
|
|
|
|
}
|
|
|
|
|
|
|
|
return w, h
|
|
|
|
}
|