Line | Branch | Exec | Source |
---|---|---|---|
1 | /* | ||
2 | * MCC subtitle muxer | ||
3 | * Copyright (c) 2025 Jacob Lifshay | ||
4 | * Copyright (c) 2017 Paul B Mahol | ||
5 | * | ||
6 | * This file is part of FFmpeg. | ||
7 | * | ||
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. | ||
12 | * | ||
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. | ||
17 | * | ||
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 | ||
21 | */ | ||
22 | |||
23 | #include "avformat.h" | ||
24 | #include "internal.h" | ||
25 | #include "mux.h" | ||
26 | |||
27 | #include "libavcodec/codec_id.h" | ||
28 | #include "libavcodec/smpte_436m.h" | ||
29 | |||
30 | #include "libavutil/avassert.h" | ||
31 | #include "libavutil/avstring.h" | ||
32 | #include "libavutil/error.h" | ||
33 | #include "libavutil/ffversion.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" | ||
41 | |||
42 | typedef struct MCCContext { | ||
43 | const AVClass *class; | ||
44 | AVTimecode timecode; | ||
45 | int64_t twenty_four_hr; | ||
46 | char *override_time_code_rate; | ||
47 | int use_u_alias; | ||
48 | unsigned mcc_version; | ||
49 | char *creation_program; | ||
50 | char *creation_time; | ||
51 | } MCCContext; | ||
52 | |||
53 | typedef enum MCCVersion | ||
54 | { | ||
55 | MCC_VERSION_1 = 1, | ||
56 | MCC_VERSION_2 = 2, | ||
57 | MCC_VERSION_MIN = MCC_VERSION_1, | ||
58 | MCC_VERSION_MAX = MCC_VERSION_2, | ||
59 | } MCCVersion; | ||
60 | |||
61 | static const char mcc_header_v1[] = // | ||
62 | "File Format=MacCaption_MCC V1.0\n" | ||
63 | "\n" | ||
64 | "///////////////////////////////////////////////////////////////////////////////////\n" | ||
65 | "// Computer Prompting and Captioning Company\n" | ||
66 | "// Ancillary Data Packet Transfer File\n" | ||
67 | "//\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" | ||
71 | "//\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]\n" | ||
79 | "//\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" | ||
93 | "// S 96h 69h\n" | ||
94 | "// T 61h 01h\n" | ||
95 | "// U E1h 00h 00h 00h\n" | ||
96 | "// Z 00h\n" | ||
97 | "//\n" | ||
98 | "///////////////////////////////////////////////////////////////////////////////////\n"; | ||
99 | |||
100 | static const char mcc_header_v2[] = // | ||
101 | "File Format=MacCaption_MCC V2.0\n" | ||
102 | "\n" | ||
103 | "///////////////////////////////////////////////////////////////////////////////////\n" | ||
104 | "// Computer Prompting and Captioning Company\n" | ||
105 | "// Ancillary Data Packet Transfer File\n" | ||
106 | "//\n" | ||
107 | "// Permission to generate this format is granted provided that\n" | ||
108 | "// 1. This ANC Transfer file format is used on an as-is basis and no warranty is given, and\n" | ||
109 | "// 2. This entire descriptive information text is included in a generated .mcc file.\n" | ||
110 | "//\n" | ||
111 | "// General file format:\n" | ||
112 | "// HH:MM:SS:FF(tab)[Hexadecimal ANC data in groups of 2 characters]\n" | ||
113 | "// Hexadecimal data starts with the Ancillary Data Packet DID (Data ID defined in S291M)\n" | ||
114 | "// and concludes with the Check Sum following the User Data Words.\n" | ||
115 | "// Each time code line must contain at most one complete ancillary data packet.\n" | ||
116 | "// To transfer additional ANC Data successive lines may contain identical time code.\n" | ||
117 | "// Time Code Rate=[24, 25, 30, 30DF, 50, 60, 60DF]\n" | ||
118 | "//\n" | ||
119 | "// ANC data bytes may be represented by one ASCII character according to the following schema:\n" | ||
120 | "// G FAh 00h 00h\n" | ||
121 | "// H 2 x (FAh 00h 00h)\n" | ||
122 | "// I 3 x (FAh 00h 00h)\n" | ||
123 | "// J 4 x (FAh 00h 00h)\n" | ||
124 | "// K 5 x (FAh 00h 00h)\n" | ||
125 | "// L 6 x (FAh 00h 00h)\n" | ||
126 | "// M 7 x (FAh 00h 00h)\n" | ||
127 | "// N 8 x (FAh 00h 00h)\n" | ||
128 | "// O 9 x (FAh 00h 00h)\n" | ||
129 | "// P FBh 80h 80h\n" | ||
130 | "// Q FCh 80h 80h\n" | ||
131 | "// R FDh 80h 80h\n" | ||
132 | "// S 96h 69h\n" | ||
133 | "// T 61h 01h\n" | ||
134 | "// U E1h 00h 00h 00h\n" | ||
135 | "// Z 00h\n" | ||
136 | "//\n" | ||
137 | "///////////////////////////////////////////////////////////////////////////////////\n"; | ||
138 | |||
139 | /** | ||
140 | * generated with the bash command: | ||
141 | * ```bash | ||
142 | * URL="https://code.ffmpeg.org/FFmpeg/FFmpeg/src/branch/master/libavformat/mccenc.c" | ||
143 | * python3 -c "from uuid import *; print(str(uuid5(NAMESPACE_URL, '$URL')).upper())" | ||
144 | * ``` | ||
145 | */ | ||
146 | static const char mcc_ffmpeg_uuid[] = "0087C4F6-A6B4-5469-8C8E-BBF44950401D"; | ||
147 | |||
148 | static AVRational valid_time_code_rates[] = { | ||
149 | { .num = 24, .den = 1 }, | ||
150 | { .num = 25, .den = 1 }, | ||
151 | { .num = 30000, .den = 1001 }, | ||
152 | { .num = 30, .den = 1 }, | ||
153 | { .num = 50, .den = 1 }, | ||
154 | { .num = 60000, .den = 1001 }, | ||
155 | { .num = 60, .den = 1 }, | ||
156 | }; | ||
157 | |||
158 | 5 | static int mcc_write_header(AVFormatContext *avf) | |
159 | { | ||
160 | 5 | MCCContext *mcc = avf->priv_data; | |
161 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
|
5 | if (avf->nb_streams != 1) { |
162 | ✗ | av_log(avf, AV_LOG_ERROR, "mcc muxer supports at most one stream\n"); | |
163 | ✗ | return AVERROR(EINVAL); | |
164 | } | ||
165 | 5 | avpriv_set_pts_info(avf->streams[0], 64, mcc->timecode.rate.den, mcc->timecode.rate.num); | |
166 | 5 | const char *mcc_header = mcc_header_v1; | |
167 |
1/3✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
|
5 | switch ((MCCVersion)mcc->mcc_version) { |
168 | ✗ | case MCC_VERSION_1: | |
169 | ✗ | if (mcc->timecode.fps == 60 && mcc->timecode.flags & AV_TIMECODE_FLAG_DROPFRAME) { | |
170 | ✗ | av_log(avf, AV_LOG_FATAL, "MCC Version 1.0 doesn't support 60DF (59.94 fps drop-frame)"); | |
171 | ✗ | return AVERROR(EINVAL); | |
172 | } | ||
173 | ✗ | break; | |
174 | 5 | case MCC_VERSION_2: | |
175 | 5 | mcc_header = mcc_header_v2; | |
176 | 5 | break; | |
177 | } | ||
178 | 5 | const char *creation_program = mcc->creation_program; | |
179 |
1/2✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
|
5 | if (!creation_program) { |
180 |
1/2✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
|
5 | if (avf->flags & AVFMT_FLAG_BITEXACT) |
181 | 5 | creation_program = "FFmpeg"; | |
182 | else | ||
183 | ✗ | creation_program = "FFmpeg version " FFMPEG_VERSION; | |
184 | ✗ | } else if (strchr(creation_program, '\n')) { | |
185 | ✗ | av_log(avf, AV_LOG_FATAL, "creation_program must not contain multiple lines of text"); | |
186 | ✗ | return AVERROR(EINVAL); | |
187 | } | ||
188 |
2/4✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 5 times.
|
5 | if (avf->flags & AVFMT_FLAG_BITEXACT && !av_strcasecmp(mcc->creation_time, "now")) |
189 | ✗ | av_log(avf, AV_LOG_ERROR, "creation_time must be overridden for bit-exact output"); | |
190 | 5 | int64_t timeval = 0; | |
191 | 5 | int ret = av_parse_time(&timeval, mcc->creation_time, 0); | |
192 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
|
5 | if (ret < 0) { |
193 | ✗ | av_log(avf, AV_LOG_FATAL, "can't parse creation_time"); | |
194 | ✗ | return ret; | |
195 | } | ||
196 | struct tm tm; | ||
197 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
|
5 | if (!localtime_r((time_t[1]){ timeval / 1000000 }, &tm)) |
198 | ✗ | return AVERROR(EINVAL); | |
199 | // we can't rely on having the C locale, so convert the date/time to a string ourselves: | ||
200 | static const char *const months[12] = { | ||
201 | "January", | ||
202 | "February", | ||
203 | "March", | ||
204 | "April", | ||
205 | "May", | ||
206 | "June", | ||
207 | "July", | ||
208 | "August", | ||
209 | "September", | ||
210 | "October", | ||
211 | "November", | ||
212 | "December", | ||
213 | }; | ||
214 | // assert that values are sane so we don't index out of bounds | ||
215 |
2/4✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 5 times.
|
5 | av_assert0(tm.tm_mon >= 0 && tm.tm_mon <= FF_ARRAY_ELEMS(months)); |
216 | 5 | const char *month = months[tm.tm_mon]; | |
217 | |||
218 | static const char *const weekdays[7] = { | ||
219 | "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" | ||
220 | }; | ||
221 | // assert that values are sane so we don't index out of bounds | ||
222 |
2/4✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 5 times.
|
5 | av_assert0(tm.tm_wday >= 0 && tm.tm_wday < FF_ARRAY_ELEMS(weekdays)); |
223 | 5 | const char *weekday = weekdays[tm.tm_wday]; | |
224 | |||
225 | 5 | avio_printf(avf->pb, | |
226 | "%s\n" | ||
227 | "UUID=%s\n" | ||
228 | "Creation Program=%s\n" | ||
229 | "Creation Date=%s, %s %d, %d\n" | ||
230 | "Creation Time=%02d:%02d:%02d\n" | ||
231 | "Time Code Rate=%u%s\n\n", | ||
232 | mcc_header, | ||
233 | mcc_ffmpeg_uuid, | ||
234 | creation_program, | ||
235 | weekday, | ||
236 | month, | ||
237 | tm.tm_mday, | ||
238 | 5 | tm.tm_year + 1900, | |
239 | tm.tm_hour, | ||
240 | tm.tm_min, | ||
241 | tm.tm_sec, | ||
242 | mcc->timecode.fps, | ||
243 |
1/2✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
|
5 | mcc->timecode.flags & AV_TIMECODE_FLAG_DROPFRAME ? "DF" : ""); |
244 | |||
245 | 5 | return 0; | |
246 | } | ||
247 | |||
248 | /// convert the input bytes to hexadecimal with mcc's aliases | ||
249 | 346 | static void mcc_bytes_to_hex(char *dest, const uint8_t *bytes, size_t bytes_size, int use_u_alias) | |
250 | { | ||
251 |
2/2✓ Branch 0 taken 6120 times.
✓ Branch 1 taken 346 times.
|
6466 | while (bytes_size != 0) { |
252 |
6/7✓ Branch 0 taken 256 times.
✓ Branch 1 taken 667 times.
✓ Branch 2 taken 332 times.
✓ Branch 3 taken 335 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 666 times.
✓ Branch 6 taken 3864 times.
|
6120 | switch (bytes[0]) { |
253 | 256 | case 0xFA: | |
254 | 256 | *dest = '\0'; | |
255 |
2/2✓ Branch 0 taken 2304 times.
✓ Branch 1 taken 256 times.
|
2560 | for (unsigned char code = 'G'; code <= (unsigned char)'O'; code++) { |
256 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2304 times.
|
2304 | if (bytes_size < 3) |
257 | ✗ | break; | |
258 |
3/6✓ Branch 0 taken 2304 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2304 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2304 times.
✗ Branch 5 not taken.
|
2304 | if (bytes[0] != 0xFA || bytes[1] != 0 || bytes[2] != 0) |
259 | break; | ||
260 | 2304 | *dest = code; | |
261 | 2304 | bytes += 3; | |
262 | 2304 | bytes_size -= 3; | |
263 | } | ||
264 |
1/2✓ Branch 0 taken 256 times.
✗ Branch 1 not taken.
|
256 | if (*dest) { |
265 | 256 | dest++; | |
266 | 256 | continue; | |
267 | } | ||
268 | ✗ | break; | |
269 | 667 | case 0xFB: | |
270 | case 0xFC: | ||
271 | case 0xFD: | ||
272 |
5/6✓ Branch 0 taken 664 times.
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 603 times.
✓ Branch 3 taken 61 times.
✓ Branch 4 taken 603 times.
✗ Branch 5 not taken.
|
667 | if (bytes_size >= 3 && bytes[1] == 0x80 && bytes[2] == 0x80) { |
273 | 603 | *dest++ = bytes[0] - 0xFB + 'P'; | |
274 | 603 | bytes += 3; | |
275 | 603 | bytes_size -= 3; | |
276 | 603 | continue; | |
277 | } | ||
278 | 64 | break; | |
279 | 332 | case 0x96: | |
280 |
3/4✓ Branch 0 taken 332 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 330 times.
✓ Branch 3 taken 2 times.
|
332 | if (bytes_size >= 2 && bytes[1] == 0x69) { |
281 | 330 | *dest++ = 'S'; | |
282 | 330 | bytes += 2; | |
283 | 330 | bytes_size -= 2; | |
284 | 330 | continue; | |
285 | } | ||
286 | 2 | break; | |
287 | 335 | case 0x61: | |
288 |
3/4✓ Branch 0 taken 335 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 330 times.
✓ Branch 3 taken 5 times.
|
335 | if (bytes_size >= 2 && bytes[1] == 0x01) { |
289 | 330 | *dest++ = 'T'; | |
290 | 330 | bytes += 2; | |
291 | 330 | bytes_size -= 2; | |
292 | 330 | continue; | |
293 | } | ||
294 | 5 | break; | |
295 | ✗ | case 0xE1: | |
296 | ✗ | if (use_u_alias && bytes_size >= 4 && bytes[1] == 0 && bytes[2] == 0 && bytes[3] == 0) { | |
297 | ✗ | *dest++ = 'U'; | |
298 | ✗ | bytes += 4; | |
299 | ✗ | bytes_size -= 4; | |
300 | ✗ | continue; | |
301 | } | ||
302 | ✗ | break; | |
303 | 666 | case 0: | |
304 | 666 | *dest++ = 'Z'; | |
305 | 666 | bytes++; | |
306 | 666 | bytes_size--; | |
307 | 666 | continue; | |
308 | 3864 | default: | |
309 | // any other bytes falls through to writing hex | ||
310 | 3864 | break; | |
311 | } | ||
312 |
2/2✓ Branch 0 taken 7870 times.
✓ Branch 1 taken 3935 times.
|
11805 | for (int shift = 4; shift >= 0; shift -= 4) { |
313 | 7870 | int v = (bytes[0] >> shift) & 0xF; | |
314 |
2/2✓ Branch 0 taken 6174 times.
✓ Branch 1 taken 1696 times.
|
7870 | if (v < 0xA) |
315 | 6174 | *dest++ = v + '0'; | |
316 | else | ||
317 | 1696 | *dest++ = v - 0xA + 'A'; | |
318 | } | ||
319 | 3935 | bytes++; | |
320 | 3935 | bytes_size--; | |
321 | } | ||
322 | 346 | *dest = '\0'; | |
323 | 346 | } | |
324 | |||
325 | 330 | static int mcc_write_packet(AVFormatContext *avf, AVPacket *pkt) | |
326 | { | ||
327 | 330 | MCCContext *mcc = avf->priv_data; | |
328 | 330 | int64_t pts = pkt->pts; | |
329 | int ret; | ||
330 | |||
331 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 330 times.
|
330 | if (pts == AV_NOPTS_VALUE) { |
332 | ✗ | av_log(avf, AV_LOG_WARNING, "Insufficient timestamps.\n"); | |
333 | ✗ | return 0; | |
334 | } | ||
335 | |||
336 | char timecode_str[AV_TIMECODE_STR_SIZE]; | ||
337 | |||
338 | // wrap pts values at 24hr ourselves since they can be bigger than fits in an int | ||
339 | 330 | av_timecode_make_string(&mcc->timecode, timecode_str, pts % mcc->twenty_four_hr); | |
340 | |||
341 |
2/2✓ Branch 0 taken 3630 times.
✓ Branch 1 taken 330 times.
|
3960 | for (char *p = timecode_str; *p; p++) { |
342 | // .mcc doesn't use ; for drop-frame time codes | ||
343 |
2/2✓ Branch 0 taken 330 times.
✓ Branch 1 taken 3300 times.
|
3630 | if (*p == ';') |
344 | 330 | *p = ':'; | |
345 | } | ||
346 | |||
347 | AVSmpte436mAncIterator iter; | ||
348 | 330 | ret = av_smpte_436m_anc_iter_init(&iter, pkt->data, pkt->size); | |
349 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 330 times.
|
330 | if (ret < 0) |
350 | ✗ | return ret; | |
351 | AVSmpte436mCodedAnc coded_anc; | ||
352 |
2/2✓ Branch 1 taken 346 times.
✓ Branch 2 taken 330 times.
|
676 | while ((ret = av_smpte_436m_anc_iter_next(&iter, &coded_anc)) >= 0) { |
353 | AVSmpte291mAnc8bit anc; | ||
354 | 346 | ret = av_smpte_291m_anc_8bit_decode( | |
355 | 346 | &anc, coded_anc.payload_sample_coding, coded_anc.payload_sample_count, coded_anc.payload, avf); | |
356 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 346 times.
|
346 | if (ret < 0) |
357 | ✗ | return ret; | |
358 | // 4 for did, sdid_or_dbn, data_count, and checksum fields. | ||
359 | uint8_t mcc_anc[4 + AV_SMPTE_291M_ANC_PAYLOAD_CAPACITY]; | ||
360 | 346 | size_t mcc_anc_len = 0; | |
361 | |||
362 | 346 | mcc_anc[mcc_anc_len++] = anc.did; | |
363 | 346 | mcc_anc[mcc_anc_len++] = anc.sdid_or_dbn; | |
364 | 346 | mcc_anc[mcc_anc_len++] = anc.data_count; | |
365 | 346 | memcpy(mcc_anc + mcc_anc_len, anc.payload, anc.data_count); | |
366 | 346 | mcc_anc_len += anc.data_count; | |
367 | 346 | mcc_anc[mcc_anc_len++] = anc.checksum; | |
368 | |||
369 | unsigned field_number; | ||
370 |
2/3✓ Branch 0 taken 338 times.
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
|
346 | switch (coded_anc.wrapping_type) { |
371 | 338 | case AV_SMPTE_436M_WRAPPING_TYPE_VANC_FRAME: | |
372 | case AV_SMPTE_436M_WRAPPING_TYPE_VANC_FIELD_1: | ||
373 | case AV_SMPTE_436M_WRAPPING_TYPE_VANC_PROGRESSIVE_FRAME: | ||
374 | 338 | field_number = 0; | |
375 | 338 | break; | |
376 | 8 | case AV_SMPTE_436M_WRAPPING_TYPE_VANC_FIELD_2: | |
377 | 8 | field_number = 1; | |
378 | 8 | break; | |
379 | ✗ | default: | |
380 | ✗ | av_log(avf, | |
381 | AV_LOG_WARNING, | ||
382 | "Unsupported SMPTE 436M ANC Wrapping Type %#x -- discarding ANC packet", | ||
383 | ✗ | (unsigned)coded_anc.wrapping_type); | |
384 | ✗ | continue; | |
385 | } | ||
386 | |||
387 | 346 | char field_and_line[32] = ""; | |
388 |
2/2✓ Branch 0 taken 48 times.
✓ Branch 1 taken 298 times.
|
346 | if (coded_anc.line_number != 9) { |
389 | 48 | snprintf(field_and_line, sizeof(field_and_line), ".%u,%u", field_number, (unsigned)coded_anc.line_number); | |
390 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 298 times.
|
298 | } else if (field_number != 0) { |
391 | ✗ | snprintf(field_and_line, sizeof(field_and_line), ".%u", field_number); | |
392 | } | ||
393 | |||
394 |
1/3✗ Branch 0 not taken.
✓ Branch 1 taken 346 times.
✗ Branch 2 not taken.
|
346 | switch ((MCCVersion)mcc->mcc_version) { |
395 | ✗ | case MCC_VERSION_1: | |
396 | ✗ | if (field_and_line[0] != '\0') { | |
397 | ✗ | av_log(avf, | |
398 | AV_LOG_WARNING, | ||
399 | "MCC Version 1.0 doesn't support ANC packets where the field number (got %u) isn't 0 and " | ||
400 | "line number (got %u) isn't 9: discarding ANC packet", | ||
401 | field_number, | ||
402 | ✗ | (unsigned)coded_anc.line_number); | |
403 | ✗ | continue; | |
404 | } | ||
405 | ✗ | break; | |
406 | 346 | case MCC_VERSION_2: | |
407 | 346 | break; | |
408 | } | ||
409 | |||
410 | // 1 for terminating nul. 2 since there's 2 hex digits per byte. | ||
411 | char hex[1 + 2 * sizeof(mcc_anc)]; | ||
412 | 346 | mcc_bytes_to_hex(hex, mcc_anc, mcc_anc_len, mcc->use_u_alias); | |
413 | 346 | avio_printf(avf->pb, "%s%s\t%s\n", timecode_str, field_and_line, hex); | |
414 | } | ||
415 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 330 times.
|
330 | if (ret != AVERROR_EOF) |
416 | ✗ | return ret; | |
417 | 330 | return 0; | |
418 | } | ||
419 | |||
420 | 5 | static int mcc_init(AVFormatContext *avf) | |
421 | { | ||
422 | 5 | MCCContext *mcc = avf->priv_data; | |
423 | int ret; | ||
424 | |||
425 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
|
5 | if (avf->nb_streams != 1) { |
426 | ✗ | av_log(avf, AV_LOG_ERROR, "mcc muxer supports at most one stream\n"); | |
427 | ✗ | return AVERROR(EINVAL); | |
428 | } | ||
429 | |||
430 | 5 | AVStream *st = avf->streams[0]; | |
431 | 5 | AVRational time_code_rate = st->avg_frame_rate; | |
432 | 5 | int timecode_flags = 0; | |
433 | AVTimecode twenty_four_hr; | ||
434 | |||
435 |
2/4✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 5 times.
|
5 | if (mcc->override_time_code_rate && (ret = av_parse_video_rate(&time_code_rate, mcc->override_time_code_rate)) < 0) |
436 | ✗ | return ret; | |
437 | |||
438 | 5 | ret = AVERROR(EINVAL); | |
439 | |||
440 |
1/2✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
|
15 | for (size_t i = 0; i < FF_ARRAY_ELEMS(valid_time_code_rates); i++) { |
441 |
3/4✓ Branch 0 taken 5 times.
✓ Branch 1 taken 10 times.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
|
15 | if (time_code_rate.num == valid_time_code_rates[i].num && time_code_rate.den == valid_time_code_rates[i].den) { |
442 | 5 | ret = 0; | |
443 | 5 | break; | |
444 | } | ||
445 | } | ||
446 | |||
447 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
|
5 | if (ret != 0) { |
448 | ✗ | if (!mcc->override_time_code_rate && (time_code_rate.num <= 0 || time_code_rate.den <= 0)) { | |
449 | ✗ | av_log(avf, AV_LOG_FATAL, "time code rate not set, you need to use -override_time_code_rate to set it\n"); | |
450 | } else { | ||
451 | ✗ | av_log(avf, | |
452 | AV_LOG_FATAL, | ||
453 | "time code rate not supported by mcc: %d/%d\n", | ||
454 | time_code_rate.num, | ||
455 | time_code_rate.den); | ||
456 | } | ||
457 | ✗ | return AVERROR(EINVAL); | |
458 | } | ||
459 | |||
460 | 5 | avpriv_set_pts_info(st, 64, time_code_rate.den, time_code_rate.num); | |
461 | |||
462 |
2/4✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
|
5 | if (time_code_rate.den == 1001 && time_code_rate.num % 30000 == 0) { |
463 | 5 | timecode_flags |= AV_TIMECODE_FLAG_DROPFRAME; | |
464 | } | ||
465 | |||
466 | 5 | ret = av_timecode_init(&mcc->timecode, time_code_rate, timecode_flags, 0, avf); | |
467 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
|
5 | if (ret < 0) |
468 | ✗ | return ret; | |
469 | |||
470 | // get av_timecode to calculate how many frames are in 24hr | ||
471 | 5 | ret = av_timecode_init_from_components(&twenty_four_hr, time_code_rate, timecode_flags, 24, 0, 0, 0, avf); | |
472 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
|
5 | if (ret < 0) |
473 | ✗ | return ret; | |
474 | |||
475 | 5 | mcc->twenty_four_hr = twenty_four_hr.start; | |
476 | |||
477 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 2 times.
|
5 | if (st->codecpar->codec_id == AV_CODEC_ID_EIA_608) { |
478 | char args[64]; | ||
479 | 3 | snprintf(args, sizeof(args), "cdp_frame_rate=%d/%d", time_code_rate.num, time_code_rate.den); | |
480 | 3 | ret = ff_stream_add_bitstream_filter(st, "eia608_to_smpte436m", args); | |
481 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
|
3 | if (ret < 0) |
482 | ✗ | return ret; | |
483 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | } else if (st->codecpar->codec_id != AV_CODEC_ID_SMPTE_436M_ANC) { |
484 | ✗ | av_log(avf, | |
485 | AV_LOG_ERROR, | ||
486 | "mcc muxer supports only codec %s or codec %s\n", | ||
487 | avcodec_get_name(AV_CODEC_ID_SMPTE_436M_ANC), | ||
488 | avcodec_get_name(AV_CODEC_ID_EIA_608)); | ||
489 | ✗ | return AVERROR(EINVAL); | |
490 | } | ||
491 | |||
492 | 5 | return 0; | |
493 | } | ||
494 | |||
495 | ✗ | static int mcc_query_codec(enum AVCodecID codec_id, int std_compliance) | |
496 | { | ||
497 | (void)std_compliance; | ||
498 | ✗ | if (codec_id == AV_CODEC_ID_EIA_608 || codec_id == AV_CODEC_ID_SMPTE_436M_ANC) | |
499 | ✗ | return 1; | |
500 | ✗ | return 0; | |
501 | } | ||
502 | |||
503 | #define OFFSET(x) offsetof(MCCContext, x) | ||
504 | #define ENC AV_OPT_FLAG_ENCODING_PARAM | ||
505 | // clang-format off | ||
506 | static const AVOption options[] = { | ||
507 | { "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 }, | ||
508 | { "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 }, | ||
509 | { "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 }, | ||
510 | { "creation_program", "the creation program", OFFSET(creation_program), AV_OPT_TYPE_STRING, { .str = NULL }, 0, INT_MAX, ENC }, | ||
511 | { "creation_time", "the creation time", OFFSET(creation_time), AV_OPT_TYPE_STRING, { .str = "now" }, 0, INT_MAX, ENC }, | ||
512 | { NULL }, | ||
513 | }; | ||
514 | // clang-format on | ||
515 | |||
516 | static const AVClass mcc_muxer_class = { | ||
517 | .class_name = "mcc muxer", | ||
518 | .item_name = av_default_item_name, | ||
519 | .option = options, | ||
520 | .version = LIBAVUTIL_VERSION_INT, | ||
521 | }; | ||
522 | |||
523 | const FFOutputFormat ff_mcc_muxer = { | ||
524 | .p.name = "mcc", | ||
525 | .p.long_name = NULL_IF_CONFIG_SMALL("MacCaption"), | ||
526 | .p.extensions = "mcc", | ||
527 | .p.flags = AVFMT_GLOBALHEADER, | ||
528 | .p.video_codec = AV_CODEC_ID_NONE, | ||
529 | .p.audio_codec = AV_CODEC_ID_NONE, | ||
530 | .p.subtitle_codec = AV_CODEC_ID_EIA_608, | ||
531 | .p.priv_class = &mcc_muxer_class, | ||
532 | .priv_data_size = sizeof(MCCContext), | ||
533 | .init = mcc_init, | ||
534 | .query_codec = mcc_query_codec, | ||
535 | .write_header = mcc_write_header, | ||
536 | .write_packet = mcc_write_packet, | ||
537 | }; | ||
538 |