Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEATURE] Add SCC support to CEA-708 decoder #1595

Merged
merged 16 commits into from
Feb 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/CHANGES.TXT
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
0.95 (to be released)
-----------------
- New: Add SCC support for CEA-708 decoder (#1595)
- Fix: respect `-stdout` even if multiple CC tracks are present in a Matroska input file (#1453)
- Fix: crash in Rust decoder on ATSC1.0 TS Files (#1407)
- Removed the --with-gui flag for linux/configure and mac/configure (use the Flutter GUI instead)
Expand Down
28 changes: 28 additions & 0 deletions src/lib_ccx/ccx_common_timing.c
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,34 @@ LLONG get_fts_max(struct ccx_common_timing_ctx *ctx)
return ctx->fts_max + ctx->fts_global;
}

/**
* SCC Time formatting
*/
size_t print_scc_time(struct ccx_boundary_time time, char *buf)
{
char *fmt = "%02u:%02u:%02u;%02u";
double frame;

frame = ((double)(time.time_in_ms - 1000 * (time.ss + 60 * (time.mm + 60 * time.hh))) * 29.97 / 1000);

return (size_t)sprintf(buf + time.set, fmt, time.hh, time.mm, time.ss, (unsigned)frame);
}

struct ccx_boundary_time get_time(LLONG time)
{
if (time < 0) // Avoid loss of data warning with abs()
time = -time;

struct ccx_boundary_time result;
result.hh = (unsigned)(time / 1000 / 60 / 60);
result.mm = (unsigned)(time / 1000 / 60 - 60 * result.hh);
result.ss = (unsigned)(time / 1000 - 60 * (result.mm + 60 * result.hh));
result.time_in_ms = time;
result.set = (time < 0 ? 1 : 0);

return result;
}

/**
* Fill buffer with a time string using specified format
* @param fmt has to contain 4 format specifiers for h, m, s and ms respectively
Expand Down
2 changes: 2 additions & 0 deletions src/lib_ccx/ccx_common_timing.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ struct ccx_common_timing_ctx *init_timing_ctx(struct ccx_common_timing_settings_

void set_current_pts(struct ccx_common_timing_ctx *ctx, LLONG pts);
void add_current_pts(struct ccx_common_timing_ctx *ctx, LLONG pts);
struct ccx_boundary_time get_time(LLONG mstime);
size_t print_scc_time(struct ccx_boundary_time time, char *buf);
int set_fts(struct ccx_common_timing_ctx *ctx);
LLONG get_fts(struct ccx_common_timing_ctx *ctx, int current_field);
LLONG get_fts_max(struct ccx_common_timing_ctx *ctx);
Expand Down
1 change: 1 addition & 0 deletions src/lib_ccx/ccx_decoders_708.h
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,7 @@ typedef struct dtvcc_tv_screen
LLONG time_ms_hide;
unsigned int cc_count;
int service_number;
int old_cc_time_end;
} dtvcc_tv_screen;

/**
Expand Down
152 changes: 152 additions & 0 deletions src/lib_ccx/ccx_decoders_708_output.c
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,155 @@ void dtvcc_write_sami(dtvcc_writer_ctx *writer, dtvcc_service_decoder *decoder,
write_wrapped(encoder->dtvcc_writers[tv->service_number - 1].fd, buf, strlen(buf));
}

unsigned char adjust_odd_parity(const unsigned char value)
{
unsigned int i, ones = 0;
for (i = 0; i < 8; i++)
{
if ((value & (1 << i)) != 0)
{
ones += 1;
}
}
if (ones % 2 == 0)
{
// make the number of ones always odd
return value | 0b10000000;
}
return value;
}

void dtvcc_write_scc_header(dtvcc_tv_screen *tv, struct encoder_ctx *encoder)
{
char *buf = (char *)encoder->buffer;
// 18 characters long + 2 new lines
memset(buf, 0, 20);
sprintf(buf, "Scenarist_SCC V1.0\n\n");

write_wrapped(encoder->dtvcc_writers[tv->service_number - 1].fd, buf, strlen(buf));
}

int count_captions_lines_scc(dtvcc_tv_screen *tv)
{
int count = 0;
for (int i = 0; i < CCX_DTVCC_SCREENGRID_ROWS; i++)
{
if (!dtvcc_is_row_empty(tv, i))
{
count++;
}
}

return count;
}

/** This function is designed to assign appropriate SSC labels for positioning subtitles based on their length.
* In some scenarios where the video stream provides lengthy subtitles that cannot fit within a single line.
* Single-line subtitle can be placed in 15th row(most bottom row)
* 2 line length subtitles can be placed in 14th and 15th row
* 3 line length subtitles can be placed in 13th, 14th and 15th row
*/
void add_needed_scc_labels(char *buf, int total_subtitle_count, int current_subtitle_count)
{
switch (total_subtitle_count)
{
case 1:
// row 15, column 00
sprintf(buf + strlen(buf), " 94e0 94e0");
break;
case 2:
// 9440: row 14, column 00 | 94e0: row 15, column 00
sprintf(buf + strlen(buf), current_subtitle_count == 1 ? " 9440 9440" : " 94e0 94e0");
break;
default:
// 13e0: row 13, column 04 | 9440: row 14, column 00 | 94e0: row 15, column 00
sprintf(buf + strlen(buf), current_subtitle_count == 1 ? " 13e0 13e0" : (current_subtitle_count == 2 ? " 9440 9440" : " 94e0 94e0"));
}
}

void dtvcc_write_scc(dtvcc_writer_ctx *writer, dtvcc_service_decoder *decoder, struct encoder_ctx *encoder)
{
dtvcc_tv_screen *tv = decoder->tv;

if (dtvcc_is_screen_empty(tv, encoder))
return;

if (tv->time_ms_show + encoder->subs_delay < 0)
return;

if (tv->cc_count == 2)
dtvcc_write_scc_header(tv, encoder);

char *buf = (char *)encoder->buffer;
struct ccx_boundary_time time_show = get_time(tv->time_ms_show + encoder->subs_delay);
// when hiding subtract a frame (1 frame = 34 ms)
struct ccx_boundary_time time_end = get_time(tv->time_ms_hide + encoder->subs_delay - 34);

if (tv->old_cc_time_end > time_show.time_in_ms)
{
// Correct the frame delay
time_show.time_in_ms -= 1000 / 29.97;
print_scc_time(time_show, buf);
sprintf(buf + strlen(buf), "\t942c 942c");
time_show.time_in_ms += 1000 / 29.97;
// Clear the buffer and start pop on caption
sprintf(buf + strlen(buf), "94ae 94ae 9420 9420");
}
else if (tv->old_cc_time_end < time_show.time_in_ms)
{
// Clear the screen for new caption
struct ccx_boundary_time time_to_display = get_time(tv->old_cc_time_end);
print_scc_time(time_to_display, buf);
sprintf(buf + strlen(buf), "\t942c 942c \n\n");
// Correct the frame delay
time_show.time_in_ms -= 1000 / 29.97;
// Clear the buffer and start pop on caption in new time
print_scc_time(time_show, buf);
sprintf(buf + strlen(buf), "\t94ae 94ae 9420 9420");
time_show.time_in_ms += 1000 / 29.97;
}
else
{
time_show.time_in_ms -= 1000 / 29.97;
print_scc_time(time_show, buf);
sprintf(buf + strlen(buf), "\t942c 942c 94ae 94ae 9420 9420");
time_show.time_in_ms += 1000 / 29.97;
}

int total_subtitle_count = count_captions_lines_scc(tv);
int current_subtitle_count = 0;

for (int i = 0; i < CCX_DTVCC_SCREENGRID_ROWS; i++)
{
if (!dtvcc_is_row_empty(tv, i))
{
current_subtitle_count++;
add_needed_scc_labels(buf, total_subtitle_count, current_subtitle_count);

int first, last, bytes_written = 0;
dtvcc_get_write_interval(tv, i, &first, &last);
for (int j = first; j <= last; j++)
{
if (bytes_written % 2 == 0)
sprintf(buf + strlen(buf), " ");
sprintf(buf + strlen(buf), "%x", adjust_odd_parity(tv->chars[i][j].sym));
bytes_written += 1;
}
// if byte pair are not even then make it even by adding 0x80 as padding
if (bytes_written % 2 == 1)
sprintf(buf + strlen(buf), "80 ");
else
sprintf(buf + strlen(buf), " ");
}
}

// Display caption (942f 942f)
sprintf(buf + strlen(buf), "942f 942f \n\n");
write_wrapped(encoder->dtvcc_writers[tv->service_number - 1].fd, buf, strlen(buf));

tv->old_cc_time_end = time_end.time_in_ms;
}

void dtvcc_write(dtvcc_writer_ctx *writer, dtvcc_service_decoder *decoder, struct encoder_ctx *encoder)
{
switch (encoder->write_format)
Expand All @@ -382,6 +531,9 @@ void dtvcc_write(dtvcc_writer_ctx *writer, dtvcc_service_decoder *decoder, struc
case CCX_OF_SAMI:
dtvcc_write_sami(writer, decoder, encoder);
break;
case CCX_OF_SCC:
dtvcc_write_scc(writer, decoder, encoder);
break;
case CCX_OF_MCC:
printf("REALLY BAD... [%s:%d]\n", __FILE__, __LINE__);
break;
Expand Down
15 changes: 9 additions & 6 deletions src/lib_ccx/ccx_decoders_708_output.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@
void dtvcc_write_done(dtvcc_tv_screen *tv, struct encoder_ctx *encoder);

void dtvcc_writer_init(dtvcc_writer_ctx *writer,
char *base_filename,
int program_number,
int service_number,
enum ccx_output_format write_format,
struct encoder_cfg *cfg);
char *base_filename,
int program_number,
int service_number,
enum ccx_output_format write_format,
struct encoder_cfg *cfg);
void dtvcc_writer_cleanup(dtvcc_writer_ctx *writer);
void dtvcc_writer_output(dtvcc_writer_ctx *writer, dtvcc_service_decoder *decoder, struct encoder_ctx *encoder);

Expand All @@ -30,6 +30,9 @@ void dtvcc_write_transcript(dtvcc_writer_ctx *writer, dtvcc_service_decoder *dec
void dtvcc_write_sami_header(dtvcc_tv_screen *tv, struct encoder_ctx *encoder);
void dtvcc_write_sami_footer(dtvcc_tv_screen *tv, struct encoder_ctx *encoder);
void dtvcc_write_sami(dtvcc_writer_ctx *writer, dtvcc_service_decoder *decoder, struct encoder_ctx *encoder);
void dtvcc_write_scc_header(dtvcc_tv_screen *tv, struct encoder_ctx *encoder);
void add_needed_scc_labels(char *buf, int total_subtitle_count, int current_subtitle_count);
void dtvcc_write_scc(dtvcc_writer_ctx *writer, dtvcc_service_decoder *decoder, struct encoder_ctx *encoder);
void dtvcc_write(dtvcc_writer_ctx *writer, dtvcc_service_decoder *decoder, struct encoder_ctx *encoder);

#endif /*_CCX_DECODERS_708_OUTPUT_H_*/
#endif /*_CCX_DECODERS_708_OUTPUT_H_*/
2 changes: 2 additions & 0 deletions src/rust/src/decoder/output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ pub struct Writer<'a> {
pub no_font_color: bool,
pub transcript_settings: &'a ccx_encoders_transcript_format,
pub no_bom: i32,
pub old_cc_time_end: i32,
}

impl<'a> Writer<'a> {
Expand All @@ -42,6 +43,7 @@ impl<'a> Writer<'a> {
no_font_color: is_true(no_font_color),
transcript_settings,
no_bom,
old_cc_time_end: 0,
}
}
/// Write subtitles to the file
Expand Down
28 changes: 28 additions & 0 deletions src/rust/src/decoder/timing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,31 @@ pub fn get_time_str(time: LLONG) -> String {
let ms = time - 1000 * (ss + 60 * (mm + 60 * hh));
format!("{:02}:{:02}:{:02},{:03}", hh, mm, ss, ms)
}

impl ccx_boundary_time {
/// Returns ccx_boundary_time from given time
pub fn get_time(time: LLONG) -> Self {
let hh = time / 1000 / 60 / 60;
let mm = time / 1000 / 60 - 60 * hh;
let ss = time / 1000 - 60 * (mm + 60 * hh);

Self {
hh: hh as i32,
mm: mm as i32,
ss: ss as i32,
time_in_ms: time,
set: Default::default(),
}
}
}

/// Returns a hh:mm:ss;frame string of time for SCC format
pub fn get_scc_time_str(time: ccx_boundary_time) -> String {
// Feel sorry for formatting:(
let frame: u8 = (((time.time_in_ms
- 1000 * ((time.ss as i64) + 60 * ((time.mm as i64) + 60 * (time.hh as i64))))
as f64)
* 29.97
/ 1000.0) as u8;
format!("{:02}:{:02}:{:02};{:02}", time.hh, time.mm, time.ss, frame)
}
Loading
Loading