File: ccextractor.c

package info (click to toggle)
ccextractor 0.87%2Bds1-1
  • links: PTS, VCS
  • area: main
  • in suites: buster
  • size: 10,064 kB
  • sloc: ansic: 172,772; makefile: 777; sh: 622; python: 319
file content (534 lines) | stat: -rw-r--r-- 19,857 bytes parent folder | download
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;
}