Subversion Repositories Kolibri OS

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
6147 serge 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
};