OSDN Git Service

Universal Text Subtitle Support Initial Implementation
[handbrake-jp/handbrake-jp-git.git] / libhb / dectx3gsub.c
1 /* 
2    This file is part of the HandBrake source code.
3    Homepage: <http://handbrake.fr/>.
4    It may be used under the terms of the GNU General Public License. */
5
6 /*
7  * Converts TX3G subtitles to UTF-8 subtitles with limited HTML-style markup (<b>, <i>, <u>).
8  * 
9  * TX3G == MPEG 4, Part 17 (ISO/IEC 14496-17) == 3GPP Timed Text (26.245)
10  * A full reference to the format can be found here:
11  * http://www.3gpp.org/ftp/Specs/html-info/26245.htm
12  * 
13  * @author David Foster (davidfstr)
14  */
15
16 #include <stdlib.h>
17 #include <stdio.h>
18 #include "hb.h"
19
20 typedef enum {
21     BOLD        = 0x1,
22     ITALIC      = 0x2,
23     UNDERLINE   = 0x4
24 } FaceStyleFlag;
25
26 #define NUM_FACE_STYLE_FLAGS 3
27 #define MAX_OPEN_TAG_SIZE 3     // "<b>"
28 #define MAX_CLOSE_TAG_SIZE 4    // "</b>"
29
30 typedef struct {
31     uint16_t startChar;       // NOTE: indices in terms of *character* (not: byte) positions
32     uint16_t endChar;
33     uint16_t fontID;
34     uint8_t faceStyleFlags;   // FaceStyleFlag
35     uint8_t fontSize;
36     uint32_t textColorRGBA;
37 } StyleRecord;
38
39 // NOTE: None of these macros check for buffer overflow
40 #define READ_U8()       *pos;                                                       pos += 1;
41 #define READ_U16()      (pos[0] << 8) | pos[1];                                     pos += 2;
42 #define READ_U32()      (pos[0] << 24) | (pos[1] << 16) | (pos[2] << 8) | pos[3];   pos += 4;
43 #define READ_ARRAY(n)   pos;                                                        pos += n;
44
45 #define WRITE_CHAR(c)       {dst[0]=c;                                              dst += 1;}
46 #define WRITE_START_TAG(c)  {dst[0]='<'; dst[1]=c;   dst[2]='>';                    dst += 3;}
47 #define WRITE_END_TAG(c)    {dst[0]='<'; dst[1]='/'; dst[2]=c; dst[3]='>';          dst += 4;}
48
49 #define FOURCC(str)    ((((uint32_t) str[0]) << 24) | \
50                         (((uint32_t) str[1]) << 16) | \
51                         (((uint32_t) str[2]) << 8) | \
52                         (((uint32_t) str[3]) << 0))
53 #define IS_10xxxxxx(c) ((c & 0xC0) == 0x80)
54
55 static hb_buffer_t *tx3g_decode_to_utf8( hb_buffer_t *in )
56 {
57     uint8_t *pos = in->data;
58     uint8_t *end = in->data + in->size;
59     
60     uint16_t numStyleRecords = 0;
61     
62     uint8_t *startStyle;
63     uint8_t *endStyle;
64     
65     /*
66      * Parse the packet as a TX3G TextSample.
67      * 
68      * Look for a single StyleBox ('styl') and read all contained StyleRecords.
69      * Ignore all other box types.
70      * 
71      * NOTE: Buffer overflows on read are not checked.
72      */
73     uint16_t textLength = READ_U16();
74     uint8_t *text = READ_ARRAY(textLength);
75     startStyle = calloc( textLength, 1 );
76     endStyle = calloc( textLength, 1 );
77     while ( pos < end ) {
78         /*
79          * Read TextSampleModifierBox
80          */
81         uint32_t size = READ_U32();
82         if ( size == 0 ) {
83             size = pos - end;   // extends to end of packet
84         }
85         if ( size == 1 ) {
86             hb_log( "dectx3gsub: TextSampleModifierBox has unsupported large size" );
87             break;
88         }
89         uint32_t type = READ_U32();
90         if ( type == FOURCC("uuid") ) {
91             hb_log( "dectx3gsub: TextSampleModifierBox has unsupported extended type" );
92             break;
93         }
94         
95         if ( type == FOURCC("styl") ) {
96             // Found a StyleBox. Parse the contained StyleRecords
97             
98             if ( numStyleRecords != 0 ) {
99                 hb_log( "dectx3gsub: found additional StyleBoxes on subtitle; skipping" );
100                 READ_ARRAY(size);
101                 continue;
102             }
103             
104             numStyleRecords = READ_U16();
105             
106             int i;
107             for (i=0; i<numStyleRecords; i++) {
108                 StyleRecord curRecord;
109                 curRecord.startChar         = READ_U16();
110                 curRecord.endChar           = READ_U16();
111                 curRecord.fontID            = READ_U16();
112                 curRecord.faceStyleFlags    = READ_U8();
113                 curRecord.fontSize          = READ_U8();
114                 curRecord.textColorRGBA     = READ_U32();
115                 
116                 startStyle[curRecord.startChar] |= curRecord.faceStyleFlags;
117                 endStyle[curRecord.endChar]     |= curRecord.faceStyleFlags;
118             }
119         } else {
120             // Found some other kind of TextSampleModifierBox. Skip it.
121             READ_ARRAY(size);
122         }
123     }
124     
125     /*
126      * Copy text to output buffer, and add HTML markup for the style records
127      */
128     int maxOutputSize = textLength + (numStyleRecords * NUM_FACE_STYLE_FLAGS * (MAX_OPEN_TAG_SIZE + MAX_CLOSE_TAG_SIZE));
129     hb_buffer_t *out = hb_buffer_init( maxOutputSize );
130     uint8_t *dst = out->data;
131     int charIndex = 0;
132     for ( pos = text, end = text + textLength; pos < end; pos++ ) {
133         if (IS_10xxxxxx(*pos)) {
134             // Is a non-first byte of a multi-byte UTF-8 character
135             WRITE_CHAR(*pos);
136             continue;   // ...without incrementing 'charIndex'
137         }
138         
139         uint8_t plusStyles = startStyle[charIndex];
140         uint8_t minusStyles = endStyle[charIndex];
141         
142         if (minusStyles & UNDERLINE)
143             WRITE_END_TAG('u');
144         if (minusStyles & ITALIC)
145             WRITE_END_TAG('i');
146         if (minusStyles & BOLD)
147             WRITE_END_TAG('b');
148         
149         if (plusStyles & BOLD)
150             WRITE_START_TAG('b');
151         if (plusStyles & ITALIC)
152             WRITE_START_TAG('i');
153         if (plusStyles & UNDERLINE)
154             WRITE_START_TAG('u');
155         
156         WRITE_CHAR(*pos);
157         charIndex++;
158     }
159     
160     // Trim output buffer to the actual amount of data written
161     out->size = dst - out->data;
162     
163     // Copy metadata from the input packet to the output packet
164     out->start = in->start;
165     out->stop = in->stop;
166     
167     free( startStyle );
168     free( endStyle );
169     
170     return out;
171 }
172
173 #undef READ_U8
174 #undef READ_U16
175 #undef READ_U32
176 #undef READ_ARRAY
177
178 #undef WRITE_CHAR
179 #undef WRITE_START_TAG
180 #undef WRITE_END_TAG
181
182 static int dectx3gInit( hb_work_object_t * w, hb_job_t * job )
183 {
184     return 0;
185 }
186
187 static int dectx3gWork( hb_work_object_t * w, hb_buffer_t ** buf_in,
188                         hb_buffer_t ** buf_out )
189 {
190     hb_buffer_t * in = *buf_in;
191     hb_buffer_t * out = NULL;
192     
193     // Warn if the subtitle's duration has not been passed through by the demuxer,
194     // which will prevent the subtitle from displaying at all
195     if ( in->stop == 0 ) {
196         hb_log( "dectx3gsub: subtitle packet lacks duration" );
197     }
198     
199     if ( in->size > 0 ) {
200         out = tx3g_decode_to_utf8(in);
201     } else {
202         out = hb_buffer_init( 0 );
203     }
204     
205     // We shouldn't be storing the extra NULL character,
206     // but the MP4 muxer expects this, unfortunately.
207     if ( out->size > 0 && out->data[out->size - 1] != '\0' ) {
208         // NOTE: out->size remains unchanged
209         hb_buffer_realloc( out, out->size + 1 );
210         out->data[out->size] = '\0';
211     }
212     
213     // If the input packet was non-empty, do not pass through
214     // an empty output packet (even if the subtitle was empty),
215     // as this would be interpreted as an end-of-stream
216     if ( in->size > 0 && out->size == 0 ) {
217         hb_buffer_close(&out);
218     }
219     
220     // Dispose the input packet, as it is no longer needed
221     hb_buffer_close(&in);
222     
223     *buf_in = NULL;
224     *buf_out = out;
225     return HB_WORK_OK;
226 }
227
228 static void dectx3gClose( hb_work_object_t * w )
229 {
230     // nothing
231 }
232
233 hb_work_object_t hb_dectx3gsub =
234 {
235     WORK_DECTX3GSUB,
236     "TX3G Subtitle Decoder",
237     dectx3gInit,
238     dectx3gWork,
239     dectx3gClose
240 };