3 * Copyright (c) 2025 Jacob Lifshay
4 * Copyright (c) 2017 Paul B Mahol
6 * This file is part of FFmpeg.
8 * FFmpeg is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License as published by the Free Software Foundation; either
11 * version 2.1 of the License, or (at your option) any later version.
13 * FFmpeg is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with FFmpeg; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
28 #include "libavcodec/codec_id.h"
29 #include "libavcodec/smpte_436m.h"
31 #include "libavutil/avassert.h"
32 #include "libavutil/avstring.h"
33 #include "libavutil/error.h"
34 #include "libavutil/log.h"
35 #include "libavutil/macros.h"
36 #include "libavutil/opt.h"
37 #include "libavutil/parseutils.h"
38 #include "libavutil/rational.h"
39 #include "libavutil/time_internal.h" // for localtime_r
40 #include "libavutil/timecode.h"
42 typedef struct MCCContext
{
45 int64_t twenty_four_hr
;
46 char *override_time_code_rate
;
49 char *creation_program
;
53 typedef enum MCCVersion
57 MCC_VERSION_MIN
= MCC_VERSION_1
,
58 MCC_VERSION_MAX
= MCC_VERSION_2
,
62 "File Format=MacCaption_MCC V%c.0\n" \
64 "///////////////////////////////////////////////////////////////////////////////////\n" \
65 "// Computer Prompting and Captioning Company\n" \
66 "// Ancillary Data Packet Transfer File\n" \
68 "// Permission to generate this format is granted provided that\n" \
69 "// 1. This ANC Transfer file format is used on an as-is basis and no warranty is given, and\n" \
70 "// 2. This entire descriptive information text is included in a generated .mcc file.\n" \
72 "// General file format:\n" \
73 "// HH:MM:SS:FF(tab)[Hexadecimal ANC data in groups of 2 characters]\n" \
74 "// Hexadecimal data starts with the Ancillary Data Packet DID (Data ID defined in S291M)\n" \
75 "// and concludes with the Check Sum following the User Data Words.\n" \
76 "// Each time code line must contain at most one complete ancillary data packet.\n" \
77 "// To transfer additional ANC Data successive lines may contain identical time code.\n" \
78 "// Time Code Rate=[24, 25, 30, 30DF, 50, 60%s]\n" \
80 "// ANC data bytes may be represented by one ASCII character according to the following schema:\n" \
81 "// G FAh 00h 00h\n" \
82 "// H 2 x (FAh 00h 00h)\n" \
83 "// I 3 x (FAh 00h 00h)\n" \
84 "// J 4 x (FAh 00h 00h)\n" \
85 "// K 5 x (FAh 00h 00h)\n" \
86 "// L 6 x (FAh 00h 00h)\n" \
87 "// M 7 x (FAh 00h 00h)\n" \
88 "// N 8 x (FAh 00h 00h)\n" \
89 "// O 9 x (FAh 00h 00h)\n" \
90 "// P FBh 80h 80h\n" \
91 "// Q FCh 80h 80h\n" \
92 "// R FDh 80h 80h\n" \
95 "// U E1h 00h 00h 00h\n" \
98 "///////////////////////////////////////////////////////////////////////////////////\n"
100 #define MCC_HEADER_PRINTF_ARGS(mcc_version) (mcc_version) + '0', \
101 (mcc_version) == MCC_VERSION_1 ? "" : ", 60DF"
104 * generated with the bash command:
106 * URL="https://code.ffmpeg.org/FFmpeg/FFmpeg/src/branch/master/libavformat/mccenc.c"
107 * python3 -c "from uuid import *; print(str(uuid5(NAMESPACE_URL, '$URL')).upper())"
110 static const char mcc_ffmpeg_uuid
[] = "0087C4F6-A6B4-5469-8C8E-BBF44950401D";
112 static const AVRational valid_time_code_rates
[] = {
113 { .num
= 24, .den
= 1 },
114 { .num
= 25, .den
= 1 },
115 { .num
= 30000, .den
= 1001 },
116 { .num
= 30, .den
= 1 },
117 { .num
= 50, .den
= 1 },
118 { .num
= 60000, .den
= 1001 },
119 { .num
= 60, .den
= 1 },
122 static int mcc_write_header(AVFormatContext
*avf
)
124 MCCContext
*mcc
= avf
->priv_data
;
125 const char *creation_program
= mcc
->creation_program
;
126 if (!creation_program
) {
127 if (avf
->flags
& AVFMT_FLAG_BITEXACT
)
128 creation_program
= "Lavf";
130 creation_program
= LIBAVFORMAT_IDENT
;
131 } else if (strchr(creation_program
, '\n')) {
132 av_log(avf
, AV_LOG_FATAL
, "creation_program must not contain multiple lines of text\n");
133 return AVERROR(EINVAL
);
135 if (avf
->flags
& AVFMT_FLAG_BITEXACT
&& !av_strcasecmp(mcc
->creation_time
, "now"))
136 av_log(avf
, AV_LOG_ERROR
, "creation_time must be overridden for bit-exact output\n");
138 int ret
= av_parse_time(&timeval
, mcc
->creation_time
, 0);
140 av_log(avf
, AV_LOG_FATAL
, "can't parse creation_time\n");
144 if (!localtime_r((time_t[1]){ timeval
/ 1000000 }, &tm
))
145 return AVERROR(EINVAL
);
146 // we can't rely on having the C locale, so convert the date/time to a string ourselves:
147 static const char months
[12][10] = {
161 // assert that values are sane so we don't index out of bounds
162 av_assert0(tm
.tm_mon
>= 0 && tm
.tm_mon
< FF_ARRAY_ELEMS(months
));
163 const char *month
= months
[tm
.tm_mon
];
165 static const char weekdays
[7][10] = {
166 "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
168 // assert that values are sane so we don't index out of bounds
169 av_assert0(tm
.tm_wday
>= 0 && tm
.tm_wday
< FF_ARRAY_ELEMS(weekdays
));
170 const char *weekday
= weekdays
[tm
.tm_wday
];
175 "Creation Program=%s\n"
176 "Creation Date=%s, %s %d, %d\n"
177 "Creation Time=%02d:%02d:%02d\n"
178 "Time Code Rate=%u%s\n\n",
179 MCC_HEADER_PRINTF_ARGS(mcc
->mcc_version
),
190 mcc
->timecode
.flags
& AV_TIMECODE_FLAG_DROPFRAME
? "DF" : "");
195 /// convert the input bytes to hexadecimal with mcc's aliases
196 static void mcc_bytes_to_hex(char *dest
, const uint8_t *bytes
, size_t bytes_size
, int use_u_alias
)
198 while (bytes_size
!= 0) {
202 for (unsigned char code
= 'G'; code
<= (unsigned char)'O'; code
++) {
205 if (bytes
[0] != 0xFA || bytes
[1] != 0 || bytes
[2] != 0)
219 if (bytes_size
>= 3 && bytes
[1] == 0x80 && bytes
[2] == 0x80) {
220 *dest
++ = bytes
[0] - 0xFB + 'P';
227 if (bytes_size
>= 2 && bytes
[1] == 0x69) {
235 if (bytes_size
>= 2 && bytes
[1] == 0x01) {
243 if (use_u_alias
&& bytes_size
>= 4 && bytes
[1] == 0 && bytes
[2] == 0 && bytes
[3] == 0) {
256 // any other bytes falls through to writing hex
259 for (int shift
= 4; shift
>= 0; shift
-= 4) {
260 int v
= (bytes
[0] >> shift
) & 0xF;
264 *dest
++ = v
- 0xA + 'A';
272 static int mcc_write_packet(AVFormatContext
*avf
, AVPacket
*pkt
)
274 MCCContext
*mcc
= avf
->priv_data
;
275 int64_t pts
= pkt
->pts
;
278 if (pts
== AV_NOPTS_VALUE
) {
279 av_log(avf
, AV_LOG_WARNING
, "Insufficient timestamps.\n");
283 char timecode_str
[AV_TIMECODE_STR_SIZE
];
285 // wrap pts values at 24hr ourselves since they can be bigger than fits in an int
286 av_timecode_make_string(&mcc
->timecode
, timecode_str
, pts
% mcc
->twenty_four_hr
);
288 for (char *p
= timecode_str
; *p
; p
++) {
289 // .mcc doesn't use ; for drop-frame time codes
294 AVSmpte436mAncIterator iter
;
295 ret
= av_smpte_436m_anc_iter_init(&iter
, pkt
->data
, pkt
->size
);
298 AVSmpte436mCodedAnc coded_anc
;
299 while ((ret
= av_smpte_436m_anc_iter_next(&iter
, &coded_anc
)) >= 0) {
300 AVSmpte291mAnc8bit anc
;
301 ret
= av_smpte_291m_anc_8bit_decode(
302 &anc
, coded_anc
.payload_sample_coding
, coded_anc
.payload_sample_count
, coded_anc
.payload
, avf
);
305 // 4 for did, sdid_or_dbn, data_count, and checksum fields.
306 uint8_t mcc_anc
[4 + AV_SMPTE_291M_ANC_PAYLOAD_CAPACITY
];
307 size_t mcc_anc_len
= 0;
309 mcc_anc
[mcc_anc_len
++] = anc
.did
;
310 mcc_anc
[mcc_anc_len
++] = anc
.sdid_or_dbn
;
311 mcc_anc
[mcc_anc_len
++] = anc
.data_count
;
312 memcpy(mcc_anc
+ mcc_anc_len
, anc
.payload
, anc
.data_count
);
313 mcc_anc_len
+= anc
.data_count
;
314 mcc_anc
[mcc_anc_len
++] = anc
.checksum
;
316 unsigned field_number
;
317 switch (coded_anc
.wrapping_type
) {
318 case AV_SMPTE_436M_WRAPPING_TYPE_VANC_FRAME
:
319 case AV_SMPTE_436M_WRAPPING_TYPE_VANC_FIELD_1
:
320 case AV_SMPTE_436M_WRAPPING_TYPE_VANC_PROGRESSIVE_FRAME
:
323 case AV_SMPTE_436M_WRAPPING_TYPE_VANC_FIELD_2
:
329 "Unsupported SMPTE 436M ANC Wrapping Type %#x -- discarding ANC packet\n",
330 (unsigned)coded_anc
.wrapping_type
);
334 char field_and_line
[32] = "";
335 if (coded_anc
.line_number
!= 9) {
336 snprintf(field_and_line
, sizeof(field_and_line
), ".%u,%u", field_number
, (unsigned)coded_anc
.line_number
);
337 } else if (field_number
!= 0) {
338 snprintf(field_and_line
, sizeof(field_and_line
), ".%u", field_number
);
341 switch ((MCCVersion
)mcc
->mcc_version
) {
343 if (field_and_line
[0] != '\0') {
346 "MCC Version 1.0 doesn't support ANC packets where the field number (got %u) isn't 0 and "
347 "line number (got %u) isn't 9: discarding ANC packet\n",
349 (unsigned)coded_anc
.line_number
);
357 // 1 for terminating nul. 2 since there's 2 hex digits per byte.
358 char hex
[1 + 2 * sizeof(mcc_anc
)];
359 mcc_bytes_to_hex(hex
, mcc_anc
, mcc_anc_len
, mcc
->use_u_alias
);
360 avio_printf(avf
->pb
, "%s%s\t%s\n", timecode_str
, field_and_line
, hex
);
362 if (ret
!= AVERROR_EOF
)
367 static int mcc_init(AVFormatContext
*avf
)
369 MCCContext
*mcc
= avf
->priv_data
;
372 if (avf
->nb_streams
!= 1) {
373 av_log(avf
, AV_LOG_ERROR
, "mcc muxer supports at most one stream\n");
374 return AVERROR(EINVAL
);
377 AVStream
*st
= avf
->streams
[0];
378 AVRational time_code_rate
= st
->avg_frame_rate
;
379 int timecode_flags
= 0;
380 AVTimecode twenty_four_hr
;
382 if (mcc
->override_time_code_rate
&& (ret
= av_parse_video_rate(&time_code_rate
, mcc
->override_time_code_rate
)) < 0)
385 ret
= AVERROR(EINVAL
);
387 for (size_t i
= 0; i
< FF_ARRAY_ELEMS(valid_time_code_rates
); i
++) {
388 if (time_code_rate
.num
== valid_time_code_rates
[i
].num
&& time_code_rate
.den
== valid_time_code_rates
[i
].den
) {
395 if (!mcc
->override_time_code_rate
&& (time_code_rate
.num
<= 0 || time_code_rate
.den
<= 0)) {
396 av_log(avf
, AV_LOG_FATAL
, "time code rate not set, you need to use -override_time_code_rate to set it\n");
400 "time code rate not supported by mcc: %d/%d\n",
404 return AVERROR(EINVAL
);
407 avpriv_set_pts_info(st
, 64, time_code_rate
.den
, time_code_rate
.num
);
409 if (time_code_rate
.den
== 1001 && time_code_rate
.num
% 30000 == 0) {
410 timecode_flags
|= AV_TIMECODE_FLAG_DROPFRAME
;
413 ret
= av_timecode_init(&mcc
->timecode
, time_code_rate
, timecode_flags
, 0, avf
);
417 if (mcc
->mcc_version
== MCC_VERSION_1
) {
418 if (mcc
->timecode
.fps
== 60 && mcc
->timecode
.flags
& AV_TIMECODE_FLAG_DROPFRAME
) {
419 av_log(avf
, AV_LOG_FATAL
, "MCC Version 1.0 doesn't support 60DF (59.94 fps drop-frame)\n");
420 return AVERROR(EINVAL
);
424 // get av_timecode to calculate how many frames are in 24hr
425 ret
= av_timecode_init_from_components(&twenty_four_hr
, time_code_rate
, timecode_flags
, 24, 0, 0, 0, avf
);
429 mcc
->twenty_four_hr
= twenty_four_hr
.start
;
431 if (st
->codecpar
->codec_id
== AV_CODEC_ID_EIA_608
) {
433 snprintf(args
, sizeof(args
), "cdp_frame_rate=%d/%d", time_code_rate
.num
, time_code_rate
.den
);
434 ret
= ff_stream_add_bitstream_filter(st
, "eia608_to_smpte436m", args
);
437 } else if (st
->codecpar
->codec_id
!= AV_CODEC_ID_SMPTE_436M_ANC
) {
440 "mcc muxer supports only codec smpte_436m_anc or codec eia_608\n");
441 return AVERROR(EINVAL
);
447 static int mcc_query_codec(enum AVCodecID codec_id
, int std_compliance
)
449 (void)std_compliance
;
450 if (codec_id
== AV_CODEC_ID_EIA_608
|| codec_id
== AV_CODEC_ID_SMPTE_436M_ANC
)
455 #define OFFSET(x) offsetof(MCCContext, x)
456 #define ENC AV_OPT_FLAG_ENCODING_PARAM
458 static const AVOption options
[] = {
459 { "override_time_code_rate", "override the `Time Code Rate` value in the output", OFFSET(override_time_code_rate
), AV_OPT_TYPE_STRING
, { .str
= NULL
}, 0, INT_MAX
, ENC
},
460 { "use_u_alias", "use the U alias for E1h 00h 00h 00h, disabled by default because some .mcc files disagree on whether it has 2 or 3 zero bytes", OFFSET(use_u_alias
), AV_OPT_TYPE_BOOL
, { .i64
= 0 }, 0, 1, ENC
},
461 { "mcc_version", "the mcc file format version", OFFSET(mcc_version
), AV_OPT_TYPE_UINT
, { .i64
= MCC_VERSION_2
}, MCC_VERSION_MIN
, MCC_VERSION_MAX
, ENC
},
462 { "creation_program", "the creation program", OFFSET(creation_program
), AV_OPT_TYPE_STRING
, { .str
= NULL
}, 0, INT_MAX
, ENC
},
463 { "creation_time", "the creation time", OFFSET(creation_time
), AV_OPT_TYPE_STRING
, { .str
= "now" }, 0, INT_MAX
, ENC
},
468 static const AVClass mcc_muxer_class
= {
469 .class_name
= "mcc muxer",
470 .item_name
= av_default_item_name
,
472 .version
= LIBAVUTIL_VERSION_INT
,
475 const FFOutputFormat ff_mcc_muxer
= {
477 .p
.long_name
= NULL_IF_CONFIG_SMALL("MacCaption"),
478 .p
.extensions
= "mcc",
479 .p
.flags
= AVFMT_GLOBALHEADER
,
480 .p
.video_codec
= AV_CODEC_ID_NONE
,
481 .p
.audio_codec
= AV_CODEC_ID_NONE
,
482 .p
.subtitle_codec
= AV_CODEC_ID_EIA_608
,
483 .p
.priv_class
= &mcc_muxer_class
,
484 .priv_data_size
= sizeof(MCCContext
),
486 .query_codec
= mcc_query_codec
,
487 .write_header
= mcc_write_header
,
488 .write_packet
= mcc_write_packet
,