1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534
|
/* CCExtractor, originally by carlos at ccextractor.org, now a lot of people.
Credits: See AUTHORS.TXT
License: GPL 2.0
*/
#include "ccextractor.h"
#include <stdio.h>
volatile int terminate_asap = 0;
void sigusr1_handler(int sig)
{
mprint("Caught SIGUSR1. Filename Change Requested\n");
change_filename_requested = 1;
}
void sigterm_handler(int sig)
{
printf("Received SIGTERM, terminating as soon as possible.\n");
terminate_asap = 1;
}
void sigint_handler(int sig)
{
if (ccx_options.print_file_reports)
print_file_report(signal_ctx);
exit(EXIT_SUCCESS);
}
void print_end_msg(void)
{
mprint("Issues? Open a ticket here\n");
mprint("https://github.com/CCExtractor/ccextractor/issues\n");
}
int api_start(struct ccx_s_options api_options)
{
struct lib_ccx_ctx *ctx;
struct lib_cc_decode *dec_ctx = NULL;
int ret = 0, tmp;
enum ccx_stream_mode_enum stream_mode;
#if defined(ENABLE_OCR) && defined(_WIN32)
setMsgSeverity(LEPT_MSG_SEVERITY);
#endif
#ifdef ENABLE_HARDSUBX
if(api_options.hardsubx)
{
// Perform burned in subtitle extraction
hardsubx(&api_options);
return 0;
}
#endif
// Initialize CCExtractor libraries
ctx = init_libraries(&api_options);
#ifdef ENABLE_PYTHON
int i=0;
while(i<api_options.python_param_count)
{
free(api_options.python_params[i]);
i++;
}
#endif
if (!ctx && errno == ENOMEM)
fatal (EXIT_NOT_ENOUGH_MEMORY, "Not enough memory, could not initialize libraries\n");
else if (!ctx && errno == EINVAL)
fatal (CCX_COMMON_EXIT_BUG_BUG, "Invalid option to CCextractor Library\n");
else if (!ctx && errno == EPERM)
fatal (CCX_COMMON_EXIT_FILE_CREATION_FAILED, "Unable to create output file: Operation not permitted.\n");
else if (!ctx && errno == EACCES)
fatal (CCX_COMMON_EXIT_FILE_CREATION_FAILED, "Unable to create output file: Permission denied\n");
else if (!ctx)
fatal (EXIT_NOT_CLASSIFIED, "Unable to create Library Context %d\n",errno);
#ifdef WITH_LIBCURL
curl_global_init(CURL_GLOBAL_ALL);
/* get a curl handle */
curl = curl_easy_init();
if (!curl)
{
curl_global_cleanup(); // Must be done even on init fail
fatal (EXIT_NOT_CLASSIFIED, "Unable to init curl.");
}
#endif
int show_myth_banner = 0;
params_dump(ctx);
// default teletext page
if (tlt_config.page > 0) {
// dec to BCD, magazine pages numbers are in BCD (ETSI 300 706)
tlt_config.page = ((tlt_config.page / 100) << 8) | (((tlt_config.page / 10) % 10) << 4) | (tlt_config.page % 10);
}
if (api_options.transcript_settings.xds)
{
if (api_options.write_format != CCX_OF_TRANSCRIPT)
{
api_options.transcript_settings.xds = 0;
mprint ("Warning: -xds ignored, XDS can only be exported to transcripts at this time.\n");
}
}
time_t start, final;
time(&start);
if (api_options.binary_concat)
{
ctx->total_inputsize=get_total_file_size(ctx);
if (ctx->total_inputsize < 0)
{
switch (ctx->total_inputsize)
{
case -1*ENOENT:
fatal(EXIT_NO_INPUT_FILES, "Failed to open input file: File does not exist.");
case -1*EACCES:
fatal(EXIT_READ_ERROR, "Failed to open input file: Unable to access");
case -1*EINVAL:
fatal(EXIT_READ_ERROR, "Failed to open input file: Invalid opening flag.");
case -1*EMFILE:
fatal(EXIT_TOO_MANY_INPUT_FILES, "Failed to open input file: Too many files are open.");
default:
fatal(EXIT_READ_ERROR, "Failed to open input file: Reason unknown");
}
}
}
#ifndef _WIN32
signal_ctx = ctx;
m_signal(SIGINT, sigint_handler);
m_signal(SIGTERM, sigterm_handler);
m_signal(SIGUSR1, sigusr1_handler);
#endif
terminate_asap = 0;
#ifdef ENABLE_SHARING
if (api_options.translate_enabled && ctx->num_input_files > 1)
{
mprint("[share] WARNING: simultaneous translation of several input files is not supported yet\n");
api_options.translate_enabled = 0;
api_options.sharing_enabled = 0;
}
if (api_options.translate_enabled)
{
mprint("[share] launching translate service\n");
ccx_share_launch_translator(api_options.translate_langs, api_options.translate_key);
}
#endif //ENABLE_SHARING
ret = 0;
while (switch_to_next_file(ctx, 0))
{
prepare_for_new_file(ctx);
#ifdef ENABLE_SHARING
if (api_options.sharing_enabled)
ccx_share_start(ctx->basefilename);
#endif //ENABLE_SHARING
stream_mode = ctx->demux_ctx->get_stream_mode(ctx->demux_ctx);
// Disable sync check for raw formats - they have the right timeline.
// Also true for bin formats, but -nosync might have created a
// broken timeline for debug purposes.
// Disable too in MP4, specs doesn't say that there can't be a jump
switch (stream_mode)
{
case CCX_SM_MCPOODLESRAW:
case CCX_SM_RCWT:
case CCX_SM_MP4:
#ifdef WTV_DEBUG
case CCX_SM_HEX_DUMP:
#endif
ccx_common_timing_settings.disable_sync_check = 1;
break;
default:
break;
}
/* -----------------------------------------------------------------
MAIN LOOP
----------------------------------------------------------------- */
switch (stream_mode)
{
case CCX_SM_ELEMENTARY_OR_NOT_FOUND:
if (!api_options.use_gop_as_pts) // If !0 then the user selected something
api_options.use_gop_as_pts = 1; // Force GOP timing for ES
ccx_common_timing_settings.is_elementary_stream = 1;
case CCX_SM_TRANSPORT:
case CCX_SM_PROGRAM:
case CCX_SM_ASF:
case CCX_SM_WTV:
case CCX_SM_GXF:
case CCX_SM_MXF:
#ifdef ENABLE_FFMPEG
case CCX_SM_FFMPEG:
#endif
if (!api_options.use_gop_as_pts) // If !0 then the user selected something
api_options.use_gop_as_pts = 0;
if (api_options.ignore_pts_jumps)
ccx_common_timing_settings.disable_sync_check = 1;
mprint ("\rAnalyzing data in general mode\n");
tmp = general_loop(ctx);
if (!ret) ret = tmp;
break;
case CCX_SM_MCPOODLESRAW:
mprint ("\rAnalyzing data in McPoodle raw mode\n");
tmp = raw_loop(ctx);
if (!ret) ret = tmp;
break;
case CCX_SM_RCWT:
mprint ("\rAnalyzing data in CCExtractor's binary format\n");
tmp = rcwt_loop(ctx);
if (!ret) ret = tmp;
break;
case CCX_SM_MYTH:
mprint ("\rAnalyzing data in MythTV mode\n");
show_myth_banner = 1;
tmp = myth_loop(ctx);
if (!ret) ret = tmp;
break;
case CCX_SM_MP4:
mprint ("\rAnalyzing data with GPAC (MP4 library)\n");
close_input_file(ctx); // No need to have it open. GPAC will do it for us
if (ctx->current_file == -1) // We don't have a file to open, must be stdin, and GPAC is incompatible with stdin
{
fatal (EXIT_INCOMPATIBLE_PARAMETERS, "MP4 requires an actual file, it's not possible to read from a stream, including stdin.\n");
}
if(api_options.extract_chapters)
{
tmp = dumpchapters(ctx, &ctx->mp4_cfg, ctx->inputfile[ctx->current_file]);
}
else
{
tmp = processmp4(ctx, &ctx->mp4_cfg, ctx->inputfile[ctx->current_file]);
}
if (api_options.print_file_reports)
print_file_report(ctx);
if (!ret) ret = tmp;
break;
case CCX_SM_MKV:
mprint ("\rAnalyzing data in Matroska mode\n");
tmp = matroska_loop(ctx);
if (!ret) ret = tmp;
break;
#ifdef WTV_DEBUG
case CCX_SM_HEX_DUMP:
close_input_file(ctx); // process_hex will open it in text mode
process_hex (ctx, ctx->inputfile[0]);
break;
#endif
case CCX_SM_AUTODETECT:
fatal(CCX_COMMON_EXIT_BUG_BUG, "Cannot be reached!");
break;
}
list_for_each_entry(dec_ctx, &ctx->dec_ctx_head, list, struct lib_cc_decode)
{
mprint("\n");
dbg_print(CCX_DMT_DECODER_608, "\nTime stamps after last caption block was written:\n");
dbg_print(CCX_DMT_DECODER_608, "GOP: %s \n", print_mstime_static(gop_time.ms) );
dbg_print(CCX_DMT_DECODER_608, "GOP: %s (%+3dms incl.)\n",
print_mstime_static((LLONG)(gop_time.ms
-first_gop_time.ms
+get_fts_max(dec_ctx->timing)-fts_at_gop_start)),
(int)(get_fts_max(dec_ctx->timing)-fts_at_gop_start));
// When padding is active the CC block time should be within
// 1000/29.97 us of the differences.
dbg_print(CCX_DMT_DECODER_608, "Max. FTS: %s (without caption blocks since then)\n",
print_mstime_static(get_fts_max(dec_ctx->timing)));
if (dec_ctx->codec == CCX_CODEC_ATSC_CC)
{
mprint ("\nTotal frames time: %s (%u frames at %.2ffps)\n",
print_mstime_static( (LLONG)(total_frames_count*1000/current_fps) ),
total_frames_count, current_fps);
}
if (ctx->stat_hdtv)
{
mprint ("\rCC type 0: %d (%s)\n", dec_ctx->cc_stats[0], cc_types[0]);
mprint ("CC type 1: %d (%s)\n", dec_ctx->cc_stats[1], cc_types[1]);
mprint ("CC type 2: %d (%s)\n", dec_ctx->cc_stats[2], cc_types[2]);
mprint ("CC type 3: %d (%s)\n", dec_ctx->cc_stats[3], cc_types[3]);
}
// Add one frame as fts_max marks the beginning of the last frame,
// but we need the end.
dec_ctx->timing->fts_global += dec_ctx->timing->fts_max + (LLONG) (1000.0/current_fps);
// CFS: At least in Hauppage mode, cb_field can be responsible for ALL the
// timing (cb_fields having a huge number and fts_now and fts_global being 0 all
// the time), so we need to take that into account in fts_global before resetting
// counters.
if (cb_field1!=0)
dec_ctx->timing->fts_global += cb_field1*1001/3;
else if (cb_field2!=0)
dec_ctx->timing->fts_global += cb_field2*1001/3;
else
dec_ctx->timing->fts_global += cb_708*1001/3;
// Reset counters - This is needed if some captions are still buffered
// and need to be written after the last file is processed.
cb_field1 = 0; cb_field2 = 0; cb_708 = 0;
dec_ctx->timing->fts_now = 0;
dec_ctx->timing->fts_max = 0;
#ifdef ENABLE_SHARING
if (api_options.sharing_enabled)
{
ccx_share_stream_done(ctx->basefilename);
ccx_share_stop();
}
#endif //ENABLE_SHARING
if (dec_ctx->total_pulldownframes)
mprint ("incl. pulldown frames: %s (%u frames at %.2ffps)\n",
print_mstime_static( (LLONG)(dec_ctx->total_pulldownframes*1000/current_fps) ),
dec_ctx->total_pulldownframes, current_fps);
if (dec_ctx->timing->pts_set >= 1 && dec_ctx->timing->min_pts != 0x01FFFFFFFFLL)
{
LLONG postsyncms = (LLONG) (dec_ctx->frames_since_last_gop*1000/current_fps);
mprint ("\nMin PTS: %s\n",
print_mstime_static( dec_ctx->timing->min_pts/(MPEG_CLOCK_FREQ/1000) - dec_ctx->timing->fts_offset));
if (pts_big_change)
mprint ("(Reference clock was reset at some point, Min PTS is approximated)\n");
mprint ("Max PTS: %s\n",
print_mstime_static( dec_ctx->timing->sync_pts/(MPEG_CLOCK_FREQ/1000) + postsyncms));
mprint ("Length: %s\n",
print_mstime_static( dec_ctx->timing->sync_pts/(MPEG_CLOCK_FREQ/1000) + postsyncms
- dec_ctx->timing->min_pts/(MPEG_CLOCK_FREQ/1000) + dec_ctx->timing->fts_offset ));
}
// dvr-ms files have invalid GOPs
if (gop_time.inited && first_gop_time.inited && stream_mode != CCX_SM_ASF)
{
mprint ("\nInitial GOP time: %s\n",
print_mstime_static(first_gop_time.ms));
mprint ("Final GOP time: %s%+3dF\n",
print_mstime_static(gop_time.ms),
dec_ctx->frames_since_last_gop);
mprint ("Diff. GOP length: %s%+3dF",
print_mstime_static(gop_time.ms - first_gop_time.ms),
dec_ctx->frames_since_last_gop);
mprint (" (%s)\n\n",
print_mstime_static(gop_time.ms - first_gop_time.ms
+(LLONG) ((dec_ctx->frames_since_last_gop)*1000/29.97)) );
}
if (dec_ctx->false_pict_header)
mprint ("Number of likely false picture headers (discarded): %d\n",dec_ctx->false_pict_header);
if (dec_ctx->num_key_frames)
mprint("Number of key frames: %d\n", dec_ctx->num_key_frames);
if (dec_ctx->stat_numuserheaders)
mprint("Total user data fields: %d\n", dec_ctx->stat_numuserheaders);
if (dec_ctx->stat_dvdccheaders)
mprint("DVD-type user data fields: %d\n", dec_ctx->stat_dvdccheaders);
if (dec_ctx->stat_scte20ccheaders)
mprint("SCTE-20 type user data fields: %d\n", dec_ctx->stat_scte20ccheaders);
if (dec_ctx->stat_replay4000headers)
mprint("ReplayTV 4000 user data fields: %d\n", dec_ctx->stat_replay4000headers);
if (dec_ctx->stat_replay5000headers)
mprint("ReplayTV 5000 user data fields: %d\n", dec_ctx->stat_replay5000headers);
if (dec_ctx->stat_hdtv)
mprint("HDTV type user data fields: %d\n", dec_ctx->stat_hdtv);
if (dec_ctx->stat_dishheaders)
mprint("Dish Network user data fields: %d\n", dec_ctx->stat_dishheaders);
if (dec_ctx->stat_divicom)
{
mprint("CEA608/Divicom user data fields: %d\n", dec_ctx->stat_divicom);
mprint("\n\nNOTE! The CEA 608 / Divicom standard encoding for closed\n");
mprint("caption is not well understood!\n\n");
mprint("Please submit samples to the developers.\n\n\n");
}
}
if(is_decoder_processed_enough(ctx) == CCX_TRUE)
break;
} // file loop
close_input_file(ctx);
free((void *) ctx->extension);
prepare_for_new_file (ctx); // To reset counters used by handle_end_of_data()
time (&final);
long proc_time=(long) (final-start);
mprint ("\rDone, processing time = %ld seconds\n", proc_time);
#if 0
if (proc_time>0)
{
LLONG ratio=(get_fts_max()/10)/proc_time;
unsigned s1=(unsigned) (ratio/100);
unsigned s2=(unsigned) (ratio%100);
mprint ("Performance (real length/process time) = %u.%02u\n",
s1, s2);
}
#endif
if (is_decoder_processed_enough(ctx) == CCX_TRUE)
{
mprint ("\rNote: Processing was canceled before all data was processed because\n");
mprint ("\rone or more user-defined limits were reached.\n");
}
#ifdef CURL
if (curl)
curl_easy_cleanup(curl);
curl_global_cleanup();
#endif
dinit_libraries(&ctx);
if (!ret)
mprint("\nNo captions were found in input.\n");
print_end_msg();
if (show_myth_banner)
{
mprint("NOTICE: Due to the major rework in 0.49, we needed to change part of the timing\n");
mprint("code in the MythTV's branch. Please report results to the address above. If\n");
mprint("something is broken it will be fixed. Thanks\n");
}
return ret ? EXIT_OK : EXIT_NO_CAPTIONS;
}
struct ccx_s_options* api_init_options()
{
init_options(&ccx_options);
return &ccx_options;
}
void check_configuration_file(struct ccx_s_options api_options)
{
parse_configuration(&api_options);
}
#ifdef PYTHON_API
int compile_params(struct ccx_s_options *api_options,int argc)
{
//adding the parameter ./ccextractor to the list of python_params for further parsing
api_options->python_params = realloc(api_options->python_params, (api_options->python_param_count+1) * sizeof *api_options->python_params);
api_options->python_params[api_options->python_param_count] = malloc(strlen("./ccextractor")+1);
strcpy(api_options->python_params[api_options->python_param_count], "./ccextractor");
api_options->python_param_count++;
char* temp = api_options->python_params[api_options->python_param_count-1];
for (int i = api_options->python_param_count-1; i > 0; i--)
api_options->python_params[i] = api_options->python_params[i-1];
api_options->python_params[0] = temp;
int ret = parse_parameters (api_options, api_options->python_param_count, api_options->python_params);
api_options->messages_target = CCX_MESSAGES_QUIET;
return ret;
}
void api_add_param(struct ccx_s_options* api_options,char* arg)
{
api_options->python_params = realloc(api_options->python_params, (api_options->python_param_count+1) * sizeof *api_options->python_params);
api_options->python_params[api_options->python_param_count] = malloc(strlen(arg)+1);
strcpy(api_options->python_params[api_options->python_param_count], arg);
api_options->python_param_count++;
}
void py_callback(char *line, int encoding)
{
assert ( PyFunction_Check(py_callback_func) );
PyObject* args = Py_BuildValue("(si)",line,encoding);
PyObject_CallObject((PyObject*)py_callback_func, args);
}
/*
* Helper function to print the i-th param submitted by the user.
* Helpful for debugging
*/
char * api_param(struct ccx_s_options* api_options, int count)
{
return api_options->python_params[count];
}
/*
* Helper function to get the total number of params provided by the user.
* Helpful for debugging
*/
int api_param_count(struct ccx_s_options* api_options)
{
return api_options->python_param_count;
}
#endif // PYTHON_API
int main(int argc, char* argv[])
{
struct ccx_s_options* api_options = api_init_options();
check_configuration_file(*api_options);
#ifdef PYTHON_API
for(int i = 1; i < argc; i++)
api_add_param(api_options,argv[i]);
#endif
#ifdef PYTHON_API
int compile_ret = compile_params(api_options,argc);
#else
int compile_ret = parse_parameters (api_options, argc, argv);
#endif
if (compile_ret == EXIT_NO_INPUT_FILES)
{
print_usage ();
fatal (EXIT_NO_INPUT_FILES, "(This help screen was shown because there were no input files)\n");
}
else if (compile_ret == EXIT_WITH_HELP)
{
return EXIT_OK;
}
else if (compile_ret != EXIT_OK)
{
exit(compile_ret);
}
int start_ret = api_start(*api_options);
return start_ret;
}
|