1 /*
2 * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
3 * Copyright (C) 2005-2015, Anthony Minessale II <anthm@freeswitch.org>
4 *
5 * Version: MPL 1.1
6 *
7 * The contents of this file are subject to the Mozilla Public License Version
8 * 1.1 (the "License"); you may not use this file except in compliance with
9 * the License. You may obtain a copy of the License at
10 * http://www.mozilla.org/MPL/
11 *
12 * Software distributed under the License is distributed on an "AS IS" basis,
13 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
14 * for the specific language governing rights and limitations under the
15 * License.
16 *
17 * The Original Code is FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
18 *
19 * The Initial Developer of the Original Code is
20 * Seven Du <dujinfang@gmail.com>
21 * Portions created by the Initial Developer are Copyright (C)
22 * the Initial Developer. All Rights Reserved.
23 *
24 * Contributor(s):
25 *
26 * Seven Du <dujinfang@gmail.com>
27 *
28 * mod_imagick -- play pdf/gif as video
29 *
30 * use the magick-core API since the magick-wand API is more different on different versions
31 * http://www.imagemagick.org/script/magick-core.php
32 *
33 */
34
35
36 #include <switch.h>
37
38 #if defined(__clang__)
39 /* the imagemagick header files are very badly broken on clang. They really should be fixing this, in the mean time, this dirty hack works */
40 # define __attribute__(x) /*nothing*/
41 #define restrict restrict
42 #endif
43
44 #ifndef MAGICKCORE_QUANTUM_DEPTH
45 #define MAGICKCORE_QUANTUM_DEPTH 8
46 #endif
47
48 #ifndef MAGICKCORE_HDRI_ENABLE
49 #define MAGICKCORE_HDRI_ENABLE 0
50 #endif
51
52 #ifdef HAVE_MAGIC7
53 #include <MagickCore/MagickCore.h>
54 #else
55 #include <magick/MagickCore.h>
56 #endif
57
58 #ifdef _MSC_VER
59 // Disable MSVC warnings that suggest making code non-portable.
60 #pragma warning(disable : 4996)
61 #endif
62
63 SWITCH_MODULE_LOAD_FUNCTION(mod_imagick_load);
64 SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_imagick_shutdown);
65 SWITCH_MODULE_DEFINITION(mod_imagick, mod_imagick_load, mod_imagick_shutdown, NULL);
66
67 typedef enum pdf_loading_state_s {
68 PLS_LOADING,
69 PLS_BREAK,
70 PLS_DONE
71 } pdf_loading_state_t;
72
73 struct pdf_file_context {
74 switch_memory_pool_t *pool;
75 switch_mutex_t *mutex;
76 switch_image_t *img;
77 int reads;
78 int sent;
79 int max;
80 int samples;
81 int same_page;
82 int pagenumber;
83 int pagecount;
84 ImageInfo *image_info;
85 Image *images;
86 ExceptionInfo *exception;
87 int autoplay;
88 const char *path;
89 int lazy;
90 char *lazy_cookie;
91 pdf_loading_state_t loading_state;
92 switch_time_t next_play_time;
93 };
94
95 typedef struct pdf_file_context pdf_file_context_t;
96
open_pdf_thread_run(switch_thread_t * thread,void * obj)97 static void *SWITCH_THREAD_FUNC open_pdf_thread_run(switch_thread_t *thread, void *obj)
98 {
99 pdf_file_context_t *context = (pdf_file_context_t *)obj;
100 int pagenumber = context->lazy;
101 char path[1024];
102
103 while (context->loading_state == PLS_LOADING) {
104 Image *tmp_images;
105 switch_snprintf(path, sizeof(path), "%s[%d]", context->path, pagenumber);
106 switch_set_string(context->image_info->filename, path);
107
108 #ifdef HAVE_MAGIC7
109 if ((tmp_images = ReadImages(context->image_info, path, context->exception))) {
110 #else
111 if ((tmp_images = ReadImages(context->image_info, context->exception))) {
112 #endif
113
114 pagenumber++;
115 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "%s page %d loaded\n", context->path, pagenumber);
116 AppendImageToList(&context->images, tmp_images);
117 context->pagecount = pagenumber;
118 } else {
119 switch_event_t *event = NULL;
120
121 if (switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, "imagick::info") == SWITCH_STATUS_SUCCESS) {
122 switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "type", "loaded");
123 switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "filename", context->path);
124 switch_event_add_header(event, SWITCH_STACK_BOTTOM, "pagecount", "%d", context->pagecount);
125 if (context->lazy_cookie) {
126 switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "cookie", context->lazy_cookie);
127 }
128 switch_event_fire(&event);
129 }
130
131 break;
132 }
133 }
134
135 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "read file: %s %s, pagecount: %d\n",
136 context->path, context->loading_state == PLS_BREAK ? "break" : "done", pagenumber);
137
138 switch_mutex_lock(context->mutex);
139 context->loading_state = PLS_DONE;
140 switch_mutex_unlock(context->mutex);
141
142 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Read Images Thread Ended.\n");
143 return NULL;
144 }
145
146 static switch_status_t imagick_file_open(switch_file_handle_t *handle, const char *path)
147 {
148 pdf_file_context_t *context;
149 char *ext;
150 char range_path[1024];
151
152 if ((ext = strrchr((char *)path, '.')) == 0) {
153 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Invalid Format\n");
154 return SWITCH_STATUS_GENERR;
155 }
156
157 ext++;
158 /*
159 Prevents playing files to a conference like a slide show using conference_play api.
160 if (!switch_test_flag(handle, SWITCH_FILE_FLAG_VIDEO)) {
161 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Video only\n");
162 return SWITCH_STATUS_GENERR;
163 }
164 */
165 if ((context = (pdf_file_context_t *)switch_core_alloc(handle->memory_pool, sizeof(pdf_file_context_t))) == 0) {
166 return SWITCH_STATUS_MEMERR;
167 }
168
169 memset(context, 0, sizeof(pdf_file_context_t));
170
171 if (switch_test_flag(handle, SWITCH_FILE_FLAG_WRITE)) {
172 return SWITCH_STATUS_GENERR;
173 }
174
175 if (ext && !strcmp(ext, "gif")) {
176 context->autoplay = 1;
177 }
178
179 context->max = 10000;
180
181 context->exception = AcquireExceptionInfo();
182 context->image_info = AcquireImageInfo();
183 context->path = switch_core_strdup(handle->memory_pool, path);
184
185 if (handle->params) {
186 const char *max = switch_event_get_header(handle->params, "img_ms");
187 const char *autoplay = switch_event_get_header(handle->params, "autoplay");
188 const char *density = switch_event_get_header(handle->params, "density");
189 const char *quality = switch_event_get_header(handle->params, "quality");
190 const char *lazy = switch_event_get_header(handle->params, "lazy");
191 const char *lazy_cookie = switch_event_get_header(handle->params, "cookie");
192 int tmp;
193
194 if (max) {
195 tmp = atol(max);
196 context->max = tmp;
197 }
198
199 if (autoplay) {
200 context->autoplay = atoi(autoplay);
201 }
202
203 if (density) {
204 context->image_info->density = strdup(density);
205 }
206
207 if (quality) {
208 tmp = atoi(quality);
209
210 if (tmp > 0) context->image_info->quality = tmp;
211 }
212
213 if (lazy) {
214 int tmp = atoi(lazy);
215
216 if (tmp >= 0) {
217 context->lazy = tmp;
218 } else {
219 context->lazy = 1;
220 }
221 }
222
223 if (lazy_cookie) {
224 context->lazy_cookie = switch_core_strdup(handle->memory_pool, lazy_cookie);
225 }
226 }
227
228 if (context->lazy) {
229 switch_snprintf(range_path, sizeof(range_path), "%s[0-%d]", path, context->lazy - 1);
230 switch_set_string(context->image_info->filename, range_path);
231 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "loading first %d page%s\n", context->lazy, context->lazy > 1 ? "s" : "");
232 } else {
233 switch_set_string(context->image_info->filename, path);
234 }
235
236 #ifdef HAVE_MAGIC7
237 context->images = ReadImages(context->image_info, context->lazy ? range_path : path, context->exception);
238 #else
239 context->images = ReadImages(context->image_info, context->exception);
240 #endif
241
242 if (context->exception->severity != UndefinedException) {
243 CatchException(context->exception);
244 }
245
246 if (context->images == (Image *)NULL) {
247 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Fail to read file: %s\n", path);
248 return SWITCH_STATUS_GENERR;
249 }
250
251 context->pagecount = GetImageListLength(context->images);
252 handle->duration = context->pagecount;
253
254 if (context->max) {
255 context->samples = (handle->samplerate / 1000) * context->max;
256 }
257
258 handle->format = 0;
259 handle->sections = 0;
260 handle->seekable = 1;
261 handle->speed = 0;
262 handle->pos = 0;
263 handle->private_info = context;
264 context->pool = handle->memory_pool;
265
266 if (context->lazy) {
267 switch_thread_t *thread;
268 switch_threadattr_t *thd_attr = NULL;
269
270 switch_mutex_init(&context->mutex, SWITCH_MUTEX_NESTED, context->pool);
271 context->loading_state = PLS_LOADING;
272 switch_thread_create(&thread, thd_attr, open_pdf_thread_run, context, context->pool);
273 }
274
275 if (context->lazy) {
276 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Opening File %s, read the first %d page(s)", path, context->lazy);
277 } else {
278 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Opening File %s", path);
279 }
280
281 return SWITCH_STATUS_SUCCESS;
282 }
283
284 static switch_status_t imagick_file_close(switch_file_handle_t *handle)
285 {
286 pdf_file_context_t *context = (pdf_file_context_t *)handle->private_info;
287
288 if (context->lazy) {
289 switch_mutex_lock(context->mutex);
290 if (context->loading_state == PLS_LOADING) context->loading_state = PLS_BREAK;
291 switch_mutex_unlock(context->mutex);
292
293 while (context->loading_state != PLS_DONE) {
294 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "waiting for pdf loading thread done, loading_state: %d\n", context->loading_state);
295 switch_yield(1000000);
296 switch_cond_next();
297 }
298 }
299
300 switch_img_free(&context->img);
301
302 if (context->images) DestroyImageList(context->images);
303 if (context->exception) DestroyExceptionInfo(context->exception);
304 if (context->image_info) DestroyImageInfo(context->image_info);
305
306 return SWITCH_STATUS_SUCCESS;
307 }
308
309 static switch_status_t imagick_file_read(switch_file_handle_t *handle, void *data, size_t *len)
310 {
311 pdf_file_context_t *context = (pdf_file_context_t *)handle->private_info;
312
313 if (!context->images || !context->samples) {
314 return SWITCH_STATUS_FALSE;
315 }
316
317 if (context->samples > 0) {
318 if (*len >= context->samples) {
319 *len = context->samples;
320 }
321
322 context->samples -= *len;
323 }
324
325 if (!context->samples) {
326 return SWITCH_STATUS_FALSE;
327 }
328
329 memset(data, 0, *len * 2 * handle->channels);
330
331 return SWITCH_STATUS_SUCCESS;
332 }
333
334 static switch_status_t read_page(pdf_file_context_t *context)
335 {
336 switch_status_t status = SWITCH_STATUS_SUCCESS;
337 Image *image = GetImageFromList(context->images, context->pagenumber);
338 int W, H, w, h, x, y;
339 MagickBooleanType ret;
340 uint8_t *storage;
341
342 if (!image) return SWITCH_STATUS_FALSE;
343
344 W = image->page.width;
345 H = image->page.height;
346 w = image->columns;
347 h = image->rows;
348 x = image->page.x;
349 y = image->page.y;
350
351 switch_assert(W > 0 && H > 0);
352
353 #if 0
354 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG,
355 "page: %dx%d image: %dx%d pos: (%d,%d) pagenumber: %d,"
356 " delay: %" SWITCH_SIZE_T_FMT " ticks_per_second: %" SWITCH_SSIZE_T_FMT
357 " autoplay: %d\n",
358 W, H, w, h, x, y,
359 context->pagenumber, image->delay, image->ticks_per_second, context->autoplay);
360 #endif
361
362 if (context->autoplay) {
363 if (image->delay && image->ticks_per_second) {
364 context->next_play_time = switch_micro_time_now() / 1000 + image->delay * (1000 / image->ticks_per_second);
365 } else {
366 context->next_play_time = switch_micro_time_now() / 1000 + context->autoplay;
367 }
368 }
369
370 if (context->img && (context->img->d_w != W || context->img->d_h != H)) {
371 switch_img_free(&context->img);
372 }
373
374 if (!context->img) {
375 context->img = switch_img_alloc(NULL, SWITCH_IMG_FMT_I420, W, H, 0);
376 switch_assert(context->img);
377 }
378
379 if (W == w && H == h) {
380 storage = malloc(w * h * 3); switch_assert(storage);
381
382 ret = ExportImagePixels(image, 0, 0, w, h, "RGB", CharPixel, storage, context->exception);
383
384 if (ret == MagickFalse && context->exception->severity != UndefinedException) {
385 CatchException(context->exception);
386 free(storage);
387 return SWITCH_STATUS_FALSE;
388 }
389
390 switch_img_from_raw(context->img, storage, SWITCH_IMG_FMT_BGR24, w, h);
391 free(storage);
392 } else {
393 switch_image_t *img = switch_img_alloc(NULL, SWITCH_IMG_FMT_ARGB, image->columns, image->rows, 0);
394 switch_assert(img);
395
396 ret = ExportImagePixels(image, 0, 0, w, h, "ARGB", CharPixel, img->planes[SWITCH_PLANE_PACKED], context->exception);
397
398 if (ret == MagickFalse && context->exception->severity != UndefinedException) {
399 CatchException(context->exception);
400 return SWITCH_STATUS_FALSE;
401 }
402
403 switch_img_patch(context->img, img, x, y);
404 switch_img_free(&img);
405 }
406
407 return status;
408 }
409
410 static switch_status_t imagick_file_read_video(switch_file_handle_t *handle, switch_frame_t *frame, switch_video_read_flag_t flags)
411 {
412 pdf_file_context_t *context = (pdf_file_context_t *)handle->private_info;
413 switch_image_t *dup = NULL;
414 switch_status_t status;
415
416 if ((flags & SVR_CHECK)) {
417 return SWITCH_STATUS_BREAK;
418 }
419
420 if (!context->images || !context->samples) {
421 return SWITCH_STATUS_FALSE;
422 }
423
424 if (context->autoplay && context->next_play_time && (switch_micro_time_now() / 1000 > context->next_play_time)) {
425 context->pagenumber++;
426 if (context->pagenumber >= context->pagecount) context->pagenumber = 0;
427 context->same_page = 0;
428 }
429
430 if (!context->same_page) {
431 status = read_page(context);
432 if (status != SWITCH_STATUS_SUCCESS) return status;
433 context->same_page = 1;
434 }
435
436 if (!context->img) return SWITCH_STATUS_FALSE;
437
438 if ((context->reads++ % 20) == 0) {
439 switch_img_copy(context->img, &dup);
440 frame->img = dup;
441 context->sent++;
442 } else {
443 if ((flags && SVR_BLOCK)) {
444 switch_yield(5000);
445 }
446 return SWITCH_STATUS_BREAK;
447 }
448
449 return SWITCH_STATUS_SUCCESS;
450 }
451
452 static switch_status_t imagick_file_seek(switch_file_handle_t *handle, unsigned int *cur_sample, int64_t samples, int whence)
453 {
454 pdf_file_context_t *context = (pdf_file_context_t *)handle->private_info;
455 switch_status_t status = SWITCH_STATUS_SUCCESS;
456
457 int page = samples / (handle->samplerate / 1000);
458
459 if (whence == SEEK_SET) {
460 // page = page;
461 } else if (whence == SEEK_CUR) {
462 page += context->pagenumber;
463 } else if (whence == SEEK_END) {
464 page = context->pagecount - page;
465 }
466
467 if (page < 0) page = 0;
468 if (page > context->pagecount - 1) page = context->pagecount - 1;
469
470 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "seeking to sample=%" SWITCH_UINT64_T_FMT " cur_sample=%d where:=%d page=%d\n", samples, *cur_sample, whence, page);
471
472 if (page != context->pagenumber) {
473 context->pagenumber = page;
474 context->same_page = 0;
475 *cur_sample = page;
476 handle->vpos = page;
477 handle->pos = page * (handle->samplerate / 1000);
478 }
479
480 return status;
481 }
482
483 static void myErrorHandler(const ExceptionType t, const char *reason, const char *description)
484 {
485 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "%s: %s\n", reason, description);
486 }
487
488 static void myFatalErrorHandler(const ExceptionType t, const char *reason, const char *description)
489 {
490 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "%s: %s\n", reason, description);
491 }
492
493 static void myWarningHandler(const ExceptionType t, const char *reason, const char *description)
494 {
495 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "%s: %s\n", reason, description);
496 }
497
498 static char *supported_formats[SWITCH_MAX_CODECS] = { 0 };
499
500 SWITCH_MODULE_LOAD_FUNCTION(mod_imagick_load)
501 {
502 switch_file_interface_t *file_interface;
503 int i = 0;
504
505 supported_formats[i++] = (char *)"imgk";
506 supported_formats[i++] = (char *)"pdf";
507 supported_formats[i++] = (char *)"gif";
508
509 MagickCoreGenesis(NULL, MagickFalse);
510
511 SetErrorHandler(myErrorHandler);
512 SetWarningHandler(myWarningHandler);
513 SetFatalErrorHandler(myFatalErrorHandler);
514
515 /* connect my internal structure to the blank pointer passed to me */
516 *module_interface = switch_loadable_module_create_module_interface(pool, modname);
517
518 file_interface = (switch_file_interface_t *)switch_loadable_module_create_interface(*module_interface, SWITCH_FILE_INTERFACE);
519 file_interface->interface_name = modname;
520 file_interface->extens = supported_formats;
521 file_interface->file_open = imagick_file_open;
522 file_interface->file_close = imagick_file_close;
523 file_interface->file_read = imagick_file_read;
524 file_interface->file_read_video = imagick_file_read_video;
525 file_interface->file_seek = imagick_file_seek;
526
527 /* indicate that the module should continue to be loaded */
528 return SWITCH_STATUS_SUCCESS;
529 }
530
531 SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_imagick_shutdown)
532 {
533 MagickCoreTerminus();
534 return SWITCH_STATUS_SUCCESS;
535 }
536
537 /* For Emacs:
538 * Local Variables:
539 * mode:c
540 * indent-tabs-mode:t
541 * tab-width:4
542 * c-basic-offset:4
543 * End:
544 * For VIM:
545 * vim:set softtabstop=4 shiftwidth=4 tabstop=4 noet:
546 */
547