3 * Copyright (c) 2008 Michael Niedermayer
5 * This file is part of FFmpeg.
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.
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.
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
22 #include "libavutil/avstring.h"
26 #include "libavutil/opt.h"
28 typedef struct DialogueLine
{
31 struct DialogueLine
*prev
, *next
;
34 typedef struct ASSContext
{
36 int expected_readorder
;
37 DialogueLine
*dialogue_cache
;
38 DialogueLine
*last_added_dialogue
;
46 static int write_header(AVFormatContext
*s
)
48 ASSContext
*ass
= s
->priv_data
;
49 AVCodecParameters
*par
= s
->streams
[0]->codecpar
;
51 if (s
->nb_streams
!= 1 || par
->codec_id
!= AV_CODEC_ID_ASS
) {
52 av_log(s
, AV_LOG_ERROR
, "Exactly one ASS/SSA stream is needed.\n");
53 return AVERROR(EINVAL
);
55 avpriv_set_pts_info(s
->streams
[0], 64, 1, 100);
56 if (par
->extradata_size
> 0) {
57 size_t header_size
= par
->extradata_size
;
58 uint8_t *trailer
= strstr(par
->extradata
, "\n[Events]");
61 trailer
= strstr(trailer
, "Format:");
63 trailer
= strstr(trailer
, "\n");
66 header_size
= (trailer
- par
->extradata
);
67 ass
->trailer_size
= par
->extradata_size
- header_size
;
68 if (ass
->trailer_size
)
69 ass
->trailer
= trailer
;
72 avio_write(s
->pb
, par
->extradata
, header_size
);
73 if (par
->extradata
[header_size
- 1] != '\n')
74 avio_write(s
->pb
, "\r\n", 2);
75 ass
->ssa_mode
= !strstr(par
->extradata
, "\n[V4+ Styles]");
76 if (!strstr(par
->extradata
, "\n[Events]"))
77 avio_printf(s
->pb
, "[Events]\r\nFormat: %s, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text\r\n",
78 ass
->ssa_mode
? "Marked" : "Layer");
84 static void purge_dialogues(AVFormatContext
*s
, int force
)
87 ASSContext
*ass
= s
->priv_data
;
88 DialogueLine
*dialogue
= ass
->dialogue_cache
;
90 while (dialogue
&& (dialogue
->readorder
== ass
->expected_readorder
|| force
)) {
91 DialogueLine
*next
= dialogue
->next
;
92 if (dialogue
->readorder
!= ass
->expected_readorder
) {
93 av_log(s
, AV_LOG_WARNING
, "ReadOrder gap found between %d and %d\n",
94 ass
->expected_readorder
, dialogue
->readorder
);
95 ass
->expected_readorder
= dialogue
->readorder
;
97 avio_print(s
->pb
, "Dialogue: ", dialogue
->line
, "\r\n");
98 if (dialogue
== ass
->last_added_dialogue
)
99 ass
->last_added_dialogue
= next
;
100 av_freep(&dialogue
->line
);
104 dialogue
= ass
->dialogue_cache
= next
;
105 ass
->expected_readorder
++;
108 ass
->cache_size
-= n
;
110 av_log(s
, AV_LOG_DEBUG
, "wrote %d ASS lines, cached dialogues: %d, waiting for event id %d\n",
111 n
, ass
->cache_size
, ass
->expected_readorder
);
114 static void insert_dialogue(ASSContext
*ass
, DialogueLine
*dialogue
)
116 DialogueLine
*cur
, *next
= NULL
, *prev
= NULL
;
118 /* from the last added to the end of the list */
119 if (ass
->last_added_dialogue
) {
120 for (cur
= ass
->last_added_dialogue
; cur
; cur
= cur
->next
) {
121 if (cur
->readorder
> dialogue
->readorder
)
128 /* from the beginning to the last one added */
130 next
= ass
->dialogue_cache
;
131 for (cur
= next
; cur
!= ass
->last_added_dialogue
; cur
= cur
->next
) {
132 if (cur
->readorder
> dialogue
->readorder
)
140 prev
->next
= dialogue
;
141 dialogue
->prev
= prev
;
143 dialogue
->prev
= ass
->dialogue_cache
;
144 ass
->dialogue_cache
= dialogue
;
147 next
->prev
= dialogue
;
148 dialogue
->next
= next
;
151 ass
->last_added_dialogue
= dialogue
;
154 static int write_packet(AVFormatContext
*s
, AVPacket
*pkt
)
156 ASSContext
*ass
= s
->priv_data
;
160 int64_t start
= pkt
->pts
;
161 int64_t end
= start
+ pkt
->duration
;
162 int hh1
, mm1
, ss1
, ms1
;
163 int hh2
, mm2
, ss2
, ms2
;
164 DialogueLine
*dialogue
= av_mallocz(sizeof(*dialogue
));
167 return AVERROR(ENOMEM
);
169 dialogue
->readorder
= strtol(p
, &p
, 10);
170 if (dialogue
->readorder
< ass
->expected_readorder
)
171 av_log(s
, AV_LOG_WARNING
, "Unexpected ReadOrder %d\n",
172 dialogue
->readorder
);
176 if (ass
->ssa_mode
&& !strncmp(p
, "Marked=", 7))
179 layer
= strtol(p
, &p
, 10);
182 hh1
= (int)(start
/ 360000); mm1
= (int)(start
/ 6000) % 60;
183 hh2
= (int)(end
/ 360000); mm2
= (int)(end
/ 6000) % 60;
184 ss1
= (int)(start
/ 100) % 60; ms1
= (int)(start
% 100);
185 ss2
= (int)(end
/ 100) % 60; ms2
= (int)(end
% 100);
186 if (hh1
> 9) hh1
= 9, mm1
= 59, ss1
= 59, ms1
= 99;
187 if (hh2
> 9) hh2
= 9, mm2
= 59, ss2
= 59, ms2
= 99;
189 dialogue
->line
= av_asprintf("%s%ld,%d:%02d:%02d.%02d,%d:%02d:%02d.%02d,%s",
190 ass
->ssa_mode
? "Marked=" : "",
191 layer
, hh1
, mm1
, ss1
, ms1
, hh2
, mm2
, ss2
, ms2
, p
);
192 if (!dialogue
->line
) {
194 return AVERROR(ENOMEM
);
196 insert_dialogue(ass
, dialogue
);
197 purge_dialogues(s
, ass
->ignore_readorder
);
202 static int write_trailer(AVFormatContext
*s
)
204 ASSContext
*ass
= s
->priv_data
;
206 purge_dialogues(s
, 1);
209 avio_write(s
->pb
, ass
->trailer
, ass
->trailer_size
);
215 #define OFFSET(x) offsetof(ASSContext, x)
216 #define E AV_OPT_FLAG_ENCODING_PARAM
217 static const AVOption options
[] = {
218 { "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 static const AVClass ass_class
= {
223 .class_name
= "ass muxer",
224 .item_name
= av_default_item_name
,
226 .version
= LIBAVUTIL_VERSION_INT
,
229 AVOutputFormat ff_ass_muxer
= {
231 .long_name
= NULL_IF_CONFIG_SMALL("SSA (SubStation Alpha) subtitle"),
232 .mime_type
= "text/x-ass",
233 .extensions
= "ass,ssa",
234 .priv_data_size
= sizeof(ASSContext
),
235 .subtitle_codec
= AV_CODEC_ID_ASS
,
236 .write_header
= write_header
,
237 .write_packet
= write_packet
,
238 .write_trailer
= write_trailer
,
239 .flags
= AVFMT_GLOBALHEADER
| AVFMT_NOTIMESTAMPS
| AVFMT_TS_NONSTRICT
,
240 .priv_class
= &ass_class
,