feat(local): support both time and percent for video thumbnail (#7802)

* feat(local): support percent for video thumbnail

The percentage determines the point in the video (as a percentage of the total duration) at which the thumbnail will be generated.

* feat(local): support both time and percent for video thumbnail
This commit is contained in:
Lin Tianchuan
2025-01-10 20:48:45 +08:00
committed by GitHub
parent 687124c81d
commit 31a7470865
3 changed files with 77 additions and 4 deletions

View File

@ -2,11 +2,13 @@ package local
import (
"bytes"
"encoding/json"
"fmt"
"io/fs"
"os"
"path/filepath"
"sort"
"strconv"
"strings"
"github.com/alist-org/alist/v3/internal/conf"
@ -34,10 +36,58 @@ func isSymlinkDir(f fs.FileInfo, path string) bool {
return false
}
func GetSnapshot(videoPath string, frameNum int) (imgData *bytes.Buffer, err error) {
// Get the snapshot of the video
func (d *Local) GetSnapshot(videoPath string) (imgData *bytes.Buffer, err error) {
// Run ffprobe to get the video duration
jsonOutput, err := ffmpeg.Probe(videoPath)
if err != nil {
return nil, err
}
// get format.duration from the json string
type probeFormat struct {
Duration string `json:"duration"`
}
type probeData struct {
Format probeFormat `json:"format"`
}
var probe probeData
err = json.Unmarshal([]byte(jsonOutput), &probe)
if err != nil {
return nil, err
}
totalDuration, err := strconv.ParseFloat(probe.Format.Duration, 64)
if err != nil {
return nil, err
}
var ss string
if strings.HasSuffix(d.VideoThumbPos, "%") {
percentage, err := strconv.ParseFloat(strings.TrimSuffix(d.VideoThumbPos, "%"), 64)
if err != nil {
return nil, err
}
ss = fmt.Sprintf("%f", totalDuration*percentage/100)
} else {
val, err := strconv.ParseFloat(d.VideoThumbPos, 64)
if err != nil {
return nil, err
}
// If the value is greater than the total duration, use the total duration
if val > totalDuration {
ss = fmt.Sprintf("%f", totalDuration)
} else {
ss = d.VideoThumbPos
}
}
// Run ffmpeg to get the snapshot
srcBuf := bytes.NewBuffer(nil)
stream := ffmpeg.Input(videoPath).
Filter("select", ffmpeg.Args{fmt.Sprintf("gte(n,%d)", frameNum)}).
// If the remaining time from the seek point to the end of the video is less
// than the duration of a single frame, ffmpeg cannot extract any frames
// within the specified range and will exit with an error.
// The "noaccurate_seek" option prevents this error and would also speed up
// the seek process.
stream := ffmpeg.Input(videoPath, ffmpeg.KwArgs{"ss": ss, "noaccurate_seek": ""}).
Output("pipe:", ffmpeg.KwArgs{"vframes": 1, "format": "image2", "vcodec": "mjpeg"}).
GlobalArgs("-loglevel", "error").Silent(true).
WithOutput(srcBuf, os.Stdout)
@ -77,7 +127,7 @@ func (d *Local) getThumb(file model.Obj) (*bytes.Buffer, *string, error) {
}
var srcBuf *bytes.Buffer
if utils.GetFileType(file.GetName()) == conf.VIDEO {
videoBuf, err := GetSnapshot(fullPath, 10)
videoBuf, err := d.GetSnapshot(fullPath)
if err != nil {
return nil, nil, err
}