Allows to output LRC with more precision than standard centiseconds. Time base is determined by number of requested digits.
Unified · Split
| @@ -4629,4 +4629,18 @@ one byte per subtitle on average. · 4628 unmodified lines | ||
| 4629 | 4629 | By default, this work-around is disabled. |
| 4630 | 4630 | @end table |
| 4631 | 4631 | |
| 4632 | +@section lrc | |
| 4633 | + | |
| 4634 | +This codec encodes the LRC lyrics format. | |
| 4635 | + | |
| 4636 | +@subsection Options | |
| 4637 | + | |
| 4638 | +@table @option | |
| 4639 | +@item precision | |
| 4640 | +Specify the precision of the fractional part of the timestamp. Time base is | |
| 4641 | +determined based on this value. | |
| 4642 | + | |
| 4643 | +Defaults to 2 for centiseconds. | |
| 4644 | +@end table | |
| 4645 | + | |
| 4632 | 4646 | @c man end SUBTITLES ENCODERS |
| @@ -32,6 +32,12 @@ · 31 unmodified lines | ||
| 32 | 32 | #include "libavutil/dict.h" |
| 33 | 33 | #include "libavutil/log.h" |
| 34 | 34 | #include "libavutil/macros.h" |
| 35 | +#include "libavutil/opt.h" | |
| 36 | + | |
| 37 | +typedef struct LRCSubtitleContext { | |
| 38 | + const AVClass *class; | |
| 39 | + int precision; ///< precision of the fractional part of the timestamp, 2 for centiseconds | |
| 40 | +} LRCSubtitleContext; | |
| 35 | 41 | |
| 36 | 42 | static int lrc_write_header(AVFormatContext *s) |
| 37 | 43 | { |
| @@ -43,7 +49,7 @@ static int lrc_write_header(AVFormatContext *s) · 5 unmodified lines | ||
| 43 | 49 | avcodec_get_name(s->streams[0]->codecpar->codec_id)); |
| 44 | 50 | return AVERROR(EINVAL); |
| 45 | 51 | } |
| 46 | − avpriv_set_pts_info(s->streams[0], 64, 1, 100); | |
| 52 | + avpriv_set_pts_info(s->streams[0], 64, 1, AV_TIME_BASE); | |
| 47 | 53 | |
| 48 | 54 | ff_standardize_creation_time(s); |
| 49 | 55 | ff_metadata_conv_ctx(s, ff_lrc_metadata_conv, NULL); |
| @@ -77,6 +83,8 @@ static int lrc_write_header(AVFormatContext *s) · 27 unmodified lines | ||
| 77 | 83 | |
| 78 | 84 | static int lrc_write_packet(AVFormatContext *s, AVPacket *pkt) |
| 79 | 85 | { |
| 86 | + const LRCSubtitleContext *p = s->priv_data; | |
| 87 | + | |
| 80 | 88 | if(pkt->pts != AV_NOPTS_VALUE) { |
| 81 | 89 | const uint8_t *line = pkt->data; |
| 82 | 90 | const uint8_t *end = pkt->data + pkt->size; |
| @@ -88,6 +96,10 @@ static int lrc_write_packet(AVFormatContext *s, AVPacket *pkt) · 5 unmodified lines | ||
| 88 | 96 | line++; // Skip first empty lines |
| 89 | 97 | } |
| 90 | 98 | |
| 99 | + int frac_mult = 1; | |
| 100 | + for (int i = 0; i < p->precision; ++i) | |
| 101 | + frac_mult *= 10; | |
| 102 | + | |
| 91 | 103 | while(line) { |
| 92 | 104 | const uint8_t *next_line = memchr(line, '\n', end - line); |
| 93 | 105 | size_t size = end - line; |
| @@ -105,11 +117,14 @@ static int lrc_write_packet(AVFormatContext *s, AVPacket *pkt) · 11 unmodified lines | ||
| 105 | 117 | |
| 106 | 118 | /* Offset feature of LRC can easily make pts negative, |
| 107 | 119 | * we just output it directly and let the player drop it. */ |
| 120 | + uint64_t abs_pts = FFABS64U(pkt->pts); | |
| 121 | + uint64_t minutes = abs_pts / (60 * AV_TIME_BASE); | |
| 122 | + uint64_t seconds = (abs_pts / AV_TIME_BASE) % 60; | |
| 123 | + uint64_t fraction = abs_pts % AV_TIME_BASE; | |
| 124 | + uint64_t rescaled = av_rescale_q(fraction, AV_TIME_BASE_Q, (AVRational){1, frac_mult}); | |
| 108 | 125 | avio_write(s->pb, "[-", 1 + (pkt->pts < 0)); |
| 109 | − avio_printf(s->pb, "%02"PRIu64":%02"PRIu64".%02"PRIu64"]", | |
| 110 | − (FFABS64U(pkt->pts) / 6000), | |
| 111 | − ((FFABS64U(pkt->pts) / 100) % 60), | |
| 112 | − (FFABS64U(pkt->pts) % 100)); | |
| 126 | + avio_printf(s->pb, "%02"PRIu64":%02"PRIu64".%0*"PRIu64"]", | |
| 127 | + minutes, seconds, p->precision, rescaled); | |
| 113 | 128 | |
| 114 | 129 | avio_write(s->pb, line, size); |
| 115 | 130 | avio_w8(s->pb, '\n'); |
| @@ -119,6 +134,20 @@ static int lrc_write_packet(AVFormatContext *s, AVPacket *pkt) · 3 unmodified lines | ||
| 119 | 134 | return 0; |
| 120 | 135 | } |
| 121 | 136 | |
| 137 | +#define OFFSET(x) offsetof(LRCSubtitleContext, x) | |
| 138 | +#define SE AV_OPT_FLAG_SUBTITLE_PARAM | AV_OPT_FLAG_ENCODING_PARAM | |
| 139 | +static const AVOption options[] = { | |
| 140 | + {"precision", "precision of the fractional part of the timestamp, 2 for centiseconds", OFFSET(precision), AV_OPT_TYPE_INT, {.i64 = 2}, 1, 6, SE}, | |
| 141 | + { NULL }, | |
| 142 | +}; | |
| 143 | + | |
| 144 | +static const AVClass lrcenc_class = { | |
| 145 | + .class_name = "lrc", | |
| 146 | + .item_name = av_default_item_name, | |
| 147 | + .option = options, | |
| 148 | + .version = LIBAVUTIL_VERSION_INT, | |
| 149 | +}; | |
| 150 | + | |
| 122 | 151 | const FFOutputFormat ff_lrc_muxer = { |
| 123 | 152 | .p.name = "lrc", |
| 124 | 153 | .p.long_name = NULL_IF_CONFIG_SMALL("LRC lyrics"), |
| @@ -129,7 +158,8 @@ const FFOutputFormat ff_lrc_muxer = { · 4 unmodified lines | ||
| 129 | 158 | .p.audio_codec = AV_CODEC_ID_NONE, |
| 130 | 159 | .p.subtitle_codec = AV_CODEC_ID_SUBRIP, |
| 131 | 160 | .flags_internal = FF_OFMT_FLAG_MAX_ONE_OF_EACH, |
| 132 | − .priv_data_size = 0, | |
| 161 | + .priv_data_size = sizeof(LRCSubtitleContext), | |
| 133 | 162 | .write_header = lrc_write_header, |
| 134 | 163 | .write_packet = lrc_write_packet, |
| 164 | + .p.priv_class = &lrcenc_class, | |
| 135 | 165 | }; |