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.

382 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. tag.persistent = MICRODVD_PERSISTENT_ON;
  147. tag.data1 = (*s++ == '1');
  148. if (*s != '}')
  149. break;
  150. tag.key = 'p';
  151. break;
  152. /* Coordinates */
  153. case 'o':
  154. tag.persistent = MICRODVD_PERSISTENT_ON;
  155. tag.data1 = strtol(s, &s, 10);
  156. if (*s != ',')
  157. break;
  158. s++;
  159. tag.data2 = strtol(s, &s, 10);
  160. if (*s != '}')
  161. break;
  162. tag.key = 'o';
  163. break;
  164. default: /* Unknown tag, we consider it's text */
  165. break;
  166. }
  167. if (tag.key == 0)
  168. return start;
  169. microdvd_set_tag(tags, tag);
  170. s++;
  171. }
  172. return check_for_italic_slash_marker(tags, s);
  173. }
  174. static void microdvd_open_tags(AVBPrint *new_line, struct microdvd_tag *tags)
  175. {
  176. int i, sidx;
  177. for (i = 0; i < sizeof(MICRODVD_TAGS) - 1; i++) {
  178. if (tags[i].persistent == MICRODVD_PERSISTENT_OPENED)
  179. continue;
  180. switch (tags[i].key) {
  181. case 'Y':
  182. case 'y':
  183. for (sidx = 0; sidx < sizeof(MICRODVD_STYLES) - 1; sidx++)
  184. if (tags[i].data1 & (1 << sidx))
  185. av_bprintf(new_line, "{\\%c1}", MICRODVD_STYLES[sidx]);
  186. break;
  187. case 'c':
  188. av_bprintf(new_line, "{\\c&H%06X&}", tags[i].data1);
  189. break;
  190. case 'f':
  191. av_bprintf(new_line, "{\\fn%.*s}",
  192. tags[i].data_string_len, tags[i].data_string);
  193. break;
  194. case 's':
  195. av_bprintf(new_line, "{\\fs%d}", tags[i].data1);
  196. break;
  197. case 'p':
  198. if (tags[i].data1 == 0)
  199. av_bprintf(new_line, "{\\an8}");
  200. break;
  201. case 'o':
  202. av_bprintf(new_line, "{\\pos(%d,%d)}",
  203. tags[i].data1, tags[i].data2);
  204. break;
  205. }
  206. if (tags[i].persistent == MICRODVD_PERSISTENT_ON)
  207. tags[i].persistent = MICRODVD_PERSISTENT_OPENED;
  208. }
  209. }
  210. static void microdvd_close_no_persistent_tags(AVBPrint *new_line,
  211. struct microdvd_tag *tags)
  212. {
  213. int i, sidx;
  214. for (i = sizeof(MICRODVD_TAGS) - 2; i >= 0; i--) {
  215. if (tags[i].persistent != MICRODVD_PERSISTENT_OFF)
  216. continue;
  217. switch (tags[i].key) {
  218. case 'y':
  219. for (sidx = sizeof(MICRODVD_STYLES) - 2; sidx >= 0; sidx--)
  220. if (tags[i].data1 & (1 << sidx))
  221. av_bprintf(new_line, "{\\%c0}", MICRODVD_STYLES[sidx]);
  222. break;
  223. case 'c':
  224. av_bprintf(new_line, "{\\c}");
  225. break;
  226. case 'f':
  227. av_bprintf(new_line, "{\\fn}");
  228. break;
  229. case 's':
  230. av_bprintf(new_line, "{\\fs}");
  231. break;
  232. }
  233. tags[i].key = 0;
  234. }
  235. }
  236. static int microdvd_decode_frame(AVCodecContext *avctx,
  237. void *data, int *got_sub_ptr, AVPacket *avpkt)
  238. {
  239. AVSubtitle *sub = data;
  240. AVBPrint new_line;
  241. char *line = avpkt->data;
  242. char *end = avpkt->data + avpkt->size;
  243. struct microdvd_tag tags[sizeof(MICRODVD_TAGS) - 1] = {{0}};
  244. if (avpkt->size <= 0)
  245. return avpkt->size;
  246. av_bprint_init(&new_line, 0, 2048);
  247. // subtitle content
  248. while (line < end && *line) {
  249. // parse MicroDVD tags, and open them in ASS
  250. line = microdvd_load_tags(tags, line);
  251. microdvd_open_tags(&new_line, tags);
  252. // simple copy until EOL or forced carriage return
  253. while (line < end && *line && *line != '|') {
  254. av_bprint_chars(&new_line, *line, 1);
  255. line++;
  256. }
  257. // line split
  258. if (line < end && *line == '|') {
  259. microdvd_close_no_persistent_tags(&new_line, tags);
  260. av_bprintf(&new_line, "\\N");
  261. line++;
  262. }
  263. }
  264. if (new_line.len) {
  265. int ret;
  266. int64_t start = avpkt->pts;
  267. int64_t duration = avpkt->duration;
  268. int ts_start = av_rescale_q(start, avctx->time_base, (AVRational){1,100});
  269. int ts_duration = duration != -1 ?
  270. av_rescale_q(duration, avctx->time_base, (AVRational){1,100}) : -1;
  271. ret = ff_ass_add_rect_bprint(sub, &new_line, ts_start, ts_duration);
  272. av_bprint_finalize(&new_line, NULL);
  273. if (ret < 0)
  274. return ret;
  275. }
  276. *got_sub_ptr = sub->num_rects > 0;
  277. return avpkt->size;
  278. }
  279. static int microdvd_init(AVCodecContext *avctx)
  280. {
  281. int i, sidx;
  282. AVBPrint font_buf;
  283. int font_size = ASS_DEFAULT_FONT_SIZE;
  284. int color = ASS_DEFAULT_COLOR;
  285. int bold = ASS_DEFAULT_BOLD;
  286. int italic = ASS_DEFAULT_ITALIC;
  287. int underline = ASS_DEFAULT_UNDERLINE;
  288. int alignment = ASS_DEFAULT_ALIGNMENT;
  289. struct microdvd_tag tags[sizeof(MICRODVD_TAGS) - 1] = {{0}};
  290. av_bprint_init(&font_buf, 0, AV_BPRINT_SIZE_AUTOMATIC);
  291. av_bprintf(&font_buf, "%s", ASS_DEFAULT_FONT);
  292. if (avctx->extradata) {
  293. microdvd_load_tags(tags, avctx->extradata);
  294. for (i = 0; i < sizeof(MICRODVD_TAGS) - 1; i++) {
  295. switch (av_tolower(tags[i].key)) {
  296. case 'y':
  297. for (sidx = 0; sidx < sizeof(MICRODVD_STYLES) - 1; sidx++) {
  298. if (tags[i].data1 & (1 << sidx)) {
  299. switch (MICRODVD_STYLES[sidx]) {
  300. case 'i': italic = 1; break;
  301. case 'b': bold = 1; break;
  302. case 'u': underline = 1; break;
  303. }
  304. }
  305. }
  306. break;
  307. case 'c': color = tags[i].data1; break;
  308. case 's': font_size = tags[i].data1; break;
  309. case 'p': alignment = 8; break;
  310. case 'f':
  311. av_bprint_clear(&font_buf);
  312. av_bprintf(&font_buf, "%.*s",
  313. tags[i].data_string_len, tags[i].data_string);
  314. break;
  315. }
  316. }
  317. }
  318. return ff_ass_subtitle_header(avctx, font_buf.str, font_size, color,
  319. ASS_DEFAULT_BACK_COLOR, bold, italic,
  320. underline, alignment);
  321. }
  322. AVCodec ff_microdvd_decoder = {
  323. .name = "microdvd",
  324. .long_name = NULL_IF_CONFIG_SMALL("MicroDVD subtitle"),
  325. .type = AVMEDIA_TYPE_SUBTITLE,
  326. .id = AV_CODEC_ID_MICRODVD,
  327. .init = microdvd_init,
  328. .decode = microdvd_decode_frame,
  329. };