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 write_ts
; // 0: ssa (timing in payload), 1: ass (matroska like)
37 int expected_readorder
;
38 DialogueLine
*dialogue_cache
;
39 DialogueLine
*last_added_dialogue
;
45 static int write_header(AVFormatContext
*s
)
47 ASSContext
*ass
= s
->priv_data
;
48 AVCodecContext
*avctx
= s
->streams
[0]->codec
;
50 if (s
->nb_streams
!= 1 || (avctx
->codec_id
!= AV_CODEC_ID_SSA
&&
51 avctx
->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 ass
->write_ts
= avctx
->codec_id
== AV_CODEC_ID_ASS
;
56 avpriv_set_pts_info(s
->streams
[0], 64, 1, 100);
57 if (avctx
->extradata_size
> 0) {
58 avio_write(s
->pb
, avctx
->extradata
, avctx
->extradata_size
);
59 if (avctx
->extradata
[avctx
->extradata_size
- 1] != '\n')
60 avio_write(s
->pb
, "\r\n", 2);
61 ass
->ssa_mode
= !strstr(avctx
->extradata
, "\n[V4+ Styles]");
62 if (!strstr(avctx
->extradata
, "\n[Events]"))
63 avio_printf(s
->pb
, "[Events]\r\nFormat: %s, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text\r\n",
64 ass
->ssa_mode
? "Marked" : "Layer");
71 static void purge_dialogues(AVFormatContext
*s
, int force
)
74 ASSContext
*ass
= s
->priv_data
;
75 DialogueLine
*dialogue
= ass
->dialogue_cache
;
77 while (dialogue
&& (dialogue
->readorder
== ass
->expected_readorder
|| force
)) {
78 DialogueLine
*next
= dialogue
->next
;
79 if (dialogue
->readorder
!= ass
->expected_readorder
) {
80 av_log(s
, AV_LOG_WARNING
, "ReadOrder gap found between %d and %d\n",
81 ass
->expected_readorder
, dialogue
->readorder
);
82 ass
->expected_readorder
= dialogue
->readorder
;
84 avio_printf(s
->pb
, "Dialogue: %s\r\n", dialogue
->line
);
85 if (dialogue
== ass
->last_added_dialogue
)
86 ass
->last_added_dialogue
= next
;
87 av_free(dialogue
->line
);
91 dialogue
= ass
->dialogue_cache
= next
;
92 ass
->expected_readorder
++;
97 av_log(s
, AV_LOG_DEBUG
, "wrote %d ASS lines, cached dialogues: %d, waiting for event id %d\n",
98 n
, ass
->cache_size
, ass
->expected_readorder
);
101 static void insert_dialogue(ASSContext
*ass
, DialogueLine
*dialogue
)
103 DialogueLine
*cur
, *next
= NULL
, *prev
= NULL
;
105 /* from the last added to the end of the list */
106 if (ass
->last_added_dialogue
) {
107 for (cur
= ass
->last_added_dialogue
; cur
; cur
= cur
->next
) {
108 if (cur
->readorder
> dialogue
->readorder
)
115 /* from the beginning to the last one added */
117 next
= ass
->dialogue_cache
;
118 for (cur
= next
; cur
!= ass
->last_added_dialogue
; cur
= cur
->next
) {
119 if (cur
->readorder
> dialogue
->readorder
)
127 prev
->next
= dialogue
;
128 dialogue
->prev
= prev
;
130 dialogue
->prev
= ass
->dialogue_cache
;
131 ass
->dialogue_cache
= dialogue
;
134 next
->prev
= dialogue
;
135 dialogue
->next
= next
;
138 ass
->last_added_dialogue
= dialogue
;
141 static int write_packet(AVFormatContext
*s
, AVPacket
*pkt
)
143 ASSContext
*ass
= s
->priv_data
;
148 int64_t start
= pkt
->pts
;
149 int64_t end
= start
+ pkt
->duration
;
150 int hh1
, mm1
, ss1
, ms1
;
151 int hh2
, mm2
, ss2
, ms2
;
152 DialogueLine
*dialogue
= av_mallocz(sizeof(*dialogue
));
155 return AVERROR(ENOMEM
);
157 dialogue
->readorder
= strtol(p
, &p
, 10);
158 if (dialogue
->readorder
< ass
->expected_readorder
)
159 av_log(s
, AV_LOG_WARNING
, "Unexpected ReadOrder %d\n",
160 dialogue
->readorder
);
164 if (ass
->ssa_mode
&& !strncmp(p
, "Marked=", 7))
167 layer
= strtol(p
, &p
, 10);
170 hh1
= (int)(start
/ 360000); mm1
= (int)(start
/ 6000) % 60;
171 hh2
= (int)(end
/ 360000); mm2
= (int)(end
/ 6000) % 60;
172 ss1
= (int)(start
/ 100) % 60; ms1
= (int)(start
% 100);
173 ss2
= (int)(end
/ 100) % 60; ms2
= (int)(end
% 100);
174 if (hh1
> 9) hh1
= 9, mm1
= 59, ss1
= 59, ms1
= 99;
175 if (hh2
> 9) hh2
= 9, mm2
= 59, ss2
= 59, ms2
= 99;
177 dialogue
->line
= av_asprintf("%s%ld,%d:%02d:%02d.%02d,%d:%02d:%02d.%02d,%s",
178 ass
->ssa_mode
? "Marked=" : "",
179 layer
, hh1
, mm1
, ss1
, ms1
, hh2
, mm2
, ss2
, ms2
, p
);
180 if (!dialogue
->line
) {
182 return AVERROR(ENOMEM
);
184 insert_dialogue(ass
, dialogue
);
185 purge_dialogues(s
, ass
->ignore_readorder
);
187 avio_write(s
->pb
, pkt
->data
, pkt
->size
);
193 static int write_trailer(AVFormatContext
*s
)
195 purge_dialogues(s
, 1);
199 #define OFFSET(x) offsetof(ASSContext, x)
200 #define E AV_OPT_FLAG_ENCODING_PARAM
201 static const AVOption options
[] = {
202 { "ignore_readorder", "write events immediately, even if they're out-of-order", OFFSET(ignore_readorder
), AV_OPT_TYPE_INT
, {.i64
= 0}, 0, 1, E
},
206 static const AVClass ass_class
= {
207 .class_name
= "ass muxer",
208 .item_name
= av_default_item_name
,
210 .version
= LIBAVUTIL_VERSION_INT
,
213 AVOutputFormat ff_ass_muxer
= {
215 .long_name
= NULL_IF_CONFIG_SMALL("SSA (SubStation Alpha) subtitle"),
216 .mime_type
= "text/x-ssa",
217 .extensions
= "ass,ssa",
218 .priv_data_size
= sizeof(ASSContext
),
219 .subtitle_codec
= AV_CODEC_ID_SSA
,
220 .write_header
= write_header
,
221 .write_packet
= write_packet
,
222 .write_trailer
= write_trailer
,
223 .flags
= AVFMT_GLOBALHEADER
| AVFMT_NOTIMESTAMPS
| AVFMT_TS_NONSTRICT
,
224 .priv_class
= &ass_class
,