Skip to content

Commit

Permalink
Add subtitle feed filter, overlay filter and producer.
Browse files Browse the repository at this point in the history
  • Loading branch information
bmatherly authored and ddennedy committed Jul 9, 2024
1 parent f17e28c commit 05dc153
Show file tree
Hide file tree
Showing 10 changed files with 1,182 additions and 1 deletion.
6 changes: 6 additions & 0 deletions src/modules/plus/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
add_library(mltplus MODULE
subtitles/subtitles.cpp
consumer_blipflash.c
factory.c
filter_affine.c
Expand All @@ -17,13 +18,16 @@ add_library(mltplus MODULE
filter_shape.c
filter_spot_remover.c
filter_strobe.c
filter_subtitle_feed.cpp
filter_subtitle.cpp
filter_text.c
filter_threshold.c
filter_timer.c
interp.h
producer_blipflash.c
producer_count.c
producer_pgm.c
producer_subtitle.c
transition_affine.c
)

Expand Down Expand Up @@ -73,6 +77,8 @@ install(FILES
filter_shape.yml
filter_spot_remover.yml
filter_strobe.yml
filter_subtitle_feed.yml
filter_subtitle.yml
filter_text.yml
filter_threshold.yml
filter_timer.yml
Expand Down
23 changes: 22 additions & 1 deletion src/modules/plus/factory.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* factory.c -- the factory method interfaces
* Copyright (C) 2003-2018 Meltytech, LLC
* Copyright (C) 2003-2024 Meltytech, LLC
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
Expand Down Expand Up @@ -89,6 +89,14 @@ extern mlt_filter filter_strobe_init(mlt_profile profile,
mlt_service_type type,
const char *id,
char *arg);
extern mlt_filter filter_subtitle_feed_init(mlt_profile profile,
mlt_service_type type,
const char *id,
char *arg);
extern mlt_filter filter_subtitle_init(mlt_profile profile,
mlt_service_type type,
const char *id,
char *arg);
extern mlt_filter filter_text_init(mlt_profile profile,
mlt_service_type type,
const char *id,
Expand All @@ -113,6 +121,10 @@ extern mlt_producer producer_pgm_init(mlt_profile profile,
mlt_service_type type,
const char *id,
char *arg);
extern mlt_producer producer_subtitle_init(mlt_profile profile,
mlt_service_type type,
const char *id,
char *arg);
extern mlt_transition transition_affine_init(mlt_profile profile,
mlt_service_type type,
const char *id,
Expand Down Expand Up @@ -155,12 +167,15 @@ MLT_REPOSITORY
MLT_REGISTER(mlt_service_filter_type, "shape", filter_shape_init);
MLT_REGISTER(mlt_service_filter_type, "spot_remover", filter_spot_remover_init);
MLT_REGISTER(mlt_service_filter_type, "strobe", filter_strobe_init);
MLT_REGISTER(mlt_service_filter_type, "subtitle_feed", filter_subtitle_feed_init);
MLT_REGISTER(mlt_service_filter_type, "subtitle", filter_subtitle_init);
MLT_REGISTER(mlt_service_filter_type, "text", filter_text_init);
MLT_REGISTER(mlt_service_filter_type, "threshold", filter_threshold_init);
MLT_REGISTER(mlt_service_filter_type, "timer", filter_timer_init);
MLT_REGISTER(mlt_service_producer_type, "blipflash", producer_blipflash_init);
MLT_REGISTER(mlt_service_producer_type, "count", producer_count_init);
MLT_REGISTER(mlt_service_producer_type, "pgm", producer_pgm_init);
MLT_REGISTER(mlt_service_producer_type, "subtitle", producer_subtitle_init);
MLT_REGISTER(mlt_service_transition_type, "affine", transition_affine_init);
#ifdef USE_FFTW
MLT_REGISTER(mlt_service_filter_type, "dance", filter_dance_init);
Expand Down Expand Up @@ -205,6 +220,11 @@ MLT_REPOSITORY
metadata,
"filter_spot_remover.yml");
MLT_REGISTER_METADATA(mlt_service_filter_type, "strobe", metadata, "filter_strobe.yml");
MLT_REGISTER_METADATA(mlt_service_filter_type,
"subtitle_feed",
metadata,
"filter_subtitle_feed.yml");
MLT_REGISTER_METADATA(mlt_service_filter_type, "subtitle", metadata, "filter_subtitle.yml");
MLT_REGISTER_METADATA(mlt_service_filter_type, "text", metadata, "filter_text.yml");
MLT_REGISTER_METADATA(mlt_service_filter_type, "threshold", metadata, "filter_threshold.yml");
MLT_REGISTER_METADATA(mlt_service_filter_type, "timer", metadata, "filter_timer.yml");
Expand All @@ -214,6 +234,7 @@ MLT_REPOSITORY
"producer_blipflash.yml");
MLT_REGISTER_METADATA(mlt_service_producer_type, "count", metadata, "producer_count.yml");
MLT_REGISTER_METADATA(mlt_service_producer_type, "pgm", metadata, "producer_pgm.yml");
MLT_REGISTER_METADATA(mlt_service_producer_type, "subtitle", metadata, "producer_subtitle.yml");
MLT_REGISTER_METADATA(mlt_service_transition_type, "affine", metadata, "transition_affine.yml");
#ifdef USE_FFTW
MLT_REGISTER_METADATA(mlt_service_filter_type, "dance", metadata, "filter_dance.yml");
Expand Down
226 changes: 226 additions & 0 deletions src/modules/plus/filter_subtitle.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
/*
* filter_subtitle.cpp -- subtitle overlay filter
* Copyright (C) 2024 Meltytech, LLC
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/

#include <framework/mlt.h>

#include "subtitles/subtitles.h"

#include <stdlib.h>
#include <string.h>
#include <sys/stat.h> // for stat()
#include <sys/types.h> // for stat()

static void destroy_subtitles(void *p)
{
delete static_cast<Subtitles::SubtitleVector *>(p);
}

static void property_changed(mlt_service owner, mlt_filter filter, mlt_event_data event_data)
{
const char *name = mlt_event_data_to_string(event_data);
if (name && !strcmp(name, "text")) {
mlt_properties properties = MLT_FILTER_PROPERTIES(filter);
mlt_properties_set_int(properties, "_reset", 1);
}
}

static void refresh_resource_subtitles(mlt_filter filter)
{
mlt_properties properties = MLT_FILTER_PROPERTIES(filter);
const char *resource = mlt_properties_get(properties, "resource");

struct stat file_info;
if (mlt_stat(resource, &file_info)) {
mlt_log_debug(filter, "File not found %s\n", resource);
return;
}

int64_t mtime = (int64_t) file_info.st_mtime;
if (mtime != mlt_properties_get_int64(properties, "_mtime")) {
mlt_log_info(filter, "File has changed. Reopen: %s\n", resource);
Subtitles::SubtitleVector *psubtitles = new Subtitles::SubtitleVector();
if (!psubtitles) {
mlt_log_error(filter, "Unable to allocate subtitles %s\n", resource);
return;
}
*psubtitles = Subtitles::readFromSrtFile(resource);
mlt_properties_set_data(properties,
"_subtitles",
psubtitles,
0,
(mlt_destructor) destroy_subtitles,
NULL);
mlt_properties_set_int64(properties, "_mtime", mtime);
}
}

static void refresh_text_subtitles(mlt_filter filter)
{
mlt_properties properties = MLT_FILTER_PROPERTIES(filter);
if (mlt_properties_get_int(properties, "_reset")) {
const char *text = mlt_properties_get(properties, "text");
Subtitles::SubtitleVector *psubtitles = new Subtitles::SubtitleVector();
if (!psubtitles) {
mlt_log_error(filter, "Unable to allocate subtitles\n");
return;
}
*psubtitles = Subtitles::readFromSrtString(text);
mlt_properties_set_data(properties,
"_subtitles",
psubtitles,
0,
(mlt_destructor) destroy_subtitles,
NULL);
mlt_properties_clear(properties, "_reset");
}
}

static mlt_frame filter_process(mlt_filter filter, mlt_frame frame)
{
mlt_properties properties = MLT_FILTER_PROPERTIES(filter);
const char *text = NULL;
if (mlt_properties_exists(properties, "resource") || mlt_properties_exists(properties, "text")) {
if (mlt_properties_exists(properties, "resource")) {
refresh_resource_subtitles(filter);
} else if (mlt_properties_exists(properties, "text")) {
refresh_text_subtitles(filter);
}
Subtitles::SubtitleVector *psubtitles = static_cast<Subtitles::SubtitleVector *>(
mlt_properties_get_data(properties, "_subtitles", NULL));
if (!psubtitles) {
psubtitles = new Subtitles::SubtitleVector();
if (!psubtitles) {
mlt_log_error(filter, "Unable to allocate subtitles\n");
return frame;
}
mlt_properties_set_data(properties,
"_subtitles",
psubtitles,
0,
(mlt_destructor) destroy_subtitles,
NULL);
}
const Subtitles::SubtitleVector &subtitles = *psubtitles;
mlt_profile profile = mlt_service_profile(MLT_FILTER_SERVICE(filter));
int64_t frameMs = (int64_t) mlt_frame_get_position(frame) * 1000 * profile->frame_rate_den
/ profile->frame_rate_num;
int prevIndex = mlt_properties_get_int(properties, "_prevIndex");
int index = Subtitles::indexForTime(subtitles, frameMs, prevIndex);
if (index > -1) {
text = subtitles[index].text.c_str();
mlt_properties_set_int(properties, "_prevIndex", index);
} else {
return frame;
}
} else if (mlt_properties_exists(properties, "feed")) {
mlt_properties frame_properties = MLT_FRAME_PROPERTIES(frame);
mlt_properties subtitle_properties = mlt_properties_get_properties(frame_properties,
"subtitles");
if (!subtitle_properties) {
mlt_log_info(filter, "No feed subtitles found\n");
return frame;
}
char *feed = mlt_properties_get(properties, "feed");
mlt_properties feed_properties = mlt_properties_get_properties(subtitle_properties, feed);
if (!feed_properties) {
mlt_log_info(filter, "Feed %s not found\n", feed);
return frame;
}
text = mlt_properties_get(feed_properties, "text");
}

if (!text || !strcmp(text, "")) {
// Do not draw empty text
return frame;
}

mlt_filter text_filter = (mlt_filter) mlt_properties_get_data(properties, "_t", NULL);
if (!text_filter) {
mlt_log_error(MLT_FILTER_SERVICE(filter), "Text filter not found\n");
return frame;
}

// We have some text and a text filter. Let's apply them to the frame.
mlt_properties text_filter_properties
= mlt_frame_unique_properties(frame, MLT_FILTER_SERVICE(text_filter));
mlt_properties_set_string(text_filter_properties, "argument", text);
mlt_properties_pass_list(text_filter_properties,
properties,
"geometry family size weight style fgcolour bgcolour olcolour pad "
"halign valign outline opacity");
mlt_filter_set_in_and_out(text_filter, mlt_filter_get_in(filter), mlt_filter_get_out(filter));
return mlt_filter_process(text_filter, frame);
}

extern "C" {

mlt_filter filter_subtitle_init(mlt_profile profile,
mlt_service_type type,
const char *id,
char *arg)
{
mlt_filter text_filter = mlt_factory_filter(profile, "qtext", NULL);
if (!text_filter)
text_filter = mlt_factory_filter(profile, "text", NULL);
if (!text_filter) {
mlt_log_error(NULL, "[filter_subtitle] Unable to create text filter.\n");
return NULL;
}

mlt_filter filter = mlt_filter_new();

if (filter) {
mlt_properties properties = MLT_FILTER_PROPERTIES(filter);

// Assign default values
if (arg && strcmp(arg, "")) {
mlt_properties_set_string(properties, "resource", arg);
}
mlt_properties_set_string(properties, "geometry", "20%/80%:60%x20%:100");
mlt_properties_set_string(properties, "family", "Sans");
mlt_properties_set_string(properties, "size", "48");
mlt_properties_set_string(properties, "weight", "400");
mlt_properties_set_string(properties, "style", "normal");
mlt_properties_set_string(properties, "fgcolour", "0x000000ff");
mlt_properties_set_string(properties, "bgcolour", "0x00000020");
mlt_properties_set_string(properties, "olcolour", "0x00000000");
mlt_properties_set_string(properties, "pad", "0");
mlt_properties_set_string(properties, "halign", "left");
mlt_properties_set_string(properties, "valign", "top");
mlt_properties_set_string(properties, "outline", "0");
mlt_properties_set_string(properties, "opacity", "1.0");
mlt_properties_set_int(properties, "_filter_private", 1);

// Register the text filter for reuse/destruction
mlt_properties_set_data(properties,
"_t",
text_filter,
0,
(mlt_destructor) mlt_filter_close,
NULL);

filter->process = filter_process;
mlt_events_listen(properties, filter, "property-changed", (mlt_listener) property_changed);
} else {
mlt_log_error(NULL, "[filter_subtitle] Unable to allocate filter.\n");
mlt_filter_close(text_filter);
}
return filter;
}
}
Loading

0 comments on commit 05dc153

Please sign in to comment.