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.

384 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%06X&}", 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%d}", 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(%d,%d)}",
  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. struct microdvd_tag tags[sizeof(MICRODVD_TAGS) - 1] = {{0}};
  246. if (avpkt->size <= 0)
  247. return avpkt->size;
  248. av_bprint_init(&new_line, 0, 2048);
  249. // subtitle content
  250. while (line < end && *line) {
  251. // parse MicroDVD tags, and open them in ASS
  252. line = microdvd_load_tags(tags, line);
  253. microdvd_open_tags(&new_line, tags);
  254. // simple copy until EOL or forced carriage return
  255. while (line < end && *line && *line != '|') {
  256. av_bprint_chars(&new_line, *line, 1);
  257. line++;
  258. }
  259. // line split
  260. if (line < end && *line == '|') {
  261. microdvd_close_no_persistent_tags(&new_line, tags);
  262. av_bprintf(&new_line, "\\N");
  263. line++;
  264. }
  265. }
  266. if (new_line.len) {
  267. int ret;
  268. int64_t start = avpkt->pts;
  269. int64_t duration = avpkt->duration;
  270. int ts_start = av_rescale_q(start, avctx->time_base, (AVRational){1,100});
  271. int ts_duration = duration != -1 ?
  272. av_rescale_q(duration, avctx->time_base, (AVRational){1,100}) : -1;
  273. ret = ff_ass_add_rect_bprint(sub, &new_line, ts_start, ts_duration);
  274. av_bprint_finalize(&new_line, NULL);
  275. if (ret < 0)
  276. return ret;
  277. }
  278. *got_sub_ptr = sub->num_rects > 0;
  279. return avpkt->size;
  280. }
  281. static int microdvd_init(AVCodecContext *avctx)
  282. {
  283. int i, sidx;
  284. AVBPrint font_buf;
  285. int font_size = ASS_DEFAULT_FONT_SIZE;
  286. int color = ASS_DEFAULT_COLOR;
  287. int bold = ASS_DEFAULT_BOLD;
  288. int italic = ASS_DEFAULT_ITALIC;
  289. int underline = ASS_DEFAULT_UNDERLINE;
  290. int alignment = ASS_DEFAULT_ALIGNMENT;
  291. struct microdvd_tag tags[sizeof(MICRODVD_TAGS) - 1] = {{0}};
  292. av_bprint_init(&font_buf, 0, AV_BPRINT_SIZE_AUTOMATIC);
  293. av_bprintf(&font_buf, "%s", ASS_DEFAULT_FONT);
  294. if (avctx->extradata) {
  295. microdvd_load_tags(tags, avctx->extradata);
  296. for (i = 0; i < sizeof(MICRODVD_TAGS) - 1; i++) {
  297. switch (av_tolower(tags[i].key)) {
  298. case 'y':
  299. for (sidx = 0; sidx < sizeof(MICRODVD_STYLES) - 1; sidx++) {
  300. if (tags[i].data1 & (1 << sidx)) {
  301. switch (MICRODVD_STYLES[sidx]) {
  302. case 'i': italic = 1; break;
  303. case 'b': bold = 1; break;
  304. case 'u': underline = 1; break;
  305. }
  306. }
  307. }
  308. break;
  309. case 'c': color = tags[i].data1; break;
  310. case 's': font_size = tags[i].data1; break;
  311. case 'p': alignment = 8; break;
  312. case 'f':
  313. av_bprint_clear(&font_buf);
  314. av_bprintf(&font_buf, "%.*s",
  315. tags[i].data_string_len, tags[i].data_string);
  316. break;
  317. }
  318. }
  319. }
  320. return ff_ass_subtitle_header(avctx, font_buf.str, font_size, color,
  321. ASS_DEFAULT_BACK_COLOR, bold, italic,
  322. underline, alignment);
  323. }
  324. AVCodec ff_microdvd_decoder = {
  325. .name = "microdvd",
  326. .long_name = NULL_IF_CONFIG_SMALL("MicroDVD subtitle"),
  327. .type = AVMEDIA_TYPE_SUBTITLE,
  328. .id = AV_CODEC_ID_MICRODVD,
  329. .init = microdvd_init,
  330. .decode = microdvd_decode_frame,
  331. };