Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
52868 views
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_BOOL, {.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
};
250
251