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.

228 lines
8.5KB

  1. /*
  2. * Copyright (c) 2012 Paul B Mahol
  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. #include <caca.h>
  21. #include "libavutil/opt.h"
  22. #include "libavutil/pixdesc.h"
  23. #include "avdevice.h"
  24. typedef struct CACAContext {
  25. AVClass *class;
  26. AVFormatContext *ctx;
  27. char *window_title;
  28. int window_width, window_height;
  29. caca_canvas_t *canvas;
  30. caca_display_t *display;
  31. caca_dither_t *dither;
  32. char *algorithm, *antialias;
  33. char *charset, *colors;
  34. char *driver;
  35. char *list_dither;
  36. int list_drivers;
  37. } CACAContext;
  38. static int caca_write_trailer(AVFormatContext *s)
  39. {
  40. CACAContext *c = s->priv_data;
  41. av_freep(&c->window_title);
  42. caca_free_dither(c->dither);
  43. caca_free_display(c->display);
  44. caca_free_canvas(c->canvas);
  45. return 0;
  46. }
  47. static void list_drivers(CACAContext *c)
  48. {
  49. const char *const *drivers = caca_get_display_driver_list();
  50. int i;
  51. av_log(c->ctx, AV_LOG_INFO, "Available drivers:\n");
  52. for (i = 0; drivers[i]; i += 2)
  53. av_log(c->ctx, AV_LOG_INFO, "%s : %s\n", drivers[i], drivers[i + 1]);
  54. }
  55. #define DEFINE_LIST_DITHER(thing, thing_str) \
  56. static void list_dither_## thing(CACAContext *c) \
  57. { \
  58. const char *const *thing = caca_get_dither_## thing ##_list(c->dither); \
  59. int i; \
  60. \
  61. av_log(c->ctx, AV_LOG_INFO, "Available %s:\n", thing_str); \
  62. for (i = 0; thing[i]; i += 2) \
  63. av_log(c->ctx, AV_LOG_INFO, "%s : %s\n", thing[i], thing[i + 1]); \
  64. }
  65. DEFINE_LIST_DITHER(color, "colors");
  66. DEFINE_LIST_DITHER(charset, "charsets");
  67. DEFINE_LIST_DITHER(algorithm, "algorithms");
  68. DEFINE_LIST_DITHER(antialias, "antialias");
  69. static int caca_write_header(AVFormatContext *s)
  70. {
  71. CACAContext *c = s->priv_data;
  72. AVStream *st = s->streams[0];
  73. AVCodecContext *encctx = st->codec;
  74. int ret, bpp;
  75. c->ctx = s;
  76. if (c->list_drivers) {
  77. list_drivers(c);
  78. return AVERROR_EXIT;
  79. }
  80. if (c->list_dither) {
  81. if (!strcmp(c->list_dither, "colors")) {
  82. list_dither_color(c);
  83. } else if (!strcmp(c->list_dither, "charsets")) {
  84. list_dither_charset(c);
  85. } else if (!strcmp(c->list_dither, "algorithms")) {
  86. list_dither_algorithm(c);
  87. } else if (!strcmp(c->list_dither, "antialiases")) {
  88. list_dither_antialias(c);
  89. } else {
  90. av_log(s, AV_LOG_ERROR,
  91. "Invalid value '%s', for 'list_dither' option\n",
  92. c->list_dither);
  93. return AVERROR(EINVAL);
  94. }
  95. return AVERROR_EXIT;
  96. }
  97. if ( s->nb_streams > 1
  98. || encctx->codec_type != AVMEDIA_TYPE_VIDEO
  99. || encctx->codec_id != CODEC_ID_RAWVIDEO) {
  100. av_log(s, AV_LOG_ERROR, "Only supports one rawvideo stream\n");
  101. return AVERROR(EINVAL);
  102. }
  103. if (encctx->pix_fmt != PIX_FMT_RGB24) {
  104. av_log(s, AV_LOG_ERROR,
  105. "Unsupported pixel format '%s', choose rgb24\n",
  106. av_get_pix_fmt_name(encctx->pix_fmt));
  107. return AVERROR(EINVAL);
  108. }
  109. c->canvas = caca_create_canvas(c->window_width, c->window_height);
  110. if (!c->canvas) {
  111. av_log(s, AV_LOG_ERROR, "Failed to create canvas\n");
  112. return AVERROR(errno);
  113. }
  114. c->display = caca_create_display_with_driver(c->canvas, c->driver);
  115. if (!c->display) {
  116. av_log(s, AV_LOG_ERROR, "Failed to create display\n");
  117. list_drivers(c);
  118. caca_free_canvas(c->canvas);
  119. return AVERROR(errno);
  120. }
  121. if (!c->window_width || !c->window_height) {
  122. c->window_width = caca_get_canvas_width(c->canvas);
  123. c->window_height = caca_get_canvas_height(c->canvas);
  124. }
  125. bpp = av_get_bits_per_pixel(&av_pix_fmt_descriptors[encctx->pix_fmt]);
  126. c->dither = caca_create_dither(bpp, encctx->width, encctx->height,
  127. bpp / 8 * encctx->width,
  128. 0x0000ff, 0x00ff00, 0xff0000, 0);
  129. if (!c->dither) {
  130. av_log(s, AV_LOG_ERROR, "Failed to create dither\n");
  131. ret = AVERROR(errno);
  132. goto fail;
  133. }
  134. ret = caca_set_dither_algorithm(c->dither, c->algorithm);
  135. ret += caca_set_dither_antialias(c->dither, c->antialias);
  136. ret += caca_set_dither_charset(c->dither, c->charset);
  137. ret += caca_set_dither_color(c->dither, c->colors);
  138. if (ret) {
  139. av_log(s, AV_LOG_ERROR, "Invalid value given to one of options\n");
  140. ret = AVERROR(EINVAL);
  141. goto fail;
  142. }
  143. if (!c->window_title)
  144. c->window_title = av_strdup(s->filename);
  145. caca_set_display_title(c->display, c->window_title);
  146. caca_set_display_time(c->display, av_rescale_q(1, st->codec->time_base, AV_TIME_BASE_Q));
  147. return 0;
  148. fail:
  149. caca_write_trailer(s);
  150. return ret;
  151. }
  152. static int caca_write_packet(AVFormatContext *s, AVPacket *pkt)
  153. {
  154. CACAContext *c = s->priv_data;
  155. caca_dither_bitmap(c->canvas, 0, 0, c->window_width, c->window_height, c->dither, pkt->data);
  156. caca_refresh_display(c->display);
  157. return 0;
  158. }
  159. #define OFFSET(x) offsetof(CACAContext,x)
  160. #define ENC AV_OPT_FLAG_ENCODING_PARAM
  161. static const AVOption options[] = {
  162. { "window_size", "set window forced size", OFFSET(window_width), AV_OPT_TYPE_IMAGE_SIZE, {.str = NULL }, 0, 0, ENC},
  163. { "window_title", "set window title", OFFSET(window_title), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, ENC },
  164. { "driver", "set display driver", OFFSET(driver), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, ENC },
  165. { "algorithm", "set dithering algorithm", OFFSET(algorithm), AV_OPT_TYPE_STRING, {.str = "default" }, 0, 0, ENC },
  166. { "antialias", "set antialias method", OFFSET(antialias), AV_OPT_TYPE_STRING, {.str = "default" }, 0, 0, ENC },
  167. { "charset", "set charset used to render output", OFFSET(charset), AV_OPT_TYPE_STRING, {.str = "default" }, 0, 0, ENC },
  168. { "colors", "set colors used to render output", OFFSET(colors), AV_OPT_TYPE_STRING, {.str = "default" }, 0, 0, ENC },
  169. { "list_drivers", "list available drivers", OFFSET(list_drivers), AV_OPT_TYPE_INT, {.dbl=0}, 0, 1, ENC, "list_drivers" },
  170. { "true", NULL, 0, AV_OPT_TYPE_CONST, {.dbl = 1}, 0, 0, ENC, "list_drivers" },
  171. { "false", NULL, 0, AV_OPT_TYPE_CONST, {.dbl = 0}, 0, 0, ENC, "list_drivers" },
  172. { "list_dither", "list available dither options", OFFSET(list_dither), AV_OPT_TYPE_STRING, {.dbl=0}, 0, 1, ENC, "list_dither" },
  173. { "colors", NULL, 0, AV_OPT_TYPE_CONST, {.str = "colors"}, 0, 0, ENC, "list_dither" },
  174. { "charsets", NULL, 0, AV_OPT_TYPE_CONST, {.str = "charsets"}, 0, 0, ENC, "list_dither" },
  175. { "antialiases", NULL, 0, AV_OPT_TYPE_CONST, {.str = "antialiases"},0, 0, ENC, "list_dither" },
  176. { "algorithms", NULL, 0, AV_OPT_TYPE_CONST, {.str = "algorithms"}, 0, 0, ENC, "list_dither" },
  177. { NULL },
  178. };
  179. static const AVClass caca_class = {
  180. .class_name = "caca_outdev",
  181. .item_name = av_default_item_name,
  182. .option = options,
  183. .version = LIBAVUTIL_VERSION_INT,
  184. };
  185. AVOutputFormat ff_caca_muxer = {
  186. .name = "caca",
  187. .long_name = NULL_IF_CONFIG_SMALL("caca (color ASCII art) output device"),
  188. .priv_data_size = sizeof(CACAContext),
  189. .audio_codec = CODEC_ID_NONE,
  190. .video_codec = CODEC_ID_RAWVIDEO,
  191. .write_header = caca_write_header,
  192. .write_packet = caca_write_packet,
  193. .write_trailer = caca_write_trailer,
  194. .flags = AVFMT_NOFILE,
  195. .priv_class = &caca_class,
  196. };