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 typedef struct DialogueLine
{
29 struct DialogueLine
*prev
, *next
;
32 typedef struct ASSContext
{
33 unsigned int extra_index
;
34 int write_ts
; // 0: ssa (timing in payload), 1: ass (matroska like)
35 int expected_readorder
;
36 DialogueLine
*dialogue_cache
;
37 DialogueLine
*last_added_dialogue
;
41 static int write_header(AVFormatContext
*s
)
43 ASSContext
*ass
= s
->priv_data
;
44 AVCodecContext
*avctx
= s
->streams
[0]->codec
;
47 if (s
->nb_streams
!= 1 || (avctx
->codec_id
!= AV_CODEC_ID_SSA
&&
48 avctx
->codec_id
!= AV_CODEC_ID_ASS
)) {
49 av_log(s
, AV_LOG_ERROR
, "Exactly one ASS/SSA stream is needed.\n");
52 ass
->write_ts
= avctx
->codec_id
== AV_CODEC_ID_ASS
;
53 avpriv_set_pts_info(s
->streams
[0], 64, 1, 100);
55 while(ass
->extra_index
< avctx
->extradata_size
){
56 uint8_t *p
= avctx
->extradata
+ ass
->extra_index
;
57 uint8_t *end
= strchr(p
, '\n');
58 if(!end
) end
= avctx
->extradata
+ avctx
->extradata_size
;
61 avio_write(s
->pb
, p
, end
-p
);
62 ass
->extra_index
+= end
-p
;
64 if(last
&& !memcmp(last
, "[Events]", 8))
74 static void purge_dialogues(AVFormatContext
*s
, int force
)
77 ASSContext
*ass
= s
->priv_data
;
78 DialogueLine
*dialogue
= ass
->dialogue_cache
;
80 while (dialogue
&& (dialogue
->readorder
== ass
->expected_readorder
|| force
)) {
81 DialogueLine
*next
= dialogue
->next
;
82 if (dialogue
->readorder
!= ass
->expected_readorder
) {
83 av_log(s
, AV_LOG_WARNING
, "ReadOrder gap found between %d and %d\n",
84 ass
->expected_readorder
, dialogue
->readorder
);
85 ass
->expected_readorder
= dialogue
->readorder
;
87 avio_printf(s
->pb
, "Dialogue: %s\r\n", dialogue
->line
);
88 if (dialogue
== ass
->last_added_dialogue
)
89 ass
->last_added_dialogue
= next
;
90 av_free(dialogue
->line
);
94 dialogue
= ass
->dialogue_cache
= next
;
95 ass
->expected_readorder
++;
100 av_log(s
, AV_LOG_DEBUG
, "wrote %d ASS lines, cached dialogues: %d, waiting for event id %d\n",
101 n
, ass
->cache_size
, ass
->expected_readorder
);
104 static void insert_dialogue(ASSContext
*ass
, DialogueLine
*dialogue
)
106 DialogueLine
*cur
, *next
= NULL
, *prev
= NULL
;
108 /* from the last added to the end of the list */
109 if (ass
->last_added_dialogue
) {
110 for (cur
= ass
->last_added_dialogue
; cur
; cur
= cur
->next
) {
111 if (cur
->readorder
> dialogue
->readorder
)
118 /* from the beginning to the last one added */
120 next
= ass
->dialogue_cache
;
121 for (cur
= next
; cur
!= ass
->last_added_dialogue
; cur
= cur
->next
) {
122 if (cur
->readorder
> dialogue
->readorder
)
130 prev
->next
= dialogue
;
131 dialogue
->prev
= prev
;
133 dialogue
->prev
= ass
->dialogue_cache
;
134 ass
->dialogue_cache
= dialogue
;
137 next
->prev
= dialogue
;
138 dialogue
->next
= next
;
141 ass
->last_added_dialogue
= dialogue
;
144 static int write_packet(AVFormatContext
*s
, AVPacket
*pkt
)
146 ASSContext
*ass
= s
->priv_data
;
151 int64_t start
= pkt
->pts
;
152 int64_t end
= start
+ pkt
->duration
;
153 int hh1
, mm1
, ss1
, ms1
;
154 int hh2
, mm2
, ss2
, ms2
;
155 DialogueLine
*dialogue
= av_mallocz(sizeof(*dialogue
));
158 return AVERROR(ENOMEM
);
160 dialogue
->readorder
= strtol(p
, &p
, 10);
161 if (dialogue
->readorder
< ass
->expected_readorder
)
162 av_log(s
, AV_LOG_WARNING
, "Unexpected ReadOrder %d\n",
163 dialogue
->readorder
);
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("%ld,%d:%02d:%02d.%02d,%d:%02d:%02d.%02d,%s",
178 layer
, hh1
, mm1
, ss1
, ms1
, hh2
, mm2
, ss2
, ms2
, p
);
179 if (!dialogue
->line
) {
181 return AVERROR(ENOMEM
);
183 insert_dialogue(ass
, dialogue
);
184 purge_dialogues(s
, 0);
186 avio_write(s
->pb
, pkt
->data
, pkt
->size
);
192 static int write_trailer(AVFormatContext
*s
)
194 ASSContext
*ass
= s
->priv_data
;
195 AVCodecContext
*avctx
= s
->streams
[0]->codec
;
197 purge_dialogues(s
, 1);
198 avio_write(s
->pb
, avctx
->extradata
+ ass
->extra_index
,
199 avctx
->extradata_size
- ass
->extra_index
);
204 AVOutputFormat ff_ass_muxer
= {
206 .long_name
= NULL_IF_CONFIG_SMALL("SSA (SubStation Alpha) subtitle"),
207 .mime_type
= "text/x-ssa",
208 .extensions
= "ass,ssa",
209 .priv_data_size
= sizeof(ASSContext
),
210 .subtitle_codec
= AV_CODEC_ID_SSA
,
211 .write_header
= write_header
,
212 .write_packet
= write_packet
,
213 .write_trailer
= write_trailer
,
214 .flags
= AVFMT_GLOBALHEADER
| AVFMT_NOTIMESTAMPS
| AVFMT_TS_NONSTRICT
,