You've already forked FFmpeg
mirror of
https://github.com/FFmpeg/FFmpeg.git
synced 2025-08-10 06:10:52 +02:00
lavf: add mcc muxer
Signed-off-by: Jacob Lifshay <programmerjake@gmail.com>
This commit is contained in:
2
configure
vendored
2
configure
vendored
@@ -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"
|
||||
|
@@ -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.
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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;
|
||||
|
537
libavformat/mccenc.c
Normal file
537
libavformat/mccenc.c
Normal file
@@ -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,
|
||||
};
|
@@ -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
|
||||
|
||||
|
92
tests/ref/fate/sub-mcc-remux
Normal file
92
tests/ref/fate/sub-mcc-remux
Normal file
@@ -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
|
76
tests/ref/fate/sub-mcc-remux-eia608
Normal file
76
tests/ref/fate/sub-mcc-remux-eia608
Normal file
@@ -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
|
76
tests/ref/fate/sub-mcc-remux-eia608-bsf
Normal file
76
tests/ref/fate/sub-mcc-remux-eia608-bsf
Normal file
@@ -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
|
Reference in New Issue
Block a user