1 /*
2  *  import_im.c
3  *
4  *  Copyright (C) Thomas Oestreich - June 2001
5  *  port to MagickWand API:
6  *  Copyright (C) Francesco Romani - July 2007
7  *
8  *  This file is part of transcode, a video stream processing tool
9  *
10  *  transcode is free software; you can redistribute it and/or modify
11  *  it under the terms of the GNU General Public License as published by
12  *  the Free Software Foundation; either version 2, or (at your option)
13  *  any later version.
14  *
15  *  transcode is distributed in the hope that it will be useful,
16  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
17  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  *  GNU General Public License for more details.
19  *
20  *  You should have received a copy of the GNU General Public License
21  *  along with GNU Make; see the file COPYING.  If not, write to
22  *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
23  *
24  */
25 
26 #define MOD_NAME    "import_im.so"
27 #define MOD_VERSION "v0.1.3 (2008-10-07)"
28 #define MOD_CODEC   "(video) RGB"
29 
30 #ifdef HAVE_CONFIG_H
31 # include "config.h"
32 #endif
33 
34 /* Note: because of ImageMagick bogosity, this must be included first, so
35  * we can undefine the PACKAGE_* symbols it splats into our namespace */
36 #ifdef HAVE_BROKEN_WAND
37 #include <wand/magick-wand.h>
38 #else /* we have a SANE wand header */
39 #include <wand/MagickWand.h>
40 #endif /* HAVE_BROKEN_WAND */
41 
42 #undef PACKAGE_BUGREPORT
43 #undef PACKAGE_NAME
44 #undef PACKAGE_STRING
45 #undef PACKAGE_TARNAME
46 #undef PACKAGE_VERSION
47 
48 #include "transcode.h"
49 #include "libtc/optstr.h"
50 
51 #include <stdlib.h>
52 #include <stdio.h>
53 
54 /*%*
55  *%* DESCRIPTION
56  *%*   This module reads single images from disk using ImageMagick;
57  *%*   a stream of correlated images can be automatically read if
58  *%*   their filenames contains a common prefix and a serial number.
59  *%*   All formats supported by ImageMagick are supported as well.
60  *%*
61  *%* BUILD-DEPENDS
62  *%*   libMagick >= 6.2.4.0
63  *%*
64  *%* DEPENDS
65  *%*   libMagick >= 6.2.4.0
66  *%*
67  *%* PROCESSING
68  *%*   import/demuxer
69  *%*
70  *%* MEDIA
71  *%*   video
72  *%*
73  *%* #INPUT
74  *%*
75  *%* OUTPUT
76  *%*   RGB24
77  *%*
78  *%* OPTION
79  *%*   noseq (flag)
80  *%*     disable internal auto loading of images with similar names.
81  *%*/
82 
83 static int verbose_flag = TC_QUIET;
84 static int capability_flag = TC_CAP_RGB|TC_CAP_VID;
85 
86 #define MOD_PRE im
87 #include "import_def.h"
88 
89 #include <time.h>
90 #include <sys/types.h>
91 #include <regex.h>
92 
93 
94 static char *head = NULL, *tail = NULL;
95 static int first_frame = 0, current_frame = 0, decoded_frame = 0, pad = 0;
96 static int total_frame = 0;
97 static int width = 0, height = 0;
98 static MagickWand *wand = NULL;
99 static int auto_seq_read = TC_TRUE;
100 /*
101  * automagically read further images with filename like the first one
102  * enabled by default for backward compatibility, but obsoleted
103  * by core option --multi_input
104  */
105 
TCHandleMagickError(MagickWand * wand)106 static int TCHandleMagickError(MagickWand *wand)
107 {
108     ExceptionType severity;
109     const char *description = MagickGetException(wand, &severity);
110 
111     tc_log_error(MOD_NAME, "%s", description);
112 
113     MagickRelinquishMemory((void*)description);
114     return TC_IMPORT_ERROR;
115 }
116 
117 
118 /* ------------------------------------------------------------
119  *
120  * open stream
121  *
122  * ------------------------------------------------------------*/
123 
124 
125 /* I suspect we have a lot of potential memleaks in here -- FRomani */
126 MOD_open
127 {
128     int result, slen = 0;
129     char *regex = NULL, *frame = NULL;
130     regex_t preg;
131     regmatch_t pmatch[4];
132 
133     if (param->flag == TC_AUDIO) {
134         return TC_IMPORT_OK;
135     }
136 
137     if (param->flag == TC_VIDEO) {
138         param->fd = NULL;
139 
140         // get the frame name and range
141         regex = "\\([^0-9]\\+[-._]\\?\\)\\?\\([0-9]\\+\\)\\([-._].\\+\\)\\?";
142         result = regcomp(&preg, regex, 0);
143         if (result) {
144             tc_log_perror(MOD_NAME, "ERROR:  Regex compile failed.\n");
145             return TC_IMPORT_ERROR;
146         }
147 
148         result = regexec(&preg, vob->video_in_file, 4, pmatch, 0);
149         if (result) {
150             tc_log_warn(MOD_NAME, "Regex match failed: no image sequence");
151             slen = strlen(vob->video_in_file) + 1;
152             head = tc_malloc(slen);
153             if (head == NULL) {
154                 tc_log_perror(MOD_NAME, "filename head");
155                 return TC_IMPORT_ERROR;
156             }
157             strlcpy(head, vob->video_in_file, slen);
158             tail = tc_malloc(1); /* URGH -- FRomani */
159             tail[0] = 0;
160             first_frame = -1;
161         } else {
162             // split the name into head, frame number, and tail
163             slen = pmatch[1].rm_eo - pmatch[1].rm_so + 1;
164             head = tc_malloc(slen);
165             if (head == NULL) {
166                 tc_log_perror(MOD_NAME, "filename head");
167                 return TC_IMPORT_ERROR;
168             }
169             strlcpy(head, vob->video_in_file, slen);
170 
171             slen = pmatch[2].rm_eo - pmatch[2].rm_so + 1;
172             frame = tc_malloc(slen);
173             if (frame == NULL) {
174                 tc_log_perror(MOD_NAME, "filename frame");
175                 return TC_IMPORT_ERROR;
176             }
177             strlcpy(frame, vob->video_in_file + pmatch[2].rm_so, slen);
178 
179             // If the frame number is padded with zeros, record how many digits
180             // are actually being used.
181             if (frame[0] == '0') {
182                 pad = pmatch[2].rm_eo - pmatch[2].rm_so;
183             }
184             first_frame = atoi(frame);
185 
186             slen = pmatch[3].rm_eo - pmatch[3].rm_so + 1;
187             tail = tc_malloc(slen);
188             if (tail == NULL) {
189                 tc_log_perror(MOD_NAME, "filename tail");
190                 return TC_IMPORT_ERROR;
191             }
192             strlcpy(tail, vob->video_in_file + pmatch[3].rm_so, slen);
193 
194             tc_free(frame);
195         }
196 
197         if (vob->im_v_string != NULL) {
198             if (optstr_lookup(vob->im_v_string, "noseq")) {
199                 auto_seq_read = TC_FALSE;
200                 if (verbose > TC_INFO) {
201                     tc_log_info(MOD_NAME, "automagic image sequential read disabled");
202                 }
203             }
204         }
205 
206         current_frame = first_frame;
207         decoded_frame = 0;
208         width         = vob->im_v_width;
209         height        = vob->im_v_height;
210 
211         if (total_frame == 0) {
212             /* only the very first time */
213             MagickWandGenesis();
214         }
215 
216         wand = NewMagickWand();
217         if (wand == NULL) {
218             tc_log_error(MOD_NAME, "cannot create magick wand");
219             return TC_IMPORT_ERROR;
220         }
221 
222         return TC_IMPORT_OK;
223     }
224 
225     return TC_IMPORT_ERROR;
226 }
227 
228 
229 /* ------------------------------------------------------------
230  *
231  * decode  stream
232  *
233  * ------------------------------------------------------------*/
234 
235 MOD_decode
236 {
237     char *frame = NULL, *filename = NULL;
238     int slen;
239     MagickBooleanType status;
240 
241     if (param->flag == TC_AUDIO) {
242         return TC_IMPORT_OK;
243     }
244 
245     if (param->flag == TC_VIDEO) {
246         if (!auto_seq_read) {
247             if (decoded_frame > 0) {
248                 return TC_IMPORT_ERROR;
249             }
250             filename = tc_strdup(vob->video_in_file);
251         } else {
252             // build the filename for the current frame
253             slen = strlen(head) + pad + strlen(tail) + 1;
254             filename = tc_malloc(slen);
255             if (pad) {
256                 char framespec[10];
257                 frame = tc_malloc(pad+1);
258                 tc_snprintf(framespec, 10, "%%0%dd", pad);
259                 tc_snprintf(frame, pad+1, framespec, current_frame);
260                 frame[pad] = '\0';
261             } else if (first_frame >= 0) {
262                 frame = tc_malloc(10);
263                 tc_snprintf(frame, 10, "%d", current_frame);
264             }
265             strlcpy(filename, head, slen);
266             if (frame != NULL) {
267                 strlcat(filename, frame, slen);
268                 tc_free(frame);
269                 frame = NULL;
270             }
271             strlcat(filename, tail, slen);
272         }
273 
274         ClearMagickWand(wand);
275         /*
276          * This avoids IM to buffer all read images.
277          * I'm quite sure that this can be done in a smarter way,
278          * but I haven't yet figured out how. -- FRomani
279          */
280 
281         status = MagickReadImage(wand, filename);
282         if (status == MagickFalse) {
283             if (auto_seq_read) {
284                 /* let's assume that image sequence ends here */
285                 return TC_IMPORT_ERROR;
286             }
287             return TCHandleMagickError(wand);
288         }
289 
290         MagickSetLastIterator(wand);
291 
292         status = MagickGetImagePixels(wand,
293                                       0, 0, width, height,
294                                       "RGB", CharPixel,
295                                       param->buffer);
296         /* param->size already set correctly by caller */
297         if (status == MagickFalse) {
298             return TCHandleMagickError(wand);
299         }
300 
301         param->attributes |= TC_FRAME_IS_KEYFRAME;
302 
303         total_frame++;
304         current_frame++;
305         decoded_frame++;
306 
307         tc_free(filename);
308 
309         return TC_IMPORT_OK;
310     }
311     return TC_IMPORT_ERROR;
312 }
313 
314 /* ------------------------------------------------------------
315  *
316  * close stream
317  *
318  * ------------------------------------------------------------*/
319 
320 MOD_close
321 {
322     if (param->flag == TC_AUDIO) {
323         return TC_IMPORT_OK;
324     }
325 
326     if (param->flag == TC_VIDEO) {
327         vob_t *vob = tc_get_vob();
328 
329         if (param->fd != NULL)
330             pclose(param->fd);
331         if (head != NULL)
332             tc_free(head);
333         if (tail != NULL)
334             tc_free(tail);
335 
336         if (wand != NULL) {
337             DestroyMagickWand(wand);
338             wand = NULL;
339 
340             if (!tc_has_more_video_in_file(vob)) {
341                 /* ...can you hear that? is the sound of the ugliness... */
342                 MagickWandTerminus();
343             }
344         }
345         return TC_IMPORT_OK;
346     }
347     return TC_IMPORT_ERROR;
348 }
349 
350 /*************************************************************************/
351 
352 /*
353  * Local variables:
354  *   c-file-style: "stroustrup"
355  *   c-file-offsets: ((case-label . *) (statement-case-intro . *))
356  *   indent-tabs-mode: nil
357  * End:
358  *
359  * vim: expandtab shiftwidth=4:
360  */
361 
362