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.

463 lines
14KB

  1. /*
  2. * imlib2 based hook
  3. * Copyright (c) 2002 Philip Gladstone
  4. *
  5. * This module implements a text overlay for a video image. Currently it
  6. * supports a fixed overlay or reading the text from a file. The string
  7. * is passed through strftime so that it is easy to imprint the date and
  8. * time onto the image.
  9. *
  10. * You may also overlay an image (even semi-transparent) like TV stations do.
  11. * You may move either the text or the image around your video to create
  12. * scrolling credits, for example.
  13. *
  14. * Text fonts are being looked for in FONTPATH
  15. *
  16. * Options:
  17. *
  18. * -C <rgb.txt> The filename to read RGB color names from
  19. * Defaults if none specified:
  20. * /usr/share/X11/rgb.txt
  21. * /usr/lib/X11/rgb.txt
  22. * -c <color> The color of the text
  23. * -F <fontname> The font face and size
  24. * -t <text> The text
  25. * -f <filename> The filename to read text from
  26. * -x <expresion> X coordinate of text or image
  27. * -y <expresion> Y coordinate of text or image
  28. * -i <filename> The filename to read a image from
  29. *
  30. * Expresions are functions of:
  31. * N // frame number (starting at zero)
  32. * H // frame height
  33. * W // frame width
  34. * h // image height
  35. * w // image width
  36. * X // previous x
  37. * Y // previous y
  38. *
  39. Examples:
  40. FONTPATH="/cygdrive/c/WINDOWS/Fonts/"
  41. FONTPATH="$FONTPATH:/usr/share/imlib2/data/fonts/"
  42. FONTPATH="$FONTPATH:/usr/X11R6/lib/X11/fonts/TTF/"
  43. export FONTPATH
  44. ffmpeg -i input.avi -vhook \
  45. 'vhook/imlib2.dll -x W*(0.5+0.25*sin(N/47*PI))-w/2 -y H*(0.5+0.50*cos(N/97*PI))-h/2 -i /usr/share/imlib2/data/images/bulb.png'
  46. -acodec copy -sameq output.avi
  47. ffmpeg -i input.avi -vhook \
  48. 'vhook/imlib2.dll -c red -F Vera.ttf/20 -x 150+0.5*N -y 70+0.25*N -t Hello'
  49. -acodec copy -sameq output.avi
  50. * This module is very much intended as an example of what could be done.
  51. *
  52. * One caution is that this is an expensive process -- in particular the
  53. * conversion of the image into RGB and back is time consuming. For some
  54. * special cases -- e.g. painting black text -- it would be faster to paint
  55. * the text into a bitmap and then combine it directly into the YUV
  56. * image. However, this code is fast enough to handle 10 fps of 320x240 on a
  57. * 900MHz Duron in maybe 15% of the CPU.
  58. * See further statistics on Pentium4, 3GHz, FFMpeg is SVN-r6798
  59. * Input movie is 20.2 seconds of PAL DV on AVI
  60. * Output movie is DVD compliant VOB.
  61. *
  62. ffmpeg -i input.avi -target pal-dvd out.vob
  63. # 13.516s just transcode
  64. ffmpeg -i input.avi -vhook /usr/local/bin/vhook/null.dll -target pal-dvd out.vob
  65. # 23.546s transcode and img_convert
  66. ffmpeg -i input.avi -vhook \
  67. 'vhook/imlib2.dll -c red -F Vera/20 -x 150-0.5*N -y 70+0.25*N -t Hello_person' \
  68. -target pal-dvd out.vob
  69. # 21.454s transcode, img_convert and move text around
  70. ffmpeg -i input.avi -vhook \
  71. 'vhook/imlib2.dll -x 150-0.5*N -y 70+0.25*N -i /usr/share/imlib2/data/images/bulb.png' \
  72. -target pal-dvd out.vob
  73. # 20.828s transcode, img_convert and move image around
  74. *
  75. * This file is part of FFmpeg.
  76. *
  77. * FFmpeg is free software; you can redistribute it and/or
  78. * modify it under the terms of the GNU Lesser General Public
  79. * License as published by the Free Software Foundation; either
  80. * version 2.1 of the License, or (at your option) any later version.
  81. *
  82. * FFmpeg is distributed in the hope that it will be useful,
  83. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  84. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  85. * Lesser General Public License for more details.
  86. *
  87. * You should have received a copy of the GNU Lesser General Public
  88. * License along with FFmpeg; if not, write to the Free Software
  89. * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  90. */
  91. #include "framehook.h"
  92. #include "swscale.h"
  93. #include <stdio.h>
  94. #include <stdlib.h>
  95. #include <fcntl.h>
  96. #include <stdarg.h>
  97. #include <string.h>
  98. #include <unistd.h>
  99. #undef time
  100. #include <sys/time.h>
  101. #include <time.h>
  102. #include <Imlib2.h>
  103. #include "eval.h"
  104. const char *const_names[]={
  105. "PI",
  106. "E",
  107. "N", // frame number (starting at zero)
  108. "H", // frame height
  109. "W", // frame width
  110. "h", // image height
  111. "w", // image width
  112. "X", // previous x
  113. "Y", // previous y
  114. NULL
  115. };
  116. static int sws_flags = SWS_BICUBIC;
  117. typedef struct {
  118. int dummy;
  119. Imlib_Font fn;
  120. char *text;
  121. char *file;
  122. int r, g, b;
  123. double x, y;
  124. char *fileImage;
  125. struct _CachedImage *cache;
  126. Imlib_Image imageOverlaid;
  127. AVEvalExpr *eval_x, *eval_y;
  128. char *expr_x, *expr_y;
  129. int frame_number;
  130. int imageOverlaid_width, imageOverlaid_height;
  131. // This vhook first converts frame to RGB ...
  132. struct SwsContext *toRGB_convert_ctx;
  133. // ... and then converts back frame from RGB to initial format
  134. struct SwsContext *fromRGB_convert_ctx;
  135. } ContextInfo;
  136. typedef struct _CachedImage {
  137. struct _CachedImage *next;
  138. Imlib_Image image;
  139. int width;
  140. int height;
  141. } CachedImage;
  142. void Release(void *ctx)
  143. {
  144. ContextInfo *ci;
  145. ci = (ContextInfo *) ctx;
  146. if (ci->cache) {
  147. imlib_context_set_image(ci->cache->image);
  148. imlib_free_image();
  149. av_free(ci->cache);
  150. }
  151. if (ctx) {
  152. if (ci->imageOverlaid) {
  153. imlib_context_set_image(ci->imageOverlaid);
  154. imlib_free_image();
  155. }
  156. ff_eval_free(ci->expr_x);
  157. ff_eval_free(ci->expr_y);
  158. sws_freeContext(ci->toRGB_convert_ctx);
  159. sws_freeContext(ci->fromRGB_convert_ctx);
  160. av_free(ctx);
  161. }
  162. }
  163. int Configure(void **ctxp, int argc, char *argv[])
  164. {
  165. int c;
  166. ContextInfo *ci;
  167. char *rgbtxt = 0;
  168. char *font = "LucidaSansDemiBold/16";
  169. char *fp = getenv("FONTPATH");
  170. char *color = 0;
  171. FILE *f;
  172. char *p;
  173. *ctxp = av_mallocz(sizeof(ContextInfo));
  174. ci = (ContextInfo *) *ctxp;
  175. ci->x = 0.0;
  176. ci->y = 0.0;
  177. ci->expr_x = "0.0";
  178. ci->expr_y = "0.0";
  179. optind = 0;
  180. /* Use ':' to split FONTPATH */
  181. if (fp)
  182. while (p = strchr(fp, ':')) {
  183. *p = 0;
  184. imlib_add_path_to_font_path(fp);
  185. fp = p + 1;
  186. }
  187. if ((fp) && (*fp))
  188. imlib_add_path_to_font_path(fp);
  189. while ((c = getopt(argc, argv, "C:c:f:F:t:x:y:i:")) > 0) {
  190. switch (c) {
  191. case 'C':
  192. rgbtxt = optarg;
  193. break;
  194. case 'c':
  195. color = optarg;
  196. break;
  197. case 'F':
  198. font = optarg;
  199. break;
  200. case 't':
  201. ci->text = av_strdup(optarg);
  202. break;
  203. case 'f':
  204. ci->file = av_strdup(optarg);
  205. break;
  206. case 'x':
  207. ci->expr_x = av_strdup(optarg);
  208. break;
  209. case 'y':
  210. ci->expr_y = av_strdup(optarg);
  211. break;
  212. case 'i':
  213. ci->fileImage = av_strdup(optarg);
  214. break;
  215. case '?':
  216. fprintf(stderr, "Unrecognized argument '%s'\n", argv[optind]);
  217. return -1;
  218. }
  219. }
  220. if (ci->text || ci->file) {
  221. ci->fn = imlib_load_font(font);
  222. if (!ci->fn) {
  223. fprintf(stderr, "Failed to load font '%s'\n", font);
  224. return -1;
  225. }
  226. imlib_context_set_font(ci->fn);
  227. imlib_context_set_direction(IMLIB_TEXT_TO_RIGHT);
  228. }
  229. if (color) {
  230. char buff[256];
  231. int done = 0;
  232. if (rgbtxt)
  233. f = fopen(rgbtxt, "r");
  234. else
  235. {
  236. f = fopen("/usr/share/X11/rgb.txt", "r");
  237. if (!f)
  238. f = fopen("/usr/lib/X11/rgb.txt", "r");
  239. }
  240. if (!f) {
  241. fprintf(stderr, "Failed to find RGB color names file\n");
  242. return -1;
  243. }
  244. while (fgets(buff, sizeof(buff), f)) {
  245. int r, g, b;
  246. char colname[80];
  247. if (sscanf(buff, "%d %d %d %64s", &r, &g, &b, colname) == 4 &&
  248. strcasecmp(colname, color) == 0) {
  249. ci->r = r;
  250. ci->g = g;
  251. ci->b = b;
  252. /* fprintf(stderr, "%s -> %d,%d,%d\n", colname, r, g, b); */
  253. done = 1;
  254. break;
  255. }
  256. }
  257. fclose(f);
  258. if (!done) {
  259. fprintf(stderr, "Unable to find color '%s' in rgb.txt\n", color);
  260. return -1;
  261. }
  262. }
  263. imlib_context_set_color(ci->r, ci->g, ci->b, 255);
  264. /* load the image (for example, credits for a movie) */
  265. if (ci->fileImage) {
  266. ci->imageOverlaid = imlib_load_image_immediately(ci->fileImage);
  267. if (!(ci->imageOverlaid)){
  268. av_log(NULL, AV_LOG_ERROR, "Couldn't load image '%s'\n", ci->fileImage);
  269. return -1;
  270. }
  271. imlib_context_set_image(ci->imageOverlaid);
  272. ci->imageOverlaid_width = imlib_image_get_width();
  273. ci->imageOverlaid_height = imlib_image_get_height();
  274. }
  275. if (!(ci->eval_x = ff_parse(ci->expr_x, const_names, NULL, NULL, NULL, NULL, NULL))){
  276. av_log(NULL, AV_LOG_ERROR, "Couldn't parse x expression '%s'\n", ci->expr_x);
  277. return -1;
  278. }
  279. if (!(ci->eval_y = ff_parse(ci->expr_y, const_names, NULL, NULL, NULL, NULL, NULL))){
  280. av_log(NULL, AV_LOG_ERROR, "Couldn't parse y expression '%s'\n", ci->expr_y);
  281. return -1;
  282. }
  283. return 0;
  284. }
  285. static Imlib_Image get_cached_image(ContextInfo *ci, int width, int height)
  286. {
  287. CachedImage *cache;
  288. for (cache = ci->cache; cache; cache = cache->next) {
  289. if (width == cache->width && height == cache->height)
  290. return cache->image;
  291. }
  292. return NULL;
  293. }
  294. static void put_cached_image(ContextInfo *ci, Imlib_Image image, int width, int height)
  295. {
  296. CachedImage *cache = av_mallocz(sizeof(*cache));
  297. cache->image = image;
  298. cache->width = width;
  299. cache->height = height;
  300. cache->next = ci->cache;
  301. ci->cache = cache;
  302. }
  303. void Process(void *ctx, AVPicture *picture, enum PixelFormat pix_fmt, int width, int height, int64_t pts)
  304. {
  305. ContextInfo *ci = (ContextInfo *) ctx;
  306. AVPicture picture1;
  307. Imlib_Image image;
  308. DATA32 *data;
  309. image = get_cached_image(ci, width, height);
  310. if (!image) {
  311. image = imlib_create_image(width, height);
  312. put_cached_image(ci, image, width, height);
  313. }
  314. imlib_context_set_image(image);
  315. data = imlib_image_get_data();
  316. avpicture_fill(&picture1, (uint8_t *) data, PIX_FMT_RGB32, width, height);
  317. // if we already got a SWS context, let's realloc if is not re-useable
  318. ci->toRGB_convert_ctx = sws_getCachedContext(ci->toRGB_convert_ctx,
  319. width, height, pix_fmt,
  320. width, height, PIX_FMT_RGB32,
  321. sws_flags, NULL, NULL, NULL);
  322. if (ci->toRGB_convert_ctx == NULL) {
  323. av_log(NULL, AV_LOG_ERROR,
  324. "Cannot initialize the toRGB conversion context\n");
  325. return;
  326. }
  327. // img_convert parameters are 2 first destination, then 4 source
  328. // sws_scale parameters are context, 4 first source, then 2 destination
  329. sws_scale(ci->toRGB_convert_ctx,
  330. picture->data, picture->linesize, 0, height,
  331. picture1.data, picture1.linesize);
  332. imlib_image_set_has_alpha(0);
  333. {
  334. int wid, hig, h_a, v_a;
  335. char buff[1000];
  336. char tbuff[1000];
  337. char *tbp = ci->text;
  338. time_t now = time(0);
  339. char *p, *q;
  340. int y;
  341. double const_values[]={
  342. M_PI,
  343. M_E,
  344. ci->frame_number, // frame number (starting at zero)
  345. height, // frame height
  346. width, // frame width
  347. ci->imageOverlaid_height, // image height
  348. ci->imageOverlaid_width, // image width
  349. ci->x, // previous x
  350. ci->y, // previous y
  351. 0
  352. };
  353. if (ci->file) {
  354. int fd = open(ci->file, O_RDONLY);
  355. if (fd < 0) {
  356. tbp = "[File not found]";
  357. } else {
  358. int l = read(fd, tbuff, sizeof(tbuff) - 1);
  359. if (l >= 0) {
  360. tbuff[l] = 0;
  361. tbp = tbuff;
  362. } else {
  363. tbp = "[I/O Error]";
  364. }
  365. close(fd);
  366. }
  367. }
  368. if (tbp)
  369. strftime(buff, sizeof(buff), tbp, localtime(&now));
  370. else if (!(ci->imageOverlaid))
  371. strftime(buff, sizeof(buff), "[No data]", localtime(&now));
  372. ci->x = ff_parse_eval(ci->eval_x, const_values, ci);
  373. ci->y = ff_parse_eval(ci->eval_y, const_values, ci);
  374. y = ci->y;
  375. if (!(ci->imageOverlaid))
  376. for (p = buff; p; p = q) {
  377. q = strchr(p, '\n');
  378. if (q)
  379. *q++ = 0;
  380. imlib_text_draw_with_return_metrics(ci->x, y, p, &wid, &hig, &h_a, &v_a);
  381. y += v_a;
  382. }
  383. if (ci->imageOverlaid) {
  384. imlib_context_set_image(image);
  385. imlib_blend_image_onto_image(ci->imageOverlaid, 0,
  386. 0, 0, ci->imageOverlaid_width, ci->imageOverlaid_height,
  387. ci->x, ci->y, ci->imageOverlaid_width, ci->imageOverlaid_height);
  388. }
  389. }
  390. ci->fromRGB_convert_ctx = sws_getCachedContext(ci->fromRGB_convert_ctx,
  391. width, height, PIX_FMT_RGB32,
  392. width, height, pix_fmt,
  393. sws_flags, NULL, NULL, NULL);
  394. if (ci->fromRGB_convert_ctx == NULL) {
  395. av_log(NULL, AV_LOG_ERROR,
  396. "Cannot initialize the fromRGB conversion context\n");
  397. return;
  398. }
  399. // img_convert parameters are 2 first destination, then 4 source
  400. // sws_scale parameters are context, 4 first source, then 2 destination
  401. sws_scale(ci->fromRGB_convert_ctx,
  402. picture1.data, picture1.linesize, 0, height,
  403. picture->data, picture->linesize);
  404. ci->frame_number++;
  405. }