[FIX] Route --hardsubx --tickertext through subtitle encoder (#2230)

* fix(hardsubx): route --tickertext through subtitle encoder (#2229)

* chore(pr): drop changelog entry for bug fix
This commit is contained in:
Rizky Mirzaviandy Priambodo
2026-03-29 03:00:20 +07:00
committed by GitHub
parent 1b4123b302
commit 47ad8388b1
2 changed files with 84 additions and 20 deletions

View File

@@ -96,9 +96,13 @@ void _display_frame(struct lib_hardsubx_ctx *ctx, AVFrame *frame, int width, int
int hardsubx_process_frames_tickertext(struct lib_hardsubx_ctx *ctx, struct encoder_ctx *enc_ctx)
{
// Search for ticker text at the bottom of the screen, such as in Russia TV1 or stock prices
int prev_sub_encoded = 1; // Previous seen subtitle encoded or not
int dist = 0;
int cur_sec = 0, total_sec, progress;
int frame_number = 0;
int64_t prev_begin_time = 0, prev_end_time = 0; // Begin and end time of previous seen subtitle
char *ticker_text = NULL;
char *prev_ticker_text = NULL;
while (av_read_frame(ctx->format_ctx, &ctx->packet) >= 0)
{
@@ -109,6 +113,9 @@ int hardsubx_process_frames_tickertext(struct lib_hardsubx_ctx *ctx, struct enco
avcodec_send_packet(ctx->codec_ctx, &ctx->packet);
if (avcodec_receive_frame(ctx->codec_ctx, ctx->frame) == 0 && frame_number % 1000 == 0)
{
int64_t current_pts_ms = 0;
current_pts_ms = convert_pts_to_ms(ctx->packet.pts, ctx->format_ctx->streams[ctx->video_stream_id]->time_base);
// sws_scale is used to convert the pixel format to RGB24 from all other cases
sws_scale(
ctx->sws_ctx,
@@ -120,19 +127,88 @@ int hardsubx_process_frames_tickertext(struct lib_hardsubx_ctx *ctx, struct enco
ctx->rgb_frame->linesize);
ticker_text = _process_frame_tickertext(ctx, ctx->rgb_frame, ctx->codec_ctx->width, ctx->codec_ctx->height, frame_number);
printf("frame_number: %d\n", frame_number);
if (strlen(ticker_text) > 0)
printf("%s\n", ticker_text);
if (ticker_text)
{
char *ticker_text_copy = strdup(ticker_text);
free_rust_c_string(ticker_text);
ticker_text = ticker_text_copy;
}
cur_sec = (int)convert_pts_to_s(ctx->packet.pts, ctx->format_ctx->streams[ctx->video_stream_id]->time_base);
total_sec = (int)convert_pts_to_s(ctx->format_ctx->duration, AV_TIME_BASE_Q);
progress = (cur_sec * 100) / total_sec;
activity_progress(progress, cur_sec / 60, cur_sec % 60);
if ((!ticker_text && !prev_ticker_text) || (ticker_text && !strlen(ticker_text) && !prev_ticker_text))
{
prev_end_time = current_pts_ms;
}
if (ticker_text)
{
char *double_enter = strstr(ticker_text, "\n\n");
if (double_enter != NULL)
*(double_enter) = '\0';
}
if (!prev_sub_encoded && prev_ticker_text)
{
if (ticker_text)
{
dist = edit_distance(ticker_text, prev_ticker_text, (int)strlen(ticker_text), (int)strlen(prev_ticker_text));
if (dist < (0.2 * MIN(strlen(ticker_text), strlen(prev_ticker_text))))
{
dist = -1;
free(ticker_text);
ticker_text = NULL;
prev_end_time = current_pts_ms;
}
}
if (dist != -1)
{
add_cc_sub_text(ctx->dec_sub, prev_ticker_text, prev_begin_time, prev_end_time, "", "BURN", CCX_ENC_UTF_8);
encode_sub(enc_ctx, ctx->dec_sub);
prev_begin_time = prev_end_time + 1;
free(prev_ticker_text);
prev_ticker_text = NULL;
prev_sub_encoded = 1;
prev_end_time = current_pts_ms;
if (ticker_text)
{
prev_ticker_text = ticker_text;
ticker_text = NULL;
prev_sub_encoded = 0;
}
}
dist = 0;
}
if (!prev_ticker_text && ticker_text)
{
prev_begin_time = prev_end_time + 1;
prev_end_time = current_pts_ms;
prev_ticker_text = ticker_text;
ticker_text = NULL;
prev_sub_encoded = 0;
}
// Free any unconsumed C-owned ticker text from this iteration.
free(ticker_text);
ticker_text = NULL;
}
}
av_packet_unref(&ctx->packet);
}
if (!prev_sub_encoded)
{
add_cc_sub_text(ctx->dec_sub, prev_ticker_text, prev_begin_time, prev_end_time, "", "BURN", CCX_ENC_UTF_8);
encode_sub(enc_ctx, ctx->dec_sub);
prev_sub_encoded = 1;
}
// Cleanup
free(prev_ticker_text);
activity_progress(100, cur_sec / 60, cur_sec % 60);
return 0;
}

View File

@@ -4,9 +4,7 @@ use leptonica_sys::*;
// #[cfg(feature = "hardsubx_ocr")]
// use rsmpeg::*;
use std::eprintln;
use std::ffi;
use std::format;
use std::os::raw::c_char;
use std::process::exit;
use std::ptr::null;
@@ -255,7 +253,7 @@ pub unsafe extern "C" fn _process_frame_tickertext(
frame: *mut AVFrame,
width: ::std::os::raw::c_int,
height: ::std::os::raw::c_int,
index: ::std::os::raw::c_int,
_index: ::std::os::raw::c_int,
) -> *mut ::std::os::raw::c_char {
let mut im: *mut Pix = pixCreate(width, height, 32);
let mut lum_im: *mut Pix = pixCreate(width, height, 32);
@@ -290,7 +288,7 @@ pub unsafe extern "C" fn _process_frame_tickertext(
let mut feat_im: *mut Pix = pixCreate(width, height, 32);
for i in (92 * (height / 100))..height {
for i in ((92 * height) / 100)..height {
for j in 0..width {
let mut p1: u32 = 0;
let mut p2: u32 = 0;
@@ -306,17 +304,7 @@ pub unsafe extern "C" fn _process_frame_tickertext(
}
}
let subtitle_text = get_ocr_text_simple_threshold(ctx, lum_im, 0.0);
let write_path: String = format!("./lum_im{}.jpg", index);
let write_path_c: *mut c_char = string_to_c_char(&write_path);
pixWrite(write_path_c, lum_im, IFF_JFIF_JPEG as i32);
let _dealloc = std::ffi::CString::from_raw(write_path_c); // for memory reasons
let write_path: String = format!("./im{}.jpg", index);
let write_path_c: *mut c_char = string_to_c_char(&write_path);
pixWrite(write_path_c, lum_im, IFF_JFIF_JPEG as i32);
let _dealloc = std::ffi::CString::from_raw(write_path_c); // for memory reasons
let subtitle_text = dispatch_classifier_functions(ctx, feat_im);
pixDestroy(&mut im as *mut *mut Pix);
pixDestroy(&mut gray_im as *mut *mut Pix);
@@ -326,5 +314,5 @@ pub unsafe extern "C" fn _process_frame_tickertext(
pixDestroy(&mut lum_im as *mut *mut Pix);
pixDestroy(&mut feat_im as *mut *mut Pix);
subtitle_text
string_to_c_char(&subtitle_text)
}