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 |