diff options
author | Jacob Lifshay <programmerjake@gmail.com> | 2025-07-22 23:32:07 -0700 |
---|---|---|
committer | Jacob Lifshay <programmerjake@gmail.com> | 2025-08-08 03:04:42 -0700 |
commit | 14a95ab47142c5a6ec801186c77f0e0afa8ccb1c (patch) | |
tree | a0e12038fde9ad3ebf8ed3f1283753f51dd60cfa | |
parent | ceb9688fee7188f506e4ad9f8f5f9cd148d7afb5 (diff) | |
download | ffmpeg-14a95ab47142c5a6ec801186c77f0e0afa8ccb1c.tar.gz |
lavf: add mcc muxer
Signed-off-by: Jacob Lifshay <programmerjake@gmail.com>
-rwxr-xr-x | configure | 2 | ||||
-rw-r--r-- | doc/muxers.texi | 38 | ||||
-rw-r--r-- | libavformat/Makefile | 1 | ||||
-rw-r--r-- | libavformat/allformats.c | 1 | ||||
-rw-r--r-- | libavformat/mccenc.c | 537 | ||||
-rw-r--r-- | tests/fate/subtitles.mak | 17 | ||||
-rw-r--r-- | tests/ref/fate/sub-mcc-remux | 92 | ||||
-rw-r--r-- | tests/ref/fate/sub-mcc-remux-eia608 | 76 | ||||
-rw-r--r-- | tests/ref/fate/sub-mcc-remux-eia608-bsf | 76 |
9 files changed, 840 insertions, 0 deletions
@@ -3727,6 +3727,8 @@ matroska_demuxer_select="riffdec" matroska_demuxer_suggest="bzlib zlib" matroska_muxer_select="iso_writer mpeg4audio riffenc aac_adtstoasc_bsf pgs_frame_merge_bsf vp9_superframe_bsf" mcc_demuxer_select="smpte_436m" +mcc_muxer_select="smpte_436m" +mcc_muxer_suggest="eia608_to_smpte436m_bsf" mlp_demuxer_select="mlp_parser" mmf_muxer_select="riffenc" mov_demuxer_select="iso_media riffdec" diff --git a/doc/muxers.texi b/doc/muxers.texi index 7101df072d..8c45b7d47a 100644 --- a/doc/muxers.texi +++ b/doc/muxers.texi @@ -2940,6 +2940,44 @@ ffmpeg -i INPUT -f md5 - @end example @end itemize +@anchor{mccenc} +@section mcc +Muxer for MacCaption MCC files, it supports MCC versions 1.0 and 2.0. +MCC files store VANC data, which can include closed captions (EIA-608 and CEA-708), ancillary time code, pan-scan data, etc. + +@subsection Options + +The muxer options are: + +@table @option +@item override_time_code_rate +Override the @code{Time Code Rate} value in the output. Defaults to trying to deduce from the stream's @code{time_base}, which often doesn't work. +@item use_u_alias +Use the @code{U} alias for the byte sequence @code{E1h 00h 00h 00h}. +Disabled by default because some @file{.mcc} files disagree on whether it has 2 or 3 zero bytes. +@item mcc_version +The MCC file format version. Must be either 1 or 2, defaults to 2. +@item creation_program +The creation program. Defaults to this version of FFmpeg. +@item creation_time +The creation time. Defaults to the current time. +@end table + +@subsection Examples +@itemize +@item +Extract a MXF @code{SMPTE_436M_ANC} stream from a MXF file and write it to a MCC file at 30 fps. +@example +ffmpeg -i input.mxf -c copy -map 0:d -override_time_code_rate 30 out.mcc +@end example + +@item +Extract EIA-608/CTA-708 closed captions from a @file{.mp4} file and write them to a MCC file at 29.97 fps. +@example +ffmpeg -f lavfi -i "movie=input.mp4[out+subcc]" -c:s copy -map 0:s -override_time_code_rate 30000/1001 out.mcc +@end example +@end itemize + @section microdvd MicroDVD subtitle format muxer. diff --git a/libavformat/Makefile b/libavformat/Makefile index 2dc69315ab..ab5551a735 100644 --- a/libavformat/Makefile +++ b/libavformat/Makefile @@ -360,6 +360,7 @@ OBJS-$(CONFIG_MATROSKA_MUXER) += matroskaenc.o matroska.o \ vorbiscomment.o wv.o dovi_isom.o OBJS-$(CONFIG_MCA_DEMUXER) += mca.o OBJS-$(CONFIG_MCC_DEMUXER) += mccdec.o subtitles.o +OBJS-$(CONFIG_MCC_MUXER) += mccenc.o OBJS-$(CONFIG_MD5_MUXER) += hashenc.o OBJS-$(CONFIG_MGSTS_DEMUXER) += mgsts.o OBJS-$(CONFIG_MICRODVD_DEMUXER) += microdvddec.o subtitles.o diff --git a/libavformat/allformats.c b/libavformat/allformats.c index 4e9958bd18..e39eab8e85 100644 --- a/libavformat/allformats.c +++ b/libavformat/allformats.c @@ -267,6 +267,7 @@ extern const FFInputFormat ff_m4v_demuxer; extern const FFOutputFormat ff_m4v_muxer; extern const FFInputFormat ff_mca_demuxer; extern const FFInputFormat ff_mcc_demuxer; +extern const FFOutputFormat ff_mcc_muxer; extern const FFOutputFormat ff_md5_muxer; extern const FFInputFormat ff_matroska_demuxer; extern const FFOutputFormat ff_matroska_muxer; diff --git a/libavformat/mccenc.c b/libavformat/mccenc.c new file mode 100644 index 0000000000..298bc6dd1a --- /dev/null +++ b/libavformat/mccenc.c @@ -0,0 +1,537 @@ +/* + * MCC subtitle muxer + * Copyright (c) 2025 Jacob Lifshay + * Copyright (c) 2017 Paul B Mahol + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "avformat.h" +#include "internal.h" +#include "mux.h" + +#include "libavcodec/codec_id.h" +#include "libavcodec/smpte_436m.h" + +#include "libavutil/avassert.h" +#include "libavutil/avstring.h" +#include "libavutil/error.h" +#include "libavutil/ffversion.h" +#include "libavutil/log.h" +#include "libavutil/macros.h" +#include "libavutil/opt.h" +#include "libavutil/parseutils.h" +#include "libavutil/rational.h" +#include "libavutil/time_internal.h" // for localtime_r +#include "libavutil/timecode.h" + +typedef struct MCCContext { + const AVClass *class; + AVTimecode timecode; + int64_t twenty_four_hr; + char *override_time_code_rate; + int use_u_alias; + unsigned mcc_version; + char *creation_program; + char *creation_time; +} MCCContext; + +typedef enum MCCVersion +{ + MCC_VERSION_1 = 1, + MCC_VERSION_2 = 2, + MCC_VERSION_MIN = MCC_VERSION_1, + MCC_VERSION_MAX = MCC_VERSION_2, +} MCCVersion; + +static const char mcc_header_v1[] = // + "File Format=MacCaption_MCC V1.0\n" + "\n" + "///////////////////////////////////////////////////////////////////////////////////\n" + "// Computer Prompting and Captioning Company\n" + "// Ancillary Data Packet Transfer File\n" + "//\n" + "// Permission to generate this format is granted provided that\n" + "// 1. This ANC Transfer file format is used on an as-is basis and no warranty is given, and\n" + "// 2. This entire descriptive information text is included in a generated .mcc file.\n" + "//\n" + "// General file format:\n" + "// HH:MM:SS:FF(tab)[Hexadecimal ANC data in groups of 2 characters]\n" + "// Hexadecimal data starts with the Ancillary Data Packet DID (Data ID defined in S291M)\n" + "// and concludes with the Check Sum following the User Data Words.\n" + "// Each time code line must contain at most one complete ancillary data packet.\n" + "// To transfer additional ANC Data successive lines may contain identical time code.\n" + "// Time Code Rate=[24, 25, 30, 30DF, 50, 60]\n" + "//\n" + "// ANC data bytes may be represented by one ASCII character according to the following schema:\n" + "// G FAh 00h 00h\n" + "// H 2 x (FAh 00h 00h)\n" + "// I 3 x (FAh 00h 00h)\n" + "// J 4 x (FAh 00h 00h)\n" + "// K 5 x (FAh 00h 00h)\n" + "// L 6 x (FAh 00h 00h)\n" + "// M 7 x (FAh 00h 00h)\n" + "// N 8 x (FAh 00h 00h)\n" + "// O 9 x (FAh 00h 00h)\n" + "// P FBh 80h 80h\n" + "// Q FCh 80h 80h\n" + "// R FDh 80h 80h\n" + "// S 96h 69h\n" + "// T 61h 01h\n" + "// U E1h 00h 00h 00h\n" + "// Z 00h\n" + "//\n" + "///////////////////////////////////////////////////////////////////////////////////\n"; + +static const char mcc_header_v2[] = // + "File Format=MacCaption_MCC V2.0\n" + "\n" + "///////////////////////////////////////////////////////////////////////////////////\n" + "// Computer Prompting and Captioning Company\n" + "// Ancillary Data Packet Transfer File\n" + "//\n" + "// Permission to generate this format is granted provided that\n" + "// 1. This ANC Transfer file format is used on an as-is basis and no warranty is given, and\n" + "// 2. This entire descriptive information text is included in a generated .mcc file.\n" + "//\n" + "// General file format:\n" + "// HH:MM:SS:FF(tab)[Hexadecimal ANC data in groups of 2 characters]\n" + "// Hexadecimal data starts with the Ancillary Data Packet DID (Data ID defined in S291M)\n" + "// and concludes with the Check Sum following the User Data Words.\n" + "// Each time code line must contain at most one complete ancillary data packet.\n" + "// To transfer additional ANC Data successive lines may contain identical time code.\n" + "// Time Code Rate=[24, 25, 30, 30DF, 50, 60, 60DF]\n" + "//\n" + "// ANC data bytes may be represented by one ASCII character according to the following schema:\n" + "// G FAh 00h 00h\n" + "// H 2 x (FAh 00h 00h)\n" + "// I 3 x (FAh 00h 00h)\n" + "// J 4 x (FAh 00h 00h)\n" + "// K 5 x (FAh 00h 00h)\n" + "// L 6 x (FAh 00h 00h)\n" + "// M 7 x (FAh 00h 00h)\n" + "// N 8 x (FAh 00h 00h)\n" + "// O 9 x (FAh 00h 00h)\n" + "// P FBh 80h 80h\n" + "// Q FCh 80h 80h\n" + "// R FDh 80h 80h\n" + "// S 96h 69h\n" + "// T 61h 01h\n" + "// U E1h 00h 00h 00h\n" + "// Z 00h\n" + "//\n" + "///////////////////////////////////////////////////////////////////////////////////\n"; + +/** + * generated with the bash command: + * ```bash + * URL="https://code.ffmpeg.org/FFmpeg/FFmpeg/src/branch/master/libavformat/mccenc.c" + * python3 -c "from uuid import *; print(str(uuid5(NAMESPACE_URL, '$URL')).upper())" + * ``` + */ +static const char mcc_ffmpeg_uuid[] = "0087C4F6-A6B4-5469-8C8E-BBF44950401D"; + +static AVRational valid_time_code_rates[] = { + { .num = 24, .den = 1 }, + { .num = 25, .den = 1 }, + { .num = 30000, .den = 1001 }, + { .num = 30, .den = 1 }, + { .num = 50, .den = 1 }, + { .num = 60000, .den = 1001 }, + { .num = 60, .den = 1 }, +}; + +static int mcc_write_header(AVFormatContext *avf) +{ + MCCContext *mcc = avf->priv_data; + if (avf->nb_streams != 1) { + av_log(avf, AV_LOG_ERROR, "mcc muxer supports at most one stream\n"); + return AVERROR(EINVAL); + } + avpriv_set_pts_info(avf->streams[0], 64, mcc->timecode.rate.den, mcc->timecode.rate.num); + const char *mcc_header = mcc_header_v1; + switch ((MCCVersion)mcc->mcc_version) { + case MCC_VERSION_1: + if (mcc->timecode.fps == 60 && mcc->timecode.flags & AV_TIMECODE_FLAG_DROPFRAME) { + av_log(avf, AV_LOG_FATAL, "MCC Version 1.0 doesn't support 60DF (59.94 fps drop-frame)"); + return AVERROR(EINVAL); + } + break; + case MCC_VERSION_2: + mcc_header = mcc_header_v2; + break; + } + const char *creation_program = mcc->creation_program; + if (!creation_program) { + if (avf->flags & AVFMT_FLAG_BITEXACT) + creation_program = "FFmpeg"; + else + creation_program = "FFmpeg version " FFMPEG_VERSION; + } else if (strchr(creation_program, '\n')) { + av_log(avf, AV_LOG_FATAL, "creation_program must not contain multiple lines of text"); + return AVERROR(EINVAL); + } + if (avf->flags & AVFMT_FLAG_BITEXACT && !av_strcasecmp(mcc->creation_time, "now")) + av_log(avf, AV_LOG_ERROR, "creation_time must be overridden for bit-exact output"); + int64_t timeval = 0; + int ret = av_parse_time(&timeval, mcc->creation_time, 0); + if (ret < 0) { + av_log(avf, AV_LOG_FATAL, "can't parse creation_time"); + return ret; + } + struct tm tm; + if (!localtime_r((time_t[1]){ timeval / 1000000 }, &tm)) + return AVERROR(EINVAL); + // we can't rely on having the C locale, so convert the date/time to a string ourselves: + static const char *const months[12] = { + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December", + }; + // assert that values are sane so we don't index out of bounds + av_assert0(tm.tm_mon >= 0 && tm.tm_mon <= FF_ARRAY_ELEMS(months)); + const char *month = months[tm.tm_mon]; + + static const char *const weekdays[7] = { + "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" + }; + // assert that values are sane so we don't index out of bounds + av_assert0(tm.tm_wday >= 0 && tm.tm_wday < FF_ARRAY_ELEMS(weekdays)); + const char *weekday = weekdays[tm.tm_wday]; + + avio_printf(avf->pb, + "%s\n" + "UUID=%s\n" + "Creation Program=%s\n" + "Creation Date=%s, %s %d, %d\n" + "Creation Time=%02d:%02d:%02d\n" + "Time Code Rate=%u%s\n\n", + mcc_header, + mcc_ffmpeg_uuid, + creation_program, + weekday, + month, + tm.tm_mday, + tm.tm_year + 1900, + tm.tm_hour, + tm.tm_min, + tm.tm_sec, + mcc->timecode.fps, + mcc->timecode.flags & AV_TIMECODE_FLAG_DROPFRAME ? "DF" : ""); + + return 0; +} + +/// convert the input bytes to hexadecimal with mcc's aliases +static void mcc_bytes_to_hex(char *dest, const uint8_t *bytes, size_t bytes_size, int use_u_alias) +{ + while (bytes_size != 0) { + switch (bytes[0]) { + case 0xFA: + *dest = '\0'; + for (unsigned char code = 'G'; code <= (unsigned char)'O'; code++) { + if (bytes_size < 3) + break; + if (bytes[0] != 0xFA || bytes[1] != 0 || bytes[2] != 0) + break; + *dest = code; + bytes += 3; + bytes_size -= 3; + } + if (*dest) { + dest++; + continue; + } + break; + case 0xFB: + case 0xFC: + case 0xFD: + if (bytes_size >= 3 && bytes[1] == 0x80 && bytes[2] == 0x80) { + *dest++ = bytes[0] - 0xFB + 'P'; + bytes += 3; + bytes_size -= 3; + continue; + } + break; + case 0x96: + if (bytes_size >= 2 && bytes[1] == 0x69) { + *dest++ = 'S'; + bytes += 2; + bytes_size -= 2; + continue; + } + break; + case 0x61: + if (bytes_size >= 2 && bytes[1] == 0x01) { + *dest++ = 'T'; + bytes += 2; + bytes_size -= 2; + continue; + } + break; + case 0xE1: + if (use_u_alias && bytes_size >= 4 && bytes[1] == 0 && bytes[2] == 0 && bytes[3] == 0) { + *dest++ = 'U'; + bytes += 4; + bytes_size -= 4; + continue; + } + break; + case 0: + *dest++ = 'Z'; + bytes++; + bytes_size--; + continue; + default: + // any other bytes falls through to writing hex + break; + } + for (int shift = 4; shift >= 0; shift -= 4) { + int v = (bytes[0] >> shift) & 0xF; + if (v < 0xA) + *dest++ = v + '0'; + else + *dest++ = v - 0xA + 'A'; + } + bytes++; + bytes_size--; + } + *dest = '\0'; +} + +static int mcc_write_packet(AVFormatContext *avf, AVPacket *pkt) +{ + MCCContext *mcc = avf->priv_data; + int64_t pts = pkt->pts; + int ret; + + if (pts == AV_NOPTS_VALUE) { + av_log(avf, AV_LOG_WARNING, "Insufficient timestamps.\n"); + return 0; + } + + char timecode_str[AV_TIMECODE_STR_SIZE]; + + // wrap pts values at 24hr ourselves since they can be bigger than fits in an int + av_timecode_make_string(&mcc->timecode, timecode_str, pts % mcc->twenty_four_hr); + + for (char *p = timecode_str; *p; p++) { + // .mcc doesn't use ; for drop-frame time codes + if (*p == ';') + *p = ':'; + } + + AVSmpte436mAncIterator iter; + ret = av_smpte_436m_anc_iter_init(&iter, pkt->data, pkt->size); + if (ret < 0) + return ret; + AVSmpte436mCodedAnc coded_anc; + while ((ret = av_smpte_436m_anc_iter_next(&iter, &coded_anc)) >= 0) { + AVSmpte291mAnc8bit anc; + ret = av_smpte_291m_anc_8bit_decode( + &anc, coded_anc.payload_sample_coding, coded_anc.payload_sample_count, coded_anc.payload, avf); + if (ret < 0) + return ret; + // 4 for did, sdid_or_dbn, data_count, and checksum fields. + uint8_t mcc_anc[4 + AV_SMPTE_291M_ANC_PAYLOAD_CAPACITY]; + size_t mcc_anc_len = 0; + + mcc_anc[mcc_anc_len++] = anc.did; + mcc_anc[mcc_anc_len++] = anc.sdid_or_dbn; + mcc_anc[mcc_anc_len++] = anc.data_count; + memcpy(mcc_anc + mcc_anc_len, anc.payload, anc.data_count); + mcc_anc_len += anc.data_count; + mcc_anc[mcc_anc_len++] = anc.checksum; + + unsigned field_number; + switch (coded_anc.wrapping_type) { + case AV_SMPTE_436M_WRAPPING_TYPE_VANC_FRAME: + case AV_SMPTE_436M_WRAPPING_TYPE_VANC_FIELD_1: + case AV_SMPTE_436M_WRAPPING_TYPE_VANC_PROGRESSIVE_FRAME: + field_number = 0; + break; + case AV_SMPTE_436M_WRAPPING_TYPE_VANC_FIELD_2: + field_number = 1; + break; + default: + av_log(avf, + AV_LOG_WARNING, + "Unsupported SMPTE 436M ANC Wrapping Type %#x -- discarding ANC packet", + (unsigned)coded_anc.wrapping_type); + continue; + } + + char field_and_line[32] = ""; + if (coded_anc.line_number != 9) { + snprintf(field_and_line, sizeof(field_and_line), ".%u,%u", field_number, (unsigned)coded_anc.line_number); + } else if (field_number != 0) { + snprintf(field_and_line, sizeof(field_and_line), ".%u", field_number); + } + + switch ((MCCVersion)mcc->mcc_version) { + case MCC_VERSION_1: + if (field_and_line[0] != '\0') { + av_log(avf, + AV_LOG_WARNING, + "MCC Version 1.0 doesn't support ANC packets where the field number (got %u) isn't 0 and " + "line number (got %u) isn't 9: discarding ANC packet", + field_number, + (unsigned)coded_anc.line_number); + continue; + } + break; + case MCC_VERSION_2: + break; + } + + // 1 for terminating nul. 2 since there's 2 hex digits per byte. + char hex[1 + 2 * sizeof(mcc_anc)]; + mcc_bytes_to_hex(hex, mcc_anc, mcc_anc_len, mcc->use_u_alias); + avio_printf(avf->pb, "%s%s\t%s\n", timecode_str, field_and_line, hex); + } + if (ret != AVERROR_EOF) + return ret; + return 0; +} + +static int mcc_init(AVFormatContext *avf) +{ + MCCContext *mcc = avf->priv_data; + int ret; + + if (avf->nb_streams != 1) { + av_log(avf, AV_LOG_ERROR, "mcc muxer supports at most one stream\n"); + return AVERROR(EINVAL); + } + + AVStream *st = avf->streams[0]; + AVRational time_code_rate = st->avg_frame_rate; + int timecode_flags = 0; + AVTimecode twenty_four_hr; + + if (mcc->override_time_code_rate && (ret = av_parse_video_rate(&time_code_rate, mcc->override_time_code_rate)) < 0) + return ret; + + ret = AVERROR(EINVAL); + + for (size_t i = 0; i < FF_ARRAY_ELEMS(valid_time_code_rates); i++) { + if (time_code_rate.num == valid_time_code_rates[i].num && time_code_rate.den == valid_time_code_rates[i].den) { + ret = 0; + break; + } + } + + if (ret != 0) { + if (!mcc->override_time_code_rate && (time_code_rate.num <= 0 || time_code_rate.den <= 0)) { + av_log(avf, AV_LOG_FATAL, "time code rate not set, you need to use -override_time_code_rate to set it\n"); + } else { + av_log(avf, + AV_LOG_FATAL, + "time code rate not supported by mcc: %d/%d\n", + time_code_rate.num, + time_code_rate.den); + } + return AVERROR(EINVAL); + } + + avpriv_set_pts_info(st, 64, time_code_rate.den, time_code_rate.num); + + if (time_code_rate.den == 1001 && time_code_rate.num % 30000 == 0) { + timecode_flags |= AV_TIMECODE_FLAG_DROPFRAME; + } + + ret = av_timecode_init(&mcc->timecode, time_code_rate, timecode_flags, 0, avf); + if (ret < 0) + return ret; + + // get av_timecode to calculate how many frames are in 24hr + ret = av_timecode_init_from_components(&twenty_four_hr, time_code_rate, timecode_flags, 24, 0, 0, 0, avf); + if (ret < 0) + return ret; + + mcc->twenty_four_hr = twenty_four_hr.start; + + if (st->codecpar->codec_id == AV_CODEC_ID_EIA_608) { + char args[64]; + snprintf(args, sizeof(args), "cdp_frame_rate=%d/%d", time_code_rate.num, time_code_rate.den); + ret = ff_stream_add_bitstream_filter(st, "eia608_to_smpte436m", args); + if (ret < 0) + return ret; + } else if (st->codecpar->codec_id != AV_CODEC_ID_SMPTE_436M_ANC) { + av_log(avf, + AV_LOG_ERROR, + "mcc muxer supports only codec %s or codec %s\n", + avcodec_get_name(AV_CODEC_ID_SMPTE_436M_ANC), + avcodec_get_name(AV_CODEC_ID_EIA_608)); + return AVERROR(EINVAL); + } + + return 0; +} + +static int mcc_query_codec(enum AVCodecID codec_id, int std_compliance) +{ + (void)std_compliance; + if (codec_id == AV_CODEC_ID_EIA_608 || codec_id == AV_CODEC_ID_SMPTE_436M_ANC) + return 1; + return 0; +} + +#define OFFSET(x) offsetof(MCCContext, x) +#define ENC AV_OPT_FLAG_ENCODING_PARAM +// clang-format off +static const AVOption options[] = { + { "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 }, + { "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 }, + { "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 }, + { "creation_program", "the creation program", OFFSET(creation_program), AV_OPT_TYPE_STRING, { .str = NULL }, 0, INT_MAX, ENC }, + { "creation_time", "the creation time", OFFSET(creation_time), AV_OPT_TYPE_STRING, { .str = "now" }, 0, INT_MAX, ENC }, + { NULL }, +}; +// clang-format on + +static const AVClass mcc_muxer_class = { + .class_name = "mcc muxer", + .item_name = av_default_item_name, + .option = options, + .version = LIBAVUTIL_VERSION_INT, +}; + +const FFOutputFormat ff_mcc_muxer = { + .p.name = "mcc", + .p.long_name = NULL_IF_CONFIG_SMALL("MacCaption"), + .p.extensions = "mcc", + .p.flags = AVFMT_GLOBALHEADER, + .p.video_codec = AV_CODEC_ID_NONE, + .p.audio_codec = AV_CODEC_ID_NONE, + .p.subtitle_codec = AV_CODEC_ID_EIA_608, + .p.priv_class = &mcc_muxer_class, + .priv_data_size = sizeof(MCCContext), + .init = mcc_init, + .query_codec = mcc_query_codec, + .write_header = mcc_write_header, + .write_packet = mcc_write_packet, +}; diff --git a/tests/fate/subtitles.mak b/tests/fate/subtitles.mak index 65f161cacb..217a63791c 100644 --- a/tests/fate/subtitles.mak +++ b/tests/fate/subtitles.mak @@ -130,6 +130,23 @@ fate-sub-rcwt: CMD = md5 -i $(TARGET_SAMPLES)/sub/witch.scc -map 0 -c copy -f rc fate-sub-rcwt: CMP = oneline fate-sub-rcwt: REF = d86f179094a5752d68aa97d82cf887b0 +FATE_SUBTITLES-$(call ALLYES, AVDEVICE LAVFI_INDEV MOVIE_FILTER MPEGTS_DEMUXER MCC_MUXER EIA608_TO_SMPTE436M_BSF) += fate-sub-mcc +fate-sub-mcc: CMD = md5 -f lavfi -i "movie=$(TARGET_SAMPLES)/sub/scte20.ts[out0+subcc]" -map 0:s -c copy -override_time_code_rate ntsc -creation_time "1970-01-01T00:00:00" -bitexact -f mcc +fate-sub-mcc: CMP = oneline +fate-sub-mcc: REF = 752c60c3a74445a2a76e1a6465064763 + +FATE_SUBTITLES-$(call DEMMUX, MCC, MCC, SMPTE436M_TO_EIA608_BSF EIA608_TO_SMPTE436M_BSF) += fate-sub-mcc-remux-eia608-bsf +fate-sub-mcc-remux-eia608-bsf: CMD = fmtstdout mcc -f mcc -i tests/ref/fate/sub-mcc-remux -map 0:s -c copy -bsf "eia608_to_smpte436m=cdp_frame_rate=60000/1001:initial_cdp_sequence_cntr=65535:line_number=11" -override_time_code_rate ntsc -creation_time "1970-01-01T00:00:00" +fate-sub-mcc-remux-eia608-bsf: CMP = rawdiff + +FATE_SUBTITLES-$(call DEMMUX, MCC, MCC, SMPTE436M_TO_EIA608_BSF EIA608_TO_SMPTE436M_BSF) += fate-sub-mcc-remux-eia608 +fate-sub-mcc-remux-eia608: CMD = fmtstdout mcc -f mcc -i tests/ref/fate/sub-mcc-remux -map 0:s -c copy -override_time_code_rate ntsc -creation_time "1970-01-01T00:00:00" +fate-sub-mcc-remux-eia608: CMP = rawdiff + +FATE_SUBTITLES-$(call DEMMUX, MCC, MCC) += fate-sub-mcc-remux +fate-sub-mcc-remux: CMD = fmtstdout mcc -eia608_extract 0 -f mcc -i tests/ref/fate/sub-mcc-remux -map 0:d -c copy -override_time_code_rate ntsc -creation_time "1970-01-01T00:00:00" +fate-sub-mcc-remux: CMP = rawdiff + FATE_SUBTITLES-$(call FRAMECRC, MPEGTS, DVBSUB, DVBSUB_ENCODER) += fate-sub-dvb fate-sub-dvb: CMD = framecrc -i $(TARGET_SAMPLES)/sub/dvbsubtest_filter.ts -map s:0 -c dvbsub diff --git a/tests/ref/fate/sub-mcc-remux b/tests/ref/fate/sub-mcc-remux new file mode 100644 index 0000000000..d9d2f32dc0 --- /dev/null +++ b/tests/ref/fate/sub-mcc-remux @@ -0,0 +1,92 @@ +File Format=MacCaption_MCC V2.0 + +/////////////////////////////////////////////////////////////////////////////////// +// Computer Prompting and Captioning Company +// Ancillary Data Packet Transfer File +// +// Permission to generate this format is granted provided that +// 1. This ANC Transfer file format is used on an as-is basis and no warranty is given, and +// 2. This entire descriptive information text is included in a generated .mcc file. +// +// General file format: +// HH:MM:SS:FF(tab)[Hexadecimal ANC data in groups of 2 characters] +// Hexadecimal data starts with the Ancillary Data Packet DID (Data ID defined in S291M) +// and concludes with the Check Sum following the User Data Words. +// Each time code line must contain at most one complete ancillary data packet. +// To transfer additional ANC Data successive lines may contain identical time code. +// Time Code Rate=[24, 25, 30, 30DF, 50, 60, 60DF] +// +// ANC data bytes may be represented by one ASCII character according to the following schema: +// G FAh 00h 00h +// H 2 x (FAh 00h 00h) +// I 3 x (FAh 00h 00h) +// J 4 x (FAh 00h 00h) +// K 5 x (FAh 00h 00h) +// L 6 x (FAh 00h 00h) +// M 7 x (FAh 00h 00h) +// N 8 x (FAh 00h 00h) +// O 9 x (FAh 00h 00h) +// P FBh 80h 80h +// Q FCh 80h 80h +// R FDh 80h 80h +// S 96h 69h +// T 61h 01h +// U E1h 00h 00h 00h +// Z 00h +// +/////////////////////////////////////////////////////////////////////////////////// + +UUID=0087C4F6-A6B4-5469-8C8E-BBF44950401D +Creation Program=FFmpeg +Creation Date=Thursday, January 1, 1970 +Creation Time=00:00:00 +Time Code Rate=30DF + +00:00:58:29 T49S494F43FFFF72F4QROO74FFFFC3AB +00:00:58:29.0,10 410104890A83015D +00:00:58:29.1,10 410104890A83015D +00:00:59:00 T49S494F43ZZ72F4QROO74ZZBFAB +00:00:59:00.0,10 410104890A83015D +00:00:59:00.1,10 410104890A83015D +00:00:59:01 T49S494F43Z0172F4QROO74Z01BDAB +00:00:59:01.0,10 410104890A83015D +00:00:59:01.1,10 410104890A83015D +00:00:59:02 T49S494F43Z0272F4QROO74Z02BBAB +00:00:59:02.0,10 410104890A83015D +00:00:59:02.1,10 410104890A83015D +00:00:59:03 T49S494F43Z0372F4QROO74Z03B9AB +00:00:59:03.0,10 410104890A83015D +00:00:59:03.1,10 410104890A83015D +00:00:59:04 T49S494F43Z0472F4QROO74Z04B7AB +00:00:59:04.0,10 410104890A83015D +00:00:59:04.1,10 410104890A83015D +00:00:59:05 T49S494F43Z0572F4QROO74Z05B5AB +00:00:59:05.0,10 410104890A83015D +00:00:59:05.1,10 410104890A83015D +00:00:59:06 T49S494F43Z0672F4QROO74Z06B3AB +00:00:59:06.0,10 410104890A83015D +00:00:59:06.1,10 410104890A83015D +00:00:59:07 T49S494F43Z0772F4QROO74Z07B1AB +00:00:59:08 T49S494F43Z0872F4QROO74Z08AFAB +00:00:59:09 T49S494F43Z0972F4QROO74Z09ADAB +00:00:59:10 T49S494F43Z0A72F4QROO74Z0AABAB +00:00:59:11 T49S494F43Z0B72F4QROO74Z0BA9AB +00:00:59:12 T49S494F43Z0C72F4QROO74Z0CA7AB +00:00:59:13 T49S494F43Z0D72F4QROO74Z0DA5AB +00:00:59:14 T49S494F43Z0E72F4QROO74Z0EA3AB +00:00:59:15 T49S494F43Z0F72F4QROO74Z0FA1AB +00:00:59:16 T49S494F43Z1072F4QROO74Z109FAB +00:00:59:17 T49S494F43Z1172F4QROO74Z119DAB +00:00:59:18 T49S494F43Z1272F4QROO74Z129BAB +00:00:59:19 T49S494F43Z1372F4QROO74Z1399AB +00:00:59:20 T49S494F43Z1472F4QROO74Z1497AB +00:00:59:21 T49S494F43Z1572F4QROO74Z1595AB +00:00:59:22 T49S494F43Z1672F4QROO74Z1693AB +00:00:59:23 T49S494F43Z1772F4QROO74Z1791AB +00:00:59:24 T49S494F43Z1872F4QROO74Z188FAB +00:00:59:25 T49S494F43Z1972F4QROO74Z198DAB +00:00:59:26 T49S494F43Z1A72F4QROO74Z1A8BAB +00:00:59:27 T49S494F43Z1B72F4QROO74Z1B89AB +00:00:59:28 T49S494F43Z1C72F4QROO74Z1C87AB +00:00:59:29 T49S494F43Z1D72F4QROO74Z1D85AB +00:01:00:02 T49S494F43Z1E72F4QROO74Z1E83AB diff --git a/tests/ref/fate/sub-mcc-remux-eia608 b/tests/ref/fate/sub-mcc-remux-eia608 new file mode 100644 index 0000000000..01e8c37b2c --- /dev/null +++ b/tests/ref/fate/sub-mcc-remux-eia608 @@ -0,0 +1,76 @@ +File Format=MacCaption_MCC V2.0 + +/////////////////////////////////////////////////////////////////////////////////// +// Computer Prompting and Captioning Company +// Ancillary Data Packet Transfer File +// +// Permission to generate this format is granted provided that +// 1. This ANC Transfer file format is used on an as-is basis and no warranty is given, and +// 2. This entire descriptive information text is included in a generated .mcc file. +// +// General file format: +// HH:MM:SS:FF(tab)[Hexadecimal ANC data in groups of 2 characters] +// Hexadecimal data starts with the Ancillary Data Packet DID (Data ID defined in S291M) +// and concludes with the Check Sum following the User Data Words. +// Each time code line must contain at most one complete ancillary data packet. +// To transfer additional ANC Data successive lines may contain identical time code. +// Time Code Rate=[24, 25, 30, 30DF, 50, 60, 60DF] +// +// ANC data bytes may be represented by one ASCII character according to the following schema: +// G FAh 00h 00h +// H 2 x (FAh 00h 00h) +// I 3 x (FAh 00h 00h) +// J 4 x (FAh 00h 00h) +// K 5 x (FAh 00h 00h) +// L 6 x (FAh 00h 00h) +// M 7 x (FAh 00h 00h) +// N 8 x (FAh 00h 00h) +// O 9 x (FAh 00h 00h) +// P FBh 80h 80h +// Q FCh 80h 80h +// R FDh 80h 80h +// S 96h 69h +// T 61h 01h +// U E1h 00h 00h 00h +// Z 00h +// +/////////////////////////////////////////////////////////////////////////////////// + +UUID=0087C4F6-A6B4-5469-8C8E-BBF44950401D +Creation Program=FFmpeg +Creation Date=Thursday, January 1, 1970 +Creation Time=00:00:00 +Time Code Rate=30DF + +00:00:58:29 T49S494F43ZZ72F4QROO74ZZBFAB +00:00:59:00 T49S494F43Z0172F4QROO74Z01BDAB +00:00:59:01 T49S494F43Z0272F4QROO74Z02BBAB +00:00:59:02 T49S494F43Z0372F4QROO74Z03B9AB +00:00:59:03 T49S494F43Z0472F4QROO74Z04B7AB +00:00:59:04 T49S494F43Z0572F4QROO74Z05B5AB +00:00:59:05 T49S494F43Z0672F4QROO74Z06B3AB +00:00:59:06 T49S494F43Z0772F4QROO74Z07B1AB +00:00:59:07 T49S494F43Z0872F4QROO74Z08AFAB +00:00:59:08 T49S494F43Z0972F4QROO74Z09ADAB +00:00:59:09 T49S494F43Z0A72F4QROO74Z0AABAB +00:00:59:10 T49S494F43Z0B72F4QROO74Z0BA9AB +00:00:59:11 T49S494F43Z0C72F4QROO74Z0CA7AB +00:00:59:12 T49S494F43Z0D72F4QROO74Z0DA5AB +00:00:59:13 T49S494F43Z0E72F4QROO74Z0EA3AB +00:00:59:14 T49S494F43Z0F72F4QROO74Z0FA1AB +00:00:59:15 T49S494F43Z1072F4QROO74Z109FAB +00:00:59:16 T49S494F43Z1172F4QROO74Z119DAB +00:00:59:17 T49S494F43Z1272F4QROO74Z129BAB +00:00:59:18 T49S494F43Z1372F4QROO74Z1399AB +00:00:59:19 T49S494F43Z1472F4QROO74Z1497AB +00:00:59:20 T49S494F43Z1572F4QROO74Z1595AB +00:00:59:21 T49S494F43Z1672F4QROO74Z1693AB +00:00:59:22 T49S494F43Z1772F4QROO74Z1791AB +00:00:59:23 T49S494F43Z1872F4QROO74Z188FAB +00:00:59:24 T49S494F43Z1972F4QROO74Z198DAB +00:00:59:25 T49S494F43Z1A72F4QROO74Z1A8BAB +00:00:59:26 T49S494F43Z1B72F4QROO74Z1B89AB +00:00:59:27 T49S494F43Z1C72F4QROO74Z1C87AB +00:00:59:28 T49S494F43Z1D72F4QROO74Z1D85AB +00:00:59:29 T49S494F43Z1E72F4QROO74Z1E83AB +00:01:00:02 T49S494F43Z1F72F4QROO74Z1F81AB diff --git a/tests/ref/fate/sub-mcc-remux-eia608-bsf b/tests/ref/fate/sub-mcc-remux-eia608-bsf new file mode 100644 index 0000000000..64e428f6f1 --- /dev/null +++ b/tests/ref/fate/sub-mcc-remux-eia608-bsf @@ -0,0 +1,76 @@ +File Format=MacCaption_MCC V2.0 + +/////////////////////////////////////////////////////////////////////////////////// +// Computer Prompting and Captioning Company +// Ancillary Data Packet Transfer File +// +// Permission to generate this format is granted provided that +// 1. This ANC Transfer file format is used on an as-is basis and no warranty is given, and +// 2. This entire descriptive information text is included in a generated .mcc file. +// +// General file format: +// HH:MM:SS:FF(tab)[Hexadecimal ANC data in groups of 2 characters] +// Hexadecimal data starts with the Ancillary Data Packet DID (Data ID defined in S291M) +// and concludes with the Check Sum following the User Data Words. +// Each time code line must contain at most one complete ancillary data packet. +// To transfer additional ANC Data successive lines may contain identical time code. +// Time Code Rate=[24, 25, 30, 30DF, 50, 60, 60DF] +// +// ANC data bytes may be represented by one ASCII character according to the following schema: +// G FAh 00h 00h +// H 2 x (FAh 00h 00h) +// I 3 x (FAh 00h 00h) +// J 4 x (FAh 00h 00h) +// K 5 x (FAh 00h 00h) +// L 6 x (FAh 00h 00h) +// M 7 x (FAh 00h 00h) +// N 8 x (FAh 00h 00h) +// O 9 x (FAh 00h 00h) +// P FBh 80h 80h +// Q FCh 80h 80h +// R FDh 80h 80h +// S 96h 69h +// T 61h 01h +// U E1h 00h 00h 00h +// Z 00h +// +/////////////////////////////////////////////////////////////////////////////////// + +UUID=0087C4F6-A6B4-5469-8C8E-BBF44950401D +Creation Program=FFmpeg +Creation Date=Thursday, January 1, 1970 +Creation Time=00:00:00 +Time Code Rate=30DF + +00:00:58:29.0,11 T49S497F43FFFF72F4QROO74FFFF93AB +00:00:59:00.0,11 T49S497F43ZZ72F4QROO74ZZ8FAB +00:00:59:01.0,11 T49S497F43Z0172F4QROO74Z018DAB +00:00:59:02.0,11 T49S497F43Z0272F4QROO74Z028BAB +00:00:59:03.0,11 T49S497F43Z0372F4QROO74Z0389AB +00:00:59:04.0,11 T49S497F43Z0472F4QROO74Z0487AB +00:00:59:05.0,11 T49S497F43Z0572F4QROO74Z0585AB +00:00:59:06.0,11 T49S497F43Z0672F4QROO74Z0683AB +00:00:59:07.0,11 T49S497F43Z0772F4QROO74Z0781AB +00:00:59:08.0,11 T49S497F43Z0872F4QROO74Z087FAB +00:00:59:09.0,11 T49S497F43Z0972F4QROO74Z097DAB +00:00:59:10.0,11 T49S497F43Z0A72F4QROO74Z0A7BAB +00:00:59:11.0,11 T49S497F43Z0B72F4QROO74Z0B79AB +00:00:59:12.0,11 T49S497F43Z0C72F4QROO74Z0C77AB +00:00:59:13.0,11 T49S497F43Z0D72F4QROO74Z0D75AB +00:00:59:14.0,11 T49S497F43Z0E72F4QROO74Z0E73AB +00:00:59:15.0,11 T49S497F43Z0F72F4QROO74Z0F71AB +00:00:59:16.0,11 T49S497F43Z1072F4QROO74Z106FAB +00:00:59:17.0,11 T49S497F43Z1172F4QROO74Z116DAB +00:00:59:18.0,11 T49S497F43Z1272F4QROO74Z126BAB +00:00:59:19.0,11 T49S497F43Z1372F4QROO74Z1369AB +00:00:59:20.0,11 T49S497F43Z1472F4QROO74Z1467AB +00:00:59:21.0,11 T49S497F43Z1572F4QROO74Z1565AB +00:00:59:22.0,11 T49S497F43Z1672F4QROO74Z1663AB +00:00:59:23.0,11 T49S497F43Z1772F4QROO74Z1761AB +00:00:59:24.0,11 T49S497F43Z1872F4QROO74Z185FAB +00:00:59:25.0,11 T49S497F43Z1972F4QROO74Z195DAB +00:00:59:26.0,11 T49S497F43Z1A72F4QROO74Z1A5BAB +00:00:59:27.0,11 T49S497F43Z1B72F4QROO74Z1B59AB +00:00:59:28.0,11 T49S497F43Z1C72F4QROO74Z1C57AB +00:00:59:29.0,11 T49S497F43Z1D72F4QROO74Z1D55AB +00:01:00:02.0,11 T49S497F43Z1E72F4QROO74Z1E53AB |