图片与视频的缩略图是一个十分常见的需求,比如即时消息。这里摘取了Golang项目中的相关代码,分享图片与视频相关处理的开发经验。
缩略图的尺寸分为两种规则:
1)边长模式,生成正方形缩略图;
2)宽高模式,又分三种:指定宽高、指定宽(高等比缩放)、指定高(宽等比缩放)。
如果原图为png或gif,缩略图则采用png格式;否则,都采用jpeg格式。
func createPhotoThumbnail(src string, thumbnail string, side int, width int, height int) (err error) {
// open image
srcFile, err := os.Open(src)
if err != nil {
return fmt.Errorf("opening image failed")
}
defer srcFile.Close()
img, format, err := image.Decode(srcFile)
if err != nil {
return fmt.Errorf("decode image failed %v", err)
}
// process image
var thumbnailImg image.Image
if side > 0 { // 指定边长
var canvas image.Image
s := side
sz := img.Bounds().Size()
// 等比缩小
if sz.X > sz.Y {
canvas = resize.Thumbnail(uint(sz.X), uint(s), img, resize.Lanczos2)
} else {
canvas = resize.Thumbnail(uint(s), uint(sz.Y), img, resize.Lanczos2)
}
sz = canvas.Bounds().Size()
var l, t, r, b int
if sz.X > s {
l = (sz.X - s) / 2
r = l + s
} else {
l = 0
r = sz.X
}
if sz.Y > s {
t = (sz.Y - s) / 2
b = t + s
} else {
t = 0
b = sz.Y
}
// 裁剪
thumbnailImg = canvas.(*image.YCbCr).SubImage(image.Rect(l, t, r, b)).(*image.YCbCr)
} else { // 指定宽和高
w := width
h := height
if w > 0 && h > 0 {
} else if w > 0 {
h = img.Bounds().Dy()
} else {
w = img.Bounds().Dx()
}
thumbnailImg = resize.Thumbnail(uint(w), uint(h), img, resize.Lanczos2)
}
// save thumbnail
err = os.MkdirAll(filepath.Dir(thumbnail), os.ModePerm)
if err != nil {
return
}
thumbnailFile, err := os.Create(thumbnail)
if err != nil {
return
}
defer thumbnailFile.Close()
if format == "png" || format == "gif" {
err = png.Encode(thumbnailFile, thumbnailImg)
} else {
err = jpeg.Encode(thumbnailFile, thumbnailImg, &jpeg.Options{Quality: 80})
}
return
}
生成视频的缩略图,需要先用ffmpeg命令行程序从视频中截取2秒之后的单帧图片,这里以Linux为例。然后,用上面的图片缩略图生成函数再生成指定尺寸的缩略图。
func createVideoThumbnail(src string, thumbnail string, side int, width int, height int) (err error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(25)*time.Second)
defer cancel()
dir := filepath.Dir(thumbnail)
os.MkdirAll(dir, os.ModePerm)
tmp := thumbnail + ".tmp"
c := exec.CommandContext(ctx, "./ffmpeg",
"-loglevel", "error",
"-y",
"-ss", "2",
"-accurate_seek", "-i", src,
"-vframes", "1",
"-f", "image2",
tmp)
var stderr bytes.Buffer
c.Stderr = &stderr
err = c.Run()
errStr := ""
if err != nil {
errStr = fmt.Sprintf("ffmpeg cmderr(%v) ", err)
}
if stderr.Len() != 0 {
errStr += fmt.Sprintf("ffmpeg stderr(%v) ", stderr.String())
}
if ctx.Err() != nil {
errStr += fmt.Sprintf("ffmpeg ctxerr(%v)", ctx.Err())
}
if errStr != "" {
err = errors.New(errStr)
return
}
err = createPhotoThumbnail(tmp, thumbnail, side, width, height)
os.Remove(tmp)
return
}