Skip to content

Commit 42fe461

Browse files
authored
Add converting after stream (MKVod action) (#1605)
* Added video conversion * Fixed a bug * Changed logging behaviour * Changed logging behaviour 2
1 parent 2140cdf commit 42fe461

5 files changed

Lines changed: 48 additions & 38 deletions

File tree

runner/handlers.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ func (r *Runner) RequestStream(ctx context.Context, req *protobuf.StreamRequest)
3030
actions.MkVOD,
3131
}
3232

33-
jID := r.RunAction(a, data)
33+
jID := r.RunAction(a, data, r.log.With("stream_id", req.GetStreamId(), "stream_version", req.GetVersion(), "input", req.GetInput()))
3434
r.log.Info("job added", "ID", jID)
3535

3636
return &protobuf.StreamResponse{JobId: ptr.Take(jID)}, nil

runner/pkg/actions/mkvod.go

Lines changed: 33 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ import (
99
"log/slog"
1010
"net/url"
1111
"os"
12+
"os/exec"
1213
"path"
1314
"strings"
14-
15-
log "github.com/sirupsen/logrus"
15+
"time"
1616

1717
"github.com/tum-dev/gocast/runner/config"
1818
"github.com/tum-dev/gocast/runner/pkg/metrics"
@@ -21,7 +21,7 @@ import (
2121

2222
// MkVOD takes a stream that was streamed and moves the hls stream to long term storage.
2323
// Additionally, the playlist type is transformed from event to VOD.
24-
func MkVOD(_ context.Context, _ *slog.Logger, notify chan *protobuf.Notification, d map[string]any, metrics *metrics.Broker) error {
24+
func MkVOD(ctx context.Context, logger *slog.Logger, notify chan *protobuf.Notification, d map[string]any, metrics *metrics.Broker) error {
2525
streamID, ok := d["streamID"].(uint64)
2626
if !ok {
2727
return AbortingError(fmt.Errorf("no stream id in context"))
@@ -35,6 +35,11 @@ func MkVOD(_ context.Context, _ *slog.Logger, notify chan *protobuf.Notification
3535
return AbortingError(fmt.Errorf("no recordingDir in context"))
3636
}
3737

38+
metrics.ConvertingProgresses.With(metrics.With().Stream(streamID).L()).Inc()
39+
defer func() {
40+
metrics.ConvertingProgresses.With(metrics.With().Stream(streamID).L()).Dec()
41+
}()
42+
3843
vodDir := path.Join(config.Config.StoragePath, fmt.Sprintf("%d", streamID), streamVersion)
3944
err := os.MkdirAll(vodDir, os.ModePerm)
4045
if err != nil {
@@ -48,27 +53,13 @@ func MkVOD(_ context.Context, _ *slog.Logger, notify chan *protobuf.Notification
4853

4954
for _, entry := range recordingContent {
5055
if entry.IsDir() {
51-
log.Warn("found dir in recordingDir, skipping", "name", entry.Name())
56+
logger.Warn("found dir in recordingDir, skipping", "name", entry.Name())
5257
continue
5358
}
5459
if entry.Name() == "playlist.m3u8" {
55-
srcPlst, err := os.Open(path.Join(recordingDir, entry.Name()))
56-
if err != nil {
57-
return AbortingError(fmt.Errorf("open recording playlist: %w", err))
58-
}
59-
dstPlstS := vodFromEventPlst(srcPlst)
60-
dstPlst, err := os.Create(path.Join(vodDir, entry.Name()))
61-
if err != nil {
62-
return AbortingError(fmt.Errorf("create vod playlist: %w", err))
63-
}
64-
_, err = io.WriteString(dstPlst, dstPlstS)
65-
if err != nil {
66-
return fmt.Errorf("write vod to playlist: %w", err)
67-
}
68-
_ = dstPlst.Close()
60+
err = convertStream(ctx, logger, streamID, path.Join(recordingDir, entry.Name()), vodDir, entry.Name())
6961
continue
7062
}
71-
err = copyFile(path.Join(recordingDir, entry.Name()), path.Join(vodDir, entry.Name()))
7263
}
7364

7465
vodUrl, err := url.JoinPath(config.Config.EdgeServer, fmt.Sprintf("%d", streamID), streamVersion, "playlist.m3u8")
@@ -87,24 +78,36 @@ func MkVOD(_ context.Context, _ *slog.Logger, notify chan *protobuf.Notification
8778
return nil
8879
}
8980

90-
func copyFile(sourcePath, destPath string) error {
91-
inputFile, err := os.Open(sourcePath)
81+
func convertStream(ctx context.Context, logger *slog.Logger, streamID uint64, streamPath, vodDir string, playlistName string) error {
82+
input := "-i " + streamPath
83+
options := "-c copy -f hls -hls_time 240 -hls_playlist_type event -hls_flags append_list -hls_segment_filename " + path.Join(vodDir, "%05d.ts") + " " + path.Join(vodDir, playlistName)
84+
85+
args := strings.Split(input, " ")
86+
args = append(args, strings.Split(options, " ")...)
87+
command := exec.CommandContext(ctx, "ffmpeg", args...)
88+
89+
// give ffmpeg 10 seconds on sigterm (context cancellation) to shut down before sending sigkill.
90+
command.WaitDelay = 10 * time.Second
91+
92+
logger.Info("starting ffmpeg", "command", command.String())
93+
stderr, err := command.StderrPipe()
9294
if err != nil {
93-
return fmt.Errorf("open source file: %w", err)
95+
return err
9496
}
95-
defer inputFile.Close()
97+
go logCmdPipe(logger, stderr, []any{"stream", streamID, "logStream", "stderr"})
9698

97-
outputFile, err := os.Create(destPath)
99+
stdo, err := command.StdoutPipe()
98100
if err != nil {
99-
return fmt.Errorf("open dest file: %w", err)
101+
return err
100102
}
101-
defer outputFile.Close()
102-
103-
_, err = io.Copy(outputFile, inputFile)
103+
go logCmdPipe(logger, stdo, []any{"stream", streamID, "logStream", "stdout"})
104+
err = command.Run()
104105
if err != nil {
105-
return fmt.Errorf("copy to dest from source: %w", err)
106+
logger.Error("ffmpeg command failed", "error", err)
107+
} else {
108+
logger.Info("ffmpeg converting completed successfully", "stream_id", streamID)
106109
}
107-
return nil
110+
return err
108111
}
109112

110113
// vodFromEventPlst modifies an HLS playlist from EVENT to VOD

runner/pkg/actions/stream.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ func Stream(ctx context.Context, log *slog.Logger, notify chan *protobuf.Notific
5050
if !ok {
5151
return AbortingError(fmt.Errorf("no input in context"))
5252
}
53-
log = log.With("stream_id", streamID)
5453

5554
metrics.Streams.With(metrics.With().Stream(streamID).Source(input).L()).Inc()
5655
defer func() {

runner/pkg/metrics/broker.go

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,10 @@ import (
1313

1414
// Broker manages Prometheus metrics.
1515
type Broker struct {
16-
port int
17-
Streams *prometheus.GaugeVec
18-
StreamErrors *prometheus.CounterVec
16+
port int
17+
Streams *prometheus.GaugeVec
18+
StreamErrors *prometheus.CounterVec
19+
ConvertingProgresses *prometheus.GaugeVec
1920
}
2021

2122
// Option represents a functional option for configuring a Broker.
@@ -45,7 +46,14 @@ func NewBroker(options ...Option) *Broker {
4546
Subsystem: "stream",
4647
Name: "n_errors",
4748
Help: "Number of stream ffmpeg errors",
48-
}, []string{"stream_id", "source"}),
49+
}, []string{"stream_id"}),
50+
51+
ConvertingProgresses: promauto.NewGaugeVec(prometheus.GaugeOpts{
52+
Namespace: "runner",
53+
Subsystem: "converting",
54+
Name: "n_converting",
55+
Help: "Number of streams currently being converted",
56+
}, []string{"stream_id"}),
4957
}
5058
for _, option := range options {
5159
option(b)

runner/runner.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ func (r *Runner) InitApiGrpc() {
155155
}
156156
}
157157

158-
func (r *Runner) RunAction(a []actions.Action, data map[string]any) string {
158+
func (r *Runner) RunAction(a []actions.Action, data map[string]any, logger *slog.Logger) string {
159159
// create new context to avoid cancellation on grpc request termination
160160
c, cancel := context.WithCancel(context.Background())
161161
job := uuid.New().String()
@@ -169,7 +169,7 @@ func (r *Runner) RunAction(a []actions.Action, data map[string]any) string {
169169
}()
170170
for _, action := range a {
171171
for {
172-
log := r.log.With("action", getFunctionName(action)).With("job", job)
172+
log := logger.With("action", getFunctionName(action)).With("job", job)
173173
log.Info("running action")
174174
s := time.Now()
175175
err := action(c, log, r.notifications, data, r.Metrics)

0 commit comments

Comments
 (0)