FFmpeg
iterm2enc.c
Go to the documentation of this file.
1 /*
2  * This file is part of FFmpeg.
3  *
4  * Copyright (c) 2026 Zhao Zhili <quinkblack@foxmail.com>
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 #include <string.h>
22 
23 #include "libavutil/base64.h"
24 #include "libavutil/macros.h"
25 #include "libavutil/mem.h"
26 #include "libavutil/opt.h"
27 #include "avformat.h"
28 #include "mux.h"
29 
30 /* iTerm2 inline image protocol: https://iterm2.com/documentation-images.html */
31 
32 #define ESC "\033"
33 
34 #define SYNC_BEGIN ESC "[?2026h"
35 #define SYNC_END ESC "[?2026l"
36 #define CURSOR_SAVE ESC "7"
37 #define CURSOR_RESTORE ESC "8"
38 #define CURSOR_HOME ESC "[H"
39 #define CURSOR_BOTTOM ESC "[999H"
40 
41 #define OSC_START ESC "]1337;"
42 #define BEL "\a"
43 #define ST ESC "\\"
44 
45 /* tmux requires DCS passthrough with ST termination and ESC doubling */
46 #define TMUX_DCS ESC "Ptmux;"
47 
48 /* iTerm2 and tmux silently drop a single OSC sequence >= 1 MiB, so split the
49  * image into chunks below that limit. Old tmux capped a sequence at 256 bytes,
50  * but the tiny chunks that would require flood the tmux parser and freeze the
51  * terminal, so we do not support such versions. */
52 #define FILEPART_CHUNK ((1 << 20) - 4096)
53 
54 #define WRITE_LITERAL(pb, str) avio_write(pb, (const unsigned char *)(str), \
55  sizeof(str) - 1)
56 
57 typedef struct ITerm2Context {
58  const AVClass *class;
62  int tmux;
63  char *b64;
64  unsigned b64_size;
66 
67 static void osc_open(ITerm2Context *c, AVIOContext *pb)
68 {
69  if (c->tmux)
72 }
73 
75 {
76  WRITE_LITERAL(pb, BEL);
77  if (c->tmux)
78  WRITE_LITERAL(pb, ST);
79 }
80 
81 static void write_image(ITerm2Context *c, AVIOContext *pb, int size)
82 {
83  size_t b64_len = strlen(c->b64);
84 
85  osc_open(c, pb);
86  WRITE_LITERAL(pb, "MultipartFile=");
87  avio_printf(pb, "inline=1;size=%d", size);
88  if (c->display_width && c->display_width[0])
89  avio_printf(pb, ";width=%s", c->display_width);
90  if (c->display_height && c->display_height[0])
91  avio_printf(pb, ";height=%s", c->display_height);
92  if (!c->keep_aspect)
93  WRITE_LITERAL(pb, ";preserveAspectRatio=0");
94  osc_close(c, pb);
95 
96  for (size_t off = 0; off < b64_len; off += FILEPART_CHUNK) {
97  size_t n = FFMIN(FILEPART_CHUNK, b64_len - off);
98 
99  osc_open(c, pb);
100  WRITE_LITERAL(pb, "FilePart=");
101  avio_write(pb, c->b64 + off, n);
102  osc_close(c, pb);
103  }
104 
105  osc_open(c, pb);
106  WRITE_LITERAL(pb, "FileEnd");
107  osc_close(c, pb);
108 }
109 
111 {
112  ITerm2Context *c = s->priv_data;
113 
114  av_fast_malloc(&c->b64, &c->b64_size, AV_BASE64_SIZE(pkt->size));
115  if (!c->b64)
116  return AVERROR(ENOMEM);
117  if (!av_base64_encode(c->b64, c->b64_size, pkt->data, pkt->size))
118  return AVERROR(EINVAL);
119 
120  /* Synchronized output swaps the frame in atomically. */
122 
123  write_image(c, s->pb, pkt->size);
124 
126 
127  avio_flush(s->pb);
128 
129  return 0;
130 }
131 
133 {
134  WRITE_LITERAL(s->pb, CURSOR_BOTTOM "\n");
135 
136  return 0;
137 }
138 
140 {
141  ITerm2Context *c = s->priv_data;
142  av_freep(&c->b64);
143 }
144 
145 #define OFFSET(x) offsetof(ITerm2Context, x)
146 #define ENC AV_OPT_FLAG_ENCODING_PARAM
147 
148 static const AVOption options[] = {
149  { "display_width", "on-screen width (auto, N cells, Npx, N%%)",
150  OFFSET(display_width), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, ENC },
151  { "display_height", "on-screen height (auto, N cells, Npx, N%%)",
152  OFFSET(display_height), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, ENC },
153  { "keep_aspect", "preserve aspect ratio when scaling",
154  OFFSET(keep_aspect), AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1, ENC },
155  { "tmux", "wrap image in tmux DCS passthrough, requires tmux set -g allow-passthrough on",
156  OFFSET(tmux), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, ENC },
157  { NULL },
158 };
159 
160 static const AVClass iterm2_class = {
161  .class_name = "iTerm2 muxer",
162  .item_name = av_default_item_name,
163  .option = options,
164  .version = LIBAVUTIL_VERSION_INT,
165  .category = AV_CLASS_CATEGORY_MUXER,
166 };
167 
169  .p.name = "iterm2",
170  .p.long_name = NULL_IF_CONFIG_SMALL("iTerm2 inline image protocol"),
171  .priv_data_size = sizeof(ITerm2Context),
172  .p.audio_codec = AV_CODEC_ID_NONE,
173  .p.video_codec = AV_CODEC_ID_MJPEG,
174  .write_packet = iterm2_write_packet,
175  .write_trailer = iterm2_write_trailer,
176  .deinit = iterm2_deinit,
177  .flags_internal = FF_OFMT_FLAG_MAX_ONE_OF_EACH,
179  .p.priv_class = &iterm2_class,
180 };
AVOutputFormat::name
const char * name
Definition: avformat.h:508
AVERROR
Filter the word “frame” indicates either a video frame or a group of audio as stored in an AVFrame structure Format for each input and each output the list of supported formats For video that means pixel format For audio that means channel sample they are references to shared objects When the negotiation mechanism computes the intersection of the formats supported at each end of a all references to both lists are replaced with a reference to the intersection And when a single format is eventually chosen for a link amongst the remaining all references to the list are updated That means that if a filter requires that its input and output have the same format amongst a supported all it has to do is use a reference to the same list of formats query_formats can leave some formats unset and return AVERROR(EAGAIN) to cause the negotiation mechanism toagain later. That can be used by filters with complex requirements to use the format negotiated on one link to set the formats supported on another. Frame references ownership and permissions
opt.h
AVFMT_NODIMENSIONS
#define AVFMT_NODIMENSIONS
Format does not need width/height.
Definition: avformat.h:483
OFFSET
#define OFFSET(x)
Definition: iterm2enc.c:145
AVFMT_NOTIMESTAMPS
#define AVFMT_NOTIMESTAMPS
Format does not need / have any timestamps.
Definition: avformat.h:479
av_cold
#define av_cold
Definition: attributes.h:119
AVPacket::data
uint8_t * data
Definition: packet.h:603
AVOption
AVOption.
Definition: opt.h:428
SYNC_BEGIN
#define SYNC_BEGIN
Definition: iterm2enc.c:34
CURSOR_HOME
#define CURSOR_HOME
Definition: iterm2enc.c:38
OSC_START
#define OSC_START
Definition: iterm2enc.c:41
ENC
#define ENC
Definition: iterm2enc.c:146
ESC
#define ESC
Definition: iterm2enc.c:32
FFOutputFormat::p
AVOutputFormat p
The public AVOutputFormat.
Definition: mux.h:65
iterm2_write_packet
static int iterm2_write_packet(AVFormatContext *s, AVPacket *pkt)
Definition: iterm2enc.c:110
CURSOR_SAVE
#define CURSOR_SAVE
Definition: iterm2enc.c:36
ITerm2Context::b64
char * b64
Definition: iterm2enc.c:63
macros.h
iterm2_deinit
static av_cold void iterm2_deinit(AVFormatContext *s)
Definition: iterm2enc.c:139
iterm2_write_trailer
static int iterm2_write_trailer(AVFormatContext *s)
Definition: iterm2enc.c:132
TMUX_DCS
#define TMUX_DCS
Definition: iterm2enc.c:46
ITerm2Context::display_width
char * display_width
Definition: iterm2enc.c:59
write_image
static void write_image(ITerm2Context *c, AVIOContext *pb, int size)
Definition: iterm2enc.c:81
options
static const AVOption options[]
Definition: iterm2enc.c:148
avio_flush
void avio_flush(AVIOContext *s)
Force flushing of buffered data.
Definition: aviobuf.c:228
AVFormatContext
Format I/O context.
Definition: avformat.h:1314
ITerm2Context::b64_size
unsigned b64_size
Definition: iterm2enc.c:64
LIBAVUTIL_VERSION_INT
#define LIBAVUTIL_VERSION_INT
Definition: version.h:85
AVClass
Describe the class of an AVClass context structure.
Definition: log.h:76
NULL
#define NULL
Definition: coverity.c:32
ff_iterm2_muxer
const FFOutputFormat ff_iterm2_muxer
Definition: iterm2enc.c:168
CURSOR_RESTORE
#define CURSOR_RESTORE
Definition: iterm2enc.c:37
ST
#define ST
Definition: iterm2enc.c:43
av_default_item_name
const char * av_default_item_name(void *ptr)
Return the context name.
Definition: log.c:242
ITerm2Context
Definition: iterm2enc.c:57
osc_close
static void osc_close(ITerm2Context *c, AVIOContext *pb)
Definition: iterm2enc.c:74
options
Definition: swscale.c:50
FFOutputFormat
Definition: mux.h:61
base64.h
c
Undefined Behavior In the C some operations are like signed integer dereferencing freed accessing outside allocated Undefined Behavior must not occur in a C it is not safe even if the output of undefined operations is unused The unsafety may seem nit picking but Optimizing compilers have in fact optimized code on the assumption that no undefined Behavior occurs Optimizing code based on wrong assumptions can and has in some cases lead to effects beyond the output of computations The signed integer overflow problem in speed critical code Code which is highly optimized and works with signed integers sometimes has the problem that often the output of the computation does not c
Definition: undefined.txt:32
osc_open
static void osc_open(ITerm2Context *c, AVIOContext *pb)
Definition: iterm2enc.c:67
SYNC_END
#define SYNC_END
Definition: iterm2enc.c:35
AVIOContext
Bytestream IO Context.
Definition: avio.h:160
AVPacket::size
int size
Definition: packet.h:604
NULL_IF_CONFIG_SMALL
#define NULL_IF_CONFIG_SMALL(x)
Return NULL if CONFIG_SMALL is true, otherwise the argument without modification.
Definition: internal.h:88
size
int size
Definition: twinvq_data.h:10344
BEL
#define BEL
Definition: iterm2enc.c:42
ITerm2Context::display_height
char * display_height
Definition: iterm2enc.c:60
avio_write
void avio_write(AVIOContext *s, const unsigned char *buf, int size)
Definition: aviobuf.c:206
CURSOR_BOTTOM
#define CURSOR_BOTTOM
Definition: iterm2enc.c:39
ITerm2Context::tmux
int tmux
Definition: iterm2enc.c:62
AV_CODEC_ID_MJPEG
@ AV_CODEC_ID_MJPEG
Definition: codec_id.h:57
AV_BASE64_SIZE
#define AV_BASE64_SIZE(x)
Calculate the output size needed to base64-encode x bytes to a null-terminated string.
Definition: base64.h:66
AV_CODEC_ID_NONE
@ AV_CODEC_ID_NONE
Definition: codec_id.h:48
FF_OFMT_FLAG_MAX_ONE_OF_EACH
#define FF_OFMT_FLAG_MAX_ONE_OF_EACH
If this flag is set, it indicates that for each codec type whose corresponding default codec (i....
Definition: mux.h:50
s
uint8_t s
Definition: llvidencdsp.c:39
FFMIN
#define FFMIN(a, b)
Definition: macros.h:49
AVClass::class_name
const char * class_name
The name of the class; usually it is the same name as the context structure type to which the AVClass...
Definition: log.h:81
avformat.h
avio_printf
int avio_printf(AVIOContext *s, const char *fmt,...) av_printf_format(2
Writes a formatted string to the context.
AV_CLASS_CATEGORY_MUXER
@ AV_CLASS_CATEGORY_MUXER
Definition: log.h:32
av_base64_encode
char * av_base64_encode(char *out, int out_size, const uint8_t *in, int in_size)
Encode data to base64 and null-terminate.
Definition: base64.c:147
Windows::Graphics::DirectX::Direct3D11::p
IDirect3DDxgiInterfaceAccess _COM_Outptr_ void ** p
Definition: vsrc_gfxcapture_winrt.hpp:53
mem.h
ITerm2Context::keep_aspect
int keep_aspect
Definition: iterm2enc.c:61
AVPacket
This structure stores compressed data.
Definition: packet.h:580
AV_OPT_TYPE_BOOL
@ AV_OPT_TYPE_BOOL
Underlying C type is int.
Definition: opt.h:326
iterm2_class
static const AVClass iterm2_class
Definition: iterm2enc.c:160
av_freep
#define av_freep(p)
Definition: tableprint_vlc.h:35
av_fast_malloc
void av_fast_malloc(void *ptr, unsigned int *size, size_t min_size)
Allocate a buffer, reusing the given one if large enough.
Definition: mem.c:557
FILEPART_CHUNK
#define FILEPART_CHUNK
Definition: iterm2enc.c:52
WRITE_LITERAL
#define WRITE_LITERAL(pb, str)
Definition: iterm2enc.c:54
pkt
static AVPacket * pkt
Definition: demux_decode.c:55
AV_OPT_TYPE_STRING
@ AV_OPT_TYPE_STRING
Underlying C type is a uint8_t* that is either NULL or points to a C string allocated with the av_mal...
Definition: opt.h:275
mux.h