DungeonCrawl
Loading...
Searching...
No Matches
media_output_handler.c
Go to the documentation of this file.
1
6
8#include "../../io_handler.h" // Include this to access global nc and stdplane
9#include "../common/output_handler.h"// For get_screen_dimensions and render_frame
10#include "media_files.h"
11
12#include <notcurses/notcurses.h>
13#include <stdbool.h>
14#include <stdlib.h>
15#include <string.h>
16
17
18/* =========================================================================
19 * CONSTANTS AND DEFINITIONS
20 * ========================================================================= */
21
22// Global variables
23#define MAX_RESOURCES 128
24static loaded_visual_t resources[MAX_RESOURCES];// Array to hold loaded visuals
25static int resource_count = 0;
26
27/* =========================================================================
28 * FORWARD DECLARATIONS
29 * ========================================================================= */
30
31static void free_media_resource(loaded_visual_t* resource);
32
33/* =========================================================================
34* INITIALIZATION AND CLEANUP
35* ========================================================================= */
36
38 if (!nc) {
39 log_msg(ERROR, "media_output", "Null Notcurses instance provided");
40 return false;
41 }
42
43 if (!stdplane) {
44 log_msg(ERROR, "media_output", "Null standard plane provided");
45 return false;
46 }
47
48 // Initialize resources array
49 memset(resources, 0, sizeof(resources));
50 resource_count = 0;
51
52 // Ensure the media directory exists
53 //TODO: implement directory_exists function
54 /*if (!directory_exists(MEDIA_PATH)) {
55 log_msg(ERROR, "media_output", "Failed to ensure media directory exists");
56 }*/
57
58 return true;
59}
60
61// Clean up the media handler
63 media_cleanup();
64}
65
66void media_cleanup(void) {
67 // Free all loaded resources
68 for (int i = 0; i < resource_count; i++) {
69 if (resources[i].is_loaded) {
70 free_media_resource(&resources[i]);
71 }
72 }
73 resource_count = 0;
74}
75
77 free_media_resource(media);
78}
79
80// Refresh media display
82 //TODO: fix
83
84 // Force a redraw of the terminal with error checking
85 bool result = notcurses_render(nc);
86
87 if (!result) {
88 log_msg(ERROR, "media_output", "Failed to refresh media display");
89 }
90
91 return result;
92}
93
94/* =========================================================================
95 * LOAD AND READY FUNCTIONS
96 * ========================================================================= */
97
98// Load a media resource
99loaded_visual_t* load_media(const char* filename) {
100 // Validate parameters
101 if (!filename) {
102 log_msg(ERROR, "media_output", "Invalid filename for load_media");
103 return NULL;
104 }
105
106 media_type_t media_type = get_file_type(filename);
107
108 // Build the full file path
109 char* filepath = build_filepath(filename, media_type);
110 if (!filepath) {
111 return NULL;
112 }
113
114 // Check if we already have this resource loaded
115 for (int i = 0; i < resource_count; i++) {
116 if (strcmp(resources[i].path, filepath) == 0) {
117 free(filepath);
118 return &resources[i];// Return existing resource
119 }
120 }
121
122 // Make sure we have space for a new resource
123 if (resource_count >= MAX_RESOURCES) {
124 // Could implement a LRU cache here, but for simplicity, just fail
125 free(filepath);
126 return NULL;
127 }
128
129 // Load the visual from file with detailed error handling
130 struct ncvisual* visual = ncvisual_from_file(filepath);
131 if (!visual) {
132 log_msg(ERROR, "media_output", "Failed to load media: %s (check if file exists)", filepath);
133 return NULL;
134 }
135
136 // Allocate a new resource
137 loaded_visual_t* resource = &resources[resource_count];
138 if (!resource) {
139 log_msg(ERROR, "media_output", "Failed to allocate memory for loaded visual");
140 ncvisual_destroy(visual);
141 return NULL;
142 }
143 resource_count++;
144
145 // Initialize the structure
146 memset(resource, 0, sizeof(loaded_visual_t));
147 resource->visual = visual;
148 resource->plane = NULL;// Will be created when displayed
149
150 // Set resource properties
151 resource->media_type = media_type;
152 resource->is_loaded = true;
153 resource->is_playing = false;
154 resource->path = strdup(filepath);
155 if (!resource->path) {
156 log_msg(ERROR, "media_output", "Failed to store path");
157 ncvisual_destroy(visual);
158 resource_count--;
159 free(filepath);
160 return NULL;
161 }
162
163 // Get visual dimensions
164 struct ncvisual_options vopts = {0};
165 struct ncvgeom geom = {0};
166 int geo_ret = ncvisual_geom(nc, visual, &vopts, &geom);
167
168 if (geo_ret) {
169 log_msg(WARNING, "media_output", "Failed to get visual geometry, using default dimensions");
170 // Use defaults if we can't get the dimensions
171 resource->og_width = 20; // Default width
172 resource->og_height = 20;// Default height
173 } else {
174 // Store dimensions with detailed logging
175 if (geom.pixx <= 0 || geom.pixy <= 0) {
176 log_msg(WARNING, "media_output", "Invalid media dimensions: %dx%d, using defaults",
177 geom.pixx, geom.pixy);
178 resource->og_width = 20; // Default width
179 resource->og_height = 20;// Default height
180 } else {
181 resource->og_width = geom.pixx; // Width in pixels
182 resource->og_height = geom.pixy;// Height in pixels
183 }
184 }
185
186 // set attributes based on media media_type
187 switch (media_type) {
188 case MEDIA_PNG:
189 resource->frames = 1;// Single frame for static images
190 break;
191 case MEDIA_GIF:
192 // Count frames by decoding the whole GIF
193 resource->frames = 0;
194 while (ncvisual_decode(visual) != 1) {
195 resource->frames++;
196 }
197 // Reset to first frame
198 ncvisual_destroy(visual);
199 resource->visual = ncvisual_from_file(filepath);
200 if (!resource->visual) {
201 log_msg(ERROR, "media_output", "Failed to reload GIF after frame counting");
202 free(resource->path);
203 resource_count--;
204 free(filepath);
205 return NULL;
206 }
207 break;
208 case MEDIA_MP4:
209 // MP4 handling would go here
210 log_msg(ERROR, "media_output", "MP4 loading not implemented yet");
211 ncvisual_destroy(visual);
212 resource_count--;
213 free(resource->path);
214 break;
215 default:
216 log_msg(ERROR, "media_output", "Unsupported media media_type");
217 ncvisual_destroy(visual);
218 free(filepath);
219 return NULL;
220 }
221
222 free(filepath);
223 return resource;
224}
225
226loaded_visual_t* ready_media(const char* filename, int x, int y, int height, int width, scale_type_t scale_type) {
227 // Validate parameters
228 if (!filename) {
229 log_msg(ERROR, "media_output", "Null filename for display_media");
230 return NULL;
231 }
232
233 // Allow height=0 for fullscreen and preserving aspect ratio
234 if (height < 0) {
235 log_msg(ERROR, "media_output", "Invalid height (%d) for display_media", height);
236 return NULL;
237 }
238
239 // Load the media
240 loaded_visual_t* resource = load_media(filename);
241 if (!resource || !resource->is_loaded) {
242 log_msg(ERROR, "media_output", "Failed to load media: %s", filename);
243 return NULL;
244 }
245
246 // Set up scaling
247 setup_scaling_options(resource, scale_type, width, height);
248
249 // Set coordinates
250 resource->options.y = y;
251 resource->options.x = x;
252
253 // Clean up existing plane if needed
254 if (resource->plane) {
255 ncplane_erase(resource->plane);// Clear contents first
256 notcurses_render(nc); // Update display
257 ncplane_destroy(resource->plane);
258 resource->plane = NULL;
259 }
260
261 return resource;
262}
263
264/* =========================================================================
265 * RESOURCE MANAGEMENT FUNCTIONS
266 * ========================================================================= */
267
268// Unload a specific media resource
269bool unload_media(const char* filename) {
270 if (!filename) {
271 return false;
272 }
273
274 // Look for the resource in our cache
275 for (int i = 0; i < resource_count; i++) {
276 if (strcmp(resources[i].path, filename) == 0) {
277 free_media_resource(&resources[i]);
278
279 // Shift other resources if this isn't the last one
280 if (i < resource_count - 1) {
281 memmove(&resources[i], &resources[i + 1], (resource_count - i - 1) * sizeof(loaded_visual_t));
282 }
283
284 resource_count--;
285 return true;
286 }
287 }
288
289 return false;
290}
291
292// Preload a media file into memory
293/*bool preload_media(const char* filename) {
294 // coming soon
295 return false;
296}*/
297
298// Reload media after terminal resize
299/*bool reload_media_after_resize(void) {
300 // coming soon
301 return false;
302}*/
303
304/* =========================================================================
305 * REENDERING AND DISPLAY FUNCTIONS
306 * ========================================================================= */
307
308/*bool media_output_render(loaded_visual_t* media) {
309 //coming soon...
310 return false;
311}*/
312
313/*bool media_output_render_next_frame(loaded_visual_t* media) {
314 //coming soon...
315 return false;
316}*/
317
318/*bool media_output_can_display_images(void) {
319 //coming soon...
320 return false;
321}*/
322
323/*bool media_output_can_display_videos(void) {
324 //coming soon...
325 return false;
326}*/
327
328/* =========================================================================
329 * SCALING FUNCTIONS
330 * ========================================================================= */
331
332// Helper function to set up scaling options
333void setup_scaling_options(loaded_visual_t* visual, scale_type_t scale_type,
334 int target_width, int target_height) {
335 // Clear options first
336 memset(&visual->options, 0, sizeof(visual->options));
337
338 // Apply scaling based on type
339 switch (scale_type) {
340 case SCALE_PRESERVE:
341 // Scale preserving aspect ratio
342 visual->options.scaling = NCSCALE_SCALE;
343 // When using NCSCALE_SCALE, a zero value for lenx/leny means auto-scale
344 visual->options.leny = target_height > 0 ? target_height : 0;
345 visual->options.lenx = target_width > 0 ? target_width : 0;
346 break;
347
348 case SCALE_STRETCH:
349 // Stretch to exact dimensions
350 if (target_width > 0 && target_height > 0) {
351 visual->options.scaling = NCSCALE_STRETCH;
352 visual->options.leny = target_height;
353 visual->options.lenx = target_width;
354 } else {
355 // Fall back to no scaling if dimensions are invalid
356 visual->options.scaling = NCSCALE_NONE;
357 }
358 break;
359
360 case SCALE_CELL:
361 // Scale to fit in a single cell
362 visual->options.scaling = NCSCALE_SCALE;
363 visual->options.leny = 1;
364 visual->options.lenx = 1;
365 break;
366
367 case SCALE_FULLSCREEN:
368 // Scale to fit the entire terminal
369 visual->options.scaling = NCSCALE_STRETCH;
370 // Get terminal dimensions
371 int screen_width, screen_height;
372 if (get_screen_dimensions(&screen_width, &screen_height)) {
373 visual->options.leny = screen_height;
374 visual->options.lenx = screen_width;
375 } else {
376 // Fallback to original dimensions
377 visual->options.leny = visual->og_height;
378 visual->options.lenx = visual->og_width;
379 log_msg(WARNING, "media_output", "Could not get screen dimensions, using original size");
380 }
381 break;
382
383 case SCALE_NONE:
384 default:
385 // No scaling
386 visual->options.scaling = NCSCALE_NONE;
387 break;
388 }
389
390 // Set blitter to a reliable one
391 visual->options.blitter = NCBLIT_2x1;
392}
393
394/* =========================================================================
395 * FILE AND PATH HANDLING FUNCTIONS
396 * ========================================================================= */
397
398/*bool directory_exists(const char* path) {
399 // coming soon
400 return false;
401}*/
402
403media_type_t get_file_type(const char* filename) {
404 if (is_file_extension(filename, ".png")) {
405 return MEDIA_PNG;
406 } else if (is_file_extension(filename, ".gif")) {
407 return MEDIA_GIF;
408 } else if (is_file_extension(filename, ".mp4")) {
409 return MEDIA_MP4;
410 } else {
411 log_msg(ERROR, "media_output", "Unsupported file extension for: %s", filename);
412 return MEDIA_UNSUPPORTED;
413 }
414}
415
416// Check if a filename has a specific extension
417bool is_file_extension(const char* filename, const char* extension) {
418 if (!filename || !extension) {
419 return false;
420 }
421
422 size_t filename_len = strlen(filename);
423 size_t ext_len = strlen(extension);
424
425 if (filename_len < ext_len) {
426 return false;
427 }
428
429 return strncasecmp(filename + filename_len - ext_len, extension, ext_len) == 0;
430}
431
432// Build a full file path
433char* build_filepath(const char* filename, media_type_t media_type) {
434 // Allocate memory for the path
435 char* filepath = malloc(MAX_PATH_LEN);
436 if (!filepath) {
437 log_msg(ERROR, "media_output", "Failed to allocate memory for file path");
438 return NULL;
439 }
440
441 // Build the path based on the media media_type
442 const char* subdir = "";
443 switch (media_type) {
444 case MEDIA_PNG:
445 subdir = "png/";
446 break;
447 case MEDIA_GIF:
448 subdir = "gif/";
449 break;
450 case MEDIA_MP4:
451 subdir = "mp4/";
452 break;
453 default:
454 break;
455 }
456
457 // Construct the full path
458 snprintf(filepath, MAX_PATH_LEN, "%s%s%s", MEDIA_PATH, subdir, filename);
459
460 return filepath;
461}
462
463/* =========================================================================
464 * INTERNAL UTILITY FUNCTIONS
465 * ========================================================================= */
466
467// Free a media resource
473static void free_media_resource(loaded_visual_t* resource) {
474 if (!resource || !resource->is_loaded) {
475 return;
476 }
477
478 // Stop any animation first
479 resource->is_playing = false;
480
481 // Clean up the plane properly - first erase its contents
482 if (resource->plane) {
483 // Erase the plane contents with transparency
484 ncplane_erase(resource->plane);
485
486 // Make sure the erase is visible
487 notcurses_render(nc);// Use direct render for reliability
488
489 // Then destroy the plane
490 ncplane_destroy(resource->plane);
491 resource->plane = NULL;
492 }
493
494 // Free the visual resource
495 if (resource->visual) {
496 ncvisual_destroy(resource->visual);
497 resource->visual = NULL;
498 }
499
500 // Free path if allocated
501 if (resource->path) {
502 free(resource->path);
503 resource->path = NULL;
504 }
505
506 // Mark as not loaded
507 resource->is_loaded = false;
508
509 // Resource is part of static array, no need to free the structure itself
510}
Exposes functions for the IO-Handler.
void log_msg(const log_level_t level, const char *module, const char *format,...)
Logs a formatted message with a specified log level and module.
Definition logger.c:246
Header file for logging functionality of the game.
Defines the path and metadata to media files.
media_type_t get_file_type(const char *filename)
Get the media type based on the file extension.
char * build_filepath(const char *filename, media_type_t media_type)
Build a file path for the specified media type.
void destroy_media(loaded_visual_t *media)
Frees the memory associated with a loaded media resource.
void shutdown_media_output(void)
Shutdown the media output handler.
bool refresh_media_display(void)
Force a refresh of the media display.
bool unload_media(const char *filename)
Unload a specific media resource.
bool init_media_output(void)
Initialize the media output handler.
loaded_visual_t * ready_media(const char *filename, int x, int y, int height, int width, scale_type_t scale_type)
Prepares a media resource for display.
void setup_scaling_options(loaded_visual_t *visual, scale_type_t scale_type, int target_width, int target_height)
Enable different scaling options for the loaded visual.
bool is_file_extension(const char *filename, const char *extension)
Check if a filename has a specific extension.
loaded_visual_t * load_media(const char *filename)
load a media resource from file they can be different types (PNG, GIF, MP4)
Exposes functions for the media output handler.
struct loaded_visual_s loaded_visual_t
Structure to represent a loaded visual.
bool get_screen_dimensions(int *width, int *height)
Get the dimensions of the standard plane.
Exposes functions for outputting to the console.