Mercurial > mplayer.hg
annotate sub/subassconvert.c @ 36883:e299b6fd7eaa
Fix image size restrictions set by r35242.
Luma blocks are 16x16, but chroma blocks are (two interweaved) 8x16.
Since chroma is at quarter resolution it means that the image
width must be multiple of 16, while
height must be multiple of 32.
author | iive |
---|---|
date | Wed, 05 Mar 2014 21:58:04 +0000 |
parents | 389d43c448b3 |
children |
rev | line source |
---|---|
32462 | 1 /* |
2 * Subtitles converter to SSA/ASS in order to allow special formatting | |
3 * | |
4 * This file is part of MPlayer. | |
5 * | |
6 * MPlayer is free software; you can redistribute it and/or modify | |
7 * it under the terms of the GNU General Public License as published by | |
8 * the Free Software Foundation; either version 2 of the License, or | |
9 * (at your option) any later version. | |
10 * | |
11 * MPlayer is distributed in the hope that it will be useful, | |
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 * GNU General Public License for more details. | |
15 * | |
16 * You should have received a copy of the GNU General Public License along | |
17 * with MPlayer; if not, write to the Free Software Foundation, Inc., | |
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | |
19 */ | |
20 | |
21 #include <string.h> | |
35903 | 22 #include <strings.h> |
32462 | 23 #include <stdint.h> |
24 #include <stdlib.h> | |
25 #include <stdio.h> | |
26 #include <stdarg.h> | |
27 | |
28 #include "mp_msg.h" | |
29 #include "help_mp.h" | |
30 #include "bstr.h" | |
32464
22888a8cb312
Do not use a path for including files in the same directory.
reimar
parents:
32462
diff
changeset
|
31 #include "subassconvert.h" |
32462 | 32 #include "libavutil/common.h" |
33 | |
34 struct line { | |
35 char *buf; | |
36 size_t bufsize; | |
37 size_t len; | |
38 }; | |
39 | |
40 #ifdef __GNUC__ | |
41 static void append_text(struct line *dst, char *fmt, ...) __attribute__ ((format(printf, 2, 3))); | |
42 #endif | |
43 | |
44 static void append_text(struct line *dst, char *fmt, ...) | |
45 { | |
46 va_list va; | |
47 int ret; | |
48 | |
49 va_start(va, fmt); | |
50 ret = vsnprintf(dst->buf + dst->len, dst->bufsize - dst->len, fmt, va); | |
51 if (ret >= 0) { | |
52 dst->len += ret; | |
53 if (dst->len > dst->bufsize) | |
54 dst->len = dst->bufsize; | |
55 } | |
56 va_end(va); | |
57 } | |
58 | |
59 static int indexof(const char *s, int c) | |
60 { | |
61 char *f = strchr(s, c); | |
62 return f ? (f - s) : -1; | |
63 } | |
64 | |
65 | |
66 | |
67 /* | |
68 * SubRip | |
69 * | |
70 * Support basic tags (italic, bold, underline, strike-through) | |
71 * and font tag with size, color and face attributes. | |
72 * | |
73 */ | |
74 | |
75 struct font_tag { | |
76 struct bstr face; | |
77 int size; | |
78 uint32_t color; | |
79 }; | |
80 | |
81 static const struct tag_conv { | |
82 const char *from; | |
83 const char *to; | |
84 } subrip_basic_tags[] = { | |
85 {"<i>", "{\\i1}"}, {"</i>", "{\\i0}"}, | |
86 {"<b>", "{\\b1}"}, {"</b>", "{\\b0}"}, | |
87 {"<u>", "{\\u1}"}, {"</u>", "{\\u0}"}, | |
88 {"<s>", "{\\s1}"}, {"</s>", "{\\s0}"}, | |
89 {"{", "\\{"}, {"}", "\\}"}, | |
34757 | 90 {"\r\n", "\\N"}, {"\n", "\\N"}, {"\r", "\\N"}, |
32462 | 91 }; |
92 | |
93 static const struct { | |
94 const char *s; | |
95 uint32_t v; | |
96 } subrip_web_colors[] = { | |
34912
3f8886645ebd
Order colour names alphabetically / better match W3 list.
cehoyos
parents:
34758
diff
changeset
|
97 /* Named CSS3 colors in BGR format; a subset of those |
3f8886645ebd
Order colour names alphabetically / better match W3 list.
cehoyos
parents:
34758
diff
changeset
|
98 at http://www.w3.org/TR/css3-color/#svg-color */ |
3f8886645ebd
Order colour names alphabetically / better match W3 list.
cehoyos
parents:
34758
diff
changeset
|
99 {"aqua", 0xffff00}, |
3f8886645ebd
Order colour names alphabetically / better match W3 list.
cehoyos
parents:
34758
diff
changeset
|
100 {"black", 0x000000}, |
3f8886645ebd
Order colour names alphabetically / better match W3 list.
cehoyos
parents:
34758
diff
changeset
|
101 {"blue", 0xff0000}, |
34913 | 102 {"cyan", 0xffff00}, |
34912
3f8886645ebd
Order colour names alphabetically / better match W3 list.
cehoyos
parents:
34758
diff
changeset
|
103 {"fuchsia", 0xff00ff}, |
3f8886645ebd
Order colour names alphabetically / better match W3 list.
cehoyos
parents:
34758
diff
changeset
|
104 {"gray", 0x808080}, |
3f8886645ebd
Order colour names alphabetically / better match W3 list.
cehoyos
parents:
34758
diff
changeset
|
105 {"green", 0x008000}, |
34913 | 106 {"grey", 0x808080}, |
34912
3f8886645ebd
Order colour names alphabetically / better match W3 list.
cehoyos
parents:
34758
diff
changeset
|
107 {"lime", 0x00ff00}, |
34913 | 108 {"magenta", 0xff00ff}, |
34912
3f8886645ebd
Order colour names alphabetically / better match W3 list.
cehoyos
parents:
34758
diff
changeset
|
109 {"maroon", 0x000080}, |
3f8886645ebd
Order colour names alphabetically / better match W3 list.
cehoyos
parents:
34758
diff
changeset
|
110 {"navy", 0x800000}, |
3f8886645ebd
Order colour names alphabetically / better match W3 list.
cehoyos
parents:
34758
diff
changeset
|
111 {"olive", 0x008080}, |
34913 | 112 {"orange", 0x00a5ff}, |
113 {"pink", 0xcbc0ff}, | |
34912
3f8886645ebd
Order colour names alphabetically / better match W3 list.
cehoyos
parents:
34758
diff
changeset
|
114 {"purple", 0x800080}, |
3f8886645ebd
Order colour names alphabetically / better match W3 list.
cehoyos
parents:
34758
diff
changeset
|
115 {"red", 0x0000ff}, |
3f8886645ebd
Order colour names alphabetically / better match W3 list.
cehoyos
parents:
34758
diff
changeset
|
116 {"silver", 0xc0c0c0}, |
3f8886645ebd
Order colour names alphabetically / better match W3 list.
cehoyos
parents:
34758
diff
changeset
|
117 {"teal", 0x808000}, |
3f8886645ebd
Order colour names alphabetically / better match W3 list.
cehoyos
parents:
34758
diff
changeset
|
118 {"white", 0xffffff}, |
3f8886645ebd
Order colour names alphabetically / better match W3 list.
cehoyos
parents:
34758
diff
changeset
|
119 {"yellow", 0x00ffff}, |
32462 | 120 }; |
121 | |
122 #define SUBRIP_MAX_STACKED_FONT_TAGS 16 | |
123 #define SUBRIP_FLAG_COLOR 0x01000000 | |
124 | |
125 /** | |
126 * \brief Convert SubRip lines into ASS markup | |
127 * \param orig original SubRip lines. The content will remain untouched. | |
128 * \param dest ASS markup destination buffer. | |
129 * \param dest_buffer_size maximum size for the destination buffer. | |
130 */ | |
131 void subassconvert_subrip(const char *orig, char *dest, size_t dest_buffer_size) | |
132 { | |
133 /* line is not const to avoid warnings with strtol, etc. | |
134 * orig content won't be changed */ | |
135 char *line = (char *)orig; | |
136 struct line new_line = { | |
137 .buf = dest, | |
138 .bufsize = dest_buffer_size, | |
139 }; | |
140 struct font_tag font_stack[SUBRIP_MAX_STACKED_FONT_TAGS]; | |
141 int sp = 0; | |
142 | |
143 font_stack[0] = (struct font_tag){}; // type with all defaults | |
144 while (*line && new_line.len < new_line.bufsize - 1) { | |
145 char *orig_line = line; | |
146 int i; | |
147 | |
148 for (i = 0; i < FF_ARRAY_ELEMS(subrip_basic_tags); i++) { | |
149 const struct tag_conv *tag = &subrip_basic_tags[i]; | |
150 int from_len = strlen(tag->from); | |
151 if (strncmp(line, tag->from, from_len) == 0) { | |
152 append_text(&new_line, "%s", tag->to); | |
153 line += from_len; | |
154 } | |
155 } | |
156 | |
157 if (strncmp(line, "</font>", 7) == 0) { | |
158 /* Closing font tag */ | |
159 line += 7; | |
160 | |
161 if (sp > 0) { | |
162 struct font_tag *tag = &font_stack[sp]; | |
163 struct font_tag *last_tag = &tag[-1]; | |
164 sp--; | |
165 | |
166 if (tag->size) { | |
167 if (!last_tag->size) | |
168 append_text(&new_line, "{\\fs}"); | |
169 else if (last_tag->size != tag->size) | |
170 append_text(&new_line, "{\\fs%d}", last_tag->size); | |
171 } | |
172 | |
173 if (tag->color & SUBRIP_FLAG_COLOR) { | |
174 if (!(last_tag->color & SUBRIP_FLAG_COLOR)) | |
175 append_text(&new_line, "{\\c}"); | |
176 else if (last_tag->color != tag->color) | |
177 append_text(&new_line, "{\\c&H%06X&}", | |
178 last_tag->color & 0xffffff); | |
179 } | |
180 | |
181 if (tag->face.len) { | |
182 if (!last_tag->face.len) | |
183 append_text(&new_line, "{\\fn}"); | |
184 else if (bstrcmp(last_tag->face, tag->face) != 0) | |
185 append_text(&new_line, "{\\fn%.*s}", | |
186 BSTR_P(last_tag->face)); | |
187 } | |
188 } | |
189 } else if (strncmp(line, "<font ", 6) == 0 | |
190 && sp + 1 < FF_ARRAY_ELEMS(font_stack)) { | |
191 /* Opening font tag */ | |
192 char *potential_font_tag_start = line; | |
193 int len_backup = new_line.len; | |
194 struct font_tag *tag = &font_stack[sp + 1]; | |
195 int has_valid_attr = 0; | |
196 | |
197 *tag = tag[-1]; // keep values from previous tag | |
198 line += 6; | |
199 | |
200 while (*line && *line != '>') { | |
34976
482418e9e922
subassconvert: better handling of invalid SubRip tag attributes.
cboesch
parents:
34913
diff
changeset
|
201 if (strncmp(line, "size=", 5) == 0) { |
482418e9e922
subassconvert: better handling of invalid SubRip tag attributes.
cboesch
parents:
34913
diff
changeset
|
202 line += 5; |
482418e9e922
subassconvert: better handling of invalid SubRip tag attributes.
cboesch
parents:
34913
diff
changeset
|
203 if (*line == '"') line++; |
32462 | 204 tag->size = strtol(line, &line, 10); |
34976
482418e9e922
subassconvert: better handling of invalid SubRip tag attributes.
cboesch
parents:
34913
diff
changeset
|
205 if (!tag->size) |
32462 | 206 break; |
207 append_text(&new_line, "{\\fs%d}", tag->size); | |
208 has_valid_attr = 1; | |
34976
482418e9e922
subassconvert: better handling of invalid SubRip tag attributes.
cboesch
parents:
34913
diff
changeset
|
209 } else if (strncmp(line, "color=", 6) == 0) { |
482418e9e922
subassconvert: better handling of invalid SubRip tag attributes.
cboesch
parents:
34913
diff
changeset
|
210 line += 6; |
482418e9e922
subassconvert: better handling of invalid SubRip tag attributes.
cboesch
parents:
34913
diff
changeset
|
211 if (*line == '"') line++; |
32462 | 212 if (*line == '#') { |
213 // #RRGGBB format | |
214 line++; | |
215 tag->color = strtol(line, &line, 16) & 0x00ffffff; | |
216 tag->color = ((tag->color & 0xff) << 16) | | |
217 (tag->color & 0xff00) | | |
218 ((tag->color & 0xff0000) >> 16) | | |
219 SUBRIP_FLAG_COLOR; | |
220 } else { | |
221 // Standard web colors | |
34976
482418e9e922
subassconvert: better handling of invalid SubRip tag attributes.
cboesch
parents:
34913
diff
changeset
|
222 int i; |
32462 | 223 for (i = 0; i < FF_ARRAY_ELEMS(subrip_web_colors); i++) { |
224 const char *color = subrip_web_colors[i].s; | |
34976
482418e9e922
subassconvert: better handling of invalid SubRip tag attributes.
cboesch
parents:
34913
diff
changeset
|
225 const int len = strlen(color); |
482418e9e922
subassconvert: better handling of invalid SubRip tag attributes.
cboesch
parents:
34913
diff
changeset
|
226 if (strncasecmp(line, color, len) == 0) { |
32462 | 227 tag->color = SUBRIP_FLAG_COLOR | subrip_web_colors[i].v; |
34976
482418e9e922
subassconvert: better handling of invalid SubRip tag attributes.
cboesch
parents:
34913
diff
changeset
|
228 line += len; |
32462 | 229 break; |
230 } | |
231 } | |
232 | |
233 if (i == FF_ARRAY_ELEMS(subrip_web_colors)) { | |
234 /* We didn't find any matching color */ | |
34976
482418e9e922
subassconvert: better handling of invalid SubRip tag attributes.
cboesch
parents:
34913
diff
changeset
|
235 line += strcspn(line, "\" >"); |
32462 | 236 mp_msg(MSGT_SUBREADER, MSGL_WARN, |
237 MSGTR_SUBTITLES_SubRip_UnknownFontColor, orig); | |
238 append_text(&new_line, "{\\c}"); | |
239 continue; | |
240 } | |
241 } | |
242 append_text(&new_line, "{\\c&H%06X&}", tag->color & 0xffffff); | |
243 has_valid_attr = 1; | |
244 } else if (strncmp(line, "face=\"", 6) == 0) { | |
245 /* Font face attribute */ | |
246 int len; | |
247 line += 6; | |
248 len = indexof(line, '"'); | |
249 if (len <= 0) | |
250 break; | |
251 tag->face.start = line; | |
252 tag->face.len = len; | |
253 line += len; | |
254 append_text(&new_line, "{\\fn%.*s}", BSTR_P(tag->face)); | |
255 has_valid_attr = 1; | |
256 } | |
34976
482418e9e922
subassconvert: better handling of invalid SubRip tag attributes.
cboesch
parents:
34913
diff
changeset
|
257 if (*line != '>') |
482418e9e922
subassconvert: better handling of invalid SubRip tag attributes.
cboesch
parents:
34913
diff
changeset
|
258 line++; |
32462 | 259 } |
260 | |
261 if (!has_valid_attr || *line != '>') { /* Not valid font tag */ | |
262 line = potential_font_tag_start; | |
263 new_line.len = len_backup; | |
264 } else { | |
265 sp++; | |
266 line++; | |
267 } | |
268 } | |
269 | |
270 /* Tag conversion code didn't match */ | |
271 if (line == orig_line) | |
272 new_line.buf[new_line.len++] = *line++; | |
273 } | |
274 new_line.buf[new_line.len] = 0; | |
275 } | |
276 | |
277 | |
278 /* | |
279 * MicroDVD | |
280 * | |
281 * Based on the specifications found here: | |
282 * https://trac.videolan.org/vlc/ticket/1825#comment:6 | |
283 */ | |
284 | |
285 struct microdvd_tag { | |
286 char key; | |
287 int persistent; | |
288 uint32_t data1; | |
289 uint32_t data2; | |
290 struct bstr data_string; | |
291 }; | |
292 | |
293 #define MICRODVD_PERSISTENT_OFF 0 | |
294 #define MICRODVD_PERSISTENT_ON 1 | |
295 #define MICRODVD_PERSISTENT_OPENED 2 | |
296 | |
297 // Color, Font, Size, cHarset, stYle, Position, cOordinate | |
298 #define MICRODVD_TAGS "cfshyYpo" | |
299 | |
300 static void microdvd_set_tag(struct microdvd_tag *tags, struct microdvd_tag tag) | |
301 { | |
302 int tag_index = indexof(MICRODVD_TAGS, tag.key); | |
303 | |
304 if (tag_index < 0) | |
305 return; | |
306 memcpy(&tags[tag_index], &tag, sizeof(tag)); | |
307 } | |
308 | |
309 // italic, bold, underline, strike-through | |
310 #define MICRODVD_STYLES "ibus" | |
311 | |
312 static char *microdvd_load_tags(struct microdvd_tag *tags, char *s) | |
313 { | |
314 while (*s == '{') { | |
315 char *start = s; | |
316 char tag_char = *(s + 1); | |
34758 | 317 struct microdvd_tag tag = {0}; |
32462 | 318 |
319 if (!tag_char || *(s + 2) != ':') | |
320 break; | |
321 s += 3; | |
322 | |
323 switch (tag_char) { | |
324 | |
325 /* Style */ | |
326 case 'Y': | |
327 tag.persistent = MICRODVD_PERSISTENT_ON; | |
328 case 'y': | |
329 while (*s && *s != '}') { | |
330 int style_index = indexof(MICRODVD_STYLES, *s); | |
331 | |
332 if (style_index >= 0) | |
333 tag.data1 |= (1 << style_index); | |
334 s++; | |
335 } | |
336 if (*s != '}') | |
337 break; | |
338 /* We must distinguish persistent and non-persistent styles | |
339 * to handle this kind of style tags: {y:ib}{Y:us} */ | |
340 tag.key = tag_char; | |
341 break; | |
342 | |
343 /* Color */ | |
344 case 'C': | |
345 tag.persistent = MICRODVD_PERSISTENT_ON; | |
346 case 'c': | |
347 tag.data1 = strtol(s, &s, 16) & 0x00ffffff; | |
348 if (*s != '}') | |
349 break; | |
350 tag.key = 'c'; | |
351 break; | |
352 | |
353 /* Font name */ | |
354 case 'F': | |
355 tag.persistent = MICRODVD_PERSISTENT_ON; | |
356 case 'f': | |
357 { | |
358 int len = indexof(s, '}'); | |
359 if (len < 0) | |
360 break; | |
361 tag.data_string.start = s; | |
362 tag.data_string.len = len; | |
363 s += len; | |
364 tag.key = 'f'; | |
365 break; | |
366 } | |
367 | |
368 /* Font size */ | |
369 case 'S': | |
370 tag.persistent = MICRODVD_PERSISTENT_ON; | |
371 case 's': | |
372 tag.data1 = strtol(s, &s, 10); | |
373 if (*s != '}') | |
374 break; | |
375 tag.key = 's'; | |
376 break; | |
377 | |
378 /* Charset */ | |
379 case 'H': | |
380 { | |
381 //TODO: not yet handled, just parsed. | |
382 int len = indexof(s, '}'); | |
383 if (len < 0) | |
384 break; | |
385 tag.data_string.start = s; | |
386 tag.data_string.len = len; | |
387 s += len; | |
388 tag.key = 'h'; | |
389 break; | |
390 } | |
391 | |
392 /* Position */ | |
393 case 'P': | |
394 tag.persistent = MICRODVD_PERSISTENT_ON; | |
395 tag.data1 = (*s++ == '1'); | |
396 if (*s != '}') | |
397 break; | |
398 tag.key = 'p'; | |
399 break; | |
400 | |
401 /* Coordinates */ | |
402 case 'o': | |
403 tag.persistent = MICRODVD_PERSISTENT_ON; | |
404 tag.data1 = strtol(s, &s, 10); | |
405 if (*s != ',') | |
406 break; | |
407 s++; | |
408 tag.data2 = strtol(s, &s, 10); | |
409 if (*s != '}') | |
410 break; | |
411 tag.key = 'o'; | |
412 break; | |
413 | |
414 default: /* Unknown tag, we consider it's text */ | |
415 break; | |
416 } | |
417 | |
418 if (tag.key == 0) | |
419 return start; | |
420 | |
421 microdvd_set_tag(tags, tag); | |
422 s++; | |
423 } | |
424 return s; | |
425 } | |
426 | |
427 static void microdvd_open_tags(struct line *new_line, struct microdvd_tag *tags) | |
428 { | |
429 int i, sidx; | |
430 for (i = 0; i < sizeof(MICRODVD_TAGS) - 1; i++) { | |
431 if (tags[i].persistent == MICRODVD_PERSISTENT_OPENED) | |
432 continue; | |
433 switch (tags[i].key) { | |
434 case 'Y': | |
435 case 'y': | |
436 for (sidx = 0; sidx < sizeof(MICRODVD_STYLES) - 1; sidx++) | |
437 if (tags[i].data1 & (1 << sidx)) | |
438 append_text(new_line, "{\\%c1}", MICRODVD_STYLES[sidx]); | |
439 break; | |
440 | |
441 case 'c': | |
442 append_text(new_line, "{\\c&H%06X&}", tags[i].data1); | |
443 break; | |
444 | |
445 case 'f': | |
446 append_text(new_line, "{\\fn%.*s}", BSTR_P(tags[i].data_string)); | |
447 break; | |
448 | |
449 case 's': | |
450 append_text(new_line, "{\\fs%d}", tags[i].data1); | |
451 break; | |
452 | |
453 case 'p': | |
454 if (tags[i].data1 == 0) | |
455 append_text(new_line, "{\\an8}"); | |
456 break; | |
457 | |
458 case 'o': | |
459 append_text(new_line, "{\\pos(%d,%d)}", | |
460 tags[i].data1, tags[i].data2); | |
461 break; | |
462 } | |
463 if (tags[i].persistent == MICRODVD_PERSISTENT_ON) | |
464 tags[i].persistent = MICRODVD_PERSISTENT_OPENED; | |
465 } | |
466 } | |
467 | |
468 static void microdvd_close_no_persistent_tags(struct line *new_line, | |
469 struct microdvd_tag *tags) | |
470 { | |
471 int i, sidx; | |
472 | |
473 for (i = sizeof(MICRODVD_TAGS) - 2; i; i--) { | |
474 if (tags[i].persistent != MICRODVD_PERSISTENT_OFF) | |
475 continue; | |
476 switch (tags[i].key) { | |
477 | |
478 case 'y': | |
479 for (sidx = sizeof(MICRODVD_STYLES) - 2; sidx >= 0; sidx--) | |
480 if (tags[i].data1 & (1 << sidx)) | |
481 append_text(new_line, "{\\%c0}", MICRODVD_STYLES[sidx]); | |
482 break; | |
483 | |
484 case 'c': | |
485 append_text(new_line, "{\\c}"); | |
486 break; | |
487 | |
488 case 'f': | |
489 append_text(new_line, "{\\fn}"); | |
490 break; | |
491 | |
492 case 's': | |
493 append_text(new_line, "{\\fs}"); | |
494 break; | |
495 } | |
496 tags[i].key = 0; | |
497 } | |
498 } | |
499 | |
500 /** | |
501 * \brief Convert MicroDVD lines into ASS markup | |
502 * \param orig original MicroDVD line. The content will remain untouched. | |
503 * \param dest ASS markup destination buffer. | |
504 * \param dest_buffer_size maximum size for the destination buffer. | |
505 */ | |
506 void subassconvert_microdvd(const char *orig, char *dest, size_t dest_buffer_size) | |
507 { | |
508 /* line is not const to avoid warnings with strtol, etc. | |
509 * orig content won't be changed */ | |
510 char *line = (char *)orig; | |
511 struct line new_line = { | |
512 .buf = dest, | |
513 .bufsize = dest_buffer_size, | |
514 }; | |
34758 | 515 struct microdvd_tag tags[sizeof(MICRODVD_TAGS) - 1] = {{0}}; |
32462 | 516 |
517 while (*line) { | |
518 line = microdvd_load_tags(tags, line); | |
519 microdvd_open_tags(&new_line, tags); | |
520 | |
521 while (*line && *line != '|') | |
522 new_line.buf[new_line.len++] = *line++; | |
523 | |
524 if (*line == '|') { | |
525 microdvd_close_no_persistent_tags(&new_line, tags); | |
526 append_text(&new_line, "\\N"); | |
527 line++; | |
528 } | |
529 } | |
530 new_line.buf[new_line.len] = 0; | |
531 } |