FFmpeg coverage


Directory: ../../../ffmpeg/
File: src/libavformat/assenc.c
Date: 2024-04-25 05:10:44
Exec Total Coverage
Lines: 100 112 89.3%
Functions: 5 5 100.0%
Branches: 51 72 70.8%

Line Branch Exec Source
1 /*
2 * SSA/ASS muxer
3 * Copyright (c) 2008 Michael Niedermayer
4 *
5 * This file is part of FFmpeg.
6 *
7 * FFmpeg is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
11 *
12 * FFmpeg is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with FFmpeg; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20 */
21
22 #include "libavutil/avstring.h"
23 #include "libavutil/mem.h"
24 #include "avformat.h"
25 #include "avio_internal.h"
26 #include "internal.h"
27 #include "mux.h"
28
29 #include "libavutil/opt.h"
30
31 typedef struct DialogueLine {
32 int readorder;
33 char *line;
34 struct DialogueLine *prev, *next;
35 } DialogueLine;
36
37 typedef struct ASSContext {
38 const AVClass *class;
39 int expected_readorder;
40 DialogueLine *dialogue_cache;
41 DialogueLine *last_added_dialogue;
42 int cache_size;
43 int ssa_mode;
44 int ignore_readorder;
45 uint8_t *trailer;
46 size_t trailer_size;
47 } ASSContext;
48
49 26 static int write_header(AVFormatContext *s)
50 {
51 26 ASSContext *ass = s->priv_data;
52 26 AVCodecParameters *par = s->streams[0]->codecpar;
53
54 26 avpriv_set_pts_info(s->streams[0], 64, 1, 100);
55
1/2
✓ Branch 0 taken 26 times.
✗ Branch 1 not taken.
26 if (par->extradata_size > 0) {
56 26 size_t header_size = par->extradata_size;
57 26 uint8_t *trailer = strstr(par->extradata, "\n[Events]");
58
59
1/2
✓ Branch 0 taken 26 times.
✗ Branch 1 not taken.
26 if (trailer)
60 26 trailer = strstr(trailer, "Format:");
61
1/2
✓ Branch 0 taken 26 times.
✗ Branch 1 not taken.
26 if (trailer)
62 26 trailer = strstr(trailer, "\n");
63
64
1/2
✓ Branch 0 taken 26 times.
✗ Branch 1 not taken.
26 if (trailer) {
65 26 header_size = (++trailer - par->extradata);
66 26 ass->trailer_size = par->extradata_size - header_size;
67
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 24 times.
26 if (ass->trailer_size)
68 2 ass->trailer = trailer;
69 }
70
71 26 ffio_write_lines(s->pb, par->extradata, header_size, NULL);
72
73 26 ass->ssa_mode = !strstr(par->extradata, "\n[V4+ Styles]");
74
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 26 times.
26 if (!strstr(par->extradata, "\n[Events]"))
75 avio_printf(s->pb, "[Events]\nFormat: %s, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text\n",
76 ass->ssa_mode ? "Marked" : "Layer");
77 }
78
79 26 return 0;
80 }
81
82 511 static void purge_dialogues(AVFormatContext *s, int force)
83 {
84 511 int n = 0;
85 511 ASSContext *ass = s->priv_data;
86 511 DialogueLine *dialogue = ass->dialogue_cache;
87
88
5/6
✓ Branch 0 taken 512 times.
✓ Branch 1 taken 484 times.
✓ Branch 2 taken 485 times.
✓ Branch 3 taken 27 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 27 times.
996 while (dialogue && (dialogue->readorder == ass->expected_readorder || force)) {
89 485 DialogueLine *next = dialogue->next;
90
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 485 times.
485 if (dialogue->readorder != ass->expected_readorder) {
91 av_log(s, AV_LOG_WARNING, "ReadOrder gap found between %d and %d\n",
92 ass->expected_readorder, dialogue->readorder);
93 ass->expected_readorder = dialogue->readorder;
94 }
95 485 avio_print(s->pb, "Dialogue: ", dialogue->line, "\n");
96
1/2
✓ Branch 0 taken 485 times.
✗ Branch 1 not taken.
485 if (dialogue == ass->last_added_dialogue)
97 485 ass->last_added_dialogue = next;
98 485 av_freep(&dialogue->line);
99 485 av_free(dialogue);
100
2/2
✓ Branch 0 taken 27 times.
✓ Branch 1 taken 458 times.
485 if (next)
101 27 next->prev = NULL;
102 485 dialogue = ass->dialogue_cache = next;
103 485 ass->expected_readorder++;
104 485 n++;
105 }
106 511 ass->cache_size -= n;
107
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 509 times.
511 if (n > 1)
108 2 av_log(s, AV_LOG_DEBUG, "wrote %d ASS lines, cached dialogues: %d, waiting for event id %d\n",
109 n, ass->cache_size, ass->expected_readorder);
110 511 }
111
112 485 static void insert_dialogue(ASSContext *ass, DialogueLine *dialogue)
113 {
114 485 DialogueLine *cur, *next = NULL, *prev = NULL;
115
116 /* from the last added to the end of the list */
117
2/2
✓ Branch 0 taken 27 times.
✓ Branch 1 taken 458 times.
485 if (ass->last_added_dialogue) {
118
2/2
✓ Branch 0 taken 36 times.
✓ Branch 1 taken 14 times.
50 for (cur = ass->last_added_dialogue; cur; cur = cur->next) {
119
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 23 times.
36 if (cur->readorder > dialogue->readorder)
120 13 break;
121 23 prev = cur;
122 23 next = cur->next;
123 }
124 }
125
126 /* from the beginning to the last one added */
127
2/2
✓ Branch 0 taken 471 times.
✓ Branch 1 taken 14 times.
485 if (!prev) {
128 471 next = ass->dialogue_cache;
129
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 467 times.
471 for (cur = next; cur != ass->last_added_dialogue; cur = cur->next) {
130
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 if (cur->readorder > dialogue->readorder)
131 4 break;
132 prev = cur;
133 next = cur->next;
134 }
135 }
136
137
2/2
✓ Branch 0 taken 14 times.
✓ Branch 1 taken 471 times.
485 if (prev) {
138 14 prev->next = dialogue;
139 14 dialogue->prev = prev;
140 } else {
141 471 dialogue->prev = ass->dialogue_cache;
142 471 ass->dialogue_cache = dialogue;
143 }
144
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 472 times.
485 if (next) {
145 13 next->prev = dialogue;
146 13 dialogue->next = next;
147 }
148 485 ass->cache_size++;
149 485 ass->last_added_dialogue = dialogue;
150 485 }
151
152 485 static int write_packet(AVFormatContext *s, AVPacket *pkt)
153 {
154 485 ASSContext *ass = s->priv_data;
155
156 long int layer;
157 int text_len;
158 485 char *p = pkt->data;
159 485 int64_t start = pkt->pts;
160 485 int64_t end = start + pkt->duration;
161 int hh1, mm1, ss1, ms1;
162 int hh2, mm2, ss2, ms2;
163 485 DialogueLine *dialogue = av_mallocz(sizeof(*dialogue));
164
165
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 485 times.
485 if (!dialogue)
166 return AVERROR(ENOMEM);
167
168 485 dialogue->readorder = strtol(p, &p, 10);
169
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 485 times.
485 if (dialogue->readorder < ass->expected_readorder)
170 av_log(s, AV_LOG_WARNING, "Unexpected ReadOrder %d\n",
171 dialogue->readorder);
172
1/2
✓ Branch 0 taken 485 times.
✗ Branch 1 not taken.
485 if (*p == ',')
173 485 p++;
174
175
3/4
✓ Branch 0 taken 35 times.
✓ Branch 1 taken 450 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 35 times.
485 if (ass->ssa_mode && !strncmp(p, "Marked=", 7))
176 p += 7;
177
178 485 layer = strtol(p, &p, 10);
179
1/2
✓ Branch 0 taken 485 times.
✗ Branch 1 not taken.
485 if (*p == ',')
180 485 p++;
181 485 hh1 = (int)(start / 360000); mm1 = (int)(start / 6000) % 60;
182 485 hh2 = (int)(end / 360000); mm2 = (int)(end / 6000) % 60;
183 485 ss1 = (int)(start / 100) % 60; ms1 = (int)(start % 100);
184 485 ss2 = (int)(end / 100) % 60; ms2 = (int)(end % 100);
185
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 485 times.
485 if (hh1 > 9) hh1 = 9, mm1 = 59, ss1 = 59, ms1 = 99;
186
2/2
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 473 times.
485 if (hh2 > 9) hh2 = 9, mm2 = 59, ss2 = 59, ms2 = 99;
187
188 485 text_len = strlen(p);
189
3/6
✓ Branch 0 taken 485 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 485 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 485 times.
485 while (text_len > 0 && p[text_len - 1] == '\r' || p[text_len - 1] == '\n')
190 text_len--;
191
192 485 dialogue->line = av_asprintf("%s%ld,%d:%02d:%02d.%02d,%d:%02d:%02d.%02d,%.*s",
193
2/2
✓ Branch 0 taken 35 times.
✓ Branch 1 taken 450 times.
485 ass->ssa_mode ? "Marked=" : "",
194 layer, hh1, mm1, ss1, ms1, hh2, mm2, ss2, ms2, text_len, p);
195
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 485 times.
485 if (!dialogue->line) {
196 av_free(dialogue);
197 return AVERROR(ENOMEM);
198 }
199 485 insert_dialogue(ass, dialogue);
200 485 purge_dialogues(s, ass->ignore_readorder);
201
202 485 return 0;
203 }
204
205 26 static int write_trailer(AVFormatContext *s)
206 {
207 26 ASSContext *ass = s->priv_data;
208
209 26 purge_dialogues(s, 1);
210
211
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 24 times.
26 if (ass->trailer) {
212 2 ffio_write_lines(s->pb, ass->trailer, ass->trailer_size, NULL);
213 }
214
215 26 return 0;
216 }
217
218 #define OFFSET(x) offsetof(ASSContext, x)
219 #define E AV_OPT_FLAG_ENCODING_PARAM
220 static const AVOption options[] = {
221 { "ignore_readorder", "write events immediately, even if they're out-of-order", OFFSET(ignore_readorder), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, E },
222 { NULL },
223 };
224
225 static const AVClass ass_class = {
226 .class_name = "ass muxer",
227 .item_name = av_default_item_name,
228 .option = options,
229 .version = LIBAVUTIL_VERSION_INT,
230 };
231
232 const FFOutputFormat ff_ass_muxer = {
233 .p.name = "ass",
234 .p.long_name = NULL_IF_CONFIG_SMALL("SSA (SubStation Alpha) subtitle"),
235 .p.mime_type = "text/x-ass",
236 .p.extensions = "ass,ssa",
237 .p.audio_codec = AV_CODEC_ID_NONE,
238 .p.video_codec = AV_CODEC_ID_NONE,
239 .p.subtitle_codec = AV_CODEC_ID_ASS,
240 .p.flags = AVFMT_GLOBALHEADER | AVFMT_NOTIMESTAMPS | AVFMT_TS_NONSTRICT,
241 .flags_internal = FF_OFMT_FLAG_MAX_ONE_OF_EACH |
242 FF_OFMT_FLAG_ONLY_DEFAULT_CODECS,
243 .p.priv_class = &ass_class,
244 .priv_data_size = sizeof(ASSContext),
245 .write_header = write_header,
246 .write_packet = write_packet,
247 .write_trailer = write_trailer,
248 };
249