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.

381 lines
11KB

  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. /* some samples have lines that start with a / indicating non persistent italic
  60. * marker */
  61. static char *check_for_italic_slash_marker(struct microdvd_tag *tags, char *s)
  62. {
  63. if (*s == '/') {
  64. struct microdvd_tag tag = tags[indexof(MICRODVD_TAGS, 'y')];
  65. tag.key = 'y';
  66. tag.data1 |= 1 << 0 /* 'i' position in MICRODVD_STYLES */;
  67. microdvd_set_tag(tags, tag);
  68. s++;
  69. }
  70. return s;
  71. }
  72. static char *microdvd_load_tags(struct microdvd_tag *tags, char *s)
  73. {
  74. s = check_for_italic_slash_marker(tags, s);
  75. while (*s == '{') {
  76. char *start = s;
  77. char tag_char = *(s + 1);
  78. struct microdvd_tag tag = {0};
  79. if (!tag_char || *(s + 2) != ':')
  80. break;
  81. s += 3;
  82. switch (tag_char) {
  83. /* Style */
  84. case 'Y':
  85. tag.persistent = MICRODVD_PERSISTENT_ON;
  86. case 'y':
  87. while (*s && *s != '}') {
  88. int style_index = indexof(MICRODVD_STYLES, *s);
  89. if (style_index >= 0)
  90. tag.data1 |= (1 << style_index);
  91. s++;
  92. }
  93. if (*s != '}')
  94. break;
  95. /* We must distinguish persistent and non-persistent styles
  96. * to handle this kind of style tags: {y:ib}{Y:us} */
  97. tag.key = tag_char;
  98. break;
  99. /* Color */
  100. case 'C':
  101. tag.persistent = MICRODVD_PERSISTENT_ON;
  102. case 'c':
  103. while (*s == '$' || *s == '#')
  104. s++;
  105. tag.data1 = strtol(s, &s, 16) & 0x00ffffff;
  106. if (*s != '}')
  107. break;
  108. tag.key = 'c';
  109. break;
  110. /* Font name */
  111. case 'F':
  112. tag.persistent = MICRODVD_PERSISTENT_ON;
  113. case 'f': {
  114. int len = indexof(s, '}');
  115. if (len < 0)
  116. break;
  117. tag.data_string = s;
  118. tag.data_string_len = len;
  119. s += len;
  120. tag.key = 'f';
  121. break;
  122. }
  123. /* Font size */
  124. case 'S':
  125. tag.persistent = MICRODVD_PERSISTENT_ON;
  126. case 's':
  127. tag.data1 = strtol(s, &s, 10);
  128. if (*s != '}')
  129. break;
  130. tag.key = 's';
  131. break;
  132. /* Charset */
  133. case 'H': {
  134. //TODO: not yet handled, just parsed.
  135. int len = indexof(s, '}');
  136. if (len < 0)
  137. break;
  138. tag.data_string = s;
  139. tag.data_string_len = len;
  140. s += len;
  141. tag.key = 'h';
  142. break;
  143. }
  144. /* Position */
  145. case 'P':
  146. if (!*s)
  147. break;
  148. tag.persistent = MICRODVD_PERSISTENT_ON;
  149. tag.data1 = (*s++ == '1');
  150. if (*s != '}')
  151. break;
  152. tag.key = 'p';
  153. break;
  154. /* Coordinates */
  155. case 'o':
  156. tag.persistent = MICRODVD_PERSISTENT_ON;
  157. tag.data1 = strtol(s, &s, 10);
  158. if (*s != ',')
  159. break;
  160. s++;
  161. tag.data2 = strtol(s, &s, 10);
  162. if (*s != '}')
  163. break;
  164. tag.key = 'o';
  165. break;
  166. default: /* Unknown tag, we consider it's text */
  167. break;
  168. }
  169. if (tag.key == 0)
  170. return start;
  171. microdvd_set_tag(tags, tag);
  172. s++;
  173. }
  174. return check_for_italic_slash_marker(tags, s);
  175. }
  176. static void microdvd_open_tags(AVBPrint *new_line, struct microdvd_tag *tags)
  177. {
  178. int i, sidx;
  179. for (i = 0; i < sizeof(MICRODVD_TAGS) - 1; i++) {
  180. if (tags[i].persistent == MICRODVD_PERSISTENT_OPENED)
  181. continue;
  182. switch (tags[i].key) {
  183. case 'Y':
  184. case 'y':
  185. for (sidx = 0; sidx < sizeof(MICRODVD_STYLES) - 1; sidx++)
  186. if (tags[i].data1 & (1 << sidx))
  187. av_bprintf(new_line, "{\\%c1}", MICRODVD_STYLES[sidx]);
  188. break;
  189. case 'c':
  190. av_bprintf(new_line, "{\\c&H%06"PRIX32"&}", tags[i].data1);
  191. break;
  192. case 'f':
  193. av_bprintf(new_line, "{\\fn%.*s}",
  194. tags[i].data_string_len, tags[i].data_string);
  195. break;
  196. case 's':
  197. av_bprintf(new_line, "{\\fs%"PRId32"}", tags[i].data1);
  198. break;
  199. case 'p':
  200. if (tags[i].data1 == 0)
  201. av_bprintf(new_line, "{\\an8}");
  202. break;
  203. case 'o':
  204. av_bprintf(new_line, "{\\pos(%"PRId32",%"PRId32")}",
  205. tags[i].data1, tags[i].data2);
  206. break;
  207. }
  208. if (tags[i].persistent == MICRODVD_PERSISTENT_ON)
  209. tags[i].persistent = MICRODVD_PERSISTENT_OPENED;
  210. }
  211. }
  212. static void microdvd_close_no_persistent_tags(AVBPrint *new_line,
  213. struct microdvd_tag *tags)
  214. {
  215. int i, sidx;
  216. for (i = sizeof(MICRODVD_TAGS) - 2; i >= 0; i--) {
  217. if (tags[i].persistent != MICRODVD_PERSISTENT_OFF)
  218. continue;
  219. switch (tags[i].key) {
  220. case 'y':
  221. for (sidx = sizeof(MICRODVD_STYLES) - 2; sidx >= 0; sidx--)
  222. if (tags[i].data1 & (1 << sidx))
  223. av_bprintf(new_line, "{\\%c0}", MICRODVD_STYLES[sidx]);
  224. break;
  225. case 'c':
  226. av_bprintf(new_line, "{\\c}");
  227. break;
  228. case 'f':
  229. av_bprintf(new_line, "{\\fn}");
  230. break;
  231. case 's':
  232. av_bprintf(new_line, "{\\fs}");
  233. break;
  234. }
  235. tags[i].key = 0;
  236. }
  237. }
  238. static int microdvd_decode_frame(AVCodecContext *avctx,
  239. void *data, int *got_sub_ptr, AVPacket *avpkt)
  240. {
  241. AVSubtitle *sub = data;
  242. AVBPrint new_line;
  243. char *line = avpkt->data;
  244. char *end = avpkt->data + avpkt->size;
  245. FFASSDecoderContext *s = avctx->priv_data;
  246. struct microdvd_tag tags[sizeof(MICRODVD_TAGS) - 1] = {{0}};
  247. if (avpkt->size <= 0)
  248. return avpkt->size;
  249. av_bprint_init(&new_line, 0, 2048);
  250. // subtitle content
  251. while (line < end && *line) {
  252. // parse MicroDVD tags, and open them in ASS
  253. line = microdvd_load_tags(tags, line);
  254. microdvd_open_tags(&new_line, tags);
  255. // simple copy until EOL or forced carriage return
  256. while (line < end && *line && *line != '|') {
  257. av_bprint_chars(&new_line, *line, 1);
  258. line++;
  259. }
  260. // line split
  261. if (line < end && *line == '|') {
  262. microdvd_close_no_persistent_tags(&new_line, tags);
  263. av_bprintf(&new_line, "\\N");
  264. line++;
  265. }
  266. }
  267. if (new_line.len) {
  268. int ret = ff_ass_add_rect(sub, new_line.str, s->readorder++, 0, NULL, NULL);
  269. av_bprint_finalize(&new_line, NULL);
  270. if (ret < 0)
  271. return ret;
  272. }
  273. *got_sub_ptr = sub->num_rects > 0;
  274. return avpkt->size;
  275. }
  276. static int microdvd_init(AVCodecContext *avctx)
  277. {
  278. int i, sidx;
  279. AVBPrint font_buf;
  280. int font_size = ASS_DEFAULT_FONT_SIZE;
  281. int color = ASS_DEFAULT_COLOR;
  282. int bold = ASS_DEFAULT_BOLD;
  283. int italic = ASS_DEFAULT_ITALIC;
  284. int underline = ASS_DEFAULT_UNDERLINE;
  285. int alignment = ASS_DEFAULT_ALIGNMENT;
  286. struct microdvd_tag tags[sizeof(MICRODVD_TAGS) - 1] = {{0}};
  287. av_bprint_init(&font_buf, 0, AV_BPRINT_SIZE_AUTOMATIC);
  288. av_bprintf(&font_buf, "%s", ASS_DEFAULT_FONT);
  289. if (avctx->extradata) {
  290. microdvd_load_tags(tags, avctx->extradata);
  291. for (i = 0; i < sizeof(MICRODVD_TAGS) - 1; i++) {
  292. switch (av_tolower(tags[i].key)) {
  293. case 'y':
  294. for (sidx = 0; sidx < sizeof(MICRODVD_STYLES) - 1; sidx++) {
  295. if (tags[i].data1 & (1 << sidx)) {
  296. switch (MICRODVD_STYLES[sidx]) {
  297. case 'i': italic = 1; break;
  298. case 'b': bold = 1; break;
  299. case 'u': underline = 1; break;
  300. }
  301. }
  302. }
  303. break;
  304. case 'c': color = tags[i].data1; break;
  305. case 's': font_size = tags[i].data1; break;
  306. case 'p': alignment = 8; break;
  307. case 'f':
  308. av_bprint_clear(&font_buf);
  309. av_bprintf(&font_buf, "%.*s",
  310. tags[i].data_string_len, tags[i].data_string);
  311. break;
  312. }
  313. }
  314. }
  315. return ff_ass_subtitle_header(avctx, font_buf.str, font_size, color,
  316. ASS_DEFAULT_BACK_COLOR, bold, italic,
  317. underline, ASS_DEFAULT_BORDERSTYLE,
  318. alignment);
  319. }
  320. AVCodec ff_microdvd_decoder = {
  321. .name = "microdvd",
  322. .long_name = NULL_IF_CONFIG_SMALL("MicroDVD subtitle"),
  323. .type = AVMEDIA_TYPE_SUBTITLE,
  324. .id = AV_CODEC_ID_MICRODVD,
  325. .init = microdvd_init,
  326. .decode = microdvd_decode_frame,
  327. .flush = ff_ass_decoder_flush,
  328. .priv_data_size = sizeof(FFASSDecoderContext),
  329. };