diff --git a/libavformat/matroska.h b/libavformat/matroska.h index af662bea4d..886a34636d 100644 --- a/libavformat/matroska.h +++ b/libavformat/matroska.h @@ -230,6 +230,7 @@ typedef enum { MATROSKA_TRACK_TYPE_LOGO = 0x10, MATROSKA_TRACK_TYPE_SUBTITLE = 0x11, MATROSKA_TRACK_TYPE_CONTROL = 0x20, + MATROSKA_TRACK_TYPE_METADATA = 0x21, } MatroskaTrackType; typedef enum { diff --git a/libavformat/matroskaenc.c b/libavformat/matroskaenc.c index dfbb174981..7efda5ab6b 100644 --- a/libavformat/matroskaenc.c +++ b/libavformat/matroskaenc.c @@ -600,7 +600,11 @@ static int mkv_write_tracks(AVFormatContext *s) if ((tag = av_dict_get(st->metadata, "title", NULL, 0))) put_ebml_string(pb, MATROSKA_ID_TRACKNAME, tag->value); tag = av_dict_get(st->metadata, "language", NULL, 0); - put_ebml_string(pb, MATROSKA_ID_TRACKLANGUAGE, tag ? tag->value:"und"); + if (mkv->mode != MODE_WEBM || codec->codec_id != AV_CODEC_ID_WEBVTT) { + put_ebml_string(pb, MATROSKA_ID_TRACKLANGUAGE, tag ? tag->value:"und"); + } else if (tag && tag->value) { + put_ebml_string(pb, MATROSKA_ID_TRACKLANGUAGE, tag->value); + } if (default_stream_exists) { put_ebml_uint(pb, MATROSKA_ID_TRACKFLAGDEFAULT, !!(st->disposition & AV_DISPOSITION_DEFAULT)); @@ -608,22 +612,41 @@ static int mkv_write_tracks(AVFormatContext *s) if (st->disposition & AV_DISPOSITION_FORCED) put_ebml_uint(pb, MATROSKA_ID_TRACKFLAGFORCED, 1); - // look for a codec ID string specific to mkv to use, - // if none are found, use AVI codes - for (j = 0; ff_mkv_codec_tags[j].id != AV_CODEC_ID_NONE; j++) { - if (ff_mkv_codec_tags[j].id == codec->codec_id) { - put_ebml_string(pb, MATROSKA_ID_CODECID, ff_mkv_codec_tags[j].str); - native_id = 1; - break; + if (mkv->mode == MODE_WEBM && codec->codec_id == AV_CODEC_ID_WEBVTT) { + const char *codec_id; + if (st->disposition & AV_DISPOSITION_CAPTIONS) { + codec_id = "D_WEBVTT/CAPTIONS"; + native_id = MATROSKA_TRACK_TYPE_SUBTITLE; + } else if (st->disposition & AV_DISPOSITION_DESCRIPTIONS) { + codec_id = "D_WEBVTT/DESCRIPTIONS"; + native_id = MATROSKA_TRACK_TYPE_METADATA; + } else if (st->disposition & AV_DISPOSITION_METADATA) { + codec_id = "D_WEBVTT/METADATA"; + native_id = MATROSKA_TRACK_TYPE_METADATA; + } else { + codec_id = "D_WEBVTT/SUBTITLES"; + native_id = MATROSKA_TRACK_TYPE_SUBTITLE; + } + put_ebml_string(pb, MATROSKA_ID_CODECID, codec_id); + } else { + // look for a codec ID string specific to mkv to use, + // if none are found, use AVI codes + for (j = 0; ff_mkv_codec_tags[j].id != AV_CODEC_ID_NONE; j++) { + if (ff_mkv_codec_tags[j].id == codec->codec_id) { + put_ebml_string(pb, MATROSKA_ID_CODECID, ff_mkv_codec_tags[j].str); + native_id = 1; + break; + } } } if (mkv->mode == MODE_WEBM && !(codec->codec_id == AV_CODEC_ID_VP8 || codec->codec_id == AV_CODEC_ID_VP9 || ((codec->codec_id == AV_CODEC_ID_OPUS)&&(codec->strict_std_compliance <= FF_COMPLIANCE_EXPERIMENTAL)) || - codec->codec_id == AV_CODEC_ID_VORBIS)) { + codec->codec_id == AV_CODEC_ID_VORBIS || + codec->codec_id == AV_CODEC_ID_WEBVTT)) { av_log(s, AV_LOG_ERROR, - "Only VP8,VP9 video and Vorbis,Opus(experimental, use -strict -2) audio are supported for WebM.\n"); + "Only VP8,VP9 video and Vorbis,Opus(experimental, use -strict -2) audio and WebVTT subtitles are supported for WebM.\n"); return AVERROR(EINVAL); } @@ -717,18 +740,25 @@ static int mkv_write_tracks(AVFormatContext *s) break; case AVMEDIA_TYPE_SUBTITLE: - put_ebml_uint(pb, MATROSKA_ID_TRACKTYPE, MATROSKA_TRACK_TYPE_SUBTITLE); if (!native_id) { av_log(s, AV_LOG_ERROR, "Subtitle codec %d is not supported.\n", codec->codec_id); return AVERROR(ENOSYS); } + + if (mkv->mode != MODE_WEBM || codec->codec_id != AV_CODEC_ID_WEBVTT) + native_id = MATROSKA_TRACK_TYPE_SUBTITLE; + + put_ebml_uint(pb, MATROSKA_ID_TRACKTYPE, native_id); break; default: av_log(s, AV_LOG_ERROR, "Only audio, video, and subtitles are supported for Matroska.\n"); return AVERROR(EINVAL); } - ret = mkv_write_codecprivate(s, pb, codec, native_id, qt_id); - if (ret < 0) return ret; + + if (mkv->mode != MODE_WEBM || codec->codec_id != AV_CODEC_ID_WEBVTT) { + ret = mkv_write_codecprivate(s, pb, codec, native_id, qt_id); + if (ret < 0) return ret; + } end_ebml_master(pb, track); @@ -1308,6 +1338,44 @@ static int mkv_write_srt_blocks(AVFormatContext *s, AVIOContext *pb, AVPacket *p return duration; } +static int mkv_write_vtt_blocks(AVFormatContext *s, AVIOContext *pb, AVPacket *pkt) +{ + MatroskaMuxContext *mkv = s->priv_data; + ebml_master blockgroup; + int id_size, settings_size, size; + uint8_t *id, *settings; + int64_t ts = mkv->tracks[pkt->stream_index].write_dts ? pkt->dts : pkt->pts; + const int flags = 0; + + id_size = 0; + id = av_packet_get_side_data(pkt, AV_PKT_DATA_WEBVTT_IDENTIFIER, + &id_size); + + settings_size = 0; + settings = av_packet_get_side_data(pkt, AV_PKT_DATA_WEBVTT_SETTINGS, + &settings_size); + + size = id_size + 1 + settings_size + 1 + pkt->size; + + av_log(s, AV_LOG_DEBUG, "Writing block at offset %" PRIu64 ", size %d, " + "pts %" PRId64 ", dts %" PRId64 ", duration %d, flags %d\n", + avio_tell(pb), size, pkt->pts, pkt->dts, pkt->duration, flags); + + blockgroup = start_ebml_master(pb, MATROSKA_ID_BLOCKGROUP, mkv_blockgroup_size(size)); + + put_ebml_id(pb, MATROSKA_ID_BLOCK); + put_ebml_num(pb, size+4, 0); + avio_w8(pb, 0x80 | (pkt->stream_index + 1)); // this assumes stream_index is less than 126 + avio_wb16(pb, ts - mkv->cluster_pts); + avio_w8(pb, flags); + avio_printf(pb, "%.*s\n%.*s\n%.*s", id_size, id, settings_size, settings, pkt->size, pkt->data); + + put_ebml_uint(pb, MATROSKA_ID_BLOCKDURATION, pkt->duration); + end_ebml_master(pb, blockgroup); + + return pkt->duration; +} + static void mkv_flush_dynbuf(AVFormatContext *s) { MatroskaMuxContext *mkv = s->priv_data; @@ -1363,6 +1431,8 @@ static int mkv_write_packet_internal(AVFormatContext *s, AVPacket *pkt) #endif } else if (codec->codec_id == AV_CODEC_ID_SRT) { duration = mkv_write_srt_blocks(s, pb, pkt); + } else if (codec->codec_id == AV_CODEC_ID_WEBVTT) { + duration = mkv_write_vtt_blocks(s, pb, pkt); } else { ebml_master blockgroup = start_ebml_master(pb, MATROSKA_ID_BLOCKGROUP, mkv_blockgroup_size(pkt->size)); /* For backward compatibility, prefer convergence_duration. */ @@ -1606,6 +1676,7 @@ AVOutputFormat ff_webm_muxer = { .priv_data_size = sizeof(MatroskaMuxContext), .audio_codec = AV_CODEC_ID_VORBIS, .video_codec = AV_CODEC_ID_VP8, + .subtitle_codec = AV_CODEC_ID_WEBVTT, .write_header = mkv_write_header, .write_packet = mkv_write_packet, .write_trailer = mkv_write_trailer,