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:
@ -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
|
||||
}
|
||||
|
Reference in New Issue
Block a user