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 | By default, this work-around is disabled. | 4629 | By default, this work-around is disabled. |
| 4630 | @end table | 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 | @c man end SUBTITLES ENCODERS | 4646 | @c man end SUBTITLES ENCODERS |
| @@ -32,6 +32,12 @@ · 31 unmodified lines | |||
| 32 | #include "libavutil/dict.h" | 32 | #include "libavutil/dict.h" |
| 33 | #include "libavutil/log.h" | 33 | #include "libavutil/log.h" |
| 34 | #include "libavutil/macros.h" | 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 | static int lrc_write_header(AVFormatContext *s) | 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 | avcodec_get_name(s->streams[0]->codecpar->codec_id)); | 49 | avcodec_get_name(s->streams[0]->codecpar->codec_id)); |
| 44 | return AVERROR(EINVAL); | 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 | ff_standardize_creation_time(s); | 54 | ff_standardize_creation_time(s); |
| 49 | ff_metadata_conv_ctx(s, ff_lrc_metadata_conv, NULL); | 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 | static int lrc_write_packet(AVFormatContext *s, AVPacket *pkt) | 84 | static int lrc_write_packet(AVFormatContext *s, AVPacket *pkt) |
| 79 | { | 85 | { |
| 86 | const LRCSubtitleContext *p = s->priv_data; | ||
| 87 | |||
| 80 | if(pkt->pts != AV_NOPTS_VALUE) { | 88 | if(pkt->pts != AV_NOPTS_VALUE) { |
| 81 | const uint8_t *line = pkt->data; | 89 | const uint8_t *line = pkt->data; |
| 82 | const uint8_t *end = pkt->data + pkt->size; | 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 | line++; // Skip first empty lines | 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 | while(line) { | 103 | while(line) { |
| 92 | const uint8_t *next_line = memchr(line, '\n', end - line); | 104 | const uint8_t *next_line = memchr(line, '\n', end - line); |
| 93 | size_t size = end - line; | 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 | /* Offset feature of LRC can easily make pts negative, | 118 | /* Offset feature of LRC can easily make pts negative, |
| 107 | * we just output it directly and let the player drop it. */ | 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 | avio_write(s->pb, "[-", 1 + (pkt->pts < 0)); | 125 | avio_write(s->pb, "[-", 1 + (pkt->pts < 0)); |
| 109 | avio_printf(s->pb, "%02"PRIu64":%02"PRIu64".%02"PRIu64"]", | 126 | avio_printf(s->pb, "%02"PRIu64":%02"PRIu64".%0*"PRIu64"]", |
| 110 | (FFABS64U(pkt->pts) / 6000), | 127 | minutes, seconds, p->precision, rescaled); |
| 111 | ((FFABS64U(pkt->pts) / 100) % 60), | ||
| 112 | (FFABS64U(pkt->pts) % 100)); | ||
| 113 | 128 | ||
| 114 | avio_write(s->pb, line, size); | 129 | avio_write(s->pb, line, size); |
| 115 | avio_w8(s->pb, '\n'); | 130 | avio_w8(s->pb, '\n'); |
| @@ -119,6 +134,20 @@ static int lrc_write_packet(AVFormatContext *s, AVPacket *pkt) · 3 unmodified lines | |||
| 119 | return 0; | 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 | const FFOutputFormat ff_lrc_muxer = { | 151 | const FFOutputFormat ff_lrc_muxer = { |
| 123 | .p.name = "lrc", | 152 | .p.name = "lrc", |
| 124 | .p.long_name = NULL_IF_CONFIG_SMALL("LRC lyrics"), | 153 | .p.long_name = NULL_IF_CONFIG_SMALL("LRC lyrics"), |
| @@ -129,7 +158,8 @@ const FFOutputFormat ff_lrc_muxer = { · 4 unmodified lines | |||
| 129 | .p.audio_codec = AV_CODEC_ID_NONE, | 158 | .p.audio_codec = AV_CODEC_ID_NONE, |
| 130 | .p.subtitle_codec = AV_CODEC_ID_SUBRIP, | 159 | .p.subtitle_codec = AV_CODEC_ID_SUBRIP, |
| 131 | .flags_internal = FF_OFMT_FLAG_MAX_ONE_OF_EACH, | 160 | .flags_internal = FF_OFMT_FLAG_MAX_ONE_OF_EACH, |
| 132 | .priv_data_size = 0, | 161 | .priv_data_size = sizeof(LRCSubtitleContext), |
| 133 | .write_header = lrc_write_header, | 162 | .write_header = lrc_write_header, |
| 134 | .write_packet = lrc_write_packet, | 163 | .write_packet = lrc_write_packet, |
| 164 | .p.priv_class = &lrcenc_class, | ||
| 135 | }; | 165 | }; |