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