Skip to content

Commit 939a9f5

Browse files
authored
Merge pull request #387 from bcc-code/feat/make-shorts-publish-ready
Update shorts generation to handle subtitles and audio
2 parents 7a73f6f + 314a3bd commit 939a9f5

File tree

2 files changed

+66
-52
lines changed

2 files changed

+66
-52
lines changed

activities/crop_shorts.go

Lines changed: 38 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,18 @@ package activities
33
import (
44
"context"
55
"fmt"
6+
"github.com/bcc-code/bcc-media-flows/paths"
67
"math"
78
"strconv"
89
"strings"
10+
11+
"github.com/bcc-code/bcc-media-flows/services/ffmpeg"
912
)
1013

1114
type CropShortInput struct {
12-
InputVideoPath string
13-
AudioVideoPath string
14-
OutputVideoPath string
15+
InputVideoPath paths.Path
16+
OutputVideoPath paths.Path
17+
SubtitlePath paths.Path
1518
KeyFrames []Keyframe
1619
InSeconds float64
1720
OutSeconds float64
@@ -24,28 +27,48 @@ type CropShortResult struct {
2427
func (ua UtilActivities) CropShortActivity(ctx context.Context, params CropShortInput) (*CropShortResult, error) {
2528
cropFilter := buildCropFilter(params.KeyFrames)
2629

30+
info, err := ffmpeg.GetStreamInfo(params.InputVideoPath.Local())
31+
rate := 25
32+
if err == nil && info.FrameRate > 40 {
33+
rate = 50
34+
}
35+
36+
// Build filter: crop, then optional subtitle burn-in, then label as [v]
37+
filter := fmt.Sprintf("[0:v]%s", cropFilter)
38+
if params.SubtitlePath.Local() != "" {
39+
filter += ",subtitles=" + params.SubtitlePath.Local()
40+
}
41+
filter += "[v]"
42+
2743
args := []string{
28-
"-i", params.InputVideoPath,
29-
"-i", params.AudioVideoPath,
44+
"-i", params.InputVideoPath.Local(),
45+
"-progress", "pipe:1",
46+
"-hide_banner",
47+
"-strict", "unofficial",
3048
"-filter_complex",
31-
fmt.Sprintf(
32-
"[0:v]%s[v]; [1:a]atrim=start=%.3f:end=%.3f,asetpts=PTS-STARTPTS[a]",
33-
cropFilter, params.InSeconds, params.OutSeconds,
34-
),
49+
filter,
3550
"-map", "[v]",
36-
"-map", "[a]",
37-
"-c:v", "libx264",
38-
"-c:a", "aac",
39-
"-pix_fmt", "yuv420p",
51+
"-c:v", "prores",
52+
"-profile:v", "3",
53+
"-vendor", "ap10",
54+
"-bits_per_mb", "8000",
55+
"-r", strconv.Itoa(rate),
56+
"-pix_fmt", "yuv422p10le",
57+
"-color_primaries", "bt709",
58+
"-color_trc", "bt709",
59+
"-colorspace", "bt709",
4060
"-y",
41-
params.OutputVideoPath,
61+
params.OutputVideoPath.Local(),
4262
}
4363
return &CropShortResult{Arguments: args}, nil
4464
}
4565

4666
func buildCropFilter(keyframes []Keyframe) string {
4767
if len(keyframes) == 0 {
48-
return "crop=960:540:489:29"
68+
// Default: portrait 9:16 crop, centered horizontally, full frame height.
69+
// Ensure even dimensions for codec compatibility.
70+
// width = floor(in_h*9/16) rounded to even, height = in_h, x = centered even, y = 0
71+
return "crop=floor(in_h*9/16/2)*2:in_h:floor((in_w-out_w)/2/2)*2:0"
4972
}
5073
if len(keyframes) == 1 {
5174
kf := keyframes[0]

workflows/export/generate_short.go

Lines changed: 28 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import (
1717

1818
type GenerateShortResult struct {
1919
VideoFile *paths.Path
20+
AudioFiles map[string]paths.Path
21+
SubtitleFiles map[string]paths.Path
2022
ShortVideoFile *paths.Path
2123
Keyframes []activities.Keyframe
2224
}
@@ -52,15 +54,24 @@ func GenerateShort(ctx workflow.Context, params GenerateShortDataParams) (*Gener
5254
return nil, validationError("InSeconds must be < OutSeconds")
5355
}
5456

55-
originalFile, err := wfutils.Execute(ctx, activities.Vidispine.GetFileFromVXActivity, vsactivity.GetFileFromVXParams{
56-
VXID: params.VXID,
57-
Tags: []string{"original"},
57+
exportData, err := wfutils.Execute(ctx, activities.Vidispine.GetExportDataActivity, vsactivity.GetExportDataParams{
58+
VXID: params.VXID,
59+
Languages: []string{"no", "de", "en"},
60+
AudioSource: vidispine.ExportAudioSourceEmbedded.Value,
61+
Subclip: "",
62+
SubsAllowAI: true,
5863
}).Result(ctx)
5964

6065
if err != nil {
6166
return nil, err
6267
}
6368

69+
if len(exportData.Clips) != 1 {
70+
return nil, fmt.Errorf("only one clip supported, got %d", len(exportData.Clips))
71+
}
72+
73+
// transcriptFile := exportData.Clips[0].JSONTranscriptFile
74+
6475
activityOptions := wfutils.GetDefaultActivityOptions()
6576
ctx = workflow.WithActivityOptions(ctx, activityOptions)
6677

@@ -75,43 +86,21 @@ func GenerateShort(ctx workflow.Context, params GenerateShortDataParams) (*Gener
7586
return nil, err
7687
}
7788

78-
originalFileName := originalFile.FilePath
79-
fileNameWithoutExt := originalFileName.BaseNoExt()
80-
titleWithShort := fileNameWithoutExt + "_short"
81-
82-
clip := vidispine.Clip{
83-
VideoFile: originalFileName.Linux(),
84-
InSeconds: params.InSeconds,
85-
OutSeconds: params.OutSeconds,
86-
SequenceIn: 0,
87-
SequenceOut: params.OutSeconds - params.InSeconds,
88-
AudioFiles: nil,
89-
SubtitleFiles: nil,
90-
JSONTranscriptFile: "",
91-
VXID: "",
92-
}
93-
94-
data := vidispine.ExportData{
95-
Clips: []*vidispine.Clip{&clip},
96-
SafeTitle: titleWithShort,
97-
Title: titleWithShort,
98-
ImportDate: nil,
99-
BmmTitle: nil,
100-
BmmTrackID: nil,
101-
OriginalLanguage: "no",
102-
TranscribedLanguage: "",
103-
}
89+
titleWithShort := exportData.Title + "_short"
90+
clip := exportData.Clips[0]
91+
clip.InSeconds = params.InSeconds
92+
clip.OutSeconds = params.OutSeconds
10493

10594
mergeExportDataParams := MergeExportDataParams{
106-
ExportData: &data,
95+
ExportData: exportData,
10796
TempDir: tempFolder,
10897
SubtitlesDir: subtitlesOutputDir,
10998
MakeVideo: true,
110-
MakeAudio: false,
99+
MakeAudio: true,
111100
MakeSubtitles: true,
112101
MakeTranscript: true,
113-
Languages: []string{"no"},
114-
OriginalLanguage: data.OriginalLanguage,
102+
Languages: []string{"no", "de", "en"},
103+
OriginalLanguage: exportData.OriginalLanguage,
115104
}
116105

117106
var clipResult MergeExportDataResult
@@ -163,15 +152,15 @@ func GenerateShort(ctx workflow.Context, params GenerateShortDataParams) (*Gener
163152
}
164153
}
165154

166-
shortVideoPath := tempFolder.Append(titleWithShort + "_cropped.mp4")
155+
shortVideoPath := tempFolder.Append(titleWithShort + "_cropped.mov")
167156

168157
var cropRes activities.CropShortResult
169158
err = wfutils.Execute(ctx,
170159
activities.Util.CropShortActivity,
171160
activities.CropShortInput{
172-
InputVideoPath: clipResult.VideoFile.Local(),
173-
AudioVideoPath: originalFileName.Linux(),
174-
OutputVideoPath: shortVideoPath.Local(),
161+
InputVideoPath: *clipResult.VideoFile,
162+
OutputVideoPath: shortVideoPath,
163+
SubtitlePath: clipResult.SubtitleFiles["no"],
175164
KeyFrames: keyframes,
176165
InSeconds: params.InSeconds,
177166
OutSeconds: params.OutSeconds,
@@ -195,5 +184,7 @@ func GenerateShort(ctx workflow.Context, params GenerateShortDataParams) (*Gener
195184
VideoFile: clipResult.VideoFile,
196185
ShortVideoFile: &shortVideoPath,
197186
Keyframes: keyframes,
187+
AudioFiles: clipResult.AudioFiles,
188+
SubtitleFiles: clipResult.SubtitleFiles,
198189
}, nil
199190
}

0 commit comments

Comments
 (0)