00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021 #include <caca.h>
00022 #include "libavutil/opt.h"
00023 #include "libavutil/pixdesc.h"
00024 #include "avdevice.h"
00025
00026 typedef struct CACAContext {
00027 AVClass *class;
00028 AVFormatContext *ctx;
00029 char *window_title;
00030 int window_width, window_height;
00031
00032 caca_canvas_t *canvas;
00033 caca_display_t *display;
00034 caca_dither_t *dither;
00035
00036 char *algorithm, *antialias;
00037 char *charset, *color;
00038 char *driver;
00039
00040 char *list_dither;
00041 int list_drivers;
00042 } CACAContext;
00043
00044 static int caca_write_trailer(AVFormatContext *s)
00045 {
00046 CACAContext *c = s->priv_data;
00047
00048 av_freep(&c->window_title);
00049
00050 if (c->display) {
00051 caca_free_display(c->display);
00052 c->display = NULL;
00053 }
00054 if (c->dither) {
00055 caca_free_dither(c->dither);
00056 c->dither = NULL;
00057 }
00058 if (c->canvas) {
00059 caca_free_canvas(c->canvas);
00060 c->canvas = NULL;
00061 }
00062 return 0;
00063 }
00064
00065 static void list_drivers(CACAContext *c)
00066 {
00067 const char *const *drivers = caca_get_display_driver_list();
00068 int i;
00069
00070 av_log(c->ctx, AV_LOG_INFO, "Available drivers:\n");
00071 for (i = 0; drivers[i]; i += 2)
00072 av_log(c->ctx, AV_LOG_INFO, "%s : %s\n", drivers[i], drivers[i + 1]);
00073 }
00074
00075 #define DEFINE_LIST_DITHER(thing, thing_str) \
00076 static void list_dither_## thing(CACAContext *c) \
00077 { \
00078 const char *const *thing = caca_get_dither_## thing ##_list(c->dither); \
00079 int i; \
00080 \
00081 av_log(c->ctx, AV_LOG_INFO, "Available %s:\n", thing_str); \
00082 for (i = 0; thing[i]; i += 2) \
00083 av_log(c->ctx, AV_LOG_INFO, "%s : %s\n", thing[i], thing[i + 1]); \
00084 }
00085
00086 DEFINE_LIST_DITHER(color, "colors");
00087 DEFINE_LIST_DITHER(charset, "charsets");
00088 DEFINE_LIST_DITHER(algorithm, "algorithms");
00089 DEFINE_LIST_DITHER(antialias, "antialias");
00090
00091 static int caca_write_header(AVFormatContext *s)
00092 {
00093 CACAContext *c = s->priv_data;
00094 AVStream *st = s->streams[0];
00095 AVCodecContext *encctx = st->codec;
00096 int ret, bpp;
00097
00098 c->ctx = s;
00099 if (c->list_drivers) {
00100 list_drivers(c);
00101 return AVERROR_EXIT;
00102 }
00103 if (c->list_dither) {
00104 if (!strcmp(c->list_dither, "colors")) {
00105 list_dither_color(c);
00106 } else if (!strcmp(c->list_dither, "charsets")) {
00107 list_dither_charset(c);
00108 } else if (!strcmp(c->list_dither, "algorithms")) {
00109 list_dither_algorithm(c);
00110 } else if (!strcmp(c->list_dither, "antialiases")) {
00111 list_dither_antialias(c);
00112 } else {
00113 av_log(s, AV_LOG_ERROR,
00114 "Invalid argument '%s', for 'list_dither' option\n"
00115 "Argument must be one of 'algorithms, 'antialiases', 'charsets', 'colors'\n",
00116 c->list_dither);
00117 return AVERROR(EINVAL);
00118 }
00119 return AVERROR_EXIT;
00120 }
00121
00122 if ( s->nb_streams > 1
00123 || encctx->codec_type != AVMEDIA_TYPE_VIDEO
00124 || encctx->codec_id != AV_CODEC_ID_RAWVIDEO) {
00125 av_log(s, AV_LOG_ERROR, "Only supports one rawvideo stream\n");
00126 return AVERROR(EINVAL);
00127 }
00128
00129 if (encctx->pix_fmt != PIX_FMT_RGB24) {
00130 av_log(s, AV_LOG_ERROR,
00131 "Unsupported pixel format '%s', choose rgb24\n",
00132 av_get_pix_fmt_name(encctx->pix_fmt));
00133 return AVERROR(EINVAL);
00134 }
00135
00136 c->canvas = caca_create_canvas(c->window_width, c->window_height);
00137 if (!c->canvas) {
00138 av_log(s, AV_LOG_ERROR, "Failed to create canvas\n");
00139 ret = AVERROR(errno);
00140 goto fail;
00141 }
00142
00143 bpp = av_get_bits_per_pixel(&av_pix_fmt_descriptors[encctx->pix_fmt]);
00144 c->dither = caca_create_dither(bpp, encctx->width, encctx->height,
00145 bpp / 8 * encctx->width,
00146 0x0000ff, 0x00ff00, 0xff0000, 0);
00147 if (!c->dither) {
00148 av_log(s, AV_LOG_ERROR, "Failed to create dither\n");
00149 ret = AVERROR(errno);
00150 goto fail;
00151 }
00152
00153 #define CHECK_DITHER_OPT(opt) \
00154 if (caca_set_dither_##opt(c->dither, c->opt) < 0) { \
00155 ret = AVERROR(errno); \
00156 av_log(s, AV_LOG_ERROR, "Failed to set value '%s' for option '%s'\n", \
00157 c->opt, #opt); \
00158 goto fail; \
00159 }
00160 CHECK_DITHER_OPT(algorithm);
00161 CHECK_DITHER_OPT(antialias);
00162 CHECK_DITHER_OPT(charset);
00163 CHECK_DITHER_OPT(color);
00164
00165 c->display = caca_create_display_with_driver(c->canvas, c->driver);
00166 if (!c->display) {
00167 av_log(s, AV_LOG_ERROR, "Failed to create display\n");
00168 list_drivers(c);
00169 ret = AVERROR(errno);
00170 goto fail;
00171 }
00172
00173 if (!c->window_width || !c->window_height) {
00174 c->window_width = caca_get_canvas_width(c->canvas);
00175 c->window_height = caca_get_canvas_height(c->canvas);
00176 }
00177
00178 if (!c->window_title)
00179 c->window_title = av_strdup(s->filename);
00180 caca_set_display_title(c->display, c->window_title);
00181 caca_set_display_time(c->display, av_rescale_q(1, st->codec->time_base, AV_TIME_BASE_Q));
00182
00183 return 0;
00184
00185 fail:
00186 caca_write_trailer(s);
00187 return ret;
00188 }
00189
00190 static int caca_write_packet(AVFormatContext *s, AVPacket *pkt)
00191 {
00192 CACAContext *c = s->priv_data;
00193
00194 caca_dither_bitmap(c->canvas, 0, 0, c->window_width, c->window_height, c->dither, pkt->data);
00195 caca_refresh_display(c->display);
00196
00197 return 0;
00198 }
00199
00200 #define OFFSET(x) offsetof(CACAContext,x)
00201 #define ENC AV_OPT_FLAG_ENCODING_PARAM
00202
00203 static const AVOption options[] = {
00204 { "window_size", "set window forced size", OFFSET(window_width), AV_OPT_TYPE_IMAGE_SIZE, {.str = NULL }, 0, 0, ENC},
00205 { "window_title", "set window title", OFFSET(window_title), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, ENC },
00206 { "driver", "set display driver", OFFSET(driver), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, ENC },
00207 { "algorithm", "set dithering algorithm", OFFSET(algorithm), AV_OPT_TYPE_STRING, {.str = "default" }, 0, 0, ENC },
00208 { "antialias", "set antialias method", OFFSET(antialias), AV_OPT_TYPE_STRING, {.str = "default" }, 0, 0, ENC },
00209 { "charset", "set charset used to render output", OFFSET(charset), AV_OPT_TYPE_STRING, {.str = "default" }, 0, 0, ENC },
00210 { "color", "set color used to render output", OFFSET(color), AV_OPT_TYPE_STRING, {.str = "default" }, 0, 0, ENC },
00211 { "list_drivers", "list available drivers", OFFSET(list_drivers), AV_OPT_TYPE_INT, {.i64=0}, 0, 1, ENC, "list_drivers" },
00212 { "true", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = 1}, 0, 0, ENC, "list_drivers" },
00213 { "false", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = 0}, 0, 0, ENC, "list_drivers" },
00214 { "list_dither", "list available dither options", OFFSET(list_dither), AV_OPT_TYPE_STRING, {.dbl=0}, 0, 1, ENC, "list_dither" },
00215 { "algorithms", NULL, 0, AV_OPT_TYPE_CONST, {.str = "algorithms"}, 0, 0, ENC, "list_dither" },
00216 { "antialiases", NULL, 0, AV_OPT_TYPE_CONST, {.str = "antialiases"},0, 0, ENC, "list_dither" },
00217 { "charsets", NULL, 0, AV_OPT_TYPE_CONST, {.str = "charsets"}, 0, 0, ENC, "list_dither" },
00218 { "colors", NULL, 0, AV_OPT_TYPE_CONST, {.str = "colors"}, 0, 0, ENC, "list_dither" },
00219 { NULL },
00220 };
00221
00222 static const AVClass caca_class = {
00223 .class_name = "caca_outdev",
00224 .item_name = av_default_item_name,
00225 .option = options,
00226 .version = LIBAVUTIL_VERSION_INT,
00227 };
00228
00229 AVOutputFormat ff_caca_muxer = {
00230 .name = "caca",
00231 .long_name = NULL_IF_CONFIG_SMALL("caca (color ASCII art) output device"),
00232 .priv_data_size = sizeof(CACAContext),
00233 .audio_codec = AV_CODEC_ID_NONE,
00234 .video_codec = AV_CODEC_ID_RAWVIDEO,
00235 .write_header = caca_write_header,
00236 .write_packet = caca_write_packet,
00237 .write_trailer = caca_write_trailer,
00238 .flags = AVFMT_NOFILE,
00239 .priv_class = &caca_class,
00240 };