mirror of
https://github.com/saily/vnc-recorder.git
synced 2025-04-27 02:10:55 +00:00
181 lines
3.8 KiB
Go
181 lines
3.8 KiB
Go
package main
|
|
|
|
/**
|
|
* XXX: Ugly workaround for https://github.com/amitbet/vnc2video/issues/10. I've copied the file and build a
|
|
* X264ImageCustomEncoder. Once this is merged, we can drop the encoder.go file again.
|
|
*/
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
vnc "github.com/amitbet/vnc2video"
|
|
"github.com/amitbet/vnc2video/encoders"
|
|
"github.com/sirupsen/logrus"
|
|
"image"
|
|
"image/color"
|
|
"io"
|
|
"os"
|
|
"os/exec"
|
|
"strconv"
|
|
)
|
|
|
|
func encodePPMforRGBA(w io.Writer, img *image.RGBA) error {
|
|
maxvalue := 255
|
|
size := img.Bounds()
|
|
// write ppm header
|
|
_, err := fmt.Fprintf(w, "P6\n%d %d\n%d\n", size.Dx(), size.Dy(), maxvalue)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if convImage == nil {
|
|
convImage = make([]uint8, size.Dy()*size.Dx()*3)
|
|
}
|
|
|
|
rowCount := 0
|
|
for i := 0; i < len(img.Pix); i++ {
|
|
if (i % 4) != 3 {
|
|
convImage[rowCount] = img.Pix[i]
|
|
rowCount++
|
|
}
|
|
}
|
|
|
|
if _, err := w.Write(convImage); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func encodePPMGeneric(w io.Writer, img image.Image) error {
|
|
maxvalue := 255
|
|
size := img.Bounds()
|
|
// write ppm header
|
|
_, err := fmt.Fprintf(w, "P6\n%d %d\n%d\n", size.Dx(), size.Dy(), maxvalue)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// write the bitmap
|
|
colModel := color.RGBAModel
|
|
row := make([]uint8, size.Dx()*3)
|
|
for y := size.Min.Y; y < size.Max.Y; y++ {
|
|
i := 0
|
|
for x := size.Min.X; x < size.Max.X; x++ {
|
|
color := colModel.Convert(img.At(x, y)).(color.RGBA)
|
|
row[i] = color.R
|
|
row[i+1] = color.G
|
|
row[i+2] = color.B
|
|
i += 3
|
|
}
|
|
if _, err := w.Write(row); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
var convImage []uint8
|
|
|
|
func encodePPM(w io.Writer, img image.Image) error {
|
|
if img == nil {
|
|
return errors.New("nil image")
|
|
}
|
|
img1, isRGBImage := img.(*vnc.RGBImage)
|
|
img2, isRGBA := img.(*image.RGBA)
|
|
if isRGBImage {
|
|
return encodePPMforRGBImage(w, img1)
|
|
} else if isRGBA {
|
|
return encodePPMforRGBA(w, img2)
|
|
}
|
|
return encodePPMGeneric(w, img)
|
|
}
|
|
func encodePPMforRGBImage(w io.Writer, img *vnc.RGBImage) error {
|
|
maxvalue := 255
|
|
size := img.Bounds()
|
|
// write ppm header
|
|
_, err := fmt.Fprintf(w, "P6\n%d %d\n%d\n", size.Dx(), size.Dy(), maxvalue)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if _, err := w.Write(img.Pix); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type X264ImageCustomEncoder struct {
|
|
encoders.X264ImageEncoder
|
|
FFMpegBinPath string
|
|
cmd *exec.Cmd
|
|
input io.WriteCloser
|
|
closed bool
|
|
Framerate int
|
|
ConstantRateFactor int
|
|
}
|
|
|
|
func (enc *X264ImageCustomEncoder) Init(videoFileName string) {
|
|
if enc.Framerate == 0 {
|
|
enc.Framerate = 12
|
|
}
|
|
cmd := exec.Command(enc.FFMpegBinPath,
|
|
"-f", "image2pipe",
|
|
"-vcodec", "ppm",
|
|
"-r", strconv.Itoa(enc.Framerate),
|
|
"-an", // no audio
|
|
"-y",
|
|
"-i", "-",
|
|
"-vcodec", "libx264",
|
|
"-preset", "veryfast",
|
|
"-g", "250",
|
|
"-crf", strconv.Itoa(enc.ConstantRateFactor),
|
|
"-pix_fmt", "yuv420p",
|
|
videoFileName,
|
|
)
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
encInput, err := cmd.StdinPipe()
|
|
enc.input = encInput
|
|
if err != nil {
|
|
logrus.WithError(err).Error("can't get ffmpeg input pipe.")
|
|
}
|
|
enc.cmd = cmd
|
|
}
|
|
func (enc *X264ImageCustomEncoder) Run(videoFileName string) error {
|
|
if _, err := os.Stat(enc.FFMpegBinPath); os.IsNotExist(err) {
|
|
return err
|
|
}
|
|
|
|
enc.Init(videoFileName)
|
|
logrus.Infof("launching binary: %v", enc.cmd)
|
|
err := enc.cmd.Run()
|
|
if err != nil {
|
|
logrus.WithError(err).Errorf("error while launching ffmpeg: %v", enc.cmd.Args)
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
func (enc *X264ImageCustomEncoder) Encode(img image.Image) {
|
|
if enc.input == nil || enc.closed {
|
|
return
|
|
}
|
|
|
|
err := encodePPM(enc.input, img)
|
|
if err != nil {
|
|
logrus.WithError(err).Error("error while encoding image.")
|
|
}
|
|
}
|
|
|
|
func (enc *X264ImageCustomEncoder) Close() {
|
|
if enc.closed {
|
|
return
|
|
}
|
|
enc.closed = true
|
|
err := enc.input.Close()
|
|
if err != nil {
|
|
logrus.WithError(err).Error("could not close input.")
|
|
}
|
|
|
|
}
|