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