1
by Sérgio Benjamim
FFmpeg 2.7.1 source for ppsspp. |
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 "avformat.h" |
|
24 |
#include "internal.h" |
|
25 |
||
26 |
#include "libavutil/opt.h" |
|
27 |
||
28 |
typedef struct DialogueLine { |
|
29 |
int readorder; |
|
30 |
char *line; |
|
31 |
struct DialogueLine *prev, *next; |
|
32 |
} DialogueLine; |
|
33 |
||
34 |
typedef struct ASSContext { |
|
35 |
const AVClass *class; |
|
36 |
int write_ts; // 0: ssa (timing in payload), 1: ass (matroska like) |
|
37 |
int expected_readorder; |
|
38 |
DialogueLine *dialogue_cache; |
|
39 |
DialogueLine *last_added_dialogue; |
|
40 |
int cache_size; |
|
41 |
int ssa_mode; |
|
42 |
int ignore_readorder; |
|
43 |
uint8_t *trailer; |
|
44 |
size_t trailer_size; |
|
45 |
} ASSContext; |
|
46 |
||
47 |
static int write_header(AVFormatContext *s) |
|
48 |
{
|
|
49 |
ASSContext *ass = s->priv_data; |
|
50 |
AVCodecContext *avctx = s->streams[0]->codec; |
|
51 |
||
52 |
if (s->nb_streams != 1 || (avctx->codec_id != AV_CODEC_ID_SSA && |
|
53 |
avctx->codec_id != AV_CODEC_ID_ASS)) { |
|
54 |
av_log(s, AV_LOG_ERROR, "Exactly one ASS/SSA stream is needed.\n"); |
|
55 |
return AVERROR(EINVAL); |
|
56 |
}
|
|
57 |
ass->write_ts = avctx->codec_id == AV_CODEC_ID_ASS; |
|
58 |
avpriv_set_pts_info(s->streams[0], 64, 1, 100); |
|
59 |
if (avctx->extradata_size > 0) { |
|
60 |
size_t header_size = avctx->extradata_size; |
|
61 |
uint8_t *trailer = strstr(avctx->extradata, "\n[Events]"); |
|
62 |
||
63 |
if (trailer) |
|
64 |
trailer = strstr(trailer, "Format:"); |
|
65 |
if (trailer) |
|
66 |
trailer = strstr(trailer, "\n"); |
|
67 |
||
68 |
if (trailer++) { |
|
69 |
header_size = (trailer - avctx->extradata); |
|
70 |
ass->trailer_size = avctx->extradata_size - header_size; |
|
71 |
if (ass->trailer_size) |
|
72 |
ass->trailer = trailer; |
|
73 |
}
|
|
74 |
||
75 |
avio_write(s->pb, avctx->extradata, header_size); |
|
76 |
if (avctx->extradata[header_size - 1] != '\n') |
|
77 |
avio_write(s->pb, "\r\n", 2); |
|
78 |
ass->ssa_mode = !strstr(avctx->extradata, "\n[V4+ Styles]"); |
|
79 |
if (!strstr(avctx->extradata, "\n[Events]")) |
|
80 |
avio_printf(s->pb, "[Events]\r\nFormat: %s, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text\r\n", |
|
81 |
ass->ssa_mode ? "Marked" : "Layer"); |
|
82 |
}
|
|
83 |
avio_flush(s->pb); |
|
84 |
||
85 |
return 0; |
|
86 |
}
|
|
87 |
||
88 |
static void purge_dialogues(AVFormatContext *s, int force) |
|
89 |
{
|
|
90 |
int n = 0; |
|
91 |
ASSContext *ass = s->priv_data; |
|
92 |
DialogueLine *dialogue = ass->dialogue_cache; |
|
93 |
||
94 |
while (dialogue && (dialogue->readorder == ass->expected_readorder || force)) { |
|
95 |
DialogueLine *next = dialogue->next; |
|
96 |
if (dialogue->readorder != ass->expected_readorder) { |
|
97 |
av_log(s, AV_LOG_WARNING, "ReadOrder gap found between %d and %d\n", |
|
98 |
ass->expected_readorder, dialogue->readorder); |
|
99 |
ass->expected_readorder = dialogue->readorder; |
|
100 |
}
|
|
101 |
avio_printf(s->pb, "Dialogue: %s\r\n", dialogue->line); |
|
102 |
if (dialogue == ass->last_added_dialogue) |
|
103 |
ass->last_added_dialogue = next; |
|
104 |
av_freep(&dialogue->line); |
|
105 |
av_free(dialogue); |
|
106 |
if (next) |
|
107 |
next->prev = NULL; |
|
108 |
dialogue = ass->dialogue_cache = next; |
|
109 |
ass->expected_readorder++; |
|
110 |
n++; |
|
111 |
}
|
|
112 |
ass->cache_size -= n; |
|
113 |
if (n > 1) |
|
114 |
av_log(s, AV_LOG_DEBUG, "wrote %d ASS lines, cached dialogues: %d, waiting for event id %d\n", |
|
115 |
n, ass->cache_size, ass->expected_readorder); |
|
116 |
}
|
|
117 |
||
118 |
static void insert_dialogue(ASSContext *ass, DialogueLine *dialogue) |
|
119 |
{
|
|
120 |
DialogueLine *cur, *next = NULL, *prev = NULL; |
|
121 |
||
122 |
/* from the last added to the end of the list */
|
|
123 |
if (ass->last_added_dialogue) { |
|
124 |
for (cur = ass->last_added_dialogue; cur; cur = cur->next) { |
|
125 |
if (cur->readorder > dialogue->readorder) |
|
126 |
break; |
|
127 |
prev = cur; |
|
128 |
next = cur->next; |
|
129 |
}
|
|
130 |
}
|
|
131 |
||
132 |
/* from the beginning to the last one added */
|
|
133 |
if (!prev) { |
|
134 |
next = ass->dialogue_cache; |
|
135 |
for (cur = next; cur != ass->last_added_dialogue; cur = cur->next) { |
|
136 |
if (cur->readorder > dialogue->readorder) |
|
137 |
break; |
|
138 |
prev = cur; |
|
139 |
next = cur->next; |
|
140 |
}
|
|
141 |
}
|
|
142 |
||
143 |
if (prev) { |
|
144 |
prev->next = dialogue; |
|
145 |
dialogue->prev = prev; |
|
146 |
} else { |
|
147 |
dialogue->prev = ass->dialogue_cache; |
|
148 |
ass->dialogue_cache = dialogue; |
|
149 |
}
|
|
150 |
if (next) { |
|
151 |
next->prev = dialogue; |
|
152 |
dialogue->next = next; |
|
153 |
}
|
|
154 |
ass->cache_size++; |
|
155 |
ass->last_added_dialogue = dialogue; |
|
156 |
}
|
|
157 |
||
158 |
static int write_packet(AVFormatContext *s, AVPacket *pkt) |
|
159 |
{
|
|
160 |
ASSContext *ass = s->priv_data; |
|
161 |
||
162 |
if (ass->write_ts) { |
|
163 |
long int layer; |
|
164 |
char *p = pkt->data; |
|
165 |
int64_t start = pkt->pts; |
|
166 |
int64_t end = start + pkt->duration; |
|
167 |
int hh1, mm1, ss1, ms1; |
|
168 |
int hh2, mm2, ss2, ms2; |
|
169 |
DialogueLine *dialogue = av_mallocz(sizeof(*dialogue)); |
|
170 |
||
171 |
if (!dialogue) |
|
172 |
return AVERROR(ENOMEM); |
|
173 |
||
174 |
dialogue->readorder = strtol(p, &p, 10); |
|
175 |
if (dialogue->readorder < ass->expected_readorder) |
|
176 |
av_log(s, AV_LOG_WARNING, "Unexpected ReadOrder %d\n", |
|
177 |
dialogue->readorder); |
|
178 |
if (*p == ',') |
|
179 |
p++; |
|
180 |
||
181 |
if (ass->ssa_mode && !strncmp(p, "Marked=", 7)) |
|
182 |
p += 7; |
|
183 |
||
184 |
layer = strtol(p, &p, 10); |
|
185 |
if (*p == ',') |
|
186 |
p++; |
|
187 |
hh1 = (int)(start / 360000); mm1 = (int)(start / 6000) % 60; |
|
188 |
hh2 = (int)(end / 360000); mm2 = (int)(end / 6000) % 60; |
|
189 |
ss1 = (int)(start / 100) % 60; ms1 = (int)(start % 100); |
|
190 |
ss2 = (int)(end / 100) % 60; ms2 = (int)(end % 100); |
|
191 |
if (hh1 > 9) hh1 = 9, mm1 = 59, ss1 = 59, ms1 = 99; |
|
192 |
if (hh2 > 9) hh2 = 9, mm2 = 59, ss2 = 59, ms2 = 99; |
|
193 |
||
194 |
dialogue->line = av_asprintf("%s%ld,%d:%02d:%02d.%02d,%d:%02d:%02d.%02d,%s", |
|
195 |
ass->ssa_mode ? "Marked=" : "", |
|
196 |
layer, hh1, mm1, ss1, ms1, hh2, mm2, ss2, ms2, p); |
|
197 |
if (!dialogue->line) { |
|
198 |
av_free(dialogue); |
|
199 |
return AVERROR(ENOMEM); |
|
200 |
}
|
|
201 |
insert_dialogue(ass, dialogue); |
|
202 |
purge_dialogues(s, ass->ignore_readorder); |
|
203 |
} else { |
|
204 |
avio_write(s->pb, pkt->data, pkt->size); |
|
205 |
}
|
|
206 |
||
207 |
return 0; |
|
208 |
}
|
|
209 |
||
210 |
static int write_trailer(AVFormatContext *s) |
|
211 |
{
|
|
212 |
ASSContext *ass = s->priv_data; |
|
213 |
||
214 |
purge_dialogues(s, 1); |
|
215 |
||
216 |
if (ass->trailer) { |
|
217 |
avio_write(s->pb, ass->trailer, ass->trailer_size); |
|
218 |
}
|
|
219 |
||
220 |
return 0; |
|
221 |
}
|
|
222 |
||
223 |
#define OFFSET(x) offsetof(ASSContext, x)
|
|
224 |
#define E AV_OPT_FLAG_ENCODING_PARAM
|
|
225 |
static const AVOption options[] = { |
|
226 |
{ "ignore_readorder", "write events immediately, even if they're out-of-order", OFFSET(ignore_readorder), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, E }, |
|
227 |
{ NULL }, |
|
228 |
};
|
|
229 |
||
230 |
static const AVClass ass_class = { |
|
231 |
.class_name = "ass muxer", |
|
232 |
.item_name = av_default_item_name, |
|
233 |
.option = options, |
|
234 |
.version = LIBAVUTIL_VERSION_INT, |
|
235 |
};
|
|
236 |
||
237 |
AVOutputFormat ff_ass_muxer = { |
|
238 |
.name = "ass", |
|
239 |
.long_name = NULL_IF_CONFIG_SMALL("SSA (SubStation Alpha) subtitle"), |
|
240 |
.mime_type = "text/x-ssa", |
|
241 |
.extensions = "ass,ssa", |
|
242 |
.priv_data_size = sizeof(ASSContext), |
|
243 |
.subtitle_codec = AV_CODEC_ID_SSA, |
|
244 |
.write_header = write_header, |
|
245 |
.write_packet = write_packet, |
|
246 |
.write_trailer = write_trailer, |
|
247 |
.flags = AVFMT_GLOBALHEADER | AVFMT_NOTIMESTAMPS | AVFMT_TS_NONSTRICT, |
|
248 |
.priv_class = &ass_class, |
|
249 |
};
|