Skip to content

Commit 9565cfa

Browse files
committed
Start refactoring the screenshoter
1 parent 30db31f commit 9565cfa

7 files changed

Lines changed: 135 additions & 31 deletions

File tree

go.mod

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,7 @@ require (
298298
github.com/spf13/pflag v1.0.6
299299
github.com/stretchr/testify v1.10.0
300300
github.com/xaionaro-go/audio v0.0.0-20250210102901-abfced9d5ef3
301-
github.com/xaionaro-go/avpipeline v0.0.0-20250727184631-f13f8d149b18
301+
github.com/xaionaro-go/avpipeline v0.0.0-20250809014515-3d56d4095dc4
302302
github.com/xaionaro-go/datacounter v1.0.4
303303
github.com/xaionaro-go/go-rtmp v0.0.0-20241009130244-1e3160f27f42
304304
github.com/xaionaro-go/grpcproxy v0.0.0-20241103205849-a8fef42e72f9
@@ -310,8 +310,8 @@ require (
310310
github.com/xaionaro-go/object v0.0.0-20241026212449-753ce10ec94c
311311
github.com/xaionaro-go/obs-grpc-proxy v0.0.0-20241018162120-5faf4e7a684a
312312
github.com/xaionaro-go/observability v0.0.0-20250622130956-24b7017284e4
313-
github.com/xaionaro-go/player v0.0.0-20250805195658-8ea6ef10e5ff
314-
github.com/xaionaro-go/recoder v0.0.0-20250805195121-617f4a6a1506
313+
github.com/xaionaro-go/player v0.0.0-20250809010311-a80dbec6c520
314+
github.com/xaionaro-go/recoder v0.0.0-20250809010016-1241e0b14473
315315
github.com/xaionaro-go/secret v0.0.0-20250111141743-ced12e1082c2
316316
github.com/xaionaro-go/serializable v0.0.0-20250412140540-5ac572306599
317317
github.com/xaionaro-go/timeapiio v0.0.0-20240915203246-b907cf699af3

go.sum

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1076,8 +1076,8 @@ github.com/xaionaro-go/avcommon v0.0.0-20250805213258-459b927db31a h1:dAFZ5UCjmT
10761076
github.com/xaionaro-go/avcommon v0.0.0-20250805213258-459b927db31a/go.mod h1:kjLo1LasgdDJqbTGD5bbEM+D6RiZSbf5ZT8yiPFF1BA=
10771077
github.com/xaionaro-go/avmediacodec v0.0.0-20250505012527-c819676502d8 h1:FZn9+TN3uHhohfpanWkR9lFNHApizznZbML6XjvEgTU=
10781078
github.com/xaionaro-go/avmediacodec v0.0.0-20250505012527-c819676502d8/go.mod h1:2W2Kp/HJFXcFBppQ4YytgDy/ydFL3hGc23xSB1U/Luc=
1079-
github.com/xaionaro-go/avpipeline v0.0.0-20250727184631-f13f8d149b18 h1:OfkJnBBNbr3AUbqumbGpY78W8FoEHaz0zKdKzZMmkGc=
1080-
github.com/xaionaro-go/avpipeline v0.0.0-20250727184631-f13f8d149b18/go.mod h1:eFxxNA50Pyp1B+snK0TlmpsnrGUtzJohbY/+J3BEvqA=
1079+
github.com/xaionaro-go/avpipeline v0.0.0-20250809004114-8edb93a58cf2 h1:xKetGyk2/9XGZwiupCYGJLoAkw2dl774Gp4afxLbZoY=
1080+
github.com/xaionaro-go/avpipeline v0.0.0-20250809004114-8edb93a58cf2/go.mod h1:eFxxNA50Pyp1B+snK0TlmpsnrGUtzJohbY/+J3BEvqA=
10811081
github.com/xaionaro-go/datacounter v1.0.4 h1:+QMZLmu73R5WGkQfUPwlXF/JFN+Weo4iuDZkiL2wVm8=
10821082
github.com/xaionaro-go/datacounter v1.0.4/go.mod h1:Sf9vBevuV6w5iE6K3qJ9pWVKcyS60clWBUSQLjt5++c=
10831083
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
11241124
github.com/xaionaro-go/observability v0.0.0-20250622130956-24b7017284e4/go.mod h1:GkNC0+nPJMOzotTzJVlH4+DvDg9cKrV9qqOe+8/kVw4=
11251125
github.com/xaionaro-go/player v0.0.0-20250805195658-8ea6ef10e5ff h1:sYAwCUWGRd949aybdufcTpRsZ9go4sZ8ZTpLRF3KZe0=
11261126
github.com/xaionaro-go/player v0.0.0-20250805195658-8ea6ef10e5ff/go.mod h1:ZPfygPU8g18dyvOnYCPdjvHT7h1Q80eG/rfmWOT6Vls=
1127+
github.com/xaionaro-go/player v0.0.0-20250809010311-a80dbec6c520 h1:1KtKt2EBle1k7Tk7Kfg5vT92RHC6u7C3FtzLVKe1cAM=
1128+
github.com/xaionaro-go/player v0.0.0-20250809010311-a80dbec6c520/go.mod h1:9XTkb7WxVOYnW+UJ1+ajMzueOW1nv4uuRsPADWmzH0M=
11271129
github.com/xaionaro-go/proxy v0.0.0-20250525144747-579f5a891c15 h1:Qqoy9MDWq2Yh6uazAqQDzqU0doalTL3tRjNCo7X7GXA=
11281130
github.com/xaionaro-go/proxy v0.0.0-20250525144747-579f5a891c15/go.mod h1:6kxHtLmOImv/zwXSvaI1CW9Q8Pw+m5b891ZoejMKHPA=
11291131
github.com/xaionaro-go/pulse v0.0.0-20241023202712-7151fa00d4bb h1:9iHPI27CYbmJDhzEuCABQthE/DGVNvT60ybWvv3BV8w=
11301132
github.com/xaionaro-go/pulse v0.0.0-20241023202712-7151fa00d4bb/go.mod h1:cpYspI6YljhkUf1WLXLLDmeaaPFc3CnGLjDZf9dZ4no=
1131-
github.com/xaionaro-go/recoder v0.0.0-20250805195121-617f4a6a1506 h1:0kFR1IYlNdr8WdomIXe9zndAHdqI+9Z+yxyzT33sw58=
1132-
github.com/xaionaro-go/recoder v0.0.0-20250805195121-617f4a6a1506/go.mod h1:LStqQ27RH8oRj0rMC3zSzzfHms9vuRAorrZU8gP9WYg=
1133+
github.com/xaionaro-go/recoder v0.0.0-20250809010016-1241e0b14473 h1:VYIpYsjoZ3NdvaGLyMKR0yRqzb3vxyXI8/5LW8nu97w=
1134+
github.com/xaionaro-go/recoder v0.0.0-20250809010016-1241e0b14473/go.mod h1:Gv9ermjaTFxgqb499Jt6aHrWP/hNgRsqSvlQixG3sa4=
11331135
github.com/xaionaro-go/secret v0.0.0-20250111141743-ced12e1082c2 h1:QHpTWfyfmz65cE0MtFXe9fScdi+X0VIYR2wgolSYEUk=
11341136
github.com/xaionaro-go/secret v0.0.0-20250111141743-ced12e1082c2/go.mod h1:XKoHGZ4VKMbVBl8VotLIoWQdrB6Q7jnR++RbkiegZFU=
11351137
github.com/xaionaro-go/serializable v0.0.0-20250412140540-5ac572306599 h1:CzcQd6wLiqgjd8K/6UzR5uyt6sg4ut/kVxi6+FJMbdI=
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package main
2+
3+
func assertNoError(err error) {
4+
if err != nil {
5+
panic(err)
6+
}
7+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"flag"
6+
"fmt"
7+
"image"
8+
"image/png"
9+
"os"
10+
"time"
11+
12+
"github.com/facebookincubator/go-belt"
13+
"github.com/facebookincubator/go-belt/tool/logger"
14+
"github.com/facebookincubator/go-belt/tool/logger/implementation/logrus"
15+
"github.com/xaionaro-go/streamctl/pkg/screenshot"
16+
"github.com/xaionaro-go/streamctl/pkg/screenshoter"
17+
)
18+
19+
func main() {
20+
xMin := flag.Int("x-min", 0, "")
21+
xMax := flag.Int("x-max", 100, "")
22+
yMin := flag.Int("y-min", 0, "")
23+
yMax := flag.Int("y-max", 100, "")
24+
fps := flag.Float64("fps", 30, "")
25+
outputFile := flag.String("output-file", "", "")
26+
flag.Parse()
27+
28+
l := logrus.Default().WithLevel(logger.LevelDebug)
29+
ctx := context.Background()
30+
ctx = logger.CtxWithLogger(ctx, l)
31+
logger.Default = func() logger.Logger {
32+
return l
33+
}
34+
defer belt.Flush(ctx)
35+
36+
h := screenshoter.New()
37+
38+
startedAt := time.Now()
39+
frameCount := 0
40+
err := h.Loop(
41+
ctx,
42+
time.Duration(float64(time.Second) / *fps),
43+
screenshot.Config{
44+
Bounds: image.Rectangle{
45+
Min: image.Point{
46+
X: *xMin,
47+
Y: *yMin,
48+
},
49+
Max: image.Point{
50+
X: *xMax,
51+
Y: *yMax,
52+
},
53+
},
54+
},
55+
func(_ context.Context, img image.Image) {
56+
frameCount++
57+
fps := float64(frameCount) / time.Since(startedAt).Seconds()
58+
fmt.Printf("received a picture; overall FPS: %f\n", fps)
59+
if *outputFile == "" {
60+
return
61+
}
62+
63+
func() {
64+
f, err := os.OpenFile(*outputFile, os.O_CREATE|os.O_WRONLY, 0644)
65+
if err != nil {
66+
logger.Error(ctx, "unable to open '%s'", *outputFile)
67+
return
68+
}
69+
defer f.Close()
70+
71+
err = png.Encode(f, img)
72+
if err != nil {
73+
logger.Errorf(ctx, "unable to encode the image to PNG: %w", err)
74+
return
75+
}
76+
}()
77+
},
78+
)
79+
assertNoError(err)
80+
}

pkg/screenshoter/screenshoter.go

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,37 +10,38 @@ import (
1010
)
1111

1212
type ScreenshotEngine interface {
13-
Screenshot(cfg screenshot.Config) (*image.RGBA, error)
13+
Screenshot(cfg screenshot.Config) (image.Image, error)
1414
}
1515

1616
type Screenshoter struct {
1717
ScreenshotEngine ScreenshotEngine
1818
}
1919

20-
func New(
21-
engine ScreenshotEngine,
22-
) *Screenshoter {
23-
return &Screenshoter{
24-
ScreenshotEngine: engine,
25-
}
20+
type ScreenshotImplementation struct{}
21+
22+
func (ScreenshotImplementation) Screenshot(cfg screenshot.Config) (image.Image, error) {
23+
return screenshot.Implementation{}.Screenshot(cfg)
2624
}
2725

28-
func (s *Screenshoter) Engine() ScreenshotEngine {
29-
return s.ScreenshotEngine
26+
func New() *Screenshoter {
27+
return &Screenshoter{
28+
ScreenshotEngine: ScreenshotImplementation{},
29+
}
3030
}
3131

32+
// TODO: add the support of Wayland
3233
func (s *Screenshoter) Loop(
3334
ctx context.Context,
3435
interval time.Duration,
3536
config screenshot.Config,
36-
callback func(context.Context, *image.RGBA),
37-
) {
37+
callback func(context.Context, image.Image),
38+
) error {
3839
t := time.NewTicker(interval)
3940
defer t.Stop()
4041
for {
4142
select {
4243
case <-ctx.Done():
43-
return
44+
return ctx.Err()
4445
case <-t.C:
4546
}
4647
img, err := s.ScreenshotEngine.Screenshot(config)

pkg/streampanel/image.go

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,18 @@ import (
1717
"github.com/facebookincubator/go-belt/tool/logger"
1818
"github.com/xaionaro-go/observability"
1919
"github.com/xaionaro-go/streamctl/pkg/screenshot"
20-
"github.com/xaionaro-go/streamctl/pkg/screenshoter"
2120
streamdconsts "github.com/xaionaro-go/streamctl/pkg/streamd/consts"
2221
"github.com/xaionaro-go/streamctl/pkg/streampanel/consts"
2322
"github.com/xaionaro-go/xsync"
2423
)
2524

2625
type Screenshoter interface {
27-
Engine() screenshoter.ScreenshotEngine
2826
Loop(
2927
ctx context.Context,
3028
interval time.Duration,
3129
config screenshot.Config,
32-
callback func(context.Context, *image.RGBA),
33-
)
30+
callback func(context.Context, image.Image),
31+
) error
3432
}
3533

3634
func (p *Panel) setImage(
@@ -158,13 +156,27 @@ func imgFitTo(src image.Image, size image.Point) (image.Image, error) {
158156
factor = math.Min(factor, float64(size.Y)/float64(sizeCur.Y))
159157
newWidth := int(float64(sizeCur.X) * factor)
160158
newHeight := int(float64(sizeCur.Y) * factor)
161-
output := image.NewRGBA(image.Rectangle{Max: image.Point{
159+
newSize := image.Rectangle{Max: image.Point{
162160
X: newWidth,
163161
Y: newHeight,
164-
}})
162+
}}
163+
var output image.Image
164+
165+
switch src := src.(type) {
166+
case *image.RGBA:
167+
output = image.NewRGBA(newSize)
168+
case *image.RGBA64:
169+
output = image.NewRGBA64(newSize)
170+
case *image.Gray:
171+
output = image.NewGray(newSize)
172+
case *image.YCbCr:
173+
output = image.NewYCbCr(newSize, src.SubsampleRatio)
174+
default:
175+
return nil, fmt.Errorf("image format %T is not supported, yet", src)
176+
}
165177
err := rez.Convert(output, src, rez.NewBicubicFilter())
166178
if err != nil {
167-
return nil, err
179+
return nil, fmt.Errorf("unable to convert: %w", err)
168180
}
169181
return output, nil
170182
}
@@ -269,7 +281,7 @@ func (p *Panel) setScreenshot(
269281
screenshot image.Image,
270282
) {
271283
bounds := screenshot.Bounds()
272-
logger.Tracef(ctx, "screenshot bounds: %#+v", bounds)
284+
logger.Tracef(ctx, "screenshot %T bounds: %#+v", screenshot, bounds)
273285
if bounds.Max.X == 0 || bounds.Max.Y == 0 {
274286
p.DisplayError(fmt.Errorf("received an empty screenshot"))
275287
p.screenshoterLocker.Do(ctx, func() {
@@ -313,12 +325,15 @@ func (p *Panel) reinitScreenshoter(ctx context.Context) {
313325
ctx, cancelFunc := context.WithCancel(ctx)
314326
p.screenshoterClose = cancelFunc
315327
observability.Go(ctx, func(ctx context.Context) {
316-
p.Screenshoter.Loop(
328+
err := p.Screenshoter.Loop(
317329
ctx,
318330
200*time.Millisecond,
319331
p.Config.Screenshot.Config,
320-
func(ctx context.Context, img *image.RGBA) { p.setScreenshot(ctx, img) },
332+
func(ctx context.Context, img image.Image) { p.setScreenshot(ctx, img) },
321333
)
334+
if err != nil {
335+
logger.Errorf(ctx, "unable to run the screenshoter loop: %v", err)
336+
}
322337
})
323338
})
324339
}

pkg/streampanel/panel.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ import (
3434
"github.com/xaionaro-go/streamctl/pkg/command"
3535
gconsts "github.com/xaionaro-go/streamctl/pkg/consts"
3636
"github.com/xaionaro-go/streamctl/pkg/oauthhandler"
37-
"github.com/xaionaro-go/streamctl/pkg/screenshot"
3837
"github.com/xaionaro-go/streamctl/pkg/screenshoter"
3938
"github.com/xaionaro-go/streamctl/pkg/streamcontrol"
4039
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/kick"
@@ -204,7 +203,7 @@ func New(
204203
p := &Panel{
205204
configPath: configPath,
206205
Config: Options(opts).ApplyOverrides(cfg),
207-
Screenshoter: screenshoter.New(screenshot.Implementation{}),
206+
Screenshoter: screenshoter.New(),
208207
imageLastDownloaded: map[consts.ImageID][]byte{},
209208
imageLastParsed: map[consts.ImageID]image.Image{},
210209
streamStatus: map[streamcontrol.PlatformName]*streamStatus{},

0 commit comments

Comments
 (0)