Files
ccextractor/src/lib_ccx/ccx_encoders_common.c

1538 lines
45 KiB
C

#include "ccx_decoders_common.h"
#include "ccx_encoders_common.h"
#include "utility.h"
#include "ocr.h"
#include "ccx_decoders_608.h"
#include "ccx_decoders_708.h"
#include "ccx_decoders_708_output.h"
#include "ccx_encoders_xds.h"
#include "ccx_encoders_helpers.h"
#include "ccextractor.h"
#ifdef WIN32
int fsync(int fd)
{
return FlushFileBuffers((HANDLE)_get_osfhandle(fd)) ? 0 : -1;
}
#endif
int ccxr_get_str_basic(unsigned char *out_buffer, unsigned char *in_buffer, int trim_subs,
enum ccx_encoding_type in_enc, enum ccx_encoding_type out_enc, int max_len);
// These are the default settings for plain transcripts. No times, no CC or caption mode, and no XDS.
ccx_encoders_transcript_format ccx_encoders_default_transcript_settings =
{
.showStartTime = 0,
.showEndTime = 0,
.showMode = 0,
.showCC = 0,
.relativeTimestamp = 1,
.xds = 0,
.useColors = 1,
.isFinal = 0};
// TODO sami header doesn't care about CRLF/LF option
static const char *sami_header = // TODO: Revise the <!-- comments
"<SAMI>\n\
<HEAD>\n\
<STYLE TYPE=\"text/css\">\n\
<!--\n\
P {margin-left: 16pt; margin-right: 16pt; margin-bottom: 16pt; margin-top: 16pt;\n\
text-align: center; font-size: 18pt; font-family: arial; font-weight: bold; color: #f0f0f0;}\n\
.UNKNOWNCC {Name:Unknown; lang:en-US; SAMIType:CC;}\n\
-->\n\
</STYLE>\n\
</HEAD>\n\n\
<BODY>\n";
static const char *ssa_header =
"[Script Info]\n\
Title: Default file\n\
ScriptType: v4.00+\n\
PlayResX: 384\n\
PlayResY: 288\n\
\n\
[V4+ Styles]\n\
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding\n\
Style: Default,Arial,20,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,1,1,2,10,10,10,0\n\
\n\
[Events]\n\
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text\n\
\n";
/*
TODO -- Set correct values for "ttp:dropMode", "ttp:frameRate" .Using common for now.
Extra reference,
24 frame/sec (film, ATSC, 2k, 4k, 6k)
25 frame/sec (PAL (Europe, Uruguay, Argentina, Australia), SECAM, DVB, ATSC)
29.97 (30 ÷ 1.001) frame/sec (NTSC American System (US, Canada, Mexico, Colombia, etc.), ATSC, PAL-M (Brazil))
30 frame/sec (ATSC)
Find framerate, then use it find dropmode.
*/
static const char *smptett_header = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\" ?>\n"
" <tt xmlns=\"http://www.w3.org/ns/ttml\" xmlns:ttp=\"http://www.w3.org/ns/ttml#parameter\" ttp:dropMode=\"dropNTSC\" ttp:frameRate=\"30\" ttp:frameRateMultiplier=\"1000 1001\" ttp:timeBase=\"smpte\" xmlns:m608=\"http://www.smpte-ra.org/schemas/2052-1/2010/smpte-tt#cea608\" xmlns:smpte=\"http://www.smpte-ra.org/schemas/2052-1/2010/smpte-tt\" xmlns:ttm=\"http://www.w3.org/ns/ttml#metadata\" xmlns:tts=\"http://www.w3.org/ns/ttml#styling\">\n"
// " <tt xmlns=\"http://www.w3.org/ns/ttml\" xmlns:ttp=\"http://www.w3.org/ns/ttml#parameter\" xmlns:m608=\"http://www.smpte-ra.org/schemas/2052-1/2010/smpte-tt#cea608\" xmlns:smpte=\"http://www.smpte-ra.org/schemas/2052-1/2010/smpte-tt\" xmlns:ttm=\"http://www.w3.org/ns/ttml#metadata\" xmlns:tts=\"http://www.w3.org/ns/ttml#styling\">\n"
" <head>\n"
" <styling>\n"
" <style tts:color=\"white\" tts:fontFamily=\"monospace\" tts:fontWeight=\"normal\" tts:textAlign=\"left\" xml:id=\"basic\"/>\n"
" </styling>\n"
" <layout>\n"
" <region tts:backgroundColor=\"transparent\" xml:id=\"pop1\"/>\n"
" <region tts:backgroundColor=\"transparent\" xml:id=\"paint\"/>\n"
" <region tts:backgroundColor=\"transparent\" xml:id=\"rollup2\"/>\n"
" <region tts:backgroundColor=\"transparent\" xml:id=\"rollup3\"/>\n"
" <region tts:backgroundColor=\"transparent\" xml:id=\"rollup4\"/>\n"
" </layout>\n"
" <metadata/>\n"
" <smpte:information m608:captionService=\"F1C1CC\" m608:channel=\"cc1\"/>\n"
" </head>\n"
" <body>\n"
" <div>\n";
static const char *webvtt_header[] = {"WEBVTT", "\r\n", NULL};
static const char *simple_xml_header = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<captions>\r\n";
static const char CCD_HEADER[] = "SCC_disassembly V1.2";
static const char SCC_HEADER[] = "Scenarist_SCC V1.0";
void find_limit_characters(const unsigned char *line, int *first_non_blank, int *last_non_blank, int max_len)
{
*last_non_blank = -1;
*first_non_blank = -1;
for (int i = 0; i < max_len; i++)
{
unsigned char c = line[i];
if (c != ' ' && c != 0x89)
{
if (*first_non_blank == -1)
*first_non_blank = i;
*last_non_blank = i;
}
if (c == '\0' || c == '\n' || c == '\r')
break;
}
}
int get_str_basic(unsigned char *out_buffer, unsigned char *in_buffer, int trim_subs,
enum ccx_encoding_type in_enc, enum ccx_encoding_type out_enc, int max_len)
{
return ccxr_get_str_basic(out_buffer, in_buffer, trim_subs, in_enc, out_enc, max_len);
}
int write_subtitle_file_footer(struct encoder_ctx *ctx, struct ccx_s_write *out)
{
int used;
int ret = 0;
char str[1024];
switch (ctx->write_format)
{
case CCX_OF_SAMI:
snprintf((char *)str, sizeof(str), "</BODY></SAMI>\n");
if (ctx->encoding != CCX_ENC_UNICODE)
{
dbg_print(CCX_DMT_DECODER_608, "\r%s\n", str);
}
used = encode_line(ctx, ctx->buffer, (unsigned char *)str);
ret = write(out->fh, ctx->buffer, used);
if (ret != used)
{
mprint("WARNING: loss of data\n");
}
break;
case CCX_OF_SMPTETT:
snprintf((char *)str, sizeof(str), " </div>\n </body>\n</tt>\n");
if (ctx->encoding != CCX_ENC_UNICODE)
{
dbg_print(CCX_DMT_DECODER_608, "\r%s\n", str);
}
used = encode_line(ctx, ctx->buffer, (unsigned char *)str);
ret = write(out->fh, ctx->buffer, used);
if (ret != used)
{
mprint("WARNING: loss of data\n");
}
break;
case CCX_OF_SPUPNG:
write_spumux_footer(out);
break;
case CCX_OF_SIMPLE_XML:
snprintf((char *)str, sizeof(str), "</captions>\n");
if (ctx->encoding != CCX_ENC_UNICODE)
{
dbg_print(CCX_DMT_DECODER_608, "\r%s\n", str);
}
used = encode_line(ctx, ctx->buffer, (unsigned char *)str);
ret = write(out->fh, ctx->buffer, used);
if (ret != used)
{
mprint("WARNING: loss of data\n");
}
break;
case CCX_OF_SCC:
case CCX_OF_CCD:
ret = write(out->fh, ctx->encoded_crlf, ctx->encoded_crlf_length);
break;
case CCX_OF_WEBVTT:
// Ensure WebVTT header is written even if no subtitles were found (issue #1743)
// This is required for HLS compatibility
if (!ctx->wrote_webvtt_header)
{
write_webvtt_header(ctx);
}
break;
default: // Nothing to do, no footer on this format
break;
}
return ret;
}
static int write_bom(struct encoder_ctx *ctx, struct ccx_s_write *out)
{
int ret = 0;
if (!ctx->no_bom)
{
if (ctx->encoding == CCX_ENC_UTF_8)
{ // Write BOM
ret = write(out->fh, UTF8_BOM, sizeof(UTF8_BOM));
if (ret < sizeof(UTF8_BOM))
{
mprint("WARNING: Unable to write UTF BOM\n");
return -1;
}
}
if (ctx->encoding == CCX_ENC_UNICODE)
{ // Write BOM
ret = write(out->fh, LITTLE_ENDIAN_BOM, sizeof(LITTLE_ENDIAN_BOM));
if (ret < sizeof(LITTLE_ENDIAN_BOM))
{
mprint("WARNING: Unable to write LITTLE_ENDIAN_BOM \n");
return -1;
}
}
}
return ret;
}
// Returns -1 on error and 0 on success
static int write_subtitle_file_header(struct encoder_ctx *ctx, struct ccx_s_write *out)
{
int used;
int header_size = 0;
switch (ctx->write_format)
{
case CCX_OF_CCD:
if (write(out->fh, CCD_HEADER, sizeof(CCD_HEADER) - 1) == -1 || write(out->fh, ctx->encoded_crlf, ctx->encoded_crlf_length) == -1)
{
mprint("Unable to write CCD header to file\n");
return -1;
}
break;
case CCX_OF_SCC:
if (write(out->fh, SCC_HEADER, sizeof(SCC_HEADER) - 1) == -1)
{
mprint("Unable to write SCC header to file\n");
return -1;
}
break;
case CCX_OF_SRT: // Subrip subtitles have no header
case CCX_OF_G608:
if (write_bom(ctx, out) < 0)
return -1;
break;
case CCX_OF_SSA:
if (write_bom(ctx, out) < 0)
return -1;
REQUEST_BUFFER_CAPACITY(ctx, strlen(ssa_header) * 3);
used = encode_line(ctx, ctx->buffer, (unsigned char *)ssa_header);
if (write(out->fh, ctx->buffer, used) < used)
{
mprint("WARNING: Unable to write complete Buffer \n");
return -1;
}
break;
case CCX_OF_WEBVTT:
if (write_bom(ctx, out) < 0)
return -1;
for (int i = 0; webvtt_header[i] != NULL; i++)
{
header_size += strlen(webvtt_header[i]); // Find total size of the header
}
REQUEST_BUFFER_CAPACITY(ctx, header_size * 3);
for (int i = 0; webvtt_header[i] != NULL; i++)
{
if (ccx_options.enc_cfg.line_terminator_lf == 1 && strcmp(webvtt_header[i], "\r\n") == 0) // If -lf parameter passed, write LF instead of CRLF
{
used = encode_line(ctx, ctx->buffer, (unsigned char *)"\n");
}
else
{
used = encode_line(ctx, ctx->buffer, (unsigned char *)webvtt_header[i]);
}
if (write(out->fh, ctx->buffer, used) < used)
{
mprint("WARNING: Unable to write complete Buffer \n");
return -1;
}
}
break;
case CCX_OF_SAMI: // This header brought to you by McPoodle's CCASDI
// fprintf_encoded (wb->fh, sami_header);
if (write_bom(ctx, out) < 0)
return -1;
REQUEST_BUFFER_CAPACITY(ctx, strlen(sami_header) * 3);
used = encode_line(ctx, ctx->buffer, (unsigned char *)sami_header);
if (write(out->fh, ctx->buffer, used) < used)
{
mprint("WARNING: Unable to write complete Buffer \n");
return -1;
}
break;
case CCX_OF_SMPTETT: // This header brought to you by McPoodle's CCASDI
// fprintf_encoded (wb->fh, sami_header);
if (write_bom(ctx, out) < 0)
return -1;
REQUEST_BUFFER_CAPACITY(ctx, strlen(smptett_header) * 3);
used = encode_line(ctx, ctx->buffer, (unsigned char *)smptett_header);
if (write(out->fh, ctx->buffer, used) < used)
{
mprint("WARNING: Unable to write complete Buffer \n");
return -1;
}
break;
case CCX_OF_RCWT: // Write header
rcwt_header[7] = ctx->in_fileformat; // sets file format version
if (ctx->send_to_srv)
net_send_header(rcwt_header, sizeof(rcwt_header));
else
{
if (write(out->fh, rcwt_header, sizeof(rcwt_header)) < 0)
{
mprint("Unable to write rcwt header\n");
return -1;
}
}
break;
case CCX_OF_RAW:
if (write(out->fh, BROADCAST_HEADER, sizeof(BROADCAST_HEADER)) < sizeof(BROADCAST_HEADER))
{
mprint("Unable to write Raw header\n");
return -1;
}
break;
case CCX_OF_SPUPNG:
if (write_bom(ctx, out) < 0)
return -1;
write_spumux_header(ctx, out);
break;
case CCX_OF_TRANSCRIPT: // No header. Fall thru
if (write_bom(ctx, out) < 0)
return -1;
break;
case CCX_OF_SIMPLE_XML: // No header. Fall thru
if (write_bom(ctx, out) < 0)
return -1;
REQUEST_BUFFER_CAPACITY(ctx, strlen(simple_xml_header) * 3);
used = encode_line(ctx, ctx->buffer, (unsigned char *)simple_xml_header);
if (write(out->fh, ctx->buffer, used) < used)
{
mprint("WARNING: Unable to write complete Buffer \n");
return -1;
}
break;
case CCX_OF_MCC:
ctx->header_printed_flag = CCX_FALSE;
break;
default:
break;
}
return 0;
}
int write_cc_subtitle_as_simplexml(struct cc_subtitle *sub, struct encoder_ctx *context)
{
int length;
int ret = 0;
char *str;
char *save_str;
struct cc_subtitle *osub = sub;
struct cc_subtitle *lsub = sub;
while (sub)
{
str = sub->data;
str = strtok_r(str, "\r\n", &save_str);
do
{
length = get_str_basic(context->subline, (unsigned char *)str, context->trim_subs, sub->enc_type, context->encoding, strlen(str));
if (length <= 0)
{
continue;
}
ret = write(context->out->fh, context->encoded_crlf, context->encoded_crlf_length);
if (ret < context->encoded_crlf_length)
{
mprint("Warning:Loss of data\n");
}
} while ((str = strtok_r(NULL, "\r\n", &save_str)));
freep(&sub->data);
lsub = sub;
sub = sub->next;
}
while (lsub != osub)
{
sub = lsub->prev;
freep(&lsub);
lsub = sub;
}
return ret;
}
void write_cc_line_as_simplexml(struct eia608_screen *data, struct encoder_ctx *context, int line_number)
{
int ret;
int length = 0;
char *cap = "<caption>";
char *cap1 = "</caption>";
length = get_str_basic(context->subline, data->characters[line_number],
context->trim_subs, CCX_ENC_ASCII, context->encoding, CCX_DECODER_608_SCREEN_WIDTH);
ret = write(context->out->fh, cap, strlen(cap));
ret = write(context->out->fh, context->subline, length);
if (ret < length)
{
mprint("Warning:Loss of data\n");
}
ret = write(context->out->fh, cap1, strlen(cap1));
ret = write(context->out->fh, context->encoded_crlf, context->encoded_crlf_length);
}
int write_cc_buffer_as_simplexml(struct eia608_screen *data, struct encoder_ctx *context)
{
int wrote_something = 0;
for (int i = 0; i < 15; i++)
{
if (data->row_used[i])
{
write_cc_line_as_simplexml(data, context, i);
wrote_something = 1;
}
}
return wrote_something;
}
// Dummy Function for support DVB in simple xml
int write_cc_bitmap_as_simplexml(struct cc_subtitle *sub, struct encoder_ctx *context)
{
int ret = 0;
sub->nb_data = 0;
freep(&sub->data);
return ret;
}
/**
* @brief Function to add credits at end of subtitles File
*
* @param context encoder context in which you want to add credits at end
*
* @param out output context which usually keeps file handler
*/
static void try_to_add_end_credits(struct encoder_ctx *context, struct ccx_s_write *out, LLONG current_fts)
{
LLONG window, length, st, end;
if (out->fh == -1)
return;
window = current_fts - context->last_displayed_subs_ms - 1;
if (window < context->endcreditsforatleast.time_in_ms) // Won't happen, window is too short
return;
length = context->endcreditsforatmost.time_in_ms > window ? window : context->endcreditsforatmost.time_in_ms;
st = current_fts - length - 1;
end = current_fts;
switch (context->write_format)
{
case CCX_OF_SRT:
write_stringz_as_srt(context->end_credits_text, context, st, end);
break;
case CCX_OF_SSA:
write_stringz_as_ssa(context->end_credits_text, context, st, end);
break;
case CCX_OF_WEBVTT:
write_stringz_as_webvtt(context->end_credits_text, context, st, end);
break;
case CCX_OF_SAMI:
write_stringz_as_sami(context->end_credits_text, context, st, end);
break;
case CCX_OF_SMPTETT:
write_stringz_as_smptett(context->end_credits_text, context, st, end);
break;
default:
// Do nothing for the rest
break;
}
}
void try_to_add_start_credits(struct encoder_ctx *context, LLONG start_ms)
{
LLONG st, end, window, length;
// We have a windows from last_displayed_subs_ms to start_ms - we need to see if it fits
if (start_ms < context->startcreditsnotbefore.time_in_ms) // Too early
return;
if (context->last_displayed_subs_ms + 1 > context->startcreditsnotafter.time_in_ms) // Too late
return;
st = context->startcreditsnotbefore.time_in_ms > (context->last_displayed_subs_ms + 1) ? context->startcreditsnotbefore.time_in_ms : (context->last_displayed_subs_ms + 1); // When would credits actually start
end = context->startcreditsnotafter.time_in_ms < start_ms - 1 ? context->startcreditsnotafter.time_in_ms : start_ms - 1;
window = end - st; // Allowable time in MS
if (context->startcreditsforatleast.time_in_ms > window) // Window is too short
return;
length = context->startcreditsforatmost.time_in_ms > window ? window : context->startcreditsforatmost.time_in_ms;
dbg_print(CCX_DMT_VERBOSE, "Last subs: %lld Current position: %lld\n",
context->last_displayed_subs_ms, start_ms);
dbg_print(CCX_DMT_VERBOSE, "Not before: %lld Not after: %lld\n",
context->startcreditsnotbefore.time_in_ms,
context->startcreditsnotafter.time_in_ms);
dbg_print(CCX_DMT_VERBOSE, "Start of window: %lld End of window: %lld\n", st, end);
if (window > length + 2)
{
// Center in time window
LLONG pad = window - length;
st += (pad / 2);
}
end = st + length;
switch (context->write_format)
{
case CCX_OF_SRT:
write_stringz_as_srt(context->start_credits_text, context, st, end);
break;
case CCX_OF_SSA:
write_stringz_as_ssa(context->start_credits_text, context, st, end);
break;
case CCX_OF_WEBVTT:
write_stringz_as_webvtt(context->start_credits_text, context, st, end);
break;
case CCX_OF_SAMI:
write_stringz_as_sami(context->start_credits_text, context, st, end);
break;
case CCX_OF_SMPTETT:
write_stringz_as_smptett(context->start_credits_text, context, st, end);
break;
default:
// Do nothing for the rest
mprint("Format doesn't support credits");
break;
}
context->startcredits_displayed = 1;
return;
}
static void dinit_output_ctx(struct encoder_ctx *ctx)
{
int i;
for (i = 0; i < ctx->nb_out; i++)
{
dinit_write(ctx->out + i);
}
freep(&ctx->out);
if (ctx->dtvcc_extract)
{
for (i = 0; i < CCX_DTVCC_MAX_SERVICES; i++)
dtvcc_writer_cleanup(&ctx->dtvcc_writers[i]);
}
}
static int init_output_ctx(struct encoder_ctx *ctx, struct encoder_cfg *cfg)
{
int ret = EXIT_OK;
int nb_lang;
const char *extension; // Input filename without the extension
#define check_ret(filename) \
if (ret != EXIT_OK) \
{ \
fatal(CCX_COMMON_EXIT_FILE_CREATION_FAILED, "Failed to open output file: %s\nDetails : %s\n", filename, strerror(errno)); \
return ret; \
}
if (cfg->cc_to_stdout == CCX_FALSE && cfg->send_to_srv == CCX_FALSE && cfg->extract == 12 && cfg->write_format != CCX_OF_MCC)
nb_lang = 2;
else
nb_lang = 1;
ctx->out = malloc(sizeof(struct ccx_s_write) * nb_lang);
if (!ctx->out)
return -1;
memset(ctx->out, 0, sizeof(struct ccx_s_write) * nb_lang);
ctx->nb_out = nb_lang;
ctx->keep_output_closed = cfg->keep_output_closed;
ctx->force_flush = cfg->force_flush;
ctx->ucla = cfg->ucla;
ctx->force_dropframe = cfg->force_dropframe;
if (ctx->generates_file && cfg->cc_to_stdout == CCX_FALSE && cfg->send_to_srv == CCX_FALSE && cfg->extract_only_708 == CCX_FALSE)
{
if (cfg->output_filename != NULL)
{
// Use the given output file name for the field specified by
// the -1, -2 switch. If -12 is used, the filename is used for
// field 1.
if ((cfg->extract == 12) && (cfg->write_format != CCX_OF_MCC))
{
char *basefilename = get_basename(cfg->output_filename);
extension = get_file_extension(cfg->write_format);
char *fname0 = strdup(cfg->output_filename);
if (fname0 == NULL)
{
fatal(EXIT_NOT_ENOUGH_MEMORY,
"In init_output_ctx: Not enough memory for output filename.\n");
}
ret = init_write(&ctx->out[0], fname0, cfg->with_semaphore);
check_ret(cfg->output_filename);
ret = init_write(&ctx->out[1], create_outfilename(basefilename, "_2", extension), cfg->with_semaphore);
check_ret(ctx->out[1].filename);
free(basefilename);
}
else
{
char *fname = strdup(cfg->output_filename);
if (fname == NULL)
{
fatal(EXIT_NOT_ENOUGH_MEMORY,
"In init_output_ctx: Not enough memory for output filename.\n");
}
ret = init_write(ctx->out, fname, cfg->with_semaphore);
check_ret(cfg->output_filename);
}
}
else if (cfg->write_format != CCX_OF_NULL)
{
char *basefilename = get_basename(ctx->first_input_file);
extension = get_file_extension(cfg->write_format);
if (basefilename == NULL)
{
basefilename = get_basename("untitled");
}
if ((cfg->extract == 12) && (cfg->write_format != CCX_OF_MCC))
{
ret = init_write(&ctx->out[0], create_outfilename(basefilename, "_1", extension), cfg->with_semaphore);
check_ret(ctx->out[0].filename);
ret = init_write(&ctx->out[1], create_outfilename(basefilename, "_2", extension), cfg->with_semaphore);
check_ret(ctx->out[1].filename);
}
else
{
ret = init_write(ctx->out, create_outfilename(basefilename, NULL, extension), cfg->with_semaphore);
check_ret(ctx->out->filename);
}
free(basefilename);
}
}
if (cfg->cc_to_stdout == CCX_TRUE)
{
ctx->out[0].fh = STDOUT_FILENO;
ctx->out[0].filename = NULL;
ctx->out[0].with_semaphore = 0;
ctx->out[0].semaphore_filename = NULL;
mprint("Sending captions to stdout.\n");
}
if (cfg->send_to_srv == CCX_TRUE)
{
ctx->out[0].fh = -1;
ctx->out[0].filename = NULL;
connect_to_srv(ccx_options.srv_addr, ccx_options.srv_port, ccx_options.tcp_desc, ccx_options.tcp_password);
}
if (cfg->dtvcc_extract)
{
for (int i = 0; i < CCX_DTVCC_MAX_SERVICES; i++)
{
if (!cfg->services_enabled[i])
{
ctx->dtvcc_writers[i].fd = -1;
ctx->dtvcc_writers[i].fhandle = NULL;
ctx->dtvcc_writers[i].charset = NULL;
ctx->dtvcc_writers[i].filename = NULL;
ctx->dtvcc_writers[i].cd = (iconv_t)-1;
continue;
}
if (cfg->cc_to_stdout)
{
#ifdef WIN32
ctx->dtvcc_writers[i].fd = -1;
ctx->dtvcc_writers[i].fhandle = GetStdHandle(STD_OUTPUT_HANDLE);
#else
ctx->dtvcc_writers[i].fd = STDOUT_FILENO;
ctx->dtvcc_writers[i].fhandle = NULL;
#endif
ctx->dtvcc_writers[i].charset = NULL;
ctx->dtvcc_writers[i].filename = NULL;
ctx->dtvcc_writers[i].cd = (iconv_t)-1;
}
else
{
char *basefilename;
if (cfg->output_filename)
basefilename = get_basename(cfg->output_filename);
else
basefilename = get_basename(ctx->first_input_file);
dtvcc_writer_init(&ctx->dtvcc_writers[i], basefilename,
ctx->program_number, i + 1, cfg->write_format, cfg);
free(basefilename);
}
}
}
if (ret)
{
print_error(cfg->gui_mode_reports,
"Output filename is same as one of input filenames. Check output parameters.\n");
return CCX_COMMON_EXIT_FILE_CREATION_FAILED;
}
return EXIT_OK;
}
/**
* @param current_fts used while calculating window for end credits
*/
void dinit_encoder(struct encoder_ctx **arg, LLONG current_fts)
{
struct encoder_ctx *ctx = *arg;
int i;
if (!ctx)
return;
for (i = 0; i < ctx->nb_out; i++)
{
if (ctx->end_credits_text != NULL)
try_to_add_end_credits(ctx, ctx->out + i, current_fts);
write_subtitle_file_footer(ctx, ctx->out + i);
}
// Clean up teletext multi-page output files (issue #665)
dinit_teletext_outputs(ctx);
free_encoder_context(ctx->prev);
dinit_output_ctx(ctx);
freep(&ctx->subline);
freep(&ctx->buffer);
ctx->capacity = 0;
freep(arg);
}
int reset_output_ctx(struct encoder_ctx *ctx, struct encoder_cfg *cfg)
{
dinit_output_ctx(ctx);
return init_output_ctx(ctx, cfg);
}
struct encoder_ctx *init_encoder(struct encoder_cfg *opt)
{
int ret;
int i;
struct encoder_ctx *ctx = malloc(sizeof(struct encoder_ctx));
if (!ctx)
return NULL;
ctx->buffer = (unsigned char *)malloc(INITIAL_ENC_BUFFER_CAPACITY);
if (!ctx->buffer)
{
free(ctx);
return NULL;
}
ctx->capacity = INITIAL_ENC_BUFFER_CAPACITY;
ctx->srt_counter = 0;
ctx->cea_708_counter = 0;
ctx->wrote_webvtt_header = 0;
ctx->wrote_ccd_channel_header = false;
ctx->program_number = opt->program_number;
ctx->send_to_srv = opt->send_to_srv;
ctx->multiple_files = opt->multiple_files;
ctx->first_input_file = opt->first_input_file;
if (opt->write_format == CCX_OF_NULL || opt->write_format == CCX_OF_CURL)
ctx->generates_file = 0;
else
ctx->generates_file = 1;
ret = init_output_ctx(ctx, opt);
if (ret != EXIT_OK)
{
freep(&ctx->buffer);
free(ctx);
return NULL;
}
ctx->in_fileformat = opt->in_format;
ctx->is_pal = (opt->in_format == 2);
/** used in case of SUB_EOD_MARKER */
ctx->prev_start = -1;
ctx->subs_delay = opt->subs_delay;
ctx->last_displayed_subs_ms = 0;
ctx->date_format = opt->date_format;
ctx->millis_separator = opt->millis_separator;
ctx->startcredits_displayed = 0;
ctx->encoding = opt->encoding;
ctx->write_format = opt->write_format;
ctx->is_mkv = 0;
ctx->last_string = NULL;
ctx->transcript_settings = &opt->transcript_settings;
ctx->no_bom = opt->no_bom;
ctx->sentence_cap = opt->sentence_cap;
ctx->filter_profanity = opt->filter_profanity;
ctx->trim_subs = opt->trim_subs;
ctx->autodash = opt->autodash;
ctx->no_font_color = opt->no_font_color;
ctx->no_type_setting = opt->no_type_setting;
ctx->gui_mode_reports = opt->gui_mode_reports;
ctx->extract = opt->extract;
ctx->keep_output_closed = opt->keep_output_closed;
ctx->force_flush = opt->force_flush;
ctx->ucla = opt->ucla;
ctx->sbs_enabled = opt->splitbysentence;
ctx->subline = (unsigned char *)malloc(SUBLINESIZE);
if (!ctx->subline)
{
freep(&ctx->out);
freep(&ctx->buffer);
free(ctx);
return NULL;
}
ctx->start_credits_text = opt->start_credits_text;
ctx->end_credits_text = opt->end_credits_text;
ctx->startcreditsnotbefore = opt->startcreditsnotbefore;
ctx->startcreditsnotafter = opt->startcreditsnotafter;
ctx->startcreditsforatleast = opt->startcreditsforatleast;
ctx->startcreditsforatmost = opt->startcreditsforatmost;
ctx->endcreditsforatleast = opt->endcreditsforatleast;
ctx->endcreditsforatmost = opt->endcreditsforatmost;
ctx->new_sentence = 1; // Capitalize next letter?
if (opt->line_terminator_lf)
ctx->encoded_crlf_length = encode_line(ctx, ctx->encoded_crlf, (unsigned char *)"\n");
else
ctx->encoded_crlf_length = encode_line(ctx, ctx->encoded_crlf, (unsigned char *)"\r\n");
ctx->encoded_br_length = encode_line(ctx, ctx->encoded_br, (unsigned char *)"<br>");
if (opt->frame_terminator_0)
{
ctx->encoded_end_frame[0] = '\0';
ctx->encoded_end_frame_length = 1;
}
else
{
memcpy(ctx->encoded_end_frame, ctx->encoded_crlf, ctx->encoded_crlf_length + 1);
ctx->encoded_end_frame_length = ctx->encoded_crlf_length;
}
for (i = 0; i < ctx->nb_out; i++)
write_subtitle_file_header(ctx, ctx->out + i);
ctx->dtvcc_extract = opt->dtvcc_extract;
ctx->segment_pending = 0;
ctx->segment_last_key_frame = 0;
ctx->nospupngocr = opt->nospupngocr;
ctx->scc_framerate = opt->scc_framerate;
ctx->scc_accurate_timing = opt->scc_accurate_timing;
ctx->scc_last_transmission_end = 0;
ctx->scc_last_display_end = 0;
// Initialize teletext multi-page output arrays (issue #665)
ctx->tlt_out_count = 0;
for (int i = 0; i < MAX_TLT_PAGES_EXTRACT; i++)
{
ctx->tlt_out[i] = NULL;
ctx->tlt_out_pages[i] = 0;
ctx->tlt_srt_counter[i] = 0;
}
ctx->prev = NULL;
return ctx;
}
void set_encoder_rcwt_fileformat(struct encoder_ctx *ctx, short int format)
{
if (ctx)
{
ctx->in_fileformat = format;
}
}
static int write_newline(struct encoder_ctx *ctx, int lang)
{
return write(ctx->out[lang].fh, ctx->encoded_crlf, ctx->encoded_crlf_length);
}
struct ccx_s_write *get_output_ctx(struct encoder_ctx *ctx, int lan)
{
if (ctx->extract == 12 && lan == 2)
{
return ctx->out + 1;
}
else
{
return ctx->out;
}
}
int encode_sub(struct encoder_ctx *context, struct cc_subtitle *sub)
{
int wrote_something = 0;
int ret = 0;
/* If there is no encoder context (e.g. -out=report), we must still free
any allocated subtitle data to avoid memory leaks. */
if (!context)
{
if (sub)
{
/* DVB subtitles store bitmap planes inside cc_bitmap */
if (sub->datatype == CC_DATATYPE_DVB)
{
struct cc_bitmap *bitmap = (struct cc_bitmap *)sub->data;
if (bitmap)
{
freep(&bitmap->data0);
freep(&bitmap->data1);
}
}
/* Free generic subtitle payload buffer */
freep(&sub->data);
sub->nb_data = 0;
}
return CCX_OK;
}
context = change_filename(context);
if (context->sbs_enabled)
{
// Write to a buffer that is later s+plit to generate split
// in sentences
if (sub->type == CC_BITMAP)
sub = reformat_cc_bitmap_through_sentence_buffer(sub, context);
if (NULL == sub)
return wrote_something;
}
// Write subtitles as they come
switch (sub->type)
{
case CC_608:;
struct eia608_screen *data = NULL;
struct ccx_s_write *out;
for (data = sub->data; sub->nb_data; --sub->nb_data, ++data)
{
// Determine context based on channel. This replaces the code
// that was above, as this was incomplete (for cases where -12
// was used for example)
out = get_output_ctx(context, data->my_field);
data->end_time += context->subs_delay;
data->start_time += context->subs_delay;
// After adding delay, if start/end time is lower than 0, then continue with the next subtitle
if (data->start_time < 0 || data->end_time <= 0)
{
// Free XDS string if skipping to avoid memory leak
if (data->format == SFORMAT_XDS && data->xds_str)
{
freep(&data->xds_str);
}
continue;
}
if (data->format == SFORMAT_XDS)
{
xds_write_transcript_line_prefix(context, out, data->start_time, data->end_time, data->cur_xds_packet_class);
if (data->xds_len > 0)
{
ret = write(out->fh, data->xds_str, data->xds_len);
if (ret < data->xds_len)
{
mprint("WARNING:Loss of data\n");
}
}
freep(&data->xds_str);
write_newline(context, 0);
continue;
}
if (utc_refvalue != UINT64_MAX)
{
if (data->start_time != -1)
data->start_time += utc_refvalue * 1000;
data->end_time += utc_refvalue * 1000;
}
for (int i = 0; i < CCX_DECODER_608_SCREEN_ROWS; ++i)
correct_spelling_and_censor_words(context, data->characters[i], CCX_DECODER_608_SCREEN_WIDTH);
switch (context->write_format)
{
case CCX_OF_CCD:
// TODO: credits
wrote_something = write_cc_buffer_as_ccd(data, context);
break;
case CCX_OF_SCC:
// TODO: credits
wrote_something = write_cc_buffer_as_scc(data, context);
break;
case CCX_OF_SRT:
if (!context->startcredits_displayed && context->start_credits_text != NULL)
try_to_add_start_credits(context, data->start_time);
wrote_something = write_cc_buffer_as_srt(data, context);
break;
case CCX_OF_SSA:
if (!context->startcredits_displayed && context->start_credits_text != NULL)
try_to_add_start_credits(context, data->start_time);
wrote_something = write_cc_buffer_as_ssa(data, context);
break;
case CCX_OF_G608:
wrote_something = write_cc_buffer_as_g608(data, context);
break;
case CCX_OF_WEBVTT:
if (!context->startcredits_displayed && context->start_credits_text != NULL)
try_to_add_start_credits(context, data->start_time);
wrote_something = write_cc_buffer_as_webvtt(data, context);
break;
case CCX_OF_SAMI:
if (!context->startcredits_displayed && context->start_credits_text != NULL)
try_to_add_start_credits(context, data->start_time);
wrote_something = write_cc_buffer_as_sami(data, context);
break;
case CCX_OF_SMPTETT:
if (!context->startcredits_displayed && context->start_credits_text != NULL)
try_to_add_start_credits(context, data->start_time);
wrote_something = write_cc_buffer_as_smptett(data, context);
break;
case CCX_OF_TRANSCRIPT:
wrote_something = write_cc_buffer_as_transcript2(data, context);
break;
case CCX_OF_SPUPNG:
wrote_something = write_cc_buffer_as_spupng(data, context);
break;
case CCX_OF_SIMPLE_XML:
if (ccx_options.keep_output_closed && context->out->temporarily_closed)
{
temporarily_open_output(context->out);
write_subtitle_file_header(context, context->out);
}
wrote_something = write_cc_buffer_as_simplexml(data, context);
if (ccx_options.keep_output_closed)
{
write_subtitle_file_footer(context, context->out);
temporarily_close_output(context->out);
}
break;
default:
mprint("Output format not supported\n");
break;
}
if (wrote_something)
context->last_displayed_subs_ms = data->end_time;
if (context->gui_mode_reports)
write_cc_buffer_to_gui(sub->data, context);
}
freep(&sub->data);
break;
case CC_BITMAP:;
// Apply subs_delay to bitmap subtitles (DVB, DVD, etc.)
// This is the same as what's done for CC_608 above
sub->start_time += context->subs_delay;
sub->end_time += context->subs_delay;
// After adding delay, if start/end time is lower than 0, skip this subtitle
if (sub->start_time < 0 || sub->end_time <= 0)
{
// Free bitmap data to avoid memory leak
if (sub->datatype == CC_DATATYPE_DVB)
{
struct cc_bitmap *bitmap_tmp = (struct cc_bitmap *)sub->data;
if (bitmap_tmp)
{
freep(&bitmap_tmp->data0);
freep(&bitmap_tmp->data1);
}
}
freep(&sub->data);
sub->nb_data = 0;
break;
}
#ifdef ENABLE_OCR
struct cc_bitmap *rect;
int i;
for (i = 0, rect = sub->data; i < sub->nb_data; ++i, ++rect)
{
if (rect->ocr_text)
{
int len = strlen(rect->ocr_text);
correct_spelling_and_censor_words(context, (unsigned char *)rect->ocr_text, len);
for (int i = 0; i < len; ++i)
{
if ((unsigned char)rect->ocr_text[i] == 0x98) // asterisk in 608 encoding
rect->ocr_text[i] = '*';
}
}
}
#endif
switch (context->write_format)
{
case CCX_OF_CCD:
// TODO
fatal(1, "CC_BITMAP not implemented for CCX_OF_CCD.");
break;
case CCX_OF_SCC:
// TODO
fatal(1, "CC_BITMAP not implemented for CCX_OF_SCC.");
break;
case CCX_OF_SRT:
if (!context->startcredits_displayed && context->start_credits_text != NULL)
try_to_add_start_credits(context, sub->start_time);
wrote_something = write_cc_bitmap_as_srt(sub, context);
break;
case CCX_OF_SSA:
if (!context->startcredits_displayed && context->start_credits_text != NULL)
try_to_add_start_credits(context, sub->start_time);
wrote_something = write_cc_bitmap_as_ssa(sub, context);
break;
case CCX_OF_WEBVTT:
if (!context->startcredits_displayed && context->start_credits_text != NULL)
try_to_add_start_credits(context, sub->start_time);
wrote_something = write_cc_bitmap_as_webvtt(sub, context);
break;
case CCX_OF_SAMI:
if (!context->startcredits_displayed && context->start_credits_text != NULL)
try_to_add_start_credits(context, sub->start_time);
wrote_something = write_cc_bitmap_as_sami(sub, context);
break;
case CCX_OF_SMPTETT:
if (!context->startcredits_displayed && context->start_credits_text != NULL)
try_to_add_start_credits(context, sub->start_time);
wrote_something = write_cc_bitmap_as_smptett(sub, context);
break;
case CCX_OF_TRANSCRIPT:
wrote_something = write_cc_bitmap_as_transcript(sub, context);
break;
case CCX_OF_SPUPNG:
wrote_something = write_cc_bitmap_as_spupng(sub, context);
break;
case CCX_OF_SIMPLE_XML:
wrote_something = write_cc_bitmap_as_simplexml(sub, context);
break;
#ifdef WITH_LIBCURL
case CCX_OF_CURL:
wrote_something = write_cc_bitmap_as_libcurl(sub, context);
break;
#endif
default:
break;
}
break;
case CC_RAW:
if (context->send_to_srv)
net_send_header(sub->data, sub->nb_data);
else
{
ret = write(context->out->fh, sub->data, sub->nb_data);
if (ret < sub->nb_data)
{
mprint("WARNING: Loss of data\n");
}
}
sub->nb_data = 0;
break;
case CC_TEXT:
switch (context->write_format)
{
case CCX_OF_SRT:
if (!context->startcredits_displayed && context->start_credits_text != NULL)
try_to_add_start_credits(context, sub->start_time);
wrote_something = write_cc_subtitle_as_srt(sub, context);
break;
case CCX_OF_SSA:
if (!context->startcredits_displayed && context->start_credits_text != NULL)
try_to_add_start_credits(context, sub->start_time);
wrote_something = write_cc_subtitle_as_ssa(sub, context);
break;
case CCX_OF_WEBVTT:
if (!context->startcredits_displayed && context->start_credits_text != NULL)
try_to_add_start_credits(context, sub->start_time);
wrote_something = write_cc_subtitle_as_webvtt(sub, context);
break;
case CCX_OF_SAMI:
if (!context->startcredits_displayed && context->start_credits_text != NULL)
try_to_add_start_credits(context, sub->start_time);
wrote_something = write_cc_subtitle_as_sami(sub, context);
break;
case CCX_OF_SMPTETT:
if (!context->startcredits_displayed && context->start_credits_text != NULL)
try_to_add_start_credits(context, sub->start_time);
wrote_something = write_cc_subtitle_as_smptett(sub, context);
break;
case CCX_OF_TRANSCRIPT:
wrote_something = write_cc_subtitle_as_transcript(sub, context);
break;
case CCX_OF_SPUPNG:
wrote_something = write_cc_subtitle_as_spupng(sub, context);
break;
case CCX_OF_SIMPLE_XML:
wrote_something = write_cc_subtitle_as_simplexml(sub, context);
break;
default:
break;
}
sub->nb_data = 0;
break;
}
if (!sub->nb_data)
freep(&sub->data);
if (wrote_something && context->force_flush)
fsync(context->out->fh); // Don't buffer
return wrote_something;
}
void write_cc_buffer_to_gui(struct eia608_screen *data, struct encoder_ctx *context)
{
unsigned h1, m1, s1, ms1;
unsigned h2, m2, s2, ms2;
int with_data = 0;
for (int i = 0; i < 15; i++)
{
if (data->row_used[i])
with_data = 1;
}
if (!with_data)
return;
int time_reported = 0;
for (int i = 0; i < 15; i++)
{
if (data->row_used[i])
{
if (!time_reported)
{
millis_to_time(data->start_time, &h1, &m1, &s1, &ms1);
millis_to_time(data->end_time - 1, &h2, &m2, &s2, &ms2); // -1 To prevent overlapping with next line.
// Note, only MM:SS here as we need to save space in the preview window
fprintf(stderr, "###TIME###%02u:%02u-%02u:%02u\n###SUBTITLE###",
h1 * 60 + m1, s1, h2 * 60 + m2, s2);
time_reported = 1;
}
else
fprintf(stderr, "###SUBTITLE###");
// We don't capitalize here because whatever function that was used
// before to write to file already took care of it.
int length = get_decoder_line_encoded_for_gui(context->subline, i, data);
fwrite(context->subline, 1, length, stderr);
fwrite("\n", 1, 1, stderr);
}
}
fflush(stderr);
}
unsigned int get_line_encoded(struct encoder_ctx *ctx, unsigned char *buffer, int line_num, struct eia608_screen *data)
{
unsigned char *orig = buffer; // Keep for debugging
unsigned char *line = data->characters[line_num];
for (int i = 0; i < 32; i++)
{
int bytes = 0;
switch (ctx->encoding)
{
case CCX_ENC_UTF_8:
bytes = get_char_in_utf_8(buffer, line[i]);
break;
case CCX_ENC_LATIN_1:
get_char_in_latin_1(buffer, line[i]);
bytes = 1;
break;
case CCX_ENC_UNICODE:
get_char_in_unicode(buffer, line[i]);
bytes = 2;
case CCX_ENC_ASCII:
*buffer = line[i];
bytes = 1;
break;
}
buffer += bytes;
}
return (unsigned int)(buffer - orig); // Return length
}
unsigned int get_color_encoded(struct encoder_ctx *ctx, unsigned char *buffer, int line_num, struct eia608_screen *data)
{
unsigned char *orig = buffer; // Keep for debugging
for (int i = 0; i < 32; i++)
{
if (data->colors[line_num][i] < 10)
*buffer++ = data->colors[line_num][i] + '0';
else
*buffer++ = 'E';
}
return (unsigned)(buffer - orig); // Return length
}
unsigned int get_font_encoded(struct encoder_ctx *ctx, unsigned char *buffer, int line_num, struct eia608_screen *data)
{
unsigned char *orig = buffer; // Keep for debugging
for (int i = 0; i < 32; i++)
{
if (data->fonts[line_num][i] == FONT_REGULAR)
*buffer++ = 'R';
else if (data->fonts[line_num][i] == FONT_UNDERLINED_ITALICS)
*buffer++ = 'B';
else if (data->fonts[line_num][i] == FONT_UNDERLINED)
*buffer++ = 'U';
else if (data->fonts[line_num][i] == FONT_ITALICS)
*buffer++ = 'I';
else
*buffer++ = 'E';
}
return (unsigned)(buffer - orig); // Return length
}
void switch_output_file(struct lib_ccx_ctx *ctx, struct encoder_ctx *enc_ctx, int track_id)
{
// Do nothing when in "report" mode
if (enc_ctx == NULL || enc_ctx->out == NULL)
{
return;
}
if (enc_ctx->out->filename != NULL)
{ // Close and release the previous handle
free(enc_ctx->out->filename);
close(enc_ctx->out->fh);
}
const char *ext = get_file_extension(ctx->write_format);
char suffix[32];
snprintf(suffix, sizeof(suffix), "_%d", track_id);
char *basename = get_basename(enc_ctx->out->original_filename);
if (basename != NULL)
{
enc_ctx->out->filename = create_outfilename(basename, suffix, ext);
enc_ctx->out->fh = open(enc_ctx->out->filename, O_RDWR | O_CREAT | O_TRUNC | O_BINARY, S_IREAD | S_IWRITE);
free(basename);
}
write_subtitle_file_header(enc_ctx, enc_ctx->out);
// Reset counters as we switch output file.
enc_ctx->cea_708_counter = 0;
enc_ctx->srt_counter = 0;
}
/**
* Get or create the output file for a specific teletext page (issue #665)
* Creates output files on-demand with suffix _pNNN (e.g., output_p891.srt)
* Returns NULL if we're in stdout mode or if too many pages are being extracted
*/
struct ccx_s_write *get_teletext_output(struct encoder_ctx *ctx, uint16_t teletext_page)
{
// If teletext_page is 0, use the default output
if (teletext_page == 0 || ctx->out == NULL)
return ctx->out;
// Check if we're sending to stdout - can't do multi-page in that case
if (ctx->out[0].fh == STDOUT_FILENO)
return ctx->out;
// Check if we already have an output file for this page
for (int i = 0; i < ctx->tlt_out_count; i++)
{
if (ctx->tlt_out_pages[i] == teletext_page)
return ctx->tlt_out[i];
}
// If we only have one teletext page requested, use the default output
// (no suffix needed for backward compatibility)
extern struct ccx_s_teletext_config tlt_config;
if (tlt_config.num_user_pages <= 1 && !tlt_config.extract_all_pages)
return ctx->out;
// Need to create a new output file for this page
if (ctx->tlt_out_count >= MAX_TLT_PAGES_EXTRACT)
{
mprint("Warning: Too many teletext pages to extract (max %d), using default output for page %03d\n",
MAX_TLT_PAGES_EXTRACT, teletext_page);
return ctx->out;
}
// Allocate the new write structure
struct ccx_s_write *new_out = (struct ccx_s_write *)malloc(sizeof(struct ccx_s_write));
if (!new_out)
{
mprint("Error: Memory allocation failed for teletext output\n");
return ctx->out;
}
memset(new_out, 0, sizeof(struct ccx_s_write));
// Create the filename with page suffix
const char *ext = get_file_extension(ctx->write_format);
char suffix[16];
snprintf(suffix, sizeof(suffix), "_p%03d", teletext_page);
char *basefilename = NULL;
if (ctx->out[0].filename != NULL)
{
basefilename = get_basename(ctx->out[0].filename);
}
else if (ctx->first_input_file != NULL)
{
basefilename = get_basename(ctx->first_input_file);
}
else
{
basefilename = strdup("untitled");
}
if (basefilename == NULL)
{
free(new_out);
return ctx->out;
}
char *filename = create_outfilename(basefilename, suffix, ext);
free(basefilename);
if (filename == NULL)
{
free(new_out);
return ctx->out;
}
// Open the file
new_out->filename = filename;
new_out->fh = open(filename, O_RDWR | O_CREAT | O_TRUNC | O_BINARY, S_IREAD | S_IWRITE);
if (new_out->fh == -1)
{
mprint("Error: Failed to open output file %s: %s\n", filename, strerror(errno));
free(filename);
free(new_out);
return ctx->out;
}
mprint("Creating teletext output file: %s\n", filename);
// Store in our array
int idx = ctx->tlt_out_count;
ctx->tlt_out[idx] = new_out;
ctx->tlt_out_pages[idx] = teletext_page;
ctx->tlt_srt_counter[idx] = 0;
ctx->tlt_out_count++;
// Write the subtitle file header
write_subtitle_file_header(ctx, new_out);
return new_out;
}
/**
* Get the SRT counter for a specific teletext page (issue #665)
* Returns pointer to the counter, or NULL if page not found
*/
unsigned int *get_teletext_srt_counter(struct encoder_ctx *ctx, uint16_t teletext_page)
{
// If teletext_page is 0, use the default counter
if (teletext_page == 0)
return &ctx->srt_counter;
// Check if we're using multi-page mode
extern struct ccx_s_teletext_config tlt_config;
if (tlt_config.num_user_pages <= 1 && !tlt_config.extract_all_pages)
return &ctx->srt_counter;
// Find the counter for this page
for (int i = 0; i < ctx->tlt_out_count; i++)
{
if (ctx->tlt_out_pages[i] == teletext_page)
return &ctx->tlt_srt_counter[i];
}
// Not found, use default counter
return &ctx->srt_counter;
}
/**
* Clean up all teletext output files (issue #665)
*/
void dinit_teletext_outputs(struct encoder_ctx *ctx)
{
if (!ctx)
return;
for (int i = 0; i < ctx->tlt_out_count; i++)
{
if (ctx->tlt_out[i] != NULL)
{
// Write footer
write_subtitle_file_footer(ctx, ctx->tlt_out[i]);
// Close file
if (ctx->tlt_out[i]->fh != -1)
{
close(ctx->tlt_out[i]->fh);
}
// Free filename
if (ctx->tlt_out[i]->filename != NULL)
{
free(ctx->tlt_out[i]->filename);
}
free(ctx->tlt_out[i]);
ctx->tlt_out[i] = NULL;
}
}
ctx->tlt_out_count = 0;
}