1 /*
2  * oggSlideshow creates a slideshow from a number of pictures
3  *
4  * Copyright (C) 2008-2009 Joern Seger
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10 
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15 
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19  *
20  */
21 
22 
23 
24 #ifdef __WIN32
25 #define __GNU_LIBRARY__
26 #include "../win32/getopt_win.h"
27 #endif
28 
29 #include <iostream>
30 #include <map>
31 #include <vector>
32 #include <string>
33 #include <sstream>
34 #include <cstdlib>
35 #include <cmath>
36 #include <cstring>
37 #include <ctime>
38 #include <exception>
39 #include <memory>
40 #include <unistd.h>
41 
42 #include "th_helper.h"
43 
44 #include "definition.h"
45 #include "theoraEncoder.h"
46 #include "fileRepository.h"
47 #include "streamMux.h"
48 #include "cmdlineextractor.h"
49 
50 #include "effector.h"
51 #include "effectorTypes.h"
52 #include "crossfader.h"
53 #include "kenburnseffect.h"
54 #include "lowpassEffect.h"
55 #include "plainPicture.h"
56 #include "shiftEffect.h"
57 #include "shiftblendEffect.h"
58 
59 #include "pictureLoader.h"
60 #include "pictureResize.h"
61 
62 #include "log.h"
63 #include "exception.h"
64 
printHelpScreen(std::string & name)65 void printHelpScreen(std::string& name)
66 {
67   std::cerr << "usage: "<< name <<" [options] <picture1.bmp> <picture2.bmp> \n";
68   std::cerr << "Options: \n"
69             << " -s <width>x<height>: picture width/height of the output frame\n"
70             << " -f <frames/s>      : frames per second\n"
71             << " -o <output file>   : name of the output file\n"
72             << " -l <length>        : number of frames per picture frequence\n"
73             << " -d <datarate>      : datarate in bit/second\n"
74             << " -r <resample>      : resizes the original pictures to video frame width/height and the additional resample factor\n"
75             << " -e                 : reframe picture\n"
76             << " -t <type>          : kb - Ken Burns\n"
77             << "                      cf - cross fade\n"
78             << "                      p  - plain\n"
79             << "                      bl - blur\n"
80             << "                      s  - shift\n"
81             << "                      b  - shiftblend\n"
82             << " -q <quality>       : quality (0-63)\n"
83             << " -c                 : comments in form type=value;type=value\n";
84 
85   std::cerr << "\nadditionally you are able to set a prefix walk with -tkb:<prefixNum>\n";
86 }
87 
88 //int main(int argc, char* argv[])
oggSlideshowCmd(int argc,char * argv[])89 int oggSlideshowCmd(int argc, char* argv[])
90 {
91   /* default values */
92   uint32 width(480);
93   uint32 height(320);
94   uint32 framesPerSecond(24);
95   std::string outputFile("slideshow.ogv");
96   uint32 datarate(0);
97   uint32 quality(32);
98   float resample(1.4);
99   bool reframe(false);
100   std::vector<OggComment> oggComments;
101   int32 predefine(0);
102   SlideshowElement defaultSlide;
103   defaultSlide.duration = 8;
104   defaultSlide.type = KenBurns;
105 
106   srand(time(0));
107 
108   std::string programName(argv[0]);
109 
110   int opt;
111   while ((opt = getopt(argc, argv, "hp:f:o:l:d:r:t:s:ec:q:")) != EOF)
112 
113     switch (opt) {
114 
115     case 'h':
116     case '?':
117       printHelpScreen(programName);
118       exit(-1);
119 
120     case 's': {
121       std::deque<uint32> framesize;
122       CmdlineExtractor::extractUint32(framesize, optarg, 'x');
123       if (framesize.size() != 2) {
124         logger.error() << "please specify the size in the following way: -s320x480\n";
125         exit(-1);
126       }
127       width = framesize[0];
128       height = framesize[1];
129 
130     }
131     break;
132 
133     case 'q':
134       quality = atoi(optarg);
135       break;
136 
137     case 'f':
138       framesPerSecond = atoi(optarg);
139       break;
140 
141     case 'o':
142       outputFile = std::string(optarg);
143       break;
144 
145     case 'l':
146       defaultSlide.duration = atof(optarg);
147       break;
148 
149     case 'd':
150       datarate = atoi(optarg);
151       break;
152 
153     case 'r':
154       resample = atof(optarg);
155       if ((resample < 1) || (resample > 2))
156         resample = 1.2;
157       break;
158 
159     case 'e': {
160       logger.debug() << "reframing\n";
161       reframe = true;
162       break;
163     }
164 
165     case 't': {
166 
167       std::string typeStr;
168       std::string teststring(optarg);
169       std::stringstream tmp;
170 
171       std::string::size_type pos = teststring.find(':');
172       typeStr = teststring.substr(0,pos);
173       if ((pos != std::string::npos) && (pos+1 < teststring.size())) {
174         tmp << teststring.substr(pos+1, std::string::npos);
175         tmp >> predefine;
176         std::cerr << "Predefine: "<< predefine<<std::endl;
177       }
178 
179       if ((typeStr == "kb") || (typeStr =="KenBurns")|| (typeStr == "KB")) {
180 
181         defaultSlide.type = KenBurns;
182         break;
183       }
184       if ((typeStr == "cf") || (typeStr =="crossfade")) {
185         defaultSlide.type = Crossfade;
186         break;
187       }
188 
189       if ((typeStr == "p") || (typeStr =="plain")|| (typeStr == "simple")) {
190         defaultSlide.type = Plain;
191         break;
192       }
193 
194       if ((typeStr == "b") || (typeStr =="bl") || (typeStr == "blur") ||
195           (typeStr == "lp") || (typeStr == "lowpass")) {
196         defaultSlide.type = Blur;
197         break;
198       }
199 
200       if ((typeStr == "s") || (typeStr =="sh") || (typeStr == "shift")) {
201         defaultSlide.type = Shift;
202         break;
203       }
204 
205       if ((typeStr =="sb") || (typeStr == "shiftlend")) {
206         defaultSlide.type = ShiftBlend;
207         break;
208       }
209 
210       std::cerr << "Unknown Type: (" << typeStr << ") using Ken Burns";
211       defaultSlide.type = KenBurns;
212     }
213     break;
214 
215     case 'c': {
216       CmdlineExtractor::extractCommentPairs ( oggComments, optarg, ';', '=' );
217 
218     }
219 
220 
221     }
222 
223   argc -= optind;
224   argv += optind;
225 
226   if ((argc < 1)) {
227     printHelpScreen(programName);
228     return (-1);
229   }
230 
231   StreamConfig streamConf;
232   std::shared_ptr<TheoraStreamParameter> config = std::make_shared<TheoraStreamParameter>();
233   streamConf.parameter = config;
234 
235 // for valgrind
236 #ifdef HAVE_BZERO
237 //  bzero(config,sizeof(TheoraStreamParameter));
238 #else
239 //  memset(config, 0x00, sizeof(TheoraStreamParameter));
240 #endif
241 
242   /* create configuration */
243   config->pictureX         = width;
244   config->pictureY         = height;
245   config->calculateFrame();
246   config->aspectRatioDenom = 1;
247   config->aspectRatioNum   = 1;
248   config->framerateNum     = framesPerSecond;
249   config->framerateDenom   = 1;
250   config->keyframeShift    = 6;
251   config->pixel_fmt        = TheoraStreamParameter::pf_420;//TheoraStreamParameter::pf_444;
252   config->colorspace       = TheoraStreamParameter::unspecified;
253 
254   config->videoBitrate = datarate;
255   config->videoQuality = quality;
256 
257   /* create stream configuration */
258   TheoraEncoder theoraEncoder(0);
259 
260   /* configure the theora encoder and get a stream config back
261    * which configures the stream multiplexer */
262   try {
263     theoraEncoder.configureEncoder(streamConf, oggComments);
264   } catch (std::exception e) {
265     std::cerr << e.what();
266     exit(-1);
267   }
268 
269   // encoder might want another frame size:
270 
271   std::vector<StreamConfig> configList;
272   configList.push_back(streamConf);
273 
274   /* create a m_repository, where the data should be placed */
275   FileRepository* repository = new FileRepository(outputFile, MediaUnit::write);
276 
277   /* create a stream multiplexer */
278   StreamMux streamCreate(repository);
279 
280   /* configure the stream multiplexer */
281   streamCreate.configureStreams(configList);
282 
283   /* extract the RGB picture plane */
284   RGBPlane pictureRGB;
285 
286   std::shared_ptr<Effector> effector;
287 
288   bool first(true);
289   bool noneFound(true);
290   // run through all pictures in command line
291   for (int32 i(0); i<argc; ++i) {
292 
293     bool last = (i == (argc-1));
294     try {
295 
296       // initialize the slide specification with default values
297       SlideshowElement slide(defaultSlide);
298 
299       // extract the actual slide specifications from the next argument
300       CmdlineExtractor::extractSlideshow(argv[i],',',slide);
301 
302       logger.info() << "\ncreating video stream for picture <"
303                     << slide.filename << ">\n";
304 
305       // extract parameters
306 
307       uint32 loadWidth;
308       uint32 loadHeight;
309 
310       if (slide.type == KenBurns) {
311         loadWidth  = (uint32)(width*resample);
312         loadHeight = (uint32)(height*resample);
313       } else {
314         loadWidth = width;
315         loadHeight = height;
316       }
317 
318       bool biggest = (!reframe);
319       if (PictureLoader::load(pictureRGB, slide.filename, loadWidth, loadHeight, biggest) == false) {
320         continue;
321       }
322       noneFound = false;
323 
324       /* add borders, if aspect ratio does not match and the user wants that */
325       if (reframe && ((loadWidth != pictureRGB->width) || (loadHeight != pictureRGB->height))) {
326         logger.info() << "Picture aspect ratio does not match, doing reframing\n";
327         pictureRGB = PictureResize::reframe(pictureRGB, loadWidth, loadHeight);
328       }
329 
330       /* configure the effector */
331       switch (slide.type) {
332 
333       case KenBurns: {
334 
335         KenBurnsEffect::KenBurnsConfig config;
336         if (predefine == 0)
337           config = KenBurnsEffect::createKBconfigRandom(pictureRGB, loadWidth, loadHeight, width, height, slide.duration*framesPerSecond, framesPerSecond);
338         else if (predefine < 0) {
339           config.startpointX = slide.startPosX;
340           config.startpointY = slide.startPosY;
341           config.endpointX = slide.endPosX;
342           config.endpointY = slide.endPosY;
343           config.zoomStart = slide.startZoom;
344           config.zoomEnd = slide.endZoom;
345           config.sequenceLength = slide.duration * framesPerSecond;
346           config.blindLength = framesPerSecond;
347           config.origPlane = pictureRGB;
348           config.outputWidth = width;
349           config.outputHeight = height;
350 
351           std::cerr << "s:" << slide.startPosX<<":"<<slide.startPosY<<" -> "<<slide.endPosX<<":"<<slide.endPosY<<"\n";
352         } else
353           config = KenBurnsEffect::createKBconfigPredefine(pictureRGB, loadWidth, loadHeight, width, height, slide.duration*framesPerSecond, framesPerSecond, predefine);
354 
355 
356         config.first = first;
357         config.last = last;
358 
359         if (!effector.get() || GetEffectorType()(*effector) != KenBurns) {
360           effector.reset(new KenBurnsEffect);
361         }
362         static_cast<KenBurnsEffect*>(effector.get())->configure(config);
363 
364         break;
365       }
366 
367 
368       case Crossfade: {
369 
370         Crossfader::CrossfaderConfig config;
371 
372         config.origPlane      = pictureRGB;
373         config.blindLength    = framesPerSecond;
374         config.sequenceLength = slide.duration*framesPerSecond;
375         config.outputWidth    = width;
376         config.outputHeight   = height;
377         config.first          = first;
378 
379         if (!effector.get() || GetEffectorType()(*effector) != Crossfade) {
380           effector.reset(new Crossfader);
381         }
382         static_cast<Crossfader*>(effector.get())->configure(config);
383 
384         break;
385       }
386 
387       case Shift: {
388 
389         ShiftEffect::ShiftConfig config;
390 
391         config.origPlane      = pictureRGB;
392         config.blindLength    = framesPerSecond;
393         config.sequenceLength = slide.duration*framesPerSecond;
394         config.outputWidth    = width;
395         config.outputHeight   = height;
396         config.first          = first;
397 
398         if (!effector.get() || GetEffectorType()(*effector) != Shift) {
399           effector.reset(new ShiftEffect);
400         }
401         static_cast<ShiftEffect*>(effector.get())->configure(config);
402 
403         break;
404       }
405 
406       case ShiftBlend: {
407 
408         ShiftblendEffect::ShiftConfig config;
409 
410         config.origPlane      = pictureRGB;
411         config.blindLength    = framesPerSecond;
412         config.sequenceLength = slide.duration*framesPerSecond;
413         config.outputWidth    = width;
414         config.outputHeight   = height;
415         config.first          = first;
416         config.type           = ShiftblendEffect::ShiftConfig::Right;
417 
418         if (!effector.get() || GetEffectorType()(*effector) != ShiftBlend) {
419           effector.reset(new ShiftblendEffect);
420         }
421         static_cast<ShiftblendEffect*>(effector.get())->configure(config);
422 
423         break;
424       }
425 
426       case Plain: {
427 
428         PlainPicture::PlainPictureConfig config;
429 
430         config.origPlane      = pictureRGB;
431         config.sequenceLength = slide.duration*framesPerSecond;
432         config.outputWidth    = width;
433         config.outputHeight   = height;
434 
435         if (!effector.get() || GetEffectorType()(*effector) != Plain) {
436           effector.reset(new PlainPicture);
437         }
438         static_cast<PlainPicture*>(effector.get())->configure(config);
439 
440         break;
441       }
442 
443       case Blur : {
444 
445         LowpassEffect::LowPassPictureConfig config;
446 
447         config.origPlane = pictureRGB;
448         config.blindLength = framesPerSecond;
449         config.sequenceLength = slide.duration*framesPerSecond;
450         config.outputWidth = width;
451         config.outputHeight = height;
452         config.first = first;
453         config.last = last;
454 
455         if (!effector.get() || GetEffectorType()(*effector) != Blur) {
456           effector.reset(new LowpassEffect);
457         }
458         static_cast<LowpassEffect*>(effector.get())->configure(config);
459         break;
460       }
461       }
462 
463       RGBPlane        outputPlane;
464       OggPacket       packet;
465       th_ycbcr_buffer theoraPictureBuffer;
466       th_clean_ycbcr(theoraPictureBuffer);
467 
468       while (effector->available()) {
469 
470         (*effector) >> outputPlane;
471 
472         PictureLoader::exportYCrCb_theora(outputPlane, theoraPictureBuffer);
473 
474         theoraEncoder << theoraPictureBuffer;
475         theoraEncoder >> packet;
476         std::cerr << "\r " <<std::fixed << packet->getPacketNo()*1.0/(framesPerSecond*1.0)<<"               ";
477         streamCreate << packet;
478 
479       }
480 
481       th_free_ycbcr(theoraPictureBuffer);
482 
483     } catch (const char* errorString) {
484       std::cout << errorString << std::endl;
485       return(-1);
486     }
487     first = false;
488   }
489 
490   if (noneFound)
491     return(-1);
492 
493   streamCreate.setEndOfStream();
494   streamCreate.close();
495 
496   std::cout << std::endl;
497 #ifdef OSX_MALLOC_DEBUG
498   std::cout << "Done!\n";
499   while (1==1) { }
500 #endif
501 
502   return(0);
503 }
504 
main(int argc,char * argv[])505 int main(int argc, char* argv[])
506 {
507   logger.setLevel(OggLog::LOG_INFO);
508   try {
509     return oggSlideshowCmd(argc, argv);
510   } catch (OggException & e) {
511     logger.error() << "Fatal error: " << e.what() << std::endl;
512     return (-1);
513   }
514 }
515