mirror of
https://github.com/xaionaro-go/streamctl.git
synced 2025-09-27 03:45:52 +08:00
Start refactoring the screenshoter
This commit is contained in:
6
go.mod
6
go.mod
@@ -298,7 +298,7 @@ require (
|
||||
github.com/spf13/pflag v1.0.6
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/xaionaro-go/audio v0.0.0-20250210102901-abfced9d5ef3
|
||||
github.com/xaionaro-go/avpipeline v0.0.0-20250727184631-f13f8d149b18
|
||||
github.com/xaionaro-go/avpipeline v0.0.0-20250809014515-3d56d4095dc4
|
||||
github.com/xaionaro-go/datacounter v1.0.4
|
||||
github.com/xaionaro-go/go-rtmp v0.0.0-20241009130244-1e3160f27f42
|
||||
github.com/xaionaro-go/grpcproxy v0.0.0-20241103205849-a8fef42e72f9
|
||||
@@ -310,8 +310,8 @@ require (
|
||||
github.com/xaionaro-go/object v0.0.0-20241026212449-753ce10ec94c
|
||||
github.com/xaionaro-go/obs-grpc-proxy v0.0.0-20241018162120-5faf4e7a684a
|
||||
github.com/xaionaro-go/observability v0.0.0-20250622130956-24b7017284e4
|
||||
github.com/xaionaro-go/player v0.0.0-20250805195658-8ea6ef10e5ff
|
||||
github.com/xaionaro-go/recoder v0.0.0-20250805195121-617f4a6a1506
|
||||
github.com/xaionaro-go/player v0.0.0-20250809010311-a80dbec6c520
|
||||
github.com/xaionaro-go/recoder v0.0.0-20250809010016-1241e0b14473
|
||||
github.com/xaionaro-go/secret v0.0.0-20250111141743-ced12e1082c2
|
||||
github.com/xaionaro-go/serializable v0.0.0-20250412140540-5ac572306599
|
||||
github.com/xaionaro-go/timeapiio v0.0.0-20240915203246-b907cf699af3
|
||||
|
10
go.sum
10
go.sum
@@ -1076,8 +1076,8 @@ github.com/xaionaro-go/avcommon v0.0.0-20250805213258-459b927db31a h1:dAFZ5UCjmT
|
||||
github.com/xaionaro-go/avcommon v0.0.0-20250805213258-459b927db31a/go.mod h1:kjLo1LasgdDJqbTGD5bbEM+D6RiZSbf5ZT8yiPFF1BA=
|
||||
github.com/xaionaro-go/avmediacodec v0.0.0-20250505012527-c819676502d8 h1:FZn9+TN3uHhohfpanWkR9lFNHApizznZbML6XjvEgTU=
|
||||
github.com/xaionaro-go/avmediacodec v0.0.0-20250505012527-c819676502d8/go.mod h1:2W2Kp/HJFXcFBppQ4YytgDy/ydFL3hGc23xSB1U/Luc=
|
||||
github.com/xaionaro-go/avpipeline v0.0.0-20250727184631-f13f8d149b18 h1:OfkJnBBNbr3AUbqumbGpY78W8FoEHaz0zKdKzZMmkGc=
|
||||
github.com/xaionaro-go/avpipeline v0.0.0-20250727184631-f13f8d149b18/go.mod h1:eFxxNA50Pyp1B+snK0TlmpsnrGUtzJohbY/+J3BEvqA=
|
||||
github.com/xaionaro-go/avpipeline v0.0.0-20250809004114-8edb93a58cf2 h1:xKetGyk2/9XGZwiupCYGJLoAkw2dl774Gp4afxLbZoY=
|
||||
github.com/xaionaro-go/avpipeline v0.0.0-20250809004114-8edb93a58cf2/go.mod h1:eFxxNA50Pyp1B+snK0TlmpsnrGUtzJohbY/+J3BEvqA=
|
||||
github.com/xaionaro-go/datacounter v1.0.4 h1:+QMZLmu73R5WGkQfUPwlXF/JFN+Weo4iuDZkiL2wVm8=
|
||||
github.com/xaionaro-go/datacounter v1.0.4/go.mod h1:Sf9vBevuV6w5iE6K3qJ9pWVKcyS60clWBUSQLjt5++c=
|
||||
github.com/xaionaro-go/eventbus v0.0.0-20250720144534-4670758005d9 h1:ZAm8ueMw5D85LDeV1Kboc3ANqXr3LK/eXIl9hj1BJyM=
|
||||
@@ -1124,12 +1124,14 @@ github.com/xaionaro-go/observability v0.0.0-20250622130956-24b7017284e4 h1:dlsJ1
|
||||
github.com/xaionaro-go/observability v0.0.0-20250622130956-24b7017284e4/go.mod h1:GkNC0+nPJMOzotTzJVlH4+DvDg9cKrV9qqOe+8/kVw4=
|
||||
github.com/xaionaro-go/player v0.0.0-20250805195658-8ea6ef10e5ff h1:sYAwCUWGRd949aybdufcTpRsZ9go4sZ8ZTpLRF3KZe0=
|
||||
github.com/xaionaro-go/player v0.0.0-20250805195658-8ea6ef10e5ff/go.mod h1:ZPfygPU8g18dyvOnYCPdjvHT7h1Q80eG/rfmWOT6Vls=
|
||||
github.com/xaionaro-go/player v0.0.0-20250809010311-a80dbec6c520 h1:1KtKt2EBle1k7Tk7Kfg5vT92RHC6u7C3FtzLVKe1cAM=
|
||||
github.com/xaionaro-go/player v0.0.0-20250809010311-a80dbec6c520/go.mod h1:9XTkb7WxVOYnW+UJ1+ajMzueOW1nv4uuRsPADWmzH0M=
|
||||
github.com/xaionaro-go/proxy v0.0.0-20250525144747-579f5a891c15 h1:Qqoy9MDWq2Yh6uazAqQDzqU0doalTL3tRjNCo7X7GXA=
|
||||
github.com/xaionaro-go/proxy v0.0.0-20250525144747-579f5a891c15/go.mod h1:6kxHtLmOImv/zwXSvaI1CW9Q8Pw+m5b891ZoejMKHPA=
|
||||
github.com/xaionaro-go/pulse v0.0.0-20241023202712-7151fa00d4bb h1:9iHPI27CYbmJDhzEuCABQthE/DGVNvT60ybWvv3BV8w=
|
||||
github.com/xaionaro-go/pulse v0.0.0-20241023202712-7151fa00d4bb/go.mod h1:cpYspI6YljhkUf1WLXLLDmeaaPFc3CnGLjDZf9dZ4no=
|
||||
github.com/xaionaro-go/recoder v0.0.0-20250805195121-617f4a6a1506 h1:0kFR1IYlNdr8WdomIXe9zndAHdqI+9Z+yxyzT33sw58=
|
||||
github.com/xaionaro-go/recoder v0.0.0-20250805195121-617f4a6a1506/go.mod h1:LStqQ27RH8oRj0rMC3zSzzfHms9vuRAorrZU8gP9WYg=
|
||||
github.com/xaionaro-go/recoder v0.0.0-20250809010016-1241e0b14473 h1:VYIpYsjoZ3NdvaGLyMKR0yRqzb3vxyXI8/5LW8nu97w=
|
||||
github.com/xaionaro-go/recoder v0.0.0-20250809010016-1241e0b14473/go.mod h1:Gv9ermjaTFxgqb499Jt6aHrWP/hNgRsqSvlQixG3sa4=
|
||||
github.com/xaionaro-go/secret v0.0.0-20250111141743-ced12e1082c2 h1:QHpTWfyfmz65cE0MtFXe9fScdi+X0VIYR2wgolSYEUk=
|
||||
github.com/xaionaro-go/secret v0.0.0-20250111141743-ced12e1082c2/go.mod h1:XKoHGZ4VKMbVBl8VotLIoWQdrB6Q7jnR++RbkiegZFU=
|
||||
github.com/xaionaro-go/serializable v0.0.0-20250412140540-5ac572306599 h1:CzcQd6wLiqgjd8K/6UzR5uyt6sg4ut/kVxi6+FJMbdI=
|
||||
|
7
pkg/screenshoter/cmd/screenshoter/assert.go
Normal file
7
pkg/screenshoter/cmd/screenshoter/assert.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package main
|
||||
|
||||
func assertNoError(err error) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
80
pkg/screenshoter/cmd/screenshoter/main.go
Normal file
80
pkg/screenshoter/cmd/screenshoter/main.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/png"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/facebookincubator/go-belt"
|
||||
"github.com/facebookincubator/go-belt/tool/logger"
|
||||
"github.com/facebookincubator/go-belt/tool/logger/implementation/logrus"
|
||||
"github.com/xaionaro-go/streamctl/pkg/screenshot"
|
||||
"github.com/xaionaro-go/streamctl/pkg/screenshoter"
|
||||
)
|
||||
|
||||
func main() {
|
||||
xMin := flag.Int("x-min", 0, "")
|
||||
xMax := flag.Int("x-max", 100, "")
|
||||
yMin := flag.Int("y-min", 0, "")
|
||||
yMax := flag.Int("y-max", 100, "")
|
||||
fps := flag.Float64("fps", 30, "")
|
||||
outputFile := flag.String("output-file", "", "")
|
||||
flag.Parse()
|
||||
|
||||
l := logrus.Default().WithLevel(logger.LevelDebug)
|
||||
ctx := context.Background()
|
||||
ctx = logger.CtxWithLogger(ctx, l)
|
||||
logger.Default = func() logger.Logger {
|
||||
return l
|
||||
}
|
||||
defer belt.Flush(ctx)
|
||||
|
||||
h := screenshoter.New()
|
||||
|
||||
startedAt := time.Now()
|
||||
frameCount := 0
|
||||
err := h.Loop(
|
||||
ctx,
|
||||
time.Duration(float64(time.Second) / *fps),
|
||||
screenshot.Config{
|
||||
Bounds: image.Rectangle{
|
||||
Min: image.Point{
|
||||
X: *xMin,
|
||||
Y: *yMin,
|
||||
},
|
||||
Max: image.Point{
|
||||
X: *xMax,
|
||||
Y: *yMax,
|
||||
},
|
||||
},
|
||||
},
|
||||
func(_ context.Context, img image.Image) {
|
||||
frameCount++
|
||||
fps := float64(frameCount) / time.Since(startedAt).Seconds()
|
||||
fmt.Printf("received a picture; overall FPS: %f\n", fps)
|
||||
if *outputFile == "" {
|
||||
return
|
||||
}
|
||||
|
||||
func() {
|
||||
f, err := os.OpenFile(*outputFile, os.O_CREATE|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "unable to open '%s'", *outputFile)
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
err = png.Encode(f, img)
|
||||
if err != nil {
|
||||
logger.Errorf(ctx, "unable to encode the image to PNG: %w", err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
},
|
||||
)
|
||||
assertNoError(err)
|
||||
}
|
@@ -10,37 +10,38 @@ import (
|
||||
)
|
||||
|
||||
type ScreenshotEngine interface {
|
||||
Screenshot(cfg screenshot.Config) (*image.RGBA, error)
|
||||
Screenshot(cfg screenshot.Config) (image.Image, error)
|
||||
}
|
||||
|
||||
type Screenshoter struct {
|
||||
ScreenshotEngine ScreenshotEngine
|
||||
}
|
||||
|
||||
func New(
|
||||
engine ScreenshotEngine,
|
||||
) *Screenshoter {
|
||||
type ScreenshotImplementation struct{}
|
||||
|
||||
func (ScreenshotImplementation) Screenshot(cfg screenshot.Config) (image.Image, error) {
|
||||
return screenshot.Implementation{}.Screenshot(cfg)
|
||||
}
|
||||
|
||||
func New() *Screenshoter {
|
||||
return &Screenshoter{
|
||||
ScreenshotEngine: engine,
|
||||
ScreenshotEngine: ScreenshotImplementation{},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Screenshoter) Engine() ScreenshotEngine {
|
||||
return s.ScreenshotEngine
|
||||
}
|
||||
|
||||
// TODO: add the support of Wayland
|
||||
func (s *Screenshoter) Loop(
|
||||
ctx context.Context,
|
||||
interval time.Duration,
|
||||
config screenshot.Config,
|
||||
callback func(context.Context, *image.RGBA),
|
||||
) {
|
||||
callback func(context.Context, image.Image),
|
||||
) error {
|
||||
t := time.NewTicker(interval)
|
||||
defer t.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
return ctx.Err()
|
||||
case <-t.C:
|
||||
}
|
||||
img, err := s.ScreenshotEngine.Screenshot(config)
|
||||
|
@@ -17,20 +17,18 @@ import (
|
||||
"github.com/facebookincubator/go-belt/tool/logger"
|
||||
"github.com/xaionaro-go/observability"
|
||||
"github.com/xaionaro-go/streamctl/pkg/screenshot"
|
||||
"github.com/xaionaro-go/streamctl/pkg/screenshoter"
|
||||
streamdconsts "github.com/xaionaro-go/streamctl/pkg/streamd/consts"
|
||||
"github.com/xaionaro-go/streamctl/pkg/streampanel/consts"
|
||||
"github.com/xaionaro-go/xsync"
|
||||
)
|
||||
|
||||
type Screenshoter interface {
|
||||
Engine() screenshoter.ScreenshotEngine
|
||||
Loop(
|
||||
ctx context.Context,
|
||||
interval time.Duration,
|
||||
config screenshot.Config,
|
||||
callback func(context.Context, *image.RGBA),
|
||||
)
|
||||
callback func(context.Context, image.Image),
|
||||
) error
|
||||
}
|
||||
|
||||
func (p *Panel) setImage(
|
||||
@@ -158,13 +156,27 @@ func imgFitTo(src image.Image, size image.Point) (image.Image, error) {
|
||||
factor = math.Min(factor, float64(size.Y)/float64(sizeCur.Y))
|
||||
newWidth := int(float64(sizeCur.X) * factor)
|
||||
newHeight := int(float64(sizeCur.Y) * factor)
|
||||
output := image.NewRGBA(image.Rectangle{Max: image.Point{
|
||||
newSize := image.Rectangle{Max: image.Point{
|
||||
X: newWidth,
|
||||
Y: newHeight,
|
||||
}})
|
||||
}}
|
||||
var output image.Image
|
||||
|
||||
switch src := src.(type) {
|
||||
case *image.RGBA:
|
||||
output = image.NewRGBA(newSize)
|
||||
case *image.RGBA64:
|
||||
output = image.NewRGBA64(newSize)
|
||||
case *image.Gray:
|
||||
output = image.NewGray(newSize)
|
||||
case *image.YCbCr:
|
||||
output = image.NewYCbCr(newSize, src.SubsampleRatio)
|
||||
default:
|
||||
return nil, fmt.Errorf("image format %T is not supported, yet", src)
|
||||
}
|
||||
err := rez.Convert(output, src, rez.NewBicubicFilter())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("unable to convert: %w", err)
|
||||
}
|
||||
return output, nil
|
||||
}
|
||||
@@ -269,7 +281,7 @@ func (p *Panel) setScreenshot(
|
||||
screenshot image.Image,
|
||||
) {
|
||||
bounds := screenshot.Bounds()
|
||||
logger.Tracef(ctx, "screenshot bounds: %#+v", bounds)
|
||||
logger.Tracef(ctx, "screenshot %T bounds: %#+v", screenshot, bounds)
|
||||
if bounds.Max.X == 0 || bounds.Max.Y == 0 {
|
||||
p.DisplayError(fmt.Errorf("received an empty screenshot"))
|
||||
p.screenshoterLocker.Do(ctx, func() {
|
||||
@@ -313,12 +325,15 @@ func (p *Panel) reinitScreenshoter(ctx context.Context) {
|
||||
ctx, cancelFunc := context.WithCancel(ctx)
|
||||
p.screenshoterClose = cancelFunc
|
||||
observability.Go(ctx, func(ctx context.Context) {
|
||||
p.Screenshoter.Loop(
|
||||
err := p.Screenshoter.Loop(
|
||||
ctx,
|
||||
200*time.Millisecond,
|
||||
p.Config.Screenshot.Config,
|
||||
func(ctx context.Context, img *image.RGBA) { p.setScreenshot(ctx, img) },
|
||||
func(ctx context.Context, img image.Image) { p.setScreenshot(ctx, img) },
|
||||
)
|
||||
if err != nil {
|
||||
logger.Errorf(ctx, "unable to run the screenshoter loop: %v", err)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@@ -34,7 +34,6 @@ import (
|
||||
"github.com/xaionaro-go/streamctl/pkg/command"
|
||||
gconsts "github.com/xaionaro-go/streamctl/pkg/consts"
|
||||
"github.com/xaionaro-go/streamctl/pkg/oauthhandler"
|
||||
"github.com/xaionaro-go/streamctl/pkg/screenshot"
|
||||
"github.com/xaionaro-go/streamctl/pkg/screenshoter"
|
||||
"github.com/xaionaro-go/streamctl/pkg/streamcontrol"
|
||||
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/kick"
|
||||
@@ -204,7 +203,7 @@ func New(
|
||||
p := &Panel{
|
||||
configPath: configPath,
|
||||
Config: Options(opts).ApplyOverrides(cfg),
|
||||
Screenshoter: screenshoter.New(screenshot.Implementation{}),
|
||||
Screenshoter: screenshoter.New(),
|
||||
imageLastDownloaded: map[consts.ImageID][]byte{},
|
||||
imageLastParsed: map[consts.ImageID]image.Image{},
|
||||
streamStatus: map[streamcontrol.PlatformName]*streamStatus{},
|
||||
|
Reference in New Issue
Block a user