Skip to content

Commit 8f92bac

Browse files
authored
Merge pull request #899 from OpenShot/improved-captions
Improved Caption Effect
2 parents 70e86ef + 450af52 commit 8f92bac

File tree

9 files changed

+456
-93
lines changed

9 files changed

+456
-93
lines changed

.github/workflows/ci.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,9 @@ jobs:
202202
203203
- name: Test libopenshot
204204
run: |
205+
# Allow unit tests which require a display screen
206+
export DISPLAY=:0.0
207+
export QT_QPA_PLATFORM=offscreen
205208
cmake --build build --target coverage -- VERBOSE=1
206209
207210
- name: Install libopenshot

src/Clip.cpp

Lines changed: 32 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -164,9 +164,9 @@ Clip::Clip(std::string path) : resampler(NULL), reader(NULL), allocated_reader(N
164164
std::string ext = get_file_extension(path);
165165
std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
166166

167-
// Determine if common video formats
167+
// Determine if common video formats (or image sequences)
168168
if (ext=="avi" || ext=="mov" || ext=="mkv" || ext=="mpg" || ext=="mpeg" || ext=="mp3" || ext=="mp4" || ext=="mts" ||
169-
ext=="ogg" || ext=="wav" || ext=="wmv" || ext=="webm" || ext=="vob")
169+
ext=="ogg" || ext=="wav" || ext=="wmv" || ext=="webm" || ext=="vob" || path.find("%") != std::string::npos)
170170
{
171171
try
172172
{
@@ -443,6 +443,11 @@ std::shared_ptr<Frame> Clip::GetFrame(std::shared_ptr<openshot::Frame> backgroun
443443
// Return the frame's number so the correct keyframes are applied.
444444
original_frame->number = frame_number;
445445

446+
// Apply waveform image (if any)
447+
if (Waveform()) {
448+
apply_waveform(original_frame, background_frame->GetImage());
449+
}
450+
446451
// Apply local effects to the frame (if any)
447452
apply_effects(original_frame);
448453

@@ -1265,36 +1270,14 @@ bool Clip::isEqual(double a, double b)
12651270
// Apply keyframes to the source frame (if any)
12661271
void Clip::apply_keyframes(std::shared_ptr<Frame> frame, std::shared_ptr<QImage> background_canvas) {
12671272
// Skip out if video was disabled or only an audio frame (no visualisation in use)
1268-
if (!Waveform() && !Reader()->info.has_video) {
1273+
if (!frame->has_image_data) {
12691274
// Skip the rest of the image processing for performance reasons
12701275
return;
12711276
}
12721277

12731278
// Get image from clip
12741279
std::shared_ptr<QImage> source_image = frame->GetImage();
12751280

1276-
/* REPLACE IMAGE WITH WAVEFORM IMAGE (IF NEEDED) */
1277-
if (Waveform())
1278-
{
1279-
// Debug output
1280-
ZmqLogger::Instance()->AppendDebugMethod(
1281-
"Clip::get_transform (Generate Waveform Image)",
1282-
"frame->number", frame->number,
1283-
"Waveform()", Waveform(),
1284-
"background_canvas->width()", background_canvas->width(),
1285-
"background_canvas->height()", background_canvas->height());
1286-
1287-
// Get the color of the waveform
1288-
int red = wave_color.red.GetInt(frame->number);
1289-
int green = wave_color.green.GetInt(frame->number);
1290-
int blue = wave_color.blue.GetInt(frame->number);
1291-
int alpha = wave_color.alpha.GetInt(frame->number);
1292-
1293-
// Generate Waveform Dynamically (the size of the timeline)
1294-
source_image = frame->GetWaveform(background_canvas->width(), background_canvas->height(), red, green, blue, alpha);
1295-
frame->AddImage(source_image);
1296-
}
1297-
12981281
// Get transform from clip's keyframes
12991282
QTransform transform = get_transform(frame, background_canvas->width(), background_canvas->height());
13001283

@@ -1351,6 +1334,30 @@ void Clip::apply_keyframes(std::shared_ptr<Frame> frame, std::shared_ptr<QImage>
13511334
frame->AddImage(background_canvas);
13521335
}
13531336

1337+
// Apply apply_waveform image to the source frame (if any)
1338+
void Clip::apply_waveform(std::shared_ptr<Frame> frame, std::shared_ptr<QImage> background_canvas) {
1339+
// Get image from clip
1340+
std::shared_ptr<QImage> source_image = frame->GetImage();
1341+
1342+
// Debug output
1343+
ZmqLogger::Instance()->AppendDebugMethod(
1344+
"Clip::apply_waveform (Generate Waveform Image)",
1345+
"frame->number", frame->number,
1346+
"Waveform()", Waveform(),
1347+
"background_canvas->width()", background_canvas->width(),
1348+
"background_canvas->height()", background_canvas->height());
1349+
1350+
// Get the color of the waveform
1351+
int red = wave_color.red.GetInt(frame->number);
1352+
int green = wave_color.green.GetInt(frame->number);
1353+
int blue = wave_color.blue.GetInt(frame->number);
1354+
int alpha = wave_color.alpha.GetInt(frame->number);
1355+
1356+
// Generate Waveform Dynamically (the size of the timeline)
1357+
source_image = frame->GetWaveform(background_canvas->width(), background_canvas->height(), red, green, blue, alpha);
1358+
frame->AddImage(source_image);
1359+
}
1360+
13541361
// Apply keyframes to the source frame (if any)
13551362
QTransform Clip::get_transform(std::shared_ptr<Frame> frame, int width, int height)
13561363
{

src/Clip.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,9 @@ namespace openshot {
129129
/// Apply keyframes to an openshot::Frame and use an existing QImage as a background image (if any)
130130
void apply_keyframes(std::shared_ptr<Frame> frame, std::shared_ptr<QImage> background_canvas);
131131

132+
/// Apply waveform image to an openshot::Frame and use an existing QImage as a background image (if any)
133+
void apply_waveform(std::shared_ptr<Frame> frame, std::shared_ptr<QImage> background_canvas);
134+
132135
/// Get QTransform from keyframes
133136
QTransform get_transform(std::shared_ptr<Frame> frame, int width, int height);
134137

src/FFmpegReader.cpp

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -670,13 +670,20 @@ bool FFmpegReader::HasAlbumArt() {
670670
}
671671

672672
void FFmpegReader::UpdateAudioInfo() {
673+
// Set default audio channel layout (if needed)
674+
if (AV_GET_CODEC_ATTRIBUTES(aStream, aCodecCtx)->channel_layout == 0)
675+
AV_GET_CODEC_ATTRIBUTES(aStream, aCodecCtx)->channel_layout = av_get_default_channel_layout(AV_GET_CODEC_ATTRIBUTES(aStream, aCodecCtx)->channels);
676+
677+
if (info.sample_rate > 0) {
678+
// Skip init - if info struct already populated
679+
return;
680+
}
681+
673682
// Set values of FileInfo struct
674683
info.has_audio = true;
675684
info.file_size = pFormatCtx->pb ? avio_size(pFormatCtx->pb) : -1;
676685
info.acodec = aCodecCtx->codec->name;
677686
info.channels = AV_GET_CODEC_ATTRIBUTES(aStream, aCodecCtx)->channels;
678-
if (AV_GET_CODEC_ATTRIBUTES(aStream, aCodecCtx)->channel_layout == 0)
679-
AV_GET_CODEC_ATTRIBUTES(aStream, aCodecCtx)->channel_layout = av_get_default_channel_layout(AV_GET_CODEC_ATTRIBUTES(aStream, aCodecCtx)->channels);
680687
info.channel_layout = (ChannelLayout) AV_GET_CODEC_ATTRIBUTES(aStream, aCodecCtx)->channel_layout;
681688
info.sample_rate = AV_GET_CODEC_ATTRIBUTES(aStream, aCodecCtx)->sample_rate;
682689
info.audio_bit_rate = AV_GET_CODEC_ATTRIBUTES(aStream, aCodecCtx)->bit_rate;
@@ -720,6 +727,16 @@ void FFmpegReader::UpdateAudioInfo() {
720727
info.video_length = info.duration * info.fps.ToDouble();
721728
info.width = 720;
722729
info.height = 480;
730+
731+
// Use timeline to set correct width & height (if any)
732+
Clip *parent = (Clip *) ParentClip();
733+
if (parent) {
734+
if (parent->ParentTimeline()) {
735+
// Set max width/height based on parent clip's timeline (if attached to a timeline)
736+
info.width = parent->ParentTimeline()->preview_width;
737+
info.height = parent->ParentTimeline()->preview_height;
738+
}
739+
}
723740
}
724741

725742
// Fix invalid video lengths for certain types of files (MP3 for example)
@@ -737,6 +754,11 @@ void FFmpegReader::UpdateAudioInfo() {
737754
}
738755

739756
void FFmpegReader::UpdateVideoInfo() {
757+
if (info.vcodec.length() > 0) {
758+
// Skip init - if info struct already populated
759+
return;
760+
}
761+
740762
// Set values of FileInfo struct
741763
info.has_video = true;
742764
info.file_size = pFormatCtx->pb ? avio_size(pFormatCtx->pb) : -1;

src/FrameMapper.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -727,6 +727,9 @@ Json::Value FrameMapper::JsonValue() const {
727727
// Create root json object
728728
Json::Value root = ReaderBase::JsonValue(); // get parent properties
729729
root["type"] = "FrameMapper";
730+
if (reader) {
731+
root["reader"] = reader->JsonValue();
732+
}
730733

731734
// return JsonValue
732735
return root;
@@ -741,6 +744,12 @@ void FrameMapper::SetJson(const std::string value) {
741744
const Json::Value root = openshot::stringToJson(value);
742745
// Set all values that match
743746
SetJsonValue(root);
747+
748+
if (!root["reader"].isNull()) // does Json contain a reader?
749+
{
750+
// Placeholder to load reader
751+
// TODO: need a createReader method for this and Clip JSON methods
752+
}
744753
}
745754
catch (const std::exception& e)
746755
{

src/effects/Caption.cpp

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,8 @@ void Caption::process_regex() {
8888
caption_prepared.append("\n\n");
8989
}
9090

91-
// Parse regex and find all matches
92-
QRegularExpression allPathsRegex(QStringLiteral("(\\d{2})?:*(\\d{2}):(\\d{2}).(\\d{2,3})\\s*-->\\s*(\\d{2})?:*(\\d{2}):(\\d{2}).(\\d{2,3})([\\s\\S]*?)\\n(.*?)(?=\\n\\d{2,3}|\\Z)"), QRegularExpression::MultilineOption);
91+
// Parse regex and find all matches (i.e. 00:00.000 --> 00:10.000\ncaption-text)
92+
QRegularExpression allPathsRegex(QStringLiteral("(\\d{2})?:*(\\d{2}):(\\d{2}).(\\d{2,3})\\s*-->\\s*(\\d{2})?:*(\\d{2}):(\\d{2}).(\\d{2,3})([\\s\\S]*?)(.*?)(?=\\d{2}.\\d{2,3}|\\Z)"), QRegularExpression::MultilineOption);
9393
QRegularExpressionMatchIterator i = allPathsRegex.globalMatch(caption_prepared);
9494
while (i.hasNext()) {
9595
QRegularExpressionMatch match = i.next();
@@ -200,6 +200,13 @@ std::shared_ptr<openshot::Frame> Caption::GetFrame(std::shared_ptr<openshot::Fra
200200

201201
// Loop through words, and find word-wrap boundaries
202202
QStringList words = line.split(" ");
203+
204+
// Wrap languages which do not use spaces
205+
bool use_spaces = true;
206+
if (line.length() > 20 && words.length() == 1) {
207+
words = line.split("");
208+
use_spaces = false;
209+
}
203210
int words_remaining = words.length();
204211
while (words_remaining > 0) {
205212
bool words_displayed = false;
@@ -215,7 +222,12 @@ std::shared_ptr<openshot::Frame> Caption::GetFrame(std::shared_ptr<openshot::Fra
215222

216223
// Create path and add text to it (for correct border and fill)
217224
QPainterPath path1;
218-
QString fitting_line = words.mid(0, word_index).join(" ");
225+
QString fitting_line;
226+
if (use_spaces) {
227+
fitting_line = words.mid(0, word_index).join(" ");
228+
} else {
229+
fitting_line = words.mid(0, word_index).join("");
230+
}
219231
path1.addText(p, font, fitting_line);
220232
text_paths.push_back(path1);
221233

tests/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ set(OPENSHOT_TESTS
2424
AudioWaveformer
2525
CacheDisk
2626
CacheMemory
27+
Caption
2728
Clip
2829
Color
2930
Coordinate

0 commit comments

Comments
 (0)