ascii-chat
Binary v0.15.83-53-gb711d8720
Library lib/v0.3.2-701-gb711d8720
Video chat in your terminal
Loading...
Searching...
No Matches
http_renderer.h File Reference

Go to the source code of this file.

Data Structures

struct  http_render_frame_t
struct  http_render_source_t
struct  http_render_options_t
struct  http_render_request_t
struct  http_render_result_t
struct  http_renderer_config_t
struct  http_renderer_process_request_t
struct  http_renderer_process_result_t
struct  http_renderer_job

Macros

#define HTTP_RENDERER_MAX_OUTPUT   (8u * 1024u * 1024u)
#define HTTP_RENDERER_MAX_FRAMES   120

Typedefs

typedef struct http_renderer http_renderer_t
typedef struct http_renderer_job http_renderer_job_t
typedef void(* http_renderer_job_fn) (void *context, atomic_t *cancelled)

Enumerations

enum  http_render_source_type_t {
  HTTP_RENDER_SOURCE_UPLOAD , HTTP_RENDER_SOURCE_FILE , HTTP_RENDER_SOURCE_URL , HTTP_RENDER_SOURCE_WEBCAM ,
  HTTP_RENDER_SOURCE_VISUALIZATION
}
enum  http_render_output_type_t {
  HTTP_RENDER_OUTPUT_TEXT , HTTP_RENDER_OUTPUT_JSON , HTTP_RENDER_OUTPUT_HTML , HTTP_RENDER_OUTPUT_FILE ,
  HTTP_RENDER_OUTPUT_FILES
}

Functions

void http_render_request_init (http_render_request_t *request)
void http_render_result_destroy (http_render_result_t *result)
asciichat_error_t http_render_result_add_text_frame (http_render_result_t *result, const char *text, size_t length, uint64_t timestamp_ms)
asciichat_error_t http_render_result_add_file_frame (http_render_result_t *result, const char *path, uint64_t timestamp_ms)
void http_renderer_process (http_renderer_process_request_t *request, atomic_t *cancelled, http_renderer_process_result_t *result)
void http_renderer_process_result_destroy (http_renderer_process_result_t *result)
asciichat_error_t http_renderer_render_file (const char *path, const http_render_request_t *request, http_render_result_t *result, atomic_t *cancelled)
asciichat_error_t http_renderer_job_start (http_renderer_job_t *job, const char *name, http_renderer_job_fn execute, void *context)
void http_renderer_job_cancel (http_renderer_job_t *job)
bool http_renderer_job_is_done (const http_renderer_job_t *job)
void http_renderer_job_join (http_renderer_job_t *job)
void http_renderer_job_reset (http_renderer_job_t *job)
asciichat_error_t http_renderer_init (http_renderer_t **out, const http_renderer_config_t *config)
asciichat_error_t http_renderer_submit (http_renderer_t *renderer, const http_render_request_t *request, void *user_data, http_renderer_job_t **out_job)
asciichat_error_t http_renderer_cancel (http_renderer_job_t *job)
void http_renderer_reap (http_renderer_t *renderer, bool all)
void http_renderer_stop (http_renderer_t *renderer)
void http_renderer_destroy (http_renderer_t **renderer)
void http_renderer_job_wait (http_renderer_job_t *job)

Macro Definition Documentation

◆ HTTP_RENDERER_MAX_FRAMES

#define HTTP_RENDERER_MAX_FRAMES   120

Definition at line 30 of file http_renderer.h.

Referenced by http_renderer_render_file().

◆ HTTP_RENDERER_MAX_OUTPUT

#define HTTP_RENDERER_MAX_OUTPUT   (8u * 1024u * 1024u)

Definition at line 13 of file http_renderer.h.

Referenced by http_renderer_init(), and http_renderer_process().

Typedef Documentation

◆ http_renderer_job_fn

typedef void(* http_renderer_job_fn) (void *context, atomic_t *cancelled)

Definition at line 120 of file http_renderer.h.

◆ http_renderer_job_t

Definition at line 80 of file http_renderer.h.

◆ http_renderer_t

Definition at line 79 of file http_renderer.h.

Enumeration Type Documentation

◆ http_render_output_type_t

Enumerator
HTTP_RENDER_OUTPUT_TEXT 
HTTP_RENDER_OUTPUT_JSON 
HTTP_RENDER_OUTPUT_HTML 
HTTP_RENDER_OUTPUT_FILE 
HTTP_RENDER_OUTPUT_FILES 

Definition at line 22 of file http_renderer.h.

◆ http_render_source_type_t

Enumerator
HTTP_RENDER_SOURCE_UPLOAD 
HTTP_RENDER_SOURCE_FILE 
HTTP_RENDER_SOURCE_URL 
HTTP_RENDER_SOURCE_WEBCAM 
HTTP_RENDER_SOURCE_VISUALIZATION 

Definition at line 15 of file http_renderer.h.

Function Documentation

◆ http_render_request_init()

void http_render_request_init ( http_render_request_t * request)

Definition at line 216 of file http_renderer.c.

216 {
217 if (!request)
218 return;
219 memset(request, 0, sizeof(*request));
222}
http_render_source_t source
http_render_output_type_t output_type
http_render_source_type_t type

References HTTP_RENDER_OUTPUT_TEXT, HTTP_RENDER_SOURCE_UPLOAD, http_render_request_t::output_type, http_render_request_t::source, and http_render_source_t::type.

◆ http_render_result_add_file_frame()

asciichat_error_t http_render_result_add_file_frame ( http_render_result_t * result,
const char * path,
uint64_t timestamp_ms )

Definition at line 270 of file http_renderer.c.

271 {
272 if (!result || !path || !*path)
273 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid file frame");
274 asciichat_error_t err = http_render_result_reserve_frame(result);
275 if (err != ASCIICHAT_OK)
276 return err;
277 http_render_frame_t *frame = &result->frames[result->frame_count++];
278 SAFE_STRNCPY(frame->path, path, sizeof(frame->path));
279 frame->timestamp_ms = timestamp_ms;
280 return ASCIICHAT_OK;
281}
#define SET_ERRNO(code,...)
#define SAFE_STRNCPY(dst, src, size)
Definition common.h:423
asciichat_error_t
Error and exit codes - unified status values (0-255).
Definition error_codes.h:44
@ ASCIICHAT_OK
Definition error_codes.h:46
@ ERROR_INVALID_PARAM
char path[PLATFORM_MAX_PATH_LENGTH]
http_render_frame_t * frames

References ASCIICHAT_OK, ERROR_INVALID_PARAM, http_render_result_t::frame_count, http_render_result_t::frames, http_render_frame_t::path, SAFE_STRNCPY, SET_ERRNO, and http_render_frame_t::timestamp_ms.

Referenced by http_renderer_process().

◆ http_render_result_add_text_frame()

asciichat_error_t http_render_result_add_text_frame ( http_render_result_t * result,
const char * text,
size_t length,
uint64_t timestamp_ms )

Definition at line 254 of file http_renderer.c.

255 {
256 if (!result || (!text && length))
257 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid text frame");
258 asciichat_error_t err = http_render_result_reserve_frame(result);
259 if (err != ASCIICHAT_OK)
260 return err;
261 http_render_frame_t *frame = &result->frames[result->frame_count++];
262 frame->text = SAFE_MALLOC(length + 1, char *);
263 SAFE_MEMCPY(frame->text, length + 1, text, length);
264 frame->text[length] = '\0';
265 frame->text_length = length;
266 frame->timestamp_ms = timestamp_ms;
267 return ASCIICHAT_OK;
268}
#define SAFE_MALLOC(size, cast)
Definition common.h:265
#define SAFE_MEMCPY(dest, dest_size, src, count)
Definition common.h:483

References ASCIICHAT_OK, ERROR_INVALID_PARAM, http_render_result_t::frame_count, http_render_result_t::frames, SAFE_MALLOC, SAFE_MEMCPY, SET_ERRNO, http_render_frame_t::text, http_render_frame_t::text_length, and http_render_frame_t::timestamp_ms.

Referenced by http_renderer_process(), and http_renderer_render_file().

◆ http_render_result_destroy()

void http_render_result_destroy ( http_render_result_t * result)

Definition at line 224 of file http_renderer.c.

224 {
225 if (!result)
226 return;
227 SAFE_FREE(result->ascii);
228 for (size_t i = 0; i < result->frame_count; ++i)
229 SAFE_FREE(result->frames[i].text);
230 SAFE_FREE(result->frames);
231 result->frame_count = 0;
232 result->frame_capacity = 0;
233 result->ascii_length = 0;
234 result->output_path[0] = '\0';
235 result->mime_type[0] = '\0';
236}
#define SAFE_FREE(ptr)
Definition common.h:377
char output_path[PLATFORM_MAX_PATH_LENGTH]

References http_render_result_t::ascii, http_render_result_t::ascii_length, http_render_result_t::frame_capacity, http_render_result_t::frame_count, http_render_result_t::frames, http_render_result_t::mime_type, http_render_result_t::output_path, SAFE_FREE, and http_render_frame_t::text.

Referenced by http_renderer_reap().

◆ http_renderer_cancel()

asciichat_error_t http_renderer_cancel ( http_renderer_job_t * job)

Definition at line 160 of file http_renderer.c.

160 {
161 if (!job || !job->renderer)
162 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid HTTP renderer job");
164 return ASCIICHAT_OK;
165}
void http_renderer_job_cancel(http_renderer_job_t *job)
http_renderer_t * renderer

References ASCIICHAT_OK, ERROR_INVALID_PARAM, http_renderer_job_cancel(), http_renderer_job::renderer, and SET_ERRNO.

◆ http_renderer_destroy()

void http_renderer_destroy ( http_renderer_t ** renderer)

Definition at line 206 of file http_renderer.c.

206 {
207 if (!renderer || !*renderer)
208 return;
209 http_renderer_stop(*renderer);
210 http_renderer_reap(*renderer, true);
211 mutex_destroy(&(*renderer)->lock);
212 SAFE_FREE(*renderer);
213}
void http_renderer_stop(http_renderer_t *renderer)
void http_renderer_reap(http_renderer_t *renderer, bool all)

References http_renderer_reap(), http_renderer_stop(), and SAFE_FREE.

Referenced by api_server_main().

◆ http_renderer_init()

asciichat_error_t http_renderer_init ( http_renderer_t ** out,
const http_renderer_config_t * config )

Definition at line 108 of file http_renderer.c.

108 {
109 if (!out || !config)
110 return SET_ERRNO(ERROR_INVALID_PARAM, "Missing HTTP renderer configuration");
111 *out = NULL;
112 http_renderer_t *renderer = SAFE_CALLOC(1, sizeof(*renderer), http_renderer_t *);
113 renderer->config = *config;
114 if (!renderer->config.max_jobs)
115 renderer->config.max_jobs = 1;
116 if (!renderer->config.max_output_bytes)
118 if (mutex_init(&renderer->lock, "http_renderer") != 0) {
119 SAFE_FREE(renderer);
120 return SET_ERRNO(ERROR_THREAD, "Cannot initialize HTTP renderer lock");
121 }
122 *out = renderer;
123 return ASCIICHAT_OK;
124}
#define SAFE_CALLOC(count, size, cast)
Definition common.h:275
@ ERROR_THREAD
#define HTTP_RENDERER_MAX_OUTPUT
struct http_renderer http_renderer_t
static bool size_t gotel_query_t * out
Definition queries.h:8
http_renderer_config_t config

References ASCIICHAT_OK, http_renderer::config, ERROR_INVALID_PARAM, ERROR_THREAD, HTTP_RENDERER_MAX_OUTPUT, http_renderer::lock, http_renderer_config_t::max_jobs, http_renderer_config_t::max_output_bytes, out, SAFE_CALLOC, SAFE_FREE, and SET_ERRNO.

Referenced by api_server_main().

◆ http_renderer_job_cancel()

void http_renderer_job_cancel ( http_renderer_job_t * job)

Definition at line 61 of file http_renderer.c.

61 {
62 if (job && job->started)
63 atomic_store_bool(&job->cancelled, true);
64}

References http_renderer_job::cancelled, and http_renderer_job::started.

Referenced by http_renderer_cancel(), http_renderer_stop(), mcp_dispatch(), mcp_http_run(), mcp_server_main(), and mcp_stdio_run().

◆ http_renderer_job_is_done()

bool http_renderer_job_is_done ( const http_renderer_job_t * job)

Definition at line 66 of file http_renderer.c.

66 {
67 return job && job->started && atomic_load_bool((atomic_t *)&job->done);
68}

References http_renderer_job::done, and http_renderer_job::started.

Referenced by http_renderer_reap(), and mcp_jobs_reap().

◆ http_renderer_job_join()

void http_renderer_job_join ( http_renderer_job_t * job)

Definition at line 70 of file http_renderer.c.

70 {
71 if (job && job->started) {
72 thread_join(&job->thread, NULL);
73 job->started = false;
74 }
75}
platform_thread_t thread

References http_renderer_job::started, and http_renderer_job::thread.

Referenced by http_renderer_job_reset(), http_renderer_job_wait(), http_renderer_reap(), and mcp_jobs_reap().

◆ http_renderer_job_reset()

void http_renderer_job_reset ( http_renderer_job_t * job)

Definition at line 77 of file http_renderer.c.

77 {
78 if (job) {
80 memset(job, 0, sizeof(*job));
81 }
82}
void http_renderer_job_join(http_renderer_job_t *job)

References http_renderer_job_join().

◆ http_renderer_job_start()

asciichat_error_t http_renderer_job_start ( http_renderer_job_t * job,
const char * name,
http_renderer_job_fn execute,
void * context )

Definition at line 46 of file http_renderer.c.

47 {
48 if (!job || !execute || job->started)
49 return SET_ERRNO(ERROR_INVALID_STATE, "Renderer job is already active");
50 memset(job, 0, sizeof(*job));
51 job->execute = execute;
52 job->context = context;
53 atomic_store_bool(&job->cancelled, false);
54 atomic_store_bool(&job->done, false);
55 if (platform_thread_create(&job->thread, name ? name : "http-render", http_renderer_job_main, job))
56 return SET_ERRNO(ERROR_THREAD, "Cannot start renderer job");
57 job->started = true;
58 return ASCIICHAT_OK;
59}
@ ERROR_INVALID_STATE
http_renderer_job_fn execute

References ASCIICHAT_OK, http_renderer_job::cancelled, http_renderer_job::context, http_renderer_job::done, ERROR_INVALID_STATE, ERROR_THREAD, http_renderer_job::execute, SET_ERRNO, http_renderer_job::started, and http_renderer_job::thread.

Referenced by mcp_dispatch().

◆ http_renderer_job_wait()

void http_renderer_job_wait ( http_renderer_job_t * job)

Definition at line 167 of file http_renderer.c.

167 {
169}

References http_renderer_job_join().

◆ http_renderer_process()

void http_renderer_process ( http_renderer_process_request_t * request,
atomic_t * cancelled,
http_renderer_process_result_t * result )

Definition at line 112 of file http_renderer_process.c.

113 {
114 memset(result, 0, sizeof(*result));
115 if (r->output_type != HTTP_RENDER_OUTPUT_TEXT && r->output_type != HTTP_RENDER_OUTPUT_FILE) {
116 SAFE_STRNCPY(result->error, "Renderer output must select text or files", sizeof(result->error));
117 return;
118 }
119 render_worker_t w = {.cancelled = cancelled, .result = result, .started = time_get_ns(), .timeout = r->timeout_ms};
120 result->text = SAFE_CALLOC(HTTP_RENDERER_MAX_OUTPUT + 1, 1, char *);
121 if (uv_loop_init(&w.loop)) {
122 SAFE_STRNCPY(result->error, "Cannot initialize renderer event loop", sizeof(result->error));
123 return;
124 }
125 uv_pipe_init(&w.loop, &w.output, 0);
126 uv_pipe_init(&w.loop, &w.errors, 0);
127 uv_timer_init(&w.loop, &w.timer);
128 w.output.data = w.errors.data = w.timer.data = w.process.data = &w;
129 uv_stdio_container_t streams[3] = {0};
130 streams[0].flags = UV_IGNORE;
131 streams[1].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE;
132 streams[1].data.stream = (uv_stream_t *)&w.output;
133 streams[2].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE;
134 streams[2].data.stream = (uv_stream_t *)&w.errors;
135 // Only runtime essentials are inherited; user config, ASCII_CHAT_* and credentials are excluded.
136 const char *keys[] = {
137 "PATH", "SystemRoot", "WINDIR", "TEMP", "TMP", "TMPDIR", "LD_LIBRARY_PATH", "DYLD_LIBRARY_PATH", "TESTING", NULL};
138 char *env[MCP_RENDER_ENV_CAPACITY] = {0};
139 size_t ne = 0;
140 for (size_t i = 0; keys[i]; ++i) {
141 const char *v = platform_getenv(keys[i]);
142 if (v) {
143 size_t n = strlen(keys[i]) + strlen(v) + 2;
144 env[ne] = SAFE_MALLOC(n, char *);
145 SAFE_SNPRINTF(env[ne], n, "%s=%s", keys[i], v);
146 ++ne;
147 }
148 }
149 // The private staging directory provides an isolated home for media libraries.
150 if (r->stage_dir[0]) {
151 size_t n = strlen(r->stage_dir) + sizeof("HOME=");
152 env[ne] = SAFE_MALLOC(n, char *);
153 SAFE_SNPRINTF(env[ne], n, "HOME=%s", r->stage_dir);
154 ++ne;
155 }
156 env[ne++] = platform_strdup("ASCII_CHAT_MCP_WORKER=1");
157 env[ne++] = platform_strdup("ASCII_CHAT_OTEL_ENABLED_TELEMETRY=none");
158 if (r->capture_webcam)
159 env[ne++] = platform_strdup("ASCII_CHAT_MCP_WEBCAM=1");
160 uv_process_options_t opts = {
161 .exit_cb = exited, .file = r->argv[0], .args = r->argv, .env = env, .stdio_count = 3, .stdio = streams};
162 int rc = uv_spawn(&w.loop, &w.process, &opts);
163 for (size_t i = 0; i < ne; ++i)
164 SAFE_FREE(env[i]);
165 if (rc) {
166 SAFE_SNPRINTF(result->error, sizeof(result->error), "Cannot start renderer: %s", uv_strerror(rc));
167 uv_close((uv_handle_t *)&w.output, NULL);
168 uv_close((uv_handle_t *)&w.errors, NULL);
169 uv_close((uv_handle_t *)&w.timer, NULL);
170 } else {
171 uv_read_start((uv_stream_t *)&w.output, allocate, read_output);
172 uv_read_start((uv_stream_t *)&w.errors, allocate, read_output);
173 uv_timer_start(&w.timer, tick, 20, 20);
174 }
175 uv_run(&w.loop, UV_RUN_DEFAULT);
176 uv_loop_close(&w.loop);
177 result->elapsed_ms = (time_get_ns() - w.started) / 1000000;
178 if (rc || w.failed)
179 return;
180 result->error[0] = '\0';
181 if (!result->length && !r->stage_file[0]) {
182 SAFE_STRNCPY(result->error, "Renderer returned no ASCII art", sizeof(result->error));
183 return;
184 }
185 if (r->stage_file[0]) {
186 uv_fs_t fs;
187 int e = uv_fs_stat(NULL, &fs, r->stage_file, NULL);
188 bool valid = e == 0 && fs.statbuf.st_size > 0;
189 uv_fs_req_cleanup(&fs);
190 if (!valid) {
191 SAFE_STRNCPY(result->error, "Renderer did not create an output file", sizeof(result->error));
192 return;
193 }
194 // Hard-link publication is atomic and fails if any file/symlink already owns the destination.
195 // Staging beside the destination keeps this on the same filesystem.
196 e = uv_fs_link(NULL, &fs, r->stage_file, r->output_path, NULL);
197 uv_fs_req_cleanup(&fs);
198 if (e) {
199 SAFE_SNPRINTF(result->error, sizeof(result->error), "Cannot save without replacing an existing file: %s",
200 uv_strerror(e));
201 return;
202 }
203 SAFE_STRNCPY(result->output_path, r->output_path, sizeof(result->output_path));
204 }
205 http_render_result_t frames = {0};
206 if (result->text && result->length)
207 (void)http_render_result_add_text_frame(&frames, result->text, result->length, 0);
208 if (result->output_path[0])
209 (void)http_render_result_add_file_frame(&frames, result->output_path, 0);
210 result->frames = frames.frames;
211 result->frame_count = frames.frame_count;
212}
bool valid
Definition fft.c:27
#define SAFE_SNPRINTF(buffer, buffer_size,...)
Definition common.h:507
const char * platform_getenv(const char *name)
Get an environment variable value.
asciichat_error_t http_render_result_add_file_frame(http_render_result_t *result, const char *path, uint64_t timestamp_ms)
asciichat_error_t http_render_result_add_text_frame(http_render_result_t *result, const char *text, size_t length, uint64_t timestamp_ms)
#define MCP_RENDER_ENV_CAPACITY
char output_path[PLATFORM_MAX_PATH_LENGTH]
http_render_frame_t * frames

References http_renderer_process_request_t::argv, http_renderer_process_request_t::capture_webcam, http_renderer_process_result_t::elapsed_ms, http_renderer_process_result_t::error, render_worker_t::errors, render_worker_t::failed, http_render_result_t::frame_count, http_renderer_process_result_t::frame_count, http_render_result_t::frames, http_renderer_process_result_t::frames, HTTP_RENDER_OUTPUT_FILE, HTTP_RENDER_OUTPUT_TEXT, http_render_result_add_file_frame(), http_render_result_add_text_frame(), HTTP_RENDERER_MAX_OUTPUT, http_renderer_process_result_t::length, render_worker_t::loop, MCP_RENDER_ENV_CAPACITY, render_worker_t::output, http_renderer_process_request_t::output_path, http_renderer_process_result_t::output_path, http_renderer_process_request_t::output_type, platform_getenv(), render_worker_t::process, SAFE_CALLOC, SAFE_FREE, SAFE_MALLOC, SAFE_SNPRINTF, SAFE_STRNCPY, http_renderer_process_request_t::stage_dir, http_renderer_process_request_t::stage_file, render_worker_t::started, http_renderer_process_result_t::text, http_renderer_process_request_t::timeout_ms, render_worker_t::timer, and valid.

◆ http_renderer_process_result_destroy()

void http_renderer_process_result_destroy ( http_renderer_process_result_t * result)

Definition at line 214 of file http_renderer_process.c.

214 {
215 if (!result)
216 return;
217 for (size_t i = 0; i < result->frame_count; ++i)
218 SAFE_FREE(result->frames[i].text);
219 SAFE_FREE(result->frames);
220 SAFE_FREE(result->text);
221 memset(result, 0, sizeof(*result));
222}

References http_renderer_process_result_t::frame_count, http_renderer_process_result_t::frames, SAFE_FREE, http_render_frame_t::text, and http_renderer_process_result_t::text.

◆ http_renderer_reap()

void http_renderer_reap ( http_renderer_t * renderer,
bool all )

Definition at line 171 of file http_renderer.c.

171 {
172 if (!renderer)
173 return;
174 for (;;) {
175 mutex_lock(&renderer->lock);
176 http_renderer_job_t **link = &renderer->jobs;
177 http_renderer_job_t *job = NULL;
178 while (*link) {
179 if (all || ((*link)->user_data == NULL && http_renderer_job_is_done(*link))) {
180 job = *link;
181 *link = job->next;
182 --renderer->active_jobs;
183 break;
184 }
185 link = &(*link)->next;
186 }
187 mutex_unlock(&renderer->lock);
188 if (!job)
189 return;
192 SAFE_FREE(job);
193 }
194}
void http_render_result_destroy(http_render_result_t *result)
bool http_renderer_job_is_done(const http_renderer_job_t *job)
struct http_renderer_job http_renderer_job_t
http_render_result_t result
struct http_renderer_job * next
http_renderer_job_t * jobs
unsigned active_jobs

References http_renderer::active_jobs, http_render_result_destroy(), http_renderer_job_is_done(), http_renderer_job_join(), http_renderer::jobs, http_renderer::lock, http_renderer_job::next, http_renderer_job::result, and SAFE_FREE.

Referenced by http_renderer_destroy().

◆ http_renderer_render_file()

asciichat_error_t http_renderer_render_file ( const char * path,
const http_render_request_t * request,
http_render_result_t * result,
atomic_t * cancelled )

Definition at line 335 of file http_renderer.c.

336 {
337 if (!path || !request || !result)
338 return SET_ERRNO(ERROR_INVALID_PARAM, "HTTP render requires a source path, request, and result");
339 memset(result, 0, sizeof(*result));
340 uint64_t started = time_get_ns();
341 ffmpeg_decoder_t *decoder = ffmpeg_decoder_create_interruptible(path, http_renderer_cancel_requested, cancelled);
342 if (!decoder)
343 return SET_ERRNO(ERROR_MEDIA_INIT, "Cannot decode uploaded image");
345 ffmpeg_decoder_destroy(decoder);
346 return SET_ERRNO(ERROR_MEDIA_INIT, "Cannot start image decoder");
347 }
348 image_t *image = NULL;
349 uint64_t deadline = started + (request->timeout_ms ? request->timeout_ms : 10000) * NS_PER_MS_INT;
350 for (;;) {
351 if (cancelled && atomic_load_bool(cancelled)) {
352 ffmpeg_decoder_destroy(decoder);
353 result->cancelled = true;
354 return SET_ERRNO(ERROR_INVALID_STATE, "HTTP image render cancelled");
355 }
356 asciichat_error_context_t failure = {0};
357 ffmpeg_frame_status_t status = ffmpeg_decoder_poll_video(decoder, &image, &failure);
358 if (status == FFMPEG_FRAME_READY)
359 break;
360 if (status == FFMPEG_FRAME_FAILED || status == FFMPEG_FRAME_EOF) {
361 errno_import(&failure);
362 ffmpeg_decoder_destroy(decoder);
363 return SET_ERRNO(ERROR_MEDIA_INIT, "Uploaded data does not contain a decodable image");
364 }
365 if (time_get_ns() >= deadline) {
366 ffmpeg_decoder_destroy(decoder);
367 return SET_ERRNO(ERROR_MEDIA_DECODE, "Image decoding timed out");
368 }
370 }
371 if (!image) {
372 ffmpeg_decoder_destroy(decoder);
373 return SET_ERRNO(ERROR_MEDIA_INIT, "Uploaded data does not contain a decodable image");
374 }
375 if (image->w > 8192 || image->h > 8192) {
376 image_destroy(image);
377 ffmpeg_decoder_destroy(decoder);
378 return SET_ERRNO(ERROR_BUFFER_OVERFLOW, "Decoded image dimensions exceed the configured limit");
379 }
380 size_t total = 0;
381 size_t limit = request->max_output_bytes ? request->max_output_bytes : 8u * 1024u * 1024u;
382 for (;;) {
383 if (image->w > 8192 || image->h > 8192) {
384 image_destroy(image);
385 ffmpeg_decoder_destroy(decoder);
386 return SET_ERRNO(ERROR_BUFFER_OVERFLOW, "Decoded image dimensions exceed the configured limit");
387 }
388 char *ascii = http_renderer_render_image(image, request);
389 image_destroy(image);
390 if (!ascii) {
391 ffmpeg_decoder_destroy(decoder);
392 return SET_ERRNO(ERROR_MEDIA_INIT, "Cannot convert image to ASCII");
393 }
394 size_t length = strlen(ascii);
395 if (length > limit - (total > limit ? limit : total)) {
396 free(ascii);
397 ffmpeg_decoder_destroy(decoder);
398 return SET_ERRNO(ERROR_BUFFER_OVERFLOW, "Rendered output exceeds the configured limit");
399 }
400 if (http_render_result_add_text_frame(result, ascii, length, (time_get_ns() - started) / 1000000) != ASCIICHAT_OK) {
401 free(ascii);
402 ffmpeg_decoder_destroy(decoder);
403 return SET_ERRNO(ERROR_BUFFER_OVERFLOW, "Cannot store rendered frame");
404 }
405 if (!result->ascii) {
406 result->ascii = SAFE_MALLOC(length + 1, char *);
407 SAFE_MEMCPY(result->ascii, length + 1, ascii, length + 1);
408 result->ascii_length = length;
409 }
410 total += length;
411 free(ascii);
413 break;
414 if (cancelled && atomic_load_bool(cancelled)) {
415 result->cancelled = true;
416 ffmpeg_decoder_destroy(decoder);
417 return SET_ERRNO(ERROR_INVALID_STATE, "HTTP image render cancelled");
418 }
419 asciichat_error_context_t failure = {0};
420 image = NULL;
421 ffmpeg_frame_status_t status = ffmpeg_decoder_poll_video(decoder, &image, &failure);
422 if (status == FFMPEG_FRAME_EOF)
423 break;
424 if (status == FFMPEG_FRAME_FAILED) {
425 ffmpeg_decoder_destroy(decoder);
426 return SET_ERRNO(ERROR_MEDIA_INIT, "Video frame decoding failed");
427 }
428 while (status == FFMPEG_FRAME_WAITING) {
429 if (time_get_ns() >= deadline) {
430 ffmpeg_decoder_destroy(decoder);
431 return SET_ERRNO(ERROR_MEDIA_DECODE, "Image decoding timed out");
432 }
434 status = ffmpeg_decoder_poll_video(decoder, &image, &failure);
435 }
436 if (!image)
437 break;
438 }
439 ffmpeg_decoder_destroy(decoder);
440 result->elapsed_ms = (time_get_ns() - started) / 1000000;
441 return result->frame_count ? ASCIICHAT_OK : SET_ERRNO(ERROR_MEDIA_INIT, "Renderer returned no frames");
442}
asciichat_error_t errno_import(const asciichat_error_context_t *cause)
ffmpeg_decoder_t * ffmpeg_decoder_create_interruptible(const char *path, bool(*cancel)(void *), void *data)
unsigned long long uint64_t
Definition common.h:63
@ ERROR_MEDIA_DECODE
Definition error_codes.h:67
@ ERROR_MEDIA_INIT
Definition error_codes.h:65
@ ERROR_BUFFER_OVERFLOW
ffmpeg_frame_status_t
Decode next video frame.
asciichat_error_t ffmpeg_decoder_start_prefetch(ffmpeg_decoder_t *decoder)
Start the background frame prefetching thread.
void ffmpeg_decoder_destroy(ffmpeg_decoder_t *decoder)
Destroy FFmpeg decoder and free resources.
ffmpeg_frame_status_t ffmpeg_decoder_poll_video(ffmpeg_decoder_t *decoder, image_t **frame, asciichat_error_context_t *failure)
@ FFMPEG_FRAME_READY
@ FFMPEG_FRAME_WAITING
@ FFMPEG_FRAME_FAILED
@ FFMPEG_FRAME_EOF
#define NS_PER_MS_INT
Definition time.h:168
void platform_sleep_ms(unsigned int ms)
Sleep for a specified number of milliseconds.
void image_destroy(image_t *image)
Destroy an image allocated with image_new().
#define HTTP_RENDERER_MAX_FRAMES
FFmpeg decoder state for video and audio decoding.
SIMD-friendly storage for one RGB pixel. r, g, and b hold the color channels and padding keeps the la...
int w
Image width in pixels (must be > 0).
int h
Image height in pixels (must be > 0).

References http_render_result_t::ascii, http_render_result_t::ascii_length, ASCIICHAT_OK, http_render_result_t::cancelled, http_render_result_t::elapsed_ms, errno_import(), ERROR_BUFFER_OVERFLOW, ERROR_INVALID_PARAM, ERROR_INVALID_STATE, ERROR_MEDIA_DECODE, ERROR_MEDIA_INIT, ffmpeg_decoder_create_interruptible(), ffmpeg_decoder_destroy(), ffmpeg_decoder_poll_video(), ffmpeg_decoder_start_prefetch(), FFMPEG_FRAME_EOF, FFMPEG_FRAME_FAILED, FFMPEG_FRAME_READY, FFMPEG_FRAME_WAITING, http_render_result_t::frame_count, image_t::h, http_render_result_add_text_frame(), HTTP_RENDERER_MAX_FRAMES, image_destroy(), http_render_request_t::max_output_bytes, NS_PER_MS_INT, platform_sleep_ms(), SAFE_MALLOC, SAFE_MEMCPY, SET_ERRNO, http_render_request_t::timeout_ms, and image_t::w.

◆ http_renderer_stop()

void http_renderer_stop ( http_renderer_t * renderer)

Definition at line 196 of file http_renderer.c.

196 {
197 if (!renderer)
198 return;
199 mutex_lock(&renderer->lock);
200 renderer->stopping = true;
201 for (http_renderer_job_t *job = renderer->jobs; job; job = job->next)
203 mutex_unlock(&renderer->lock);
204}

References http_renderer_job_cancel(), http_renderer::jobs, http_renderer::lock, http_renderer_job::next, and http_renderer::stopping.

Referenced by http_renderer_destroy().

◆ http_renderer_submit()

asciichat_error_t http_renderer_submit ( http_renderer_t * renderer,
const http_render_request_t * request,
void * user_data,
http_renderer_job_t ** out_job )

Definition at line 126 of file http_renderer.c.

127 {
128 if (!renderer || !request || !out_job)
129 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid HTTP renderer submission");
130 *out_job = NULL;
131 mutex_lock(&renderer->lock);
132 if (renderer->stopping || renderer->active_jobs >= renderer->config.max_jobs) {
133 mutex_unlock(&renderer->lock);
134 return SET_ERRNO(ERROR_BUFFER_FULL, "HTTP renderer has no available job capacity");
135 }
136 http_renderer_job_t *job = SAFE_CALLOC(1, sizeof(*job), http_renderer_job_t *);
137 job->renderer = renderer;
138 job->request = *request;
139 job->user_data = user_data;
140 if (!job->request.timeout_ms)
141 job->request.timeout_ms = renderer->config.timeout_ms;
142 if (!job->request.max_output_bytes)
144 atomic_store_bool(&job->cancelled, false);
145 atomic_store_bool(&job->done, false);
146 if (platform_thread_create(&job->thread, "http-render", http_renderer_job_main, job)) {
147 SAFE_FREE(job);
148 mutex_unlock(&renderer->lock);
149 return SET_ERRNO(ERROR_THREAD, "Cannot start HTTP renderer job");
150 }
151 job->started = true;
152 job->next = renderer->jobs;
153 renderer->jobs = job;
154 ++renderer->active_jobs;
155 mutex_unlock(&renderer->lock);
156 *out_job = job;
157 return ASCIICHAT_OK;
158}
@ ERROR_BUFFER_FULL
http_render_request_t request

References http_renderer::active_jobs, ASCIICHAT_OK, http_renderer_job::cancelled, http_renderer::config, http_renderer_job::done, ERROR_BUFFER_FULL, ERROR_INVALID_PARAM, ERROR_THREAD, http_renderer::jobs, http_renderer::lock, http_renderer_config_t::max_jobs, http_render_request_t::max_output_bytes, http_renderer_config_t::max_output_bytes, http_renderer_job::next, http_renderer_job::renderer, http_renderer_job::request, SAFE_CALLOC, SAFE_FREE, SET_ERRNO, http_renderer_job::started, http_renderer::stopping, http_renderer_job::thread, http_render_request_t::timeout_ms, http_renderer_config_t::timeout_ms, and http_renderer_job::user_data.