Skip to content

Commit

Permalink
[FEATURE] Add SCC support to CEA-708 decoder (#1595)
Browse files Browse the repository at this point in the history
* feat: Add timing functions for SCC format in C & Rust

* feat: Add SCC support to Rust 708 decoder

* feat: Add SCC support to C 708 decoder

* docs: fix symbol in scc_time format

* chore: clippy fixes

* docs: Add new feature in Changelog

* fix: update SCC timing functions according to need

* feat: Add new member(old caption end time) for overlapping situations

* fix: update SCC timing functions according to need

* feat: Add support for overlapping captions situations

* fix: frame formula for timings

* feat: Add support for orientation of subtitles in C

by adding necessary labels needed for it

* feat: Add support for orientation of subtitles in Rust

by adding necessary labels needed for it

* docs: Add info for scc labels

* chore: clippy fixes

* docs: Add what `add_needed_scc_labels` do and correct parameters name
  • Loading branch information
IshanGrover2004 authored Feb 18, 2024
1 parent 2d2a210 commit 2ada36d
Show file tree
Hide file tree
Showing 9 changed files with 369 additions and 7 deletions.
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

0 comments on commit 2ada36d

Please sign in to comment.