root/trunk/uploadr/MacUploadr.app/Contents/Resources/components/flGM.cpp

Revision 386, 26.3 kB (checked in by mygrant, 3 months ago)

Seek directly to the time we want and grab that frame instead of looping through all of them

Line 
1 /*
2  * Flickr Uploadr
3  *
4  * Copyright (c) 2007-2008 Yahoo! Inc.  All rights reserved.  This library is
5  * free software; you can redistribute it and/or modify it under the terms of
6  * the GNU General Public License (GPL), version 2 only.  This library is
7  * distributed WITHOUT ANY WARRANTY, whether express or implied. See the GNU
8  * GPL for more details (http://www.gnu.org/licenses/gpl.html)
9  */
10
11 #include <stdio.h>
12
13 #include "flGM.h"
14
15 // GraphicsMagick
16 #include "Magick++.h"
17
18 // Exiv2
19 #include "image.hpp"
20 #include "exif.hpp"
21 #include "iptc.hpp"
22
23 // Goofy FFmpeg requires C linkage
24 extern "C" {
25 #include <libavcodec/avcodec.h>
26 #include <libavformat/avformat.h>
27 #include <libswscale/swscale.h>
28 }
29
30 #include <stdlib.h>
31 #include <sstream>
32 #include <string>
33 #include <sys/stat.h>
34 #include "nsCOMPtr.h"
35 #include "nsIFile.h"
36 #include "nsDirectoryServiceUtils.h"
37 #include "nsEmbedString.h"
38
39 // _NSGetExecutablePath on Macs
40 #ifdef XP_MACOSX
41 #include <mach-o/dyld.h>
42 #endif
43
44 // GetShortPathName, GetWindowsDirectory and CopyFile on Windows
45 #ifdef XP_WIN
46 #include <windows.h>
47 #endif
48
49 #define round(n) (int)(0 <= (n) ? (n) + 0.5 : (n) - 0.5)
50 static int sws_flags = SWS_BICUBIC;
51
52 using namespace std;
53
54 // Prototypes
55 string * conv_path(const nsAString &, bool);
56 string * find_path(string *, const char *);
57 int base_orient(Exiv2::ExifData &, Magick::Image &);
58 void exif_update_dim(Exiv2::ExifData &, int, int);
59 void unconv_path(string &, nsAString &);
60
61 // Convert a path from a UTF-16 nsAString to an ASCII std::string
62 //   In Windows, this will handle all the Unicode weirdness paths come with
63 //   If is_dir is true then the returned path will transparently become an
64 //   ASCII-safe path, possibly to a TEMP dir
65 //   If is_dir is false then the path will be made safe or the file will be
66 //   copied under a new name to an ASCII-safe TEMP dir
67 string * conv_path(const nsAString & utf16, bool is_dir) {
68
69         // Fun with Windows paths
70 #ifdef XP_WIN
71
72         // Is this path outside of ASCII?
73         PRUnichar * utf16_start = (PRUnichar *)utf16.BeginReading();
74         const PRUnichar * utf16_end = (const PRUnichar *)utf16.EndReading();
75         bool needs_unicode = false;
76         while (utf16_start != utf16_end) {
77                 if (0x7f < *utf16_start++) {
78                         needs_unicode = true;
79                         break;
80                 }
81         }
82
83         // We're outside of ASCII so we need help
84         if (needs_unicode) {
85
86                 // UTF-16 nsAString to wchar_t[]
87                 wchar_t * wide_arr = new wchar_t[utf16.Length() + 1];
88                 if (0 == wide_arr) return 0;
89                 wchar_t * wide_arr_p = wide_arr;
90                 PRUnichar * wide_start = (PRUnichar *)utf16.BeginReading();
91                 const PRUnichar * wide_end = (const PRUnichar *)utf16.EndReading();
92                 while (wide_start != wide_end) {
93                         *wide_arr_p++ = (wchar_t)*wide_start++;
94                 }
95                 *wide_arr_p = 0;
96
97                 // Try GetShortPathNameW to get ASCII in a wchar_t *
98                 wchar_t short_arr[4096];
99                 *short_arr = 0;
100                 int sp = GetShortPathNameW(wide_arr, short_arr, 4096);
101
102                 // See if we still need Unicode
103                 needs_unicode = false;
104                 wchar_t * short_arr_p = short_arr;
105                 while (*short_arr_p) {
106                         if (0x7f < *short_arr_p++) {
107                                 needs_unicode = true;
108                                 break;
109                         }
110                 }
111
112                 if (0 == sp || needs_unicode) {
113
114                         // Try to find a TEMP directory
115                         //   This would be the easy way except that it will never work
116                         //   for users with Unicode characters in their usernames
117                         /*
118                         char temp_arr[4096];
119                         *temp_arr = 0;
120                         if (0 == GetTempPathA(4096, temp_arr)) {
121                                 delete [] wide_arr;
122                                 return 0;
123                         }
124                         */
125
126                         // Try to find a TEMP directory
127                         //   (This is the hard way but at least it will work)
128                         //   Get the drive letter from the Windows directory and append
129                         //   :\temp, create that directory and use it for TEMP
130                         char win_arr[4096];
131                         *win_arr = 0;
132                         if (0 == GetWindowsDirectoryA(win_arr, 4096)) {
133                                 delete [] wide_arr;
134                                 return 0;
135                         }
136                         char temp_arr[9];
137                         temp_arr[0] = *win_arr;
138                         temp_arr[1] = ':'; temp_arr[2] = '\\';
139                         temp_arr[3] = 't'; temp_arr[4] = 'e';
140                         temp_arr[5] = 'm'; temp_arr[6] = 'p';
141                         temp_arr[7] = '\\'; temp_arr[8] = 0;
142                         CreateDirectoryA(temp_arr, 0);
143
144                         // Directory requests can just have the TEMP directory
145                         if (is_dir) {
146                                 delete [] wide_arr;
147                                 return new string(temp_arr);
148                         }
149
150                         // But if this is a file we actually need to copy it
151                         string base(temp_arr);
152                         base += "original";
153
154                         // Copy the file extension of the original to our base
155                         wstring wide_w(wide_arr);
156                         wstring ext_w = wide_w.substr(wide_w.rfind('.'));
157                         string ext_s;
158                         wchar_t * ext_p = (wchar_t *)ext_w.c_str();
159                         while (*ext_p) {
160                                 ext_s += (char)*ext_p++;
161                         }
162                         base += ext_s;
163
164                         // Destination path
165                         string * temp = find_path(&base, "");
166                         wchar_t * temp_wide_arr = new wchar_t[temp->size() + 1];
167                         wchar_t * temp_wide_arr_p = temp_wide_arr;
168                         char * temp_p = (char *)temp->c_str();
169                         while (*temp_p) {
170                                 *temp_wide_arr_p++ = (wchar_t)*temp_p++;
171                         }
172                         *temp_wide_arr_p = 0;
173
174                         // Copy the file
175                         if (0 == CopyFileW(wide_arr, temp_wide_arr, false)) {
176                                 delete [] wide_arr;
177                                 delete temp;
178                                 delete [] temp_wide_arr;
179                                 return 0;
180                         }
181                         delete [] wide_arr;
182                         delete [] temp_wide_arr;
183                         return temp;
184
185                 }
186                 delete [] wide_arr;
187
188                 // GetShortPathNameW was successful!
189                 // wchar_t * to std::string
190                 string * short_s = new string();
191                 if (0 == short_s) return 0;
192                 short_arr_p = short_arr;
193                 while (*short_arr_p) {
194                         *short_s += (char)*short_arr_p++;
195                 }
196                 return short_s;
197
198         }
199
200         // Within ASCII, pack it down
201         else {
202                 char * ascii_arr = new char[utf16.Length() + 1];
203                 if (0 == ascii_arr) return 0;
204                 char * ascii_arr_p = ascii_arr;
205                 utf16_start = (PRUnichar *)utf16.BeginReading();
206                 utf16_end = (const PRUnichar *)utf16.EndReading();
207                 while (utf16_start != utf16_end) {
208                         *ascii_arr_p++ = (char)*utf16_start++;
209                 }
210                 *ascii_arr_p = 0;
211                 string * ascii_s = new string(ascii_arr);
212                 delete [] ascii_arr;
213                 return ascii_s;
214         }
215
216         // Macs just need UTF-8
217 #else
218
219         // UTF-16 nsAString to UTF-8 nsCString
220         nsCString utf8 = NS_ConvertUTF16toUTF8(utf16);
221
222         // UTF-8 nsCString to std::string
223         char * utf8_arr = new char[utf8.Length() + 1];
224         if (0 == utf8_arr) return 0;
225         char * utf8_arr_p = utf8_arr;
226         char * utf8_start = (char *)utf8.BeginReading();
227         const char * utf8_end = (const char *)utf8.EndReading();
228         while (utf8_start != utf8_end) {
229                 *utf8_arr_p++ = *utf8_start++;
230         }
231         *utf8_arr_p = 0;
232         string * utf8_s = new string(utf8_arr);
233         delete [] utf8_arr;
234         return utf8_s;
235
236 #endif
237
238 }
239
240 // Find a path for the new image file in our profile
241 //   If extra is empty, the new path will be in the same directory,
242 //   otherwise it will be in the Profile
243 string * find_path(string * path_s, const char * extra) {
244         if (0 == path_s || 0 == extra) {
245                 return 0;
246         }
247         string * dir_s = 0;
248         if (*extra) {
249                 nsCOMPtr<nsIFile> dir_ptr;
250                 nsresult nsr = NS_GetSpecialDirectory("ProfD", getter_AddRefs(dir_ptr));
251                 if (NS_FAILED(nsr)) {
252                         return 0;
253                 }
254                 dir_ptr->AppendNative(NS_LITERAL_CSTRING("images"));
255                 PRBool dir_exists = PR_FALSE;
256                 dir_ptr->Exists(&dir_exists);
257                 if (!dir_exists) {
258                         dir_ptr->Create(nsIFile::DIRECTORY_TYPE, 0770);
259                 }
260                 nsEmbedString dir;
261                 dir_ptr->GetPath(dir);
262                 dir_s = conv_path(dir, true);
263                 if (0 == dir_s) {
264                         return 0;
265                 }
266 #ifdef XP_WIN
267                 dir_s->append(path_s->substr(path_s->rfind('\\')));
268 #else
269                 dir_s->append(path_s->substr(path_s->rfind('/')));
270 #endif
271                 size_t period = dir_s->rfind('.');
272                 dir_s->insert(period, extra);
273         } else {
274                 dir_s = new string(*path_s);
275                 if (0 == dir_s) {
276                         return 0;
277                 }
278         }
279         ostringstream index;
280         string dir_s_save(*dir_s);
281         int i = 0;
282         struct stat st;
283         while (0 == stat(dir_s->c_str(), &st)) {
284                 index.str("");
285                 index << ++i;
286                 *dir_s = dir_s_save;
287                 dir_s->insert(dir_s->rfind('.'), index.str());
288         }
289         return dir_s;
290 }
291
292 // Orient an image's pixels as EXIF instructs
293 int base_orient(Exiv2::ExifData & exif, Magick::Image & img) {
294         int orient = -1;
295         try {
296                 Exiv2::ExifData::iterator it = exif.findKey(Exiv2::ExifKey(string(
297                         "Exif.Image.Orientation")));
298                 if (exif.end() != it) {
299                         orient = it->toLong();
300                 } else {
301                         it = exif.findKey(Exiv2::ExifKey(string(
302                                 "Exif.Panasonic.Orientation")));
303                         if (exif.end() != it) {
304                                 orient = it->toLong();
305                         } else {
306                                 it = exif.findKey(Exiv2::ExifKey(string(
307                                         "Exif.MinoltaCs5D.Orientation")));
308                                 if (exif.end() != it) {
309                                         orient = it->toLong();
310                                 }
311                         }
312                 }
313         } catch (Exiv2::Error &) {}
314         if (1 > orient || 8 < orient) {
315                 orient = 1;
316         }
317         switch (orient) {
318                 case 2:
319                         img.flop();
320                 break;
321                 case 3:
322                         img.rotate(180.0);
323                 break;
324                 case 4:
325                         img.flip();
326                 break;
327                 case 5:
328                         img.rotate(90.0);
329                         img.flip();
330                 break;
331                 case 6:
332                         img.rotate(90.0);
333                 break;
334                 case 7:
335                         img.rotate(270.0);
336                         img.flip();
337                 break;
338                 case 8:
339                         img.rotate(270.0);
340                 break;
341                 default:
342                 break;
343         }
344         return orient;
345 }
346
347 // Update the image width and height in EXIF
348 void exif_update_dim(Exiv2::ExifData & exif, int w, int h) {
349
350         // Keys to search
351         char * keys[2][4] = {
352
353                 // Width
354                 {
355                         "Exif.Iop.RelatedImageWidth",
356                         "Exif.Photo.PixelXDimension", // Only for compressed images
357                         "Exif.Image.ImageWidth", // From TIFF-land
358                         0
359                 },
360
361                 // Height
362                 {
363                         "Exif.Iop.RelatedImageLength",
364                         "Exif.Photo.PixelYDimension", // Only for compressed images
365                         "Exif.Image.ImageLength", // From TIFF-land
366                         0
367                 }
368
369         };
370
371         // Once for width, once for height
372         int values[2] = { w, h };
373         for (unsigned int i = 0; i < 2; ++i) {
374
375                 // For each key we have, if it's set, override it
376                 unsigned int j = 0;
377                 unsigned int done = 0;
378                 while (0 != keys[i][j]) {
379                         string str = string(keys[i][j]);
380                         Exiv2::ExifKey key = Exiv2::ExifKey(str);
381                         Exiv2::ExifData::iterator it = exif.findKey(key);
382                         if (exif.end() != it) {
383                                 exif[keys[i][j]] = uint32_t(values[i]);
384                                 done = 1;
385                         }
386                         ++j;
387                 }
388
389                 // As a last resort, set the TIFF tags
390                 if (!done) {
391                         exif[keys[i][0]] = uint32_t(values[i]);
392                 }
393
394         }
395 }
396
397 // Get a path/string ready to send back to JavaScript-land
398 //   On Windows there is little to do, but Macs must go from UTF-8 to UTF-16
399 void unconv_path(string & path_s, nsAString & _retval) {
400         size_t pos = path_s.find("###", 0);
401         while (string::npos != pos) {
402                 path_s.replace(pos, 3, "{---THREE---POUND---DELIM---}");
403                 pos = path_s.find("###", pos);
404         }
405         char * o = (char *)path_s.c_str();
406 #ifdef XP_MACOSX
407         nsCString utf8;
408 #endif
409         while (*o) {
410
411                 // Macs will still have UTF-8 at this point
412 #ifdef XP_MACOSX
413                 utf8.Append(*o++);
414
415                 // Windows is good to go, being ASCII and all
416 #else
417                 _retval.Append(*o++);
418
419 #endif
420         }
421
422         // Finish up the Mac transform to UTF-16
423 #ifdef XP_MACOSX
424         _retval.Append(NS_ConvertUTF8toUTF16(utf8));
425 #endif
426
427 }
428
429 void quote(string & s) {
430         if (string::npos != s.find(" ", 0)) {
431                 s.insert(0, "\"").append("\"");
432         }
433 }
434
435 // Extract a metadata key if it exists in the collection given
436 template<class D, class K>
437 bool extract(D & data, const char * k, string & s, bool q) {
438         typename D::iterator it = data.findKey(K(string(k)));
439         if (data.end() == it) { return false; }
440         else {
441                 s = it->toString();
442                 if (q) { quote(s); }
443                 return true;
444         }
445 }
446
447 NS_IMPL_ISUPPORTS1(flGM, flIGM)
448
449 flGM::flGM() {
450 }
451
452 flGM::~flGM() {
453 }
454
455 // Initialize our GraphicsMagick/ffmpeg setup
456 NS_IMETHODIMP flGM::Init(const nsAString & pwd) {
457
458         //Mac needs to setup GraphicsMagick
459 #ifdef XP_MACOSX
460         char path[1024];
461         unsigned int size = 1024;
462         _NSGetExecutablePath(&path[0], &size);
463         Magick::InitializeMagick(&path[0]);
464 #endif
465
466         // Windows needs to get its working directory ready for GraphicsMagick
467 #ifdef XP_WIN
468         string * pwd_s = conv_path(pwd, true);
469         if (0 == pwd_s) return NS_ERROR_NULL_POINTER;
470         SetCurrentDirectoryA(pwd_s->c_str());
471         delete pwd_s;
472 #endif
473
474         // Register all video codecs
475         av_register_all();
476
477         return NS_OK;
478 }
479
480 // Create a thumbnail of the image, preserving aspect ratio and store it to the profile
481 NS_IMETHODIMP flGM::Thumb(PRInt32 square, const nsAString & path, nsAString & _retval) {
482         string * path_s = 0;
483         string * thumb_s = 0;
484         try {
485
486                 // Get the path as a C++ string
487                 path_s = conv_path(path, false);
488                 if (!path_s) { return NS_ERROR_INVALID_ARG; }
489
490                 // Open the image
491                 Magick::Image img(*path_s);
492
493                 // Extract EXIF and IPTC data that we care about
494                 int orient = 1;
495                 string title = "", description = "", tags = "", date_taken = "";
496                 try {
497                         Exiv2::Image::AutoPtr meta_r = Exiv2::ImageFactory::open(*path_s);
498                         meta_r->readMetadata();
499
500                         // EXIF orientation
501                         Exiv2::ExifData & exif = meta_r->exifData();
502                         orient = base_orient(exif, img);
503
504                         // XMP and IPTC metadata
505                         Exiv2::XmpData & xmp = meta_r->xmpData();
506                         Exiv2::IptcData & iptc = meta_r->iptcData();
507                         extract<Exiv2::XmpData, Exiv2::XmpKey>(
508                                 xmp, "Xmp.dc.title", title, false)
509                                 || extract<Exiv2::IptcData, Exiv2::IptcKey>(
510                                 iptc, "Iptc.Application2.ObjectName", title, false)
511                                 || extract<Exiv2::IptcData, Exiv2::IptcKey>(
512                                 iptc, "Iptc.Application2.Headline", title, false);
513                         extract<Exiv2::XmpData, Exiv2::XmpKey>(
514                                 xmp, "Xmp.dc.description", description, false)
515                                 || extract<Exiv2::XmpData, Exiv2::XmpKey>(
516                                 xmp, "Xmp.photoshop.Headline", description, false)
517                                 || extract<Exiv2::IptcData, Exiv2::IptcKey>(
518                                 iptc, "Iptc.Application2.Caption", description, false)
519                                 || extract<Exiv2::ExifData, Exiv2::ExifKey>(
520                                 exif, "Exif.Image.ImageDescription", description, false);
521                         string key("Iptc.Application2.Keywords");
522                         Exiv2::IptcKey k = Exiv2::IptcKey(key);
523                         Exiv2::IptcData::iterator i, ii = iptc.end();
524                         for (i = iptc.begin(); i != ii; ++i) {
525                                 if (i->key() == key) {
526                                         string val = i->toString();
527                                         quote(val);
528                                         tags += val + " ";
529                                 }
530                         }
531                         string city = "", state = "", country = "";
532                         extract<Exiv2::IptcData, Exiv2::IptcKey>(
533                                 iptc, "Iptc.Application2.City", city, true);
534                         tags += city + " ";
535                         extract<Exiv2::IptcData, Exiv2::IptcKey>(
536                                 iptc, "Iptc.Application2.ProvinceState", state, true);
537                         tags += state + " ";
538                         extract<Exiv2::IptcData, Exiv2::IptcKey>(
539                                 iptc, "Iptc.Application2.CountryName", country, true);
540                         tags += country;
541
542                         // XMP and EXIF date taken
543                         extract<Exiv2::XmpData, Exiv2::XmpKey>(
544                                 xmp, "Xmp.exif.DateTimeOriginal", date_taken, false)
545                                 || extract<Exiv2::XmpData, Exiv2::XmpKey>(
546                                 xmp, "Xmp.exif.DateTimeDigitized", date_taken, false)
547                                 || extract<Exiv2::XmpData, Exiv2::XmpKey>(
548                                 xmp, "Xmp.iptc.DateTime", date_taken, false)
549                                 || extract<Exiv2::ExifData, Exiv2::ExifKey>(            // Previously
550                                 exif, "Exif.Photo.DateTimeOriginal", date_taken, false) // this was primary
551                                 || extract<Exiv2::ExifData, Exiv2::ExifKey>(
552                                 exif, "Exif.Photo.DateTimeDigitized", date_taken, false)
553                                 || extract<Exiv2::ExifData, Exiv2::ExifKey>(
554                                 exif, "Exif.Image.DateTime", date_taken, false);
555
556                 } catch (Exiv2::Error &) {}
557                 ostringstream out1;
558                 out1 << orient << "###";
559
560                 // Original size
561                 int bw, bh;
562                 if (5 > orient) {
563                         bw = img.baseColumns();
564                         bh = img.baseRows();
565                 } else {
566                         bw = img.baseRows();
567                         bh = img.baseColumns();
568                 }
569                 int base = bw > bh ? bw : bh;
570                 out1 << bw << "###" << bh << "###";
571
572                 // Output date taken
573                 out1 << date_taken << "###";
574
575                 // Thumbnail width and height
576                 ostringstream dim;
577                 if (bw > bh) {
578                         float r = (float)bh * (float)square / (float)bw;
579                         out1 << square << "###" << round(r);
580                         dim << square << "x" << round(r);
581                 } else {
582                         float r = (float)bw * (float)square / (float)bh;
583                         out1 << round(r) << "###" << square;
584                         dim << round(r) << "x" << square;
585                 }
586                 out1 << "###";
587
588                 // Hide ### strings within the IPTC data
589                 size_t pos = title.find("###", 0);
590                 while (string::npos != pos) {
591                         title.replace(pos, 3, "{---THREE---POUND---DELIM---}");
592                         pos = title.find("###", pos);
593                 }
594                 pos = description.find("###", 0);
595                 while (string::npos != pos) {
596                         description.replace(pos, 3, "{---THREE---POUND---DELIM---}");
597                         pos = description.find("###", pos);
598                 }
599                 pos = tags.find("###", 0);
600                 while (string::npos != pos) {
601                         tags.replace(pos, 3, "{---THREE---POUND---DELIM---}");
602                         pos = tags.find("###", pos);
603                 }
604                 ostringstream out2;
605                 out2 << "###" << title << "###" << description << "###" << tags;
606
607                 // Create a new path
608                 thumb_s = find_path(path_s, "-thumb");
609                 if (!thumb_s) { return NS_ERROR_NULL_POINTER; }
610                 delete path_s; path_s = 0;
611
612                 // If this image is a TIFF, force the thumbnail to be a JPEG
613                 if (thumb_s->rfind(".tif") + 6 > thumb_s->length()) {
614                         thumb_s->append(".jpg");
615                 }
616
617                 // Find the sharpen sigma as the website does
618                 double sigma;
619                 if (base <= 800) { sigma = 1.9; }
620                 else if (base <= 1600) { sigma = 2.85; }
621                 else { sigma = 3.8; }
622
623                 // Create the actual thumbnail
624                 img.scale(dim.str());
625                 img.sharpen(1, sigma);
626                 img.compressType(Magick::NoCompression);
627                 img.write(*thumb_s);
628
629                 // If all went well, return stuff
630                 string out1_s = out1.str();
631                 char * o = (char *)out1_s.c_str();
632                 nsCString utf8;
633                 while (*o) { utf8.Append(*o++); }
634                 _retval.Append(NS_ConvertUTF8toUTF16(utf8));
635                 unconv_path(*thumb_s, _retval);
636                 delete thumb_s; thumb_s = 0;
637                 string out2_s = out2.str();
638                 o = (char *)out2_s.c_str();
639                 utf8.Assign(NS_LITERAL_CSTRING(""));
640                 while (*o) { utf8.Append(*o++); }
641                 _retval.Append(NS_ConvertUTF8toUTF16(utf8));
642
643                 return NS_OK;
644         }
645
646         // Otherwise yell about it
647         catch (Magick::Exception & e) {
648                 delete path_s;
649                 delete thumb_s;
650                 char * o = (char *)e.what();
651                 while (*o) { _retval.Append(*o++); }
652         }
653         return NS_OK;
654
655 }
656
657 // Rotate an image, preserving size and store it to the profile
658 NS_IMETHODIMP flGM::Rotate(PRInt32 degrees, const nsAString & path, nsAString & _retval) {
659         string * path_s = 0;
660         string * rotate_s = 0;
661         try {
662
663                 // Don't rotate 0 degrees
664                 if (0 == degrees) {
665                         _retval.Append('o');
666                         _retval.Append('k');
667                         return NS_OK;
668                 }
669
670                 path_s = conv_path(path, false);
671                 if (!path_s) { return NS_ERROR_INVALID_ARG; }
672
673                 // Yank out all the metadata we want to save
674                 Exiv2::ExifData exif;
675                 Exiv2::IptcData iptc;
676                 Exiv2::XmpData xmp;
677                 try {
678                         Exiv2::Image::AutoPtr meta_r = Exiv2::ImageFactory::open(*path_s);
679                         meta_r->readMetadata();
680                         exif = meta_r->exifData();
681                         iptc = meta_r->iptcData();
682                         xmp = meta_r->xmpData();
683                 } catch (Exiv2::Error &) {}
684
685                 // Create a new path
686                 rotate_s = find_path(path_s, "-rotate");
687                 if (!rotate_s) { return NS_ERROR_NULL_POINTER; }
688
689                 // Rotate the image
690                 Magick::Image img(*path_s);
691                 delete path_s; path_s = 0;
692                 base_orient(exif, img);
693                 img.rotate(degrees);
694                 img.compressType(Magick::NoCompression);
695                 img.write(*rotate_s);
696
697                 // Set the orientation to 1 because we're orienting the pixels manually
698                 exif["Exif.Image.Orientation"] = uint32_t(1);
699
700                 // Put saved metadata into the resized image
701                 try {
702                         Exiv2::Image::AutoPtr meta_w = Exiv2::ImageFactory::open(*rotate_s);
703                         meta_w->setExifData(exif);
704                         meta_w->setIptcData(iptc);
705                         meta_w->setXmpData(xmp);
706                         meta_w->writeMetadata();
707                 } catch (Exiv2::Error &) {}
708
709                 // If all went well, return new path
710                 rotate_s->insert(0, "ok");
711                 unconv_path(*rotate_s, _retval);
712                 delete rotate_s;
713                 return NS_OK;
714         }
715
716         // Otherwise yell about it
717         catch (Magick::Exception & e) {
718                 delete path_s;
719                 delete rotate_s;
720                 char * o = (char *)e.what();
721                 while (*o) { _retval.Append(*o++); }
722         }
723         return NS_OK;
724
725 }
726
727 // Resize an image and store it to the profile
728 NS_IMETHODIMP flGM::Resize(PRInt32 square, const nsAString & path, nsAString & _retval) {
729         string * path_s = 0;
730         string * resize_s = 0;
731         try {
732                 path_s = conv_path(path, false);
733                 if (!path_s) { return NS_ERROR_INVALID_ARG; }
734
735                 // Yank out all the metadata we want to save
736                 Exiv2::ExifData exif;
737                 Exiv2::IptcData iptc;
738                 Exiv2::XmpData xmp;
739                 try {
740                         Exiv2::Image::AutoPtr meta_r = Exiv2::ImageFactory::open(*path_s);
741                         meta_r->readMetadata();
742                         exif = meta_r->exifData();
743                         iptc = meta_r->iptcData();
744                         xmp = meta_r->xmpData();
745                 } catch (Exiv2::Error &) {}
746
747                 // Open the image
748                 Magick::Image img(*path_s);
749                 int bw = img.baseColumns(), bh = img.baseRows();
750                 int base = bw > bh ? bw : bh;
751
752                 // In the special -1 case, find the next-smallest size to scale to
753                 if (-1 == square) {
754                         if (base > 2048) {
755                                 square = 2048;
756                         } else if (base > 1600) {
757                                 square = 1600;
758                         } else if (base > 1280) {
759                                 square = 1280;
760                         } else {
761                                 square = 800;
762                         }
763                 }
764
765                 // Don't resize if we're already that size
766                 if (base <= square) {
767                         _retval = path;
768                         return NS_OK;
769                 }
770
771                 // Find resized width and height
772                 float r;
773                 ostringstream out;
774                 if (bw > bh) {
775                         r = (float)bh * (float)square / (float)bw;
776                         r = round(r);
777                         exif_update_dim(exif, square, r);
778                         out << square << "x" << r;
779                 } else {
780                         r = (float)bw * (float)square / (float)bh;
781                         r = round(r);
782                         exif_update_dim(exif, r, square);
783                         out << r << "x" << square;
784                 }
785                 string dim(out.str());
786
787                 // Create a new path
788                 resize_s = find_path(path_s, "-resize");
789                 if (!resize_s) { return NS_ERROR_NULL_POINTER; }
790                 delete path_s; path_s = 0;
791                 out << *resize_s;
792
793                 // Resize the image
794                 img.scale(dim);
795                 img.compressType(Magick::NoCompression);
796                 img.write(*resize_s);
797
798                 // Put saved metadata into the resized image
799                 try {
800                         Exiv2::Image::AutoPtr meta_w = Exiv2::ImageFactory::open(*resize_s);
801                         meta_w->setExifData(exif);
802                         meta_w->setIptcData(iptc);
803                         meta_w->setXmpData(xmp);
804                         meta_w->writeMetadata();
805                 } catch (Exiv2::Error &) {}
806                 delete resize_s; resize_s = 0;
807
808                 // If all went well, return stuff
809                 string o_s = out.str();
810                 unconv_path(o_s, _retval);
811
812                 return NS_OK;
813         }
814
815         // Otherwise yell about it
816         catch (Magick::Exception & e) {
817                 delete path_s;
818                 delete resize_s;
819                 char * o = (char *)e.what();
820                 while (*o) {
821                         _retval.Append(*o++);
822                 }
823         }
824         return NS_OK;
825
826 }
827
828 NS_IMETHODIMP flGM::Keyframe(PRInt32 square, const nsAString & path, nsAString & _retval) {
829         string * path_s = conv_path(path, false);
830         if (!path_s) { return NS_ERROR_INVALID_ARG; }
831
832         // Open the video file and decode it
833         AVFormatContext *format_ctx;
834         if (av_open_input_file(&format_ctx, path_s->c_str(), 0, 0, 0)) {
835                 return NS_ERROR_NULL_POINTER;
836         }
837         if (0 > av_find_stream_info(format_ctx)) { return NS_ERROR_NULL_POINTER; }
838         //dump_format(format_ctx, 0, path_s->c_str(), false);
839        
840         int stream = -1;
841         for (int i = 0; i < format_ctx->nb_streams; ++i) {
842                 if (CODEC_TYPE_VIDEO == format_ctx->streams[i]->codec->codec_type) {
843                         stream = i;
844                         break;
845                 }
846         }
847         if (-1 == stream) { return NS_ERROR_NULL_POINTER; }
848         AVCodecContext * codec_ctx = format_ctx->streams[stream]->codec;
849        
850         // Load the actual codec we found and open the video stream
851         AVCodec * codec = avcodec_find_decoder(codec_ctx->codec_id);
852         if (!codec) { return NS_ERROR_NULL_POINTER; }
853         if (0 > avcodec_open(codec_ctx, codec)) { return NS_ERROR_NULL_POINTER; }
854         AVFrame * video_frame = avcodec_alloc_frame();
855         AVFrame * img_frame = avcodec_alloc_frame();
856         if (!video_frame || !img_frame) { return NS_ERROR_NULL_POINTER; }
857         int bytes = avpicture_get_size(PIX_FMT_RGB24, codec_ctx->width,
858                 codec_ctx->height);
859         uint8_t * buffer = (uint8_t *)av_malloc(bytes * sizeof(uint8_t));
860         if (!buffer) { return NS_ERROR_NULL_POINTER; }
861         avpicture_fill((AVPicture *)img_frame, buffer, PIX_FMT_RGB24,
862                 codec_ctx->width, codec_ctx->height);
863
864         // Correct the frame rate if FFmpeg reports something stupid-high
865         //   Nokia N95 and N82 report 30000fps
866         double fps = (double)codec_ctx->time_base.den /
867                 (double)codec_ctx->time_base.num;
868         if (30000.0 == fps) { fps /= 1000.0; }
869
870         // Report the duration
871         ostringstream out;
872         out << (format_ctx->duration / 1000000) << "###";
873
874         // Play through 15% of the video
875         int64_t seek = (int64_t)(0.00000015 * (double)format_ctx->duration * fps);
876         AVPacket packet;
877         int have_frame;
878         struct SwsContext *toRGB_convert_ctx;
879        
880         // Seek to the exact frame we want
881         av_seek_frame(format_ctx, stream, seek, 0);
882        
883         while (0 <= av_read_frame(format_ctx, &packet)) {
884                 if (packet.stream_index == stream) {
885                         avcodec_decode_video(codec_ctx, video_frame, &have_frame,
886                                  packet.data, packet.size);
887                        
888                         if (have_frame) {
889                                 //img_convert((AVPicture *)img_frame, PIX_FMT_RGB24,
890                                 //      (AVPicture*)video_frame, codec_ctx->pix_fmt,
891                                 //      codec_ctx->width, codec_ctx->height);
892                                
893                                 toRGB_convert_ctx = sws_getContext(
894                                         codec_ctx->width, codec_ctx->height, codec_ctx->pix_fmt,
895                                         codec_ctx->width, codec_ctx->height, PIX_FMT_RGB24,
896                                         sws_flags, NULL, NULL, NULL);
897                                 if (toRGB_convert_ctx == NULL) {
898                                         return NS_ERROR_NULL_POINTER;
899                                 }
900
901                                 // img_convert parameters are          2 first destination, then 4 source
902                                 // sws_scale   parameters are context, 4 first source,      then 2 destination
903                                 sws_scale(toRGB_convert_ctx,
904                                         video_frame->data, video_frame->linesize, 0, codec_ctx->height,
905                                         img_frame->data, img_frame->linesize);
906                                
907