@@ -36,6 +36,87 @@ func isSymlinkDir(f fs.FileInfo, path string) bool {
3636 return false
3737}
3838
39+ // resizeImageToBufferWithFFmpegGo 使用 ffmpeg-go 调整图片大小并输出到内存缓冲区
40+ func resizeImageToBufferWithFFmpegGo (inputFile string , width int , outputFormat string /* e.g., "image2pipe", "png_pipe", "mjpeg" */ ) (* bytes.Buffer , error ) {
41+ outBuffer := bytes .NewBuffer (nil )
42+
43+ // Determine codec based on desired output format for piping
44+ // For generic image piping, 'image2' is often used with -f image2pipe
45+ // For specific formats to buffer, you might specify the codec directly
46+ var vcodec string
47+ switch outputFormat {
48+ case "png_pipe" : // if you want to ensure PNG format in buffer
49+ vcodec = "png"
50+ case "mjpeg" : // if you want to ensure JPEG format in buffer
51+ vcodec = "mjpeg"
52+ // default or "image2pipe" could leave codec choice more to ffmpeg or require -c:v later
53+ }
54+
55+ outputArgs := ffmpeg.KwArgs {
56+ "vf" : fmt .Sprintf ("scale=%d:-1:flags=lanczos,format=yuv444p" , width ),
57+ "vframes" : "1" ,
58+ "f" : outputFormat , // Format for piping (e.g., image2pipe, png_pipe)
59+ }
60+ if vcodec != "" {
61+ outputArgs ["vcodec" ] = vcodec
62+ }
63+ if outputFormat == "mjpeg" {
64+ outputArgs ["q:v" ] = "3"
65+ }
66+
67+ err := ffmpeg .Input (inputFile ).
68+ Output ("pipe:" , outputArgs ). // Output to pipe (stdout)
69+ GlobalArgs ("-loglevel" , "error" ).
70+ Silent (true ). // Suppress ffmpeg's own console output
71+ WithOutput (outBuffer , os .Stderr ). // Capture stdout to outBuffer, stderr to os.Stderr
72+ // ErrorToStdOut(). // Alternative: send ffmpeg's stderr to Go's stdout
73+ Run ()
74+
75+ if err != nil {
76+ return nil , fmt .Errorf ("ffmpeg-go failed to resize image %s to buffer: %w" , inputFile , err )
77+ }
78+ if outBuffer .Len () == 0 {
79+ return nil , fmt .Errorf ("ffmpeg-go produced empty buffer for %s" , inputFile )
80+ }
81+
82+ return outBuffer , nil
83+ }
84+
85+ func generateThumbnailWithImagingOptimized (imagePath string , targetWidth int , quality int ) (* bytes.Buffer , error ) {
86+
87+ file , err := os .Open (imagePath )
88+ if err != nil {
89+ return nil , fmt .Errorf ("failed to open image: %w" , err )
90+ }
91+ defer file .Close ()
92+
93+ img , err := imaging .Decode (file , imaging .AutoOrientation (true ))
94+ if err != nil {
95+ return nil , fmt .Errorf ("failed to decode image: %w" , err )
96+ }
97+
98+ thumbImg := imaging .Resize (img , targetWidth , 0 , imaging .Lanczos )
99+ img = nil
100+
101+ var buf bytes.Buffer
102+ // imaging.Encode
103+ // imaging.PNG, imaging.JPEG, imaging.GIF, imaging.BMP, imaging.TIFF
104+ outputFormat := imaging .JPEG
105+ encodeOptions := []imaging.EncodeOption {imaging .JPEGQuality (quality )}
106+
107+ // outputFormat := imaging.PNG
108+ // encodeOptions := []imaging.EncodeOption{}
109+
110+ err = imaging .Encode (& buf , thumbImg , outputFormat , encodeOptions ... )
111+ if err != nil {
112+ return nil , fmt .Errorf ("failed to encode thumbnail: %w" , err )
113+ }
114+
115+ thumbImg = nil
116+
117+ return & buf , nil
118+ }
119+
39120// Get the snapshot of the video
40121func (d * Local ) GetSnapshot (videoPath string ) (imgData * bytes.Buffer , err error ) {
41122 // Run ffprobe to get the video duration
@@ -80,7 +161,7 @@ func (d *Local) GetSnapshot(videoPath string) (imgData *bytes.Buffer, err error)
80161 // The "noaccurate_seek" option prevents this error and would also speed up
81162 // the seek process.
82163 stream := ffmpeg .Input (videoPath , ffmpeg.KwArgs {"ss" : ss , "noaccurate_seek" : "" }).
83- Output ("pipe:" , ffmpeg.KwArgs {"vframes" : 1 , "format" : "image2" , "vcodec" : "mjpeg" }).
164+ Output ("pipe:" , ffmpeg.KwArgs {"vframes" : 1 , "format" : "image2" , "vcodec" : "mjpeg" , "vf" : fmt . Sprintf ( "scale=%d:-1:flags=lanczos" , d . thumbPixel ) }).
84165 GlobalArgs ("-loglevel" , "error" ).Silent (true ).
85166 WithOutput (srcBuf , os .Stdout )
86167 if err = stream .Run (); err != nil {
@@ -125,29 +206,26 @@ func (d *Local) getThumb(file model.Obj) (*bytes.Buffer, *string, error) {
125206 }
126207 srcBuf = videoBuf
127208 } else {
128- imgData , err := os .ReadFile (fullPath )
129- if err != nil {
130- return nil , nil , err
209+ if d .useFFmpeg {
210+ imgData , err := resizeImageToBufferWithFFmpegGo (fullPath , d .thumbPixel , "image2pipe" )
211+ srcBuf = imgData
212+ if err != nil {
213+ return nil , nil , err
214+ }
215+ } else {
216+ imgData , err := generateThumbnailWithImagingOptimized (fullPath , d .thumbPixel , 70 )
217+ srcBuf = imgData
218+ if err != nil {
219+ return nil , nil , err
220+ }
131221 }
132- imgBuf := bytes .NewBuffer (imgData )
133- srcBuf = imgBuf
134222 }
135223
136- image , err := imaging .Decode (srcBuf , imaging .AutoOrientation (true ))
137- if err != nil {
138- return nil , nil , err
139- }
140- thumbImg := imaging .Resize (image , 144 , 0 , imaging .Lanczos )
141- var buf bytes.Buffer
142- err = imaging .Encode (& buf , thumbImg , imaging .PNG )
143- if err != nil {
144- return nil , nil , err
145- }
146224 if d .ThumbCacheFolder != "" {
147- err = os .WriteFile (filepath .Join (d .ThumbCacheFolder , thumbName ), buf .Bytes (), 0666 )
225+ err : = os .WriteFile (filepath .Join (d .ThumbCacheFolder , thumbName ), srcBuf .Bytes (), 0666 )
148226 if err != nil {
149227 return nil , nil , err
150228 }
151229 }
152- return & buf , nil , nil
230+ return srcBuf , nil , nil
153231}
0 commit comments