2 * Copyright (c) 2012 Paul B Mahol
4 * This file is part of FFmpeg.
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.
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.
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
22 #include "libavutil/opt.h"
23 #include "libavutil/pixdesc.h"
26 typedef struct CACAContext
{
30 int window_width
, window_height
;
32 caca_canvas_t
*canvas
;
33 caca_display_t
*display
;
34 caca_dither_t
*dither
;
36 char *algorithm
, *antialias
;
37 char *charset
, *color
;
44 static int caca_write_trailer(AVFormatContext
*s
)
46 CACAContext
*c
= s
->priv_data
;
48 av_freep(&c
->window_title
);
51 caca_free_display(c
->display
);
55 caca_free_dither(c
->dither
);
59 caca_free_canvas(c
->canvas
);
65 static void list_drivers(CACAContext
*c
)
67 const char *const *drivers
= caca_get_display_driver_list();
70 av_log(c
->ctx
, AV_LOG_INFO
, "Available drivers:\n");
71 for (i
= 0; drivers
[i
]; i
+= 2)
72 av_log(c
->ctx
, AV_LOG_INFO
, "%s : %s\n", drivers
[i
], drivers
[i
+ 1]);
75 #define DEFINE_LIST_DITHER(thing, thing_str) \
76 static void list_dither_## thing(CACAContext *c) \
78 const char *const *thing = caca_get_dither_## thing ##_list(c->dither); \
81 av_log(c->ctx, AV_LOG_INFO, "Available %s:\n", thing_str); \
82 for (i = 0; thing[i]; i += 2) \
83 av_log(c->ctx, AV_LOG_INFO, "%s : %s\n", thing[i], thing[i + 1]); \
86 DEFINE_LIST_DITHER(color
, "colors");
87 DEFINE_LIST_DITHER(charset
, "charsets");
88 DEFINE_LIST_DITHER(algorithm
, "algorithms");
89 DEFINE_LIST_DITHER(antialias
, "antialias");
91 static int caca_write_header(AVFormatContext
*s
)
93 CACAContext
*c
= s
->priv_data
;
94 AVStream
*st
= s
->streams
[0];
95 AVCodecContext
*encctx
= st
->codec
;
99 if (c
->list_drivers
) {
103 if (c
->list_dither
) {
104 if (!strcmp(c
->list_dither
, "colors")) {
105 list_dither_color(c
);
106 } else if (!strcmp(c
->list_dither
, "charsets")) {
107 list_dither_charset(c
);
108 } else if (!strcmp(c
->list_dither
, "algorithms")) {
109 list_dither_algorithm(c
);
110 } else if (!strcmp(c
->list_dither
, "antialiases")) {
111 list_dither_antialias(c
);
113 av_log(s
, AV_LOG_ERROR
,
114 "Invalid argument '%s', for 'list_dither' option\n"
115 "Argument must be one of 'algorithms, 'antialiases', 'charsets', 'colors'\n",
117 return AVERROR(EINVAL
);
122 if ( s
->nb_streams
> 1
123 || encctx
->codec_type
!= AVMEDIA_TYPE_VIDEO
124 || encctx
->codec_id
!= AV_CODEC_ID_RAWVIDEO
) {
125 av_log(s
, AV_LOG_ERROR
, "Only supports one rawvideo stream\n");
126 return AVERROR(EINVAL
);
129 if (encctx
->pix_fmt
!= AV_PIX_FMT_RGB24
) {
130 av_log(s
, AV_LOG_ERROR
,
131 "Unsupported pixel format '%s', choose rgb24\n",
132 av_get_pix_fmt_name(encctx
->pix_fmt
));
133 return AVERROR(EINVAL
);
136 c
->canvas
= caca_create_canvas(c
->window_width
, c
->window_height
);
138 ret
= AVERROR(errno
);
139 av_log(s
, AV_LOG_ERROR
, "Failed to create canvas\n");
143 bpp
= av_get_bits_per_pixel(av_pix_fmt_desc_get(encctx
->pix_fmt
));
144 c
->dither
= caca_create_dither(bpp
, encctx
->width
, encctx
->height
,
145 bpp
/ 8 * encctx
->width
,
146 0x0000ff, 0x00ff00, 0xff0000, 0);
148 ret
= AVERROR(errno
);
149 av_log(s
, AV_LOG_ERROR
, "Failed to create dither\n");
153 #define CHECK_DITHER_OPT(opt) \
154 if (caca_set_dither_##opt(c->dither, c->opt) < 0) { \
155 ret = AVERROR(errno); \
156 av_log(s, AV_LOG_ERROR, "Failed to set value '%s' for option '%s'\n", \
160 CHECK_DITHER_OPT(algorithm
);
161 CHECK_DITHER_OPT(antialias
);
162 CHECK_DITHER_OPT(charset
);
163 CHECK_DITHER_OPT(color
);
165 c
->display
= caca_create_display_with_driver(c
->canvas
, c
->driver
);
167 ret
= AVERROR(errno
);
168 av_log(s
, AV_LOG_ERROR
, "Failed to create display\n");
173 if (!c
->window_width
|| !c
->window_height
) {
174 c
->window_width
= caca_get_canvas_width(c
->canvas
);
175 c
->window_height
= caca_get_canvas_height(c
->canvas
);
178 if (!c
->window_title
)
179 c
->window_title
= av_strdup(s
->filename
);
180 caca_set_display_title(c
->display
, c
->window_title
);
181 caca_set_display_time(c
->display
, av_rescale_q(1, st
->codec
->time_base
, AV_TIME_BASE_Q
));
186 caca_write_trailer(s
);
190 static int caca_write_packet(AVFormatContext
*s
, AVPacket
*pkt
)
192 CACAContext
*c
= s
->priv_data
;
194 caca_dither_bitmap(c
->canvas
, 0, 0, c
->window_width
, c
->window_height
, c
->dither
, pkt
->data
);
195 caca_refresh_display(c
->display
);
200 #define OFFSET(x) offsetof(CACAContext,x)
201 #define ENC AV_OPT_FLAG_ENCODING_PARAM
203 static const AVOption options
[] = {
204 { "window_size", "set window forced size", OFFSET(window_width
), AV_OPT_TYPE_IMAGE_SIZE
, {.str
= NULL
}, 0, 0, ENC
},
205 { "window_title", "set window title", OFFSET(window_title
), AV_OPT_TYPE_STRING
, {.str
= NULL
}, 0, 0, ENC
},
206 { "driver", "set display driver", OFFSET(driver
), AV_OPT_TYPE_STRING
, {.str
= NULL
}, 0, 0, ENC
},
207 { "algorithm", "set dithering algorithm", OFFSET(algorithm
), AV_OPT_TYPE_STRING
, {.str
= "default" }, 0, 0, ENC
},
208 { "antialias", "set antialias method", OFFSET(antialias
), AV_OPT_TYPE_STRING
, {.str
= "default" }, 0, 0, ENC
},
209 { "charset", "set charset used to render output", OFFSET(charset
), AV_OPT_TYPE_STRING
, {.str
= "default" }, 0, 0, ENC
},
210 { "color", "set color used to render output", OFFSET(color
), AV_OPT_TYPE_STRING
, {.str
= "default" }, 0, 0, ENC
},
211 { "list_drivers", "list available drivers", OFFSET(list_drivers
), AV_OPT_TYPE_INT
, {.i64
=0}, 0, 1, ENC
, "list_drivers" },
212 { "true", NULL
, 0, AV_OPT_TYPE_CONST
, {.i64
= 1}, 0, 0, ENC
, "list_drivers" },
213 { "false", NULL
, 0, AV_OPT_TYPE_CONST
, {.i64
= 0}, 0, 0, ENC
, "list_drivers" },
214 { "list_dither", "list available dither options", OFFSET(list_dither
), AV_OPT_TYPE_STRING
, {.dbl
=0}, 0, 1, ENC
, "list_dither" },
215 { "algorithms", NULL
, 0, AV_OPT_TYPE_CONST
, {.str
= "algorithms"}, 0, 0, ENC
, "list_dither" },
216 { "antialiases", NULL
, 0, AV_OPT_TYPE_CONST
, {.str
= "antialiases"},0, 0, ENC
, "list_dither" },
217 { "charsets", NULL
, 0, AV_OPT_TYPE_CONST
, {.str
= "charsets"}, 0, 0, ENC
, "list_dither" },
218 { "colors", NULL
, 0, AV_OPT_TYPE_CONST
, {.str
= "colors"}, 0, 0, ENC
, "list_dither" },
222 static const AVClass caca_class
= {
223 .class_name
= "caca_outdev",
224 .item_name
= av_default_item_name
,
226 .version
= LIBAVUTIL_VERSION_INT
,
227 .category
= AV_CLASS_CATEGORY_DEVICE_VIDEO_OUTPUT
,
230 AVOutputFormat ff_caca_muxer
= {
232 .long_name
= NULL_IF_CONFIG_SMALL("caca (color ASCII art) output device"),
233 .priv_data_size
= sizeof(CACAContext
),
234 .audio_codec
= AV_CODEC_ID_NONE
,
235 .video_codec
= AV_CODEC_ID_RAWVIDEO
,
236 .write_header
= caca_write_header
,
237 .write_packet
= caca_write_packet
,
238 .write_trailer
= caca_write_trailer
,
239 .flags
= AVFMT_NOFILE
,
240 .priv_class
= &caca_class
,