253 lines
6.7 KiB
Go
253 lines
6.7 KiB
Go
package ffmpeg
|
|
|
|
import (
|
|
"alfred/config"
|
|
"alfred/pkg/log"
|
|
"alfred/utils"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"github.com/u2takey/ffmpeg-go"
|
|
"go.uber.org/zap"
|
|
"golang.org/x/image/font"
|
|
"golang.org/x/image/font/basicfont"
|
|
"golang.org/x/image/math/fixed"
|
|
"golang.org/x/image/webp"
|
|
"image"
|
|
"image/color"
|
|
"image/draw"
|
|
"image/jpeg"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strconv"
|
|
)
|
|
|
|
func GenerateVideoFromImages(folderPath, filename, imageType string, snapshotPerSecond int64) (string, error) {
|
|
|
|
err := ffmpeg_go.
|
|
Input(
|
|
filepath.Join(folderPath, utils.ASTERISK+imageType),
|
|
ffmpeg_go.KwArgs{"framerate": snapshotPerSecond},
|
|
ffmpeg_go.KwArgs{"pattern_type": "glob"},
|
|
).
|
|
Filter("scale", ffmpeg_go.Args{"trunc(iw/2)*2:trunc(ih/2)*2"}).
|
|
Output(
|
|
filepath.Join(folderPath, filename+utils.VideoExtension.String()),
|
|
ffmpeg_go.KwArgs{"codec": "libx264"},
|
|
ffmpeg_go.KwArgs{"crf": 20},
|
|
ffmpeg_go.KwArgs{"pix_fmt": "yuv420p"},
|
|
ffmpeg_go.KwArgs{"preset": "ultrafast"},
|
|
).
|
|
OverWriteOutput().Run()
|
|
if err != nil {
|
|
return utils.EMPTY, err
|
|
}
|
|
return filepath.Join(folderPath, filename, filename+utils.VideoExtension.String()), nil
|
|
}
|
|
|
|
func ApplyBlurToRegion(inputImage, destFile string, height, width int, blurRatio, blurStrength string) error {
|
|
|
|
ratio, err := strconv.ParseFloat(blurRatio, 64)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
cropHeight := int(float64(height) / ratio)
|
|
x := 0
|
|
y := cropHeight
|
|
cropWidth := width
|
|
|
|
inputVideo := ffmpeg_go.Input(inputImage)
|
|
croppedStream := inputVideo.Filter("crop", ffmpeg_go.Args{fmt.Sprintf("%d:%d:%d:%d", cropWidth, cropHeight, x, height-y)})
|
|
finalStream := croppedStream.Filter("avgblur", ffmpeg_go.Args{blurStrength})
|
|
err = ffmpeg_go.Input(inputImage).Overlay(finalStream, fmt.Sprintf("%d:%d", x, height-y)).Output(destFile).OverWriteOutput().Run()
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func ApplyTouchPoints(inputImageFile, outputImageFile string, x, y int, tempDirPath, imageType string) error {
|
|
|
|
imgFile, err := os.Open(inputImageFile)
|
|
if err != nil {
|
|
log.Error("Error while opening image", zap.String("inputImage", inputImageFile), zap.Error(err))
|
|
return err
|
|
}
|
|
defer imgFile.Close()
|
|
|
|
var img image.Image
|
|
switch imageType {
|
|
case utils.ImageExtensionWebp.String():
|
|
img, err = webp.Decode(imgFile)
|
|
case utils.ImageExtensionJpeg.String():
|
|
img, _, err = image.Decode(imgFile)
|
|
}
|
|
|
|
if err != nil {
|
|
log.Error("Error while decoding image", zap.String("inputImage", inputImageFile), zap.String("image_type", imageType), zap.Error(err))
|
|
return err
|
|
}
|
|
|
|
// Create a new RGBA image to draw on.
|
|
rgba := image.NewRGBA(img.Bounds())
|
|
|
|
draw.Draw(rgba, img.Bounds(), img, image.Point{}, draw.Src)
|
|
|
|
circleRadius := config.GetCoreConfig().TouchPointsConfig.TouchPointCircleRadius
|
|
circleColor := color.RGBA{R: uint8(config.GetCoreConfig().TouchPointsConfig.TouchPointRedComponent), G: uint8(config.GetCoreConfig().TouchPointsConfig.TouchPointGreenComponent), B: uint8(config.GetCoreConfig().TouchPointsConfig.TouchPointBlueComponent), A: uint8(config.GetCoreConfig().TouchPointsConfig.TouchPointAlphaComponent)}
|
|
|
|
for px := x - circleRadius; px <= x+circleRadius; px++ {
|
|
for py := y - circleRadius; py <= y+circleRadius; py++ {
|
|
if (px-x)*(px-x)+(py-y)*(py-y) <= circleRadius*circleRadius {
|
|
rgba.Set(px, py, circleColor)
|
|
}
|
|
}
|
|
}
|
|
|
|
err = saveImage(rgba, outputImageFile, tempDirPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func GenerateThirdPartyImage(imageDest, screen, pathToUnzippedFiles string, height, width int, imageType string) error {
|
|
|
|
tempDir := utils.ThirdPartyDirectory
|
|
tempDirPath := filepath.Join(pathToUnzippedFiles, tempDir)
|
|
validFileName := utils.ConvertToValidFilename(screen)
|
|
tempFile := filepath.Join(tempDirPath, validFileName+imageType)
|
|
|
|
if utils.FolderExists(tempDirPath) && utils.FolderExists(tempFile) {
|
|
|
|
err := utils.CopyFile(tempFile, imageDest)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
if !utils.FolderExists(tempDirPath) {
|
|
_ = utils.CreateDirectory(tempDirPath)
|
|
}
|
|
|
|
// Create a new white image
|
|
img := image.NewRGBA(image.Rect(0, 0, width, height))
|
|
draw.Draw(img, img.Bounds(), &image.Uniform{C: color.White}, image.Point{}, draw.Src)
|
|
|
|
// Use basicfont
|
|
face := basicfont.Face7x13
|
|
|
|
textWidth := font.MeasureString(face, "Third Party Screen "+screen).Ceil()
|
|
|
|
// Calculate text position at the center
|
|
x := 1
|
|
|
|
if textWidth > width {
|
|
x += (textWidth - width) / 2
|
|
} else {
|
|
x += (width - textWidth) / 2
|
|
}
|
|
|
|
y := (height) / 2
|
|
|
|
// Draw the text on the image
|
|
drawer := &font.Drawer{
|
|
Dst: img,
|
|
Src: image.Black,
|
|
Face: face,
|
|
Dot: fixed.P(x, y),
|
|
}
|
|
drawer.DrawString("Third Party Screen " + screen)
|
|
|
|
// Save the image to a file (overwriting if it already exists)
|
|
err := saveImage(img, tempFile, tempDirPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = utils.CopyFile(tempFile, imageDest)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func ExtractImageDimensions(inputImage string) (int, int, error) {
|
|
inputInfo, err := ffmpeg_go.Probe(inputImage)
|
|
if err != nil {
|
|
return 0, 0, err
|
|
}
|
|
|
|
var probeInfo struct {
|
|
Streams []struct {
|
|
Width int `json:"width"`
|
|
Height int `json:"height"`
|
|
} `json:"streams"`
|
|
}
|
|
|
|
if err := json.Unmarshal([]byte(inputInfo), &probeInfo); err != nil {
|
|
log.Error("Error unmarshaling JSON data: %v", zap.Error(err))
|
|
}
|
|
if len(probeInfo.Streams) <= 0 {
|
|
return 0, 0, err
|
|
}
|
|
|
|
width := probeInfo.Streams[0].Width
|
|
height := probeInfo.Streams[0].Height
|
|
|
|
return height, width, nil
|
|
}
|
|
|
|
func saveImage(img *image.RGBA, filename, tempDirPath string) error {
|
|
outputFile, err := os.Create(filename)
|
|
if err != nil {
|
|
log.Error("Error while creating output image file", zap.String("inputImage", filename), zap.String("outputImage", filename), zap.Error(err))
|
|
return err
|
|
}
|
|
defer outputFile.Close()
|
|
|
|
if filepath.Ext(filename) == utils.ImageExtensionJpeg.String() {
|
|
err = jpeg.Encode(outputFile, img, nil)
|
|
} else if filepath.Ext(filename) == utils.ImageExtensionWebp.String() {
|
|
err = encodeToWebp(img, filename, tempDirPath)
|
|
} else {
|
|
log.Error("Unsupported image format", zap.String("inputImage", filename), zap.String("outputImage", filename), zap.Error(err))
|
|
return errors.New("unsupported image format")
|
|
}
|
|
// Encode the modified image to a JPEG file.
|
|
if err != nil {
|
|
log.Error("Error while encoding image", zap.String("inputImage", filename), zap.String("outputImage", filename), zap.Error(err))
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func encodeToWebp(img image.Image, filename, tempDirPath string) error {
|
|
tmpFile := filepath.Join(tempDirPath, "tempImage.jpeg")
|
|
outFile, err := os.Create(tmpFile)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer outFile.Close()
|
|
|
|
err = jpeg.Encode(outFile, img, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
cmd := exec.Command("cwebp", tmpFile, "-o", filename)
|
|
err = cmd.Run()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
os.Remove(tmpFile)
|
|
return nil
|
|
}
|