You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

318 lines
8.6KB

  1. /*
  2. * Copyright (c) 2012 Clément Bœsch
  3. *
  4. * This file is part of FFmpeg.
  5. *
  6. * FFmpeg is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU Lesser General Public
  8. * License as published by the Free Software Foundation; either
  9. * version 2.1 of the License, or (at your option) any later version.
  10. *
  11. * FFmpeg 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 GNU
  14. * Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public
  17. * License along with FFmpeg; if not, write to the Free Software
  18. * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  19. */
  20. /**
  21. * @file
  22. * MicroDVD subtitle decoder
  23. *
  24. * Based on the specifications found here:
  25. * https://trac.videolan.org/vlc/ticket/1825#comment:6
  26. */
  27. #include "libavutil/avstring.h"
  28. #include "libavutil/parseutils.h"
  29. #include "libavutil/bprint.h"
  30. #include "avcodec.h"
  31. #include "ass.h"
  32. static int indexof(const char *s, int c)
  33. {
  34. char *f = strchr(s, c);
  35. return f ? (f - s) : -1;
  36. }
  37. struct microdvd_tag {
  38. char key;
  39. int persistent;
  40. uint32_t data1;
  41. uint32_t data2;
  42. char *data_string;
  43. int data_string_len;
  44. };
  45. #define MICRODVD_PERSISTENT_OFF 0
  46. #define MICRODVD_PERSISTENT_ON 1
  47. #define MICRODVD_PERSISTENT_OPENED 2
  48. // Color, Font, Size, cHarset, stYle, Position, cOordinate
  49. #define MICRODVD_TAGS "cfshyYpo"
  50. static void microdvd_set_tag(struct microdvd_tag *tags, struct microdvd_tag tag)
  51. {
  52. int tag_index = indexof(MICRODVD_TAGS, tag.key);
  53. if (tag_index < 0)
  54. return;
  55. memcpy(&tags[tag_index], &tag, sizeof(tag));
  56. }
  57. // italic, bold, underline, strike-through
  58. #define MICRODVD_STYLES "ibus"
  59. static char *microdvd_load_tags(struct microdvd_tag *tags, char *s)
  60. {
  61. while (*s == '{') {
  62. char *start = s;
  63. char tag_char = *(s + 1);
  64. struct microdvd_tag tag = {0};
  65. if (!tag_char || *(s + 2) != ':')
  66. break;
  67. s += 3;
  68. switch (tag_char) {
  69. /* Style */
  70. case 'Y':
  71. tag.persistent = MICRODVD_PERSISTENT_ON;
  72. case 'y':
  73. while (*s && *s != '}') {
  74. int style_index = indexof(MICRODVD_STYLES, *s);
  75. if (style_index >= 0)
  76. tag.data1 |= (1 << style_index);
  77. s++;
  78. }
  79. if (*s != '}')
  80. break;
  81. /* We must distinguish persistent and non-persistent styles
  82. * to handle this kind of style tags: {y:ib}{Y:us} */
  83. tag.key = tag_char;
  84. break;
  85. /* Color */
  86. case 'C':
  87. tag.persistent = MICRODVD_PERSISTENT_ON;
  88. case 'c':
  89. tag.data1 = strtol(s, &s, 16) & 0x00ffffff;
  90. if (*s != '}')
  91. break;
  92. tag.key = 'c';
  93. break;
  94. /* Font name */
  95. case 'F':
  96. tag.persistent = MICRODVD_PERSISTENT_ON;
  97. case 'f': {
  98. int len = indexof(s, '}');
  99. if (len < 0)
  100. break;
  101. tag.data_string = s;
  102. tag.data_string_len = len;
  103. s += len;
  104. tag.key = 'f';
  105. break;
  106. }
  107. /* Font size */
  108. case 'S':
  109. tag.persistent = MICRODVD_PERSISTENT_ON;
  110. case 's':
  111. tag.data1 = strtol(s, &s, 10);
  112. if (*s != '}')
  113. break;
  114. tag.key = 's';
  115. break;
  116. /* Charset */
  117. case 'H': {
  118. //TODO: not yet handled, just parsed.
  119. int len = indexof(s, '}');
  120. if (len < 0)
  121. break;
  122. tag.data_string = s;
  123. tag.data_string_len = len;
  124. s += len;
  125. tag.key = 'h';
  126. break;
  127. }
  128. /* Position */
  129. case 'P':
  130. tag.persistent = MICRODVD_PERSISTENT_ON;
  131. tag.data1 = (*s++ == '1');
  132. if (*s != '}')
  133. break;
  134. tag.key = 'p';
  135. break;
  136. /* Coordinates */
  137. case 'o':
  138. tag.persistent = MICRODVD_PERSISTENT_ON;
  139. tag.data1 = strtol(s, &s, 10);
  140. if (*s != ',')
  141. break;
  142. s++;
  143. tag.data2 = strtol(s, &s, 10);
  144. if (*s != '}')
  145. break;
  146. tag.key = 'o';
  147. break;
  148. default: /* Unknown tag, we consider it's text */
  149. break;
  150. }
  151. if (tag.key == 0)
  152. return start;
  153. microdvd_set_tag(tags, tag);
  154. s++;
  155. }
  156. return s;
  157. }
  158. static void microdvd_open_tags(AVBPrint *new_line, struct microdvd_tag *tags)
  159. {
  160. int i, sidx;
  161. for (i = 0; i < sizeof(MICRODVD_TAGS) - 1; i++) {
  162. if (tags[i].persistent == MICRODVD_PERSISTENT_OPENED)
  163. continue;
  164. switch (tags[i].key) {
  165. case 'Y':
  166. case 'y':
  167. for (sidx = 0; sidx < sizeof(MICRODVD_STYLES) - 1; sidx++)
  168. if (tags[i].data1 & (1 << sidx))
  169. av_bprintf(new_line, "{\\%c1}", MICRODVD_STYLES[sidx]);
  170. break;
  171. case 'c':
  172. av_bprintf(new_line, "{\\c&H%06X&}", tags[i].data1);
  173. break;
  174. case 'f':
  175. av_bprintf(new_line, "{\\fn%.*s}",
  176. tags[i].data_string_len, tags[i].data_string);
  177. break;
  178. case 's':
  179. av_bprintf(new_line, "{\\fs%d}", tags[i].data1);
  180. break;
  181. case 'p':
  182. if (tags[i].data1 == 0)
  183. av_bprintf(new_line, "{\\an8}");
  184. break;
  185. case 'o':
  186. av_bprintf(new_line, "{\\pos(%d,%d)}",
  187. tags[i].data1, tags[i].data2);
  188. break;
  189. }
  190. if (tags[i].persistent == MICRODVD_PERSISTENT_ON)
  191. tags[i].persistent = MICRODVD_PERSISTENT_OPENED;
  192. }
  193. }
  194. static void microdvd_close_no_persistent_tags(AVBPrint *new_line,
  195. struct microdvd_tag *tags)
  196. {
  197. int i, sidx;
  198. for (i = sizeof(MICRODVD_TAGS) - 2; i; i--) {
  199. if (tags[i].persistent != MICRODVD_PERSISTENT_OFF)
  200. continue;
  201. switch (tags[i].key) {
  202. case 'y':
  203. for (sidx = sizeof(MICRODVD_STYLES) - 2; sidx >= 0; sidx--)
  204. if (tags[i].data1 & (1 << sidx))
  205. av_bprintf(new_line, "{\\%c0}", MICRODVD_STYLES[sidx]);
  206. break;
  207. case 'c':
  208. av_bprintf(new_line, "{\\c}");
  209. break;
  210. case 'f':
  211. av_bprintf(new_line, "{\\fn}");
  212. break;
  213. case 's':
  214. av_bprintf(new_line, "{\\fs}");
  215. break;
  216. }
  217. tags[i].key = 0;
  218. }
  219. }
  220. static int microdvd_decode_frame(AVCodecContext *avctx,
  221. void *data, int *got_sub_ptr, AVPacket *avpkt)
  222. {
  223. AVSubtitle *sub = data;
  224. AVBPrint new_line;
  225. char *decoded_sub;
  226. char *line = avpkt->data;
  227. char *end = avpkt->data + avpkt->size;
  228. int64_t frame_start = avpkt->pts;
  229. int64_t frame_end = avpkt->pts + avpkt->duration;
  230. int ts_start = av_rescale_q(frame_start, avctx->time_base, (AVRational){1,100});
  231. int ts_end = av_rescale_q(frame_end, avctx->time_base, (AVRational){1,100});
  232. struct microdvd_tag tags[sizeof(MICRODVD_TAGS) - 1] = {{0}};
  233. if (avpkt->size <= 0)
  234. return avpkt->size;
  235. av_bprint_init(&new_line, 0, 2048);
  236. // skip {frame_start}{frame_end}
  237. line = strchr(line, '}'); if (!line) goto end; line++;
  238. line = strchr(line, '}'); if (!line) goto end; line++;
  239. // subtitle content
  240. while (line < end && *line) {
  241. // parse MicroDVD tags, and open them in ASS
  242. line = microdvd_load_tags(tags, line);
  243. microdvd_open_tags(&new_line, tags);
  244. // simple copy until EOL or forced carriage return
  245. while (line < end && *line && *line != '|') {
  246. av_bprint_chars(&new_line, *line, 1);
  247. line++;
  248. }
  249. // line split
  250. if (line < end && *line == '|') {
  251. microdvd_close_no_persistent_tags(&new_line, tags);
  252. av_bprintf(&new_line, "\\N");
  253. line++;
  254. }
  255. }
  256. end:
  257. av_bprint_finalize(&new_line, &decoded_sub);
  258. if (*decoded_sub)
  259. ff_ass_add_rect(sub, decoded_sub, ts_start, ts_end, 0);
  260. av_free(decoded_sub);
  261. *got_sub_ptr = sub->num_rects > 0;
  262. return avpkt->size;
  263. }
  264. AVCodec ff_microdvd_decoder = {
  265. .name = "microdvd",
  266. .long_name = NULL_IF_CONFIG_SMALL("MicroDVD subtitle"),
  267. .type = AVMEDIA_TYPE_SUBTITLE,
  268. .id = CODEC_ID_MICRODVD,
  269. .init = ff_ass_subtitle_header_default,
  270. .decode = microdvd_decode_frame,
  271. };