├── README.md
├── package.json
├── plugin.xml
├── src
├── android
│ └── LibraryHelper.java
└── ios
│ ├── ALAssetsLibrary+CustomPhotoAlbum.h
│ ├── ALAssetsLibrary+CustomPhotoAlbum.m
│ ├── LibraryHelper.h
│ └── LibraryHelper.m
└── www
└── LibraryHelper.js
/README.md:
--------------------------------------------------------------------------------
1 | cordova-library-helper
2 | ======================
3 |
4 | Library Helper is a cordova plugin to help insert videos or images into the native gallery. Also can get duration and thumbnails from a video path. Supports IOS and Android
5 |
6 | Install
7 | =======
8 |
9 | cordova plugin add cordova-library-helper
10 |
11 | Usage
12 | =====
13 | Save Photo to Camera Roll
14 | -------------------------
15 | LibraryHelper.saveImageToLibrary(onSuccess, onError, path, albumName);
16 | Save Video to Camera Roll
17 | -------------------------
18 | LibraryHelper.saveVideoToLibrary(onSuccess, onError, path, albumName);
19 | Get Thumbnail and Duration
20 | ---------------------------
21 | LibraryHelper.getVideoInfo(onSuccess, onError, path);
22 |
23 | function onSuccess(results) {
24 | console.log("Duration: " + results.duration);
25 | console.log("Thumbnail path on disk: " + results.thumbnail);
26 | }
27 |
28 |
29 | Compress Image
30 | ---------------
31 | LibraryHelper.compressImage(onSuccess, onError, path, compressionLevel);
32 |
33 | function onSuccess(results) {
34 | console.log(results.compressedImage);
35 | }
36 |
37 |
38 | Change Log
39 | ==========
40 | 27/04/2016 - Added the ability to get video thumbnails and duration for Android.
41 |
42 | 22/10/2015 - Added the ability to get video thumbnails and duration.
43 |
44 | 26/11/2014 - Converted plugin to use cordova 3.x+
45 |
46 | 13/11/2012 - Optional Callbacks
47 |
48 | 14/11/2012 - Added the ability to add assets to IOS albums
49 |
50 | Author: Cory Thompson (http://coryjthompson.com)
51 |
52 | License: http://www.opensource.org/licenses/mit-license.php The MIT License
53 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Busivid Team",
3 | "cordova": {
4 | "id": "cordova-plugin-library-helper",
5 | "platforms": [
6 | "android",
7 | "ios"
8 | ]
9 | },
10 | "description": "Library Helper is a cordova plugin to help insert videos or images into the native gallery. Supports IOS and Android",
11 | "keywords": [
12 | "cordova",
13 | "ecosystem:cordova",
14 | "library helper",
15 | "cordova-android",
16 | "cordova-ios"
17 | ],
18 | "license": "MIT",
19 | "name": "cordova-plugin-library-helper",
20 | "repository": {
21 | "type": "git",
22 | "url": "https://github.com/busivid/cordova-plugin-library-helper"
23 | },
24 | "version": "1.0.4"
25 | }
26 |
--------------------------------------------------------------------------------
/plugin.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Busivid Team
4 | Library Helper is a cordova plugin to help insert videos or images into the native gallery. Supports IOS and Android
5 |
6 |
7 |
8 |
9 |
10 |
11 | cordova, gallery, video, image
12 | MIT
13 | cordova-plugin-library-helper
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | $PHOTO_LIBRARY_USAGE_DESCRIPTION
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/src/android/LibraryHelper.java:
--------------------------------------------------------------------------------
1 | // LibraryHelper-cordova
2 | package com.busivid.cordova.libraryhelper;
3 |
4 | import java.io.File;
5 | import java.io.FileOutputStream;
6 | import java.io.IOException;
7 | import java.util.Iterator;
8 |
9 | import org.apache.cordova.CallbackContext;
10 | import org.apache.cordova.CordovaPlugin;
11 |
12 | import org.json.JSONArray;
13 | import org.json.JSONException;
14 | import org.json.JSONObject;
15 |
16 | import android.content.Context;
17 | import android.content.Intent;
18 | import android.graphics.Bitmap;
19 | import android.graphics.BitmapFactory;
20 | import android.graphics.Matrix;
21 | import android.media.ExifInterface;
22 | import android.media.MediaMetadataRetriever;
23 | import android.media.ThumbnailUtils;
24 | import android.net.Uri;
25 | import android.provider.MediaStore;
26 | import android.util.Log;
27 | import android.os.Build;
28 |
29 | /**
30 | * Original Code pulled and altered from
31 | * https://github.com/philipp-at-greenqloud/pluginRefreshMedia
32 | *
33 | * @author Philipp Veit (for GreenQloud.com)
34 | */
35 | public class LibraryHelper extends CordovaPlugin {
36 |
37 |
38 | /**
39 | * Executes the request and returns PluginResult.
40 | *
41 | * @param action
42 | * The action to execute.
43 | * @param args
44 | * JSONArray of arguments for the plugin.
45 | * @param callbackId
46 | * The callback id used when calling back into JavaScript.
47 | * @return A object with a status and message.
48 | */
49 | @Override
50 | public boolean execute(String action, JSONArray args, CallbackContext callbackContext) {
51 |
52 | try {
53 |
54 | if (action.equals("saveImageToLibrary") || action.equals("saveVideoToLibrary")) {
55 | String filePath = checkFilePath(args.getString(0));
56 | if (filePath.equals("")) {
57 | callbackContext.error("Error: filePath is empty");
58 | return true; //even though results failed, the action was valid.
59 | }
60 |
61 | boolean results = addToPhotoLibrary(filePath);
62 | if(results) {
63 | callbackContext.success();
64 | } else {
65 | callbackContext.error("Could not add to photo library");
66 | }
67 |
68 | return true;
69 | }
70 |
71 |
72 | if(action.equals("getVideoInfo")) {
73 | String filePath = checkFilePath(args.getString(0));
74 | if (filePath.equals("")) {
75 | callbackContext.error("Error: filePath is empty");
76 | return true; //even though results failed, the action was valid.
77 | }
78 |
79 | JSONObject results = getVideoInfo(filePath);
80 | callbackContext.success(results);
81 | return true;
82 | }
83 |
84 | if(action.equals("compressImage")) {
85 | String filePath = checkFilePath(args.getString(0));
86 | if (filePath.equals("")) {
87 | callbackContext.error("Error: filePath is empty");
88 | return true; //even though results failed, the action was valid.
89 | }
90 |
91 | int jpegCompression = args.optInt(1) > 0
92 | ? args.optInt(1)
93 | : 60;
94 |
95 | JSONObject results = new JSONObject();
96 | results.put("compressedImage", compressImage(filePath, jpegCompression));
97 | callbackContext.success(results);
98 | return true;
99 | }
100 |
101 | return false; //if we got this far, the action wasn't found.
102 |
103 | } catch (JSONException e) {
104 | callbackContext.error("JsonException: " + e.getMessage());
105 | } catch (Exception e) {
106 | callbackContext.error("Error: " + e.getMessage());
107 | }
108 |
109 | return true;
110 | }
111 |
112 | private String checkFilePath(String filePath) {
113 | String returnValue = "";
114 | try {
115 | returnValue = filePath.replaceAll("^file://", "").replaceAll("^file:", "");
116 | } catch (Exception e) {
117 | Log.e("RefreshMedia", "Error with the filePath: " + e.getMessage());
118 | return "";
119 | }
120 |
121 | return returnValue;
122 | }
123 |
124 | private boolean addToPhotoLibrary(String filePath) {
125 | File file = new File(filePath);
126 |
127 | Intent scanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
128 | scanIntent.setData(Uri.fromFile(file));
129 |
130 | // For more information about cordova.getContext() look here:
131 | // http://simonmacdonald.blogspot.com/2012/07/phonegap-android-plugins-sometimes-we.html?showComment=1342400224273#c8740511105206086350
132 | Context context = this.cordova.getActivity().getApplicationContext();
133 | context.sendBroadcast(scanIntent);
134 |
135 | return true;
136 | }
137 |
138 | private JSONObject getVideoInfo(String filePath) {
139 | Context context = this.cordova.getActivity().getApplicationContext();
140 | File file = new File(filePath);
141 |
142 | double duration = 0;
143 | long fileSize = file.length();
144 | float frameRate = 0;
145 | int height = 0;
146 | int width = 0;
147 |
148 | try {
149 | if (isImage(filePath)) {
150 | BitmapFactory.Options bitmapOptions = new BitmapFactory.Options();
151 | bitmapOptions.inJustDecodeBounds = true;
152 | BitmapFactory.decodeFile(filePath, bitmapOptions);
153 | height = bitmapOptions.outHeight;
154 | width = bitmapOptions.outWidth;
155 | } else {
156 | MediaMetadataRetriever retriever = new MediaMetadataRetriever();
157 | retriever.setDataSource(context, Uri.fromFile(file));
158 |
159 | boolean hasVideo = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_VIDEO).equals("yes");
160 | String time = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
161 | if (time != null && hasVideo)
162 | duration = Math.ceil(Double.parseDouble(time) / 1000);
163 |
164 | height = Integer.parseInt(retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT));
165 | width = Integer.parseInt(retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH));
166 |
167 | if (android.os.Build.VERSION.SDK_INT >= 23)
168 | frameRate = Float.parseFloat(retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_CAPTURE_FRAMERATE));
169 | }
170 | } catch (Exception e) {
171 | }
172 |
173 | JSONObject videoInfo = new JSONObject();
174 | try {
175 | videoInfo.put("duration", duration);
176 | videoInfo.put("fileSize", fileSize);
177 | videoInfo.put("frameRate", frameRate);
178 | videoInfo.put("height", height);
179 | // videoInfo.put("image", ); // frame size jpg
180 | videoInfo.put("rotation", getExifRotation(filePath));
181 | videoInfo.put("thumbnail", getThumbnailPath(filePath));
182 | videoInfo.put("width", width);
183 | } catch (Exception e) {
184 | // Do Nothing
185 | }
186 |
187 | return videoInfo;
188 | }
189 |
190 | private String getThumbnailPath(String filePath) {
191 | FileOutputStream out = null;
192 | try {
193 | File outputFile = getWritableFile("png");
194 | out = new FileOutputStream(outputFile);
195 |
196 | Bitmap thumb;
197 | if(isImage(filePath)) {
198 | BitmapFactory.Options bitmapOptions = new BitmapFactory.Options();
199 | bitmapOptions.inJustDecodeBounds = true;
200 | BitmapFactory.decodeFile(filePath, bitmapOptions);
201 |
202 | int rotate = getExifRotation(filePath);
203 |
204 | int height = -1;
205 | int width = -1;
206 |
207 | if(rotate == 90 || rotate == 270) {
208 | height = bitmapOptions.outWidth;
209 | width = bitmapOptions.outHeight;
210 | } else {
211 | height = bitmapOptions.outHeight;
212 | width = bitmapOptions.outWidth;
213 | }
214 |
215 | double aspectHeight = (double)180 / (double)height;
216 | double aspectWidth = (double)320 / (double)width;
217 | double aspectRatio = (aspectWidth > aspectHeight) // get min of aspectWidth and aspectHeight
218 | ? aspectHeight
219 | : aspectWidth;
220 |
221 | int newHeight = (int)Math.round(height * aspectRatio);
222 | int newWidth = (int)Math.round(width * aspectRatio);
223 | int sampleSize = (int)Math.round(1 / aspectRatio);
224 | if(sampleSize%2 != 0) {
225 | sampleSize -= 1;
226 | }
227 |
228 | bitmapOptions = new BitmapFactory.Options();
229 | bitmapOptions.inJustDecodeBounds = false;
230 | bitmapOptions.inSampleSize =sampleSize;
231 | Bitmap bitmap = BitmapFactory.decodeFile(filePath, bitmapOptions);
232 |
233 | if (rotate != 0) {
234 | //rotate bitmap
235 | Matrix matrix = new Matrix();
236 | matrix.setRotate(rotate);
237 | bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, false);
238 | }
239 |
240 | thumb = Bitmap.createScaledBitmap(bitmap, newWidth, newHeight, false);
241 | } else {
242 | thumb = ThumbnailUtils.createVideoThumbnail(filePath, MediaStore.Images.Thumbnails.MINI_KIND);
243 | }
244 |
245 | thumb.compress(Bitmap.CompressFormat.PNG, 100, out);// PNG is a loseless format, compress factor 100 is ignored.
246 | return outputFile.getAbsolutePath();
247 | } catch (Exception e) {
248 | e.printStackTrace();
249 | return null;
250 | } finally {
251 | if(out != null) {
252 | try {
253 | out.close();
254 | } catch (Exception e) {
255 | }
256 | }
257 | }
258 | }
259 |
260 | private int getExifRotation(String filePath) throws IOException {
261 | ExifInterface exif = new ExifInterface(filePath);
262 | int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
263 | int rotate = 0;
264 | switch (orientation) {
265 | case ExifInterface.ORIENTATION_ROTATE_270:
266 | rotate = 270;
267 | break;
268 | case ExifInterface.ORIENTATION_ROTATE_180:
269 | rotate = 180;
270 | break;
271 | case ExifInterface.ORIENTATION_ROTATE_90:
272 | rotate = 90;
273 | break;
274 | }
275 | return rotate;
276 | }
277 |
278 | private String compressImage(String filePath, int jpegCompression) {
279 | FileOutputStream out = null;
280 |
281 | try {
282 | File outputFile = getWritableFile("jpg");
283 | out = new FileOutputStream(outputFile);
284 |
285 | Bitmap inputImage = BitmapFactory.decodeFile(filePath);
286 | int rotate = getExifRotation(filePath);
287 |
288 | if (rotate != 0) {
289 | //rotate bitmap
290 | Matrix matrix = new Matrix();
291 | matrix.setRotate(rotate);
292 | inputImage = Bitmap.createBitmap(inputImage, 0, 0, inputImage.getWidth(), inputImage.getHeight(), matrix, false);
293 | }
294 |
295 | inputImage.compress(Bitmap.CompressFormat.JPEG, jpegCompression, out);
296 | return outputFile.getAbsolutePath();
297 |
298 | } catch (Exception e) {
299 | e.printStackTrace();
300 | return null;
301 | } catch (OutOfMemoryError e) {
302 | return null;
303 | } finally {
304 | if(out != null) {
305 | try {
306 | out.close();
307 | } catch (Exception e) {
308 | }
309 | }
310 | }
311 | }
312 |
313 | private File getWritableFile(String ext) {
314 | int i = 1;
315 | File dataDirectory = cordova.getActivity().getApplicationContext().getFilesDir();
316 |
317 | //hack for galaxy camera 2.
318 | if (Build.MODEL.equals("EK-GC200") && Build.MANUFACTURER.equals("samsung") && new File("/storage/extSdCard/").canRead()) {
319 | dataDirectory = new File("/storage/extSdCard/.com.buzzcard.brandingtool/");
320 | }
321 |
322 | // Create the data directory if it doesn't exist
323 | dataDirectory.mkdirs();
324 | String dataPath = dataDirectory.getAbsolutePath();
325 | File file;
326 | do {
327 | file = new File(dataPath + String.format("/capture_%05d." + ext, i));
328 | i++;
329 | } while (file.exists());
330 | return file;
331 | }
332 |
333 | private static boolean isImage(String filePath) {
334 | filePath = filePath.toLowerCase();
335 | return filePath.endsWith(".png") || filePath.endsWith(".jpg") || filePath.endsWith(".jpeg") || filePath.endsWith(".gif");
336 | }
337 | }
338 |
--------------------------------------------------------------------------------
/src/ios/ALAssetsLibrary+CustomPhotoAlbum.h:
--------------------------------------------------------------------------------
1 | //
2 | // ALAssetsLibrary category to handle a custom photo album
3 | //
4 | // Created by Marin Todorov on 10/26/11.
5 | // Copyright (c) 2011 Marin Todorov. All rights reserved.
6 | //
7 | #import
8 | #import
9 |
10 | @interface ALAssetsLibrary (CustomPhotoAlbum)
11 |
12 | // |image|: the target image to be saved
13 | // |albumName|: custom album name
14 | // |completionBlock|: block to be executed when succeed to write the image data
15 | // to the assets library (camera roll)
16 | // |failureBlock|: block to be executed when failed to add the asset to the
17 | // custom photo album
18 | -(void)saveImage:(UIImage *)image toAlbum:(NSString *)albumName completionBlock:(ALAssetsLibraryWriteImageCompletionBlock)completionBlock failureBlock:(ALAssetsLibraryAccessFailureBlock)failureBlock;
19 |
20 | // |videoUrl|: the target video to be saved
21 | // |albumName|: custom album name
22 | // |completionBlock|: block to be executed when succeed to write the image data
23 | // to the assets library (camera roll)
24 | // |failureBlock|: block to be executed when failed to add the asset to the
25 | // custom photo album
26 | -(void)saveVideo:(NSURL *)videoUrl toAlbum:(NSString *)albumName completionBlock:(ALAssetsLibraryWriteImageCompletionBlock)completionBlock failureBlock:(ALAssetsLibraryAccessFailureBlock)failureBlock;
27 | @end
--------------------------------------------------------------------------------
/src/ios/ALAssetsLibrary+CustomPhotoAlbum.m:
--------------------------------------------------------------------------------
1 | //
2 | // ALAssetsLibrary category to handle a custom photo album
3 | //
4 | // Created by Marin Todorov on 10/26/11.
5 | // Copyright (c) 2011 Marin Todorov. All rights reserved.
6 | //
7 | #import "ALAssetsLibrary+CustomPhotoAlbum.h"
8 |
9 | @interface ALAssetsLibrary (Private)
10 |
11 | - (void)_addAssetURL:(NSURL *)assetURL toAlbum:(NSString *)albumName failureBlock:(ALAssetsLibraryAccessFailureBlock)failureBlock;
12 |
13 | @end
14 |
15 | @implementation ALAssetsLibrary (CustomPhotoAlbum)
16 |
17 | #pragma mark - Public Method
18 |
19 | - (void)saveImage:(UIImage *)image toAlbum:(NSString *)albumName completionBlock:(ALAssetsLibraryWriteImageCompletionBlock)completionBlock failureBlock:(ALAssetsLibraryAccessFailureBlock)failureBlock {
20 | // write the image data to the assets library (camera roll)
21 | [self writeImageToSavedPhotosAlbum:image.CGImage orientation:(ALAssetOrientation)image.imageOrientation completionBlock:^(NSURL *assetURL, NSError *error) {
22 | // run the completion block for writing image to saved
23 | // photos album
24 | completionBlock(assetURL, error);
25 |
26 | // if an error occured, do not try to add the asset to
27 | // the custom photo album
28 | if (error != nil)
29 | return;
30 |
31 | // add the asset to the custom photo album
32 | [self _addAssetURL:assetURL toAlbum:albumName failureBlock:failureBlock];
33 | }];
34 | }
35 |
36 | - (void)saveVideo:(NSURL *)videoUrl toAlbum:(NSString *)albumName completionBlock:(ALAssetsLibraryWriteImageCompletionBlock)completionBlock failureBlock:(ALAssetsLibraryAccessFailureBlock)failureBlock {
37 | // write the video to the assets library (camera roll)
38 | [self writeVideoAtPathToSavedPhotosAlbum: videoUrl completionBlock:^(NSURL *assetURL, NSError *error) {
39 | // run the completion block for writing image to saved
40 | // photos album
41 | completionBlock(assetURL, error);
42 |
43 | // if an error occured, do not try to add the asset to
44 | // the custom photo album
45 | if (error != nil)
46 | return;
47 |
48 | // add the asset to the custom photo album
49 | [self _addAssetURL:assetURL toAlbum:albumName failureBlock:failureBlock];
50 | }];
51 | }
52 |
53 | #pragma mark - Private Method
54 |
55 | - (void)_addAssetURL:(NSURL *)assetURL toAlbum:(NSString *)albumName failureBlock:(ALAssetsLibraryAccessFailureBlock)failureBlock {
56 | __block BOOL albumWasFound = NO;
57 |
58 | ALAssetsLibraryGroupsEnumerationResultsBlock enumerationBlock;
59 | enumerationBlock = ^(ALAssetsGroup *group, BOOL *stop) {
60 | // compare the names of the albums
61 | if ([albumName compare:[group valueForProperty:ALAssetsGroupPropertyName]] == NSOrderedSame) {
62 | // target album is found
63 | albumWasFound = YES;
64 |
65 | // get a hold of the photo's asset instance
66 | [self assetForURL:assetURL
67 | resultBlock:^(ALAsset *asset) {
68 | // add photo to the target album
69 | [group addAsset:asset];
70 | }
71 | failureBlock:failureBlock
72 | ];
73 |
74 | // album was found, bail out of the method
75 | return;
76 | }
77 |
78 | if (group == nil && albumWasFound == NO) {
79 | // photo albums are over, target album does not exist, thus create it
80 |
81 | // Since you use the assets library inside the block,
82 | // ARC will complain on compile time that there’s a retain cycle.
83 | // When you have this – you just make a weak copy of your object.
84 | //
85 | // __weak ALAssetsLibrary * weakSelf = self;
86 | //
87 | // by @Marin.
88 | //
89 | // I don't use ARC right now, and it leads a warning.
90 | // by @Kjuly
91 | ALAssetsLibrary * weakSelf = self;
92 |
93 | // create new assets album
94 | [self addAssetsGroupAlbumWithName:albumName
95 | resultBlock:^(ALAssetsGroup *group) {
96 | // get the photo's instance
97 | [weakSelf assetForURL:assetURL
98 | resultBlock:^(ALAsset *asset) {
99 | // add photo to the newly created album
100 | [group addAsset:asset];
101 | }
102 | failureBlock:failureBlock
103 | ];
104 | }
105 | failureBlock:failureBlock
106 | ];
107 |
108 | // should be the last iteration anyway, but just in case
109 | return;
110 | }
111 | };
112 |
113 | // search all photo albums in the library
114 | [self enumerateGroupsWithTypes:ALAssetsGroupAlbum usingBlock:enumerationBlock failureBlock:failureBlock];
115 | }
116 |
117 | @end
118 |
--------------------------------------------------------------------------------
/src/ios/LibraryHelper.h:
--------------------------------------------------------------------------------
1 | // LibraryHelper-cordova
2 | // http://github.com/coryjthompson/LibraryHelper-cordova
3 | //
4 | #import
5 | #import
6 | #import
7 | #import
8 |
9 | @interface LibraryHelper : CDVPlugin
10 | - (void)compressImage:(CDVInvokedUrlCommand *)command;
11 | - (void)getVideoInfo:(CDVInvokedUrlCommand *)command;
12 | - (void)saveImageToLibrary:(CDVInvokedUrlCommand *)command;
13 | - (void)saveVideoToLibrary:(CDVInvokedUrlCommand *)command;
14 | @end
15 |
--------------------------------------------------------------------------------
/src/ios/LibraryHelper.m:
--------------------------------------------------------------------------------
1 | // LibraryHelper-cordova
2 | // http://github.com/coryjthompson/LibraryHelper-cordova
3 | //
4 | #import "LibraryHelper.h"
5 | #import "ALAssetsLibrary+CustomPhotoAlbum.h"
6 |
7 | @implementation LibraryHelper
8 | - (void)assetSavedToLibrary:(NSError *)error callbackId:(NSString*)callbackId {
9 | CDVPluginResult* pluginResult = error
10 | ? [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString: [error description]]
11 | : [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:@"Successfully Added to Library"];
12 |
13 | // [self writeJavascript: [pluginResult toSuccessCallbackString: self.callbackId]];
14 | [self.commandDelegate sendPluginResult:pluginResult callbackId:callbackId];
15 | }
16 |
17 | - (void)saveImageToLibrary:(CDVInvokedUrlCommand *)command {
18 | NSString* path = [command.arguments objectAtIndex: 0];
19 | NSString* albumName = [command.arguments objectAtIndex: 1];
20 |
21 | CDVPluginResult* pluginResult = nil;
22 |
23 | if(!path) {
24 | pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString: @"Path cannot be null"];
25 | [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
26 | return;
27 | }
28 |
29 | UIImage *image = [UIImage imageWithContentsOfFile:path];
30 | if(!image){
31 | pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString: @"Path is not a valid image file"];
32 | [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
33 | return;
34 | }
35 |
36 | //UIImageWriteToSavedPhotosAlbum(image, self, @selector(assetSavedToLibrary:didFinishSavingWithError:contextInfo:), nil);
37 | ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
38 | [library saveImage:image toAlbum:albumName completionBlock:^(NSURL *assetURL, NSError *error) {
39 | [self assetSavedToLibrary:error callbackId:command.callbackId];
40 | } failureBlock:^(NSError *error) {
41 | [self assetSavedToLibrary:error callbackId:command.callbackId];
42 | }];
43 | }
44 |
45 | - (void)saveVideoToLibrary:(CDVInvokedUrlCommand *)command {
46 | NSString* path = [command.arguments objectAtIndex: 0];
47 | NSString* albumName = [command.arguments objectAtIndex: 1];
48 |
49 | CDVPluginResult* pluginResult = nil;
50 |
51 | if (!path) {
52 | pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString: @"Path not a valid video file"];
53 | [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
54 | return;
55 | }
56 |
57 | NSURL *url = [NSURL URLWithString:[path stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
58 | if (!UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(path)) {
59 | pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString: @"Path is not a valid video file"];
60 | [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
61 | return;
62 | }
63 |
64 | ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
65 | [library saveVideo:url toAlbum:albumName completionBlock:^(NSURL *assetURL, NSError *error) {
66 | [self assetSavedToLibrary:error callbackId:command.callbackId];
67 | } failureBlock:^(NSError *error){
68 | [self assetSavedToLibrary:error callbackId:command.callbackId];
69 | }];
70 | }
71 |
72 | - (void)compressImage:(CDVInvokedUrlCommand *)command {
73 | NSString* path = [command.arguments objectAtIndex: 0];
74 | int jpegCompression = ([command.arguments objectAtIndex:1])
75 | ? [[command.arguments objectAtIndex:1] intValue]
76 | : 60;
77 |
78 | NSString *outputImagePath = @""; // nil is not permitted in NSDictionary;
79 | NSString *lowerPath = [path lowercaseString];
80 | if ([lowerPath hasSuffix:@".bmp"] || [lowerPath hasSuffix:@".jpg"])
81 | {
82 | // Determine outputImagePath
83 | NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES);
84 | NSString *appDocumentPath = [paths objectAtIndex:0];
85 | NSString *tmpFileName = [[NSProcessInfo processInfo] globallyUniqueString];
86 | outputImagePath = [[appDocumentPath stringByAppendingPathComponent:tmpFileName] stringByAppendingString:@".jpg"];
87 |
88 | // Load the Image
89 | UIImage *image = [[UIImage alloc]initWithContentsOfFile:path];
90 |
91 | // Compress the image
92 | [UIImageJPEGRepresentation(image, (float)jpegCompression/100.0f) writeToFile:outputImagePath atomically:YES];
93 | }
94 |
95 | NSDictionary *results = @{
96 | @"compressedImage" : outputImagePath
97 | };
98 |
99 | CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:results];
100 | [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
101 | }
102 |
103 | - (UIImage *)scaleImageToSize:(UIImage*)image maxSize:(CGSize)newSize {
104 | CGFloat aspectWidth = newSize.width / image.size.width;
105 | CGFloat aspectHeight = newSize.height / image.size.height;
106 | CGFloat aspectRatio = MIN ( aspectWidth, aspectHeight );
107 |
108 | CGRect scaledImageRect = CGRectZero;
109 | scaledImageRect.size.width = image.size.width * aspectRatio;
110 | scaledImageRect.size.height = image.size.height * aspectRatio;
111 |
112 | UIGraphicsBeginImageContextWithOptions(scaledImageRect.size, NO, 0 );
113 | [image drawInRect:scaledImageRect];
114 |
115 | UIImage* scaledImage = UIGraphicsGetImageFromCurrentImageContext();
116 | UIGraphicsEndImageContext();
117 |
118 | return scaledImage;
119 | }
120 |
121 | //Many thanks to @jbavari
122 | //Parts of this code taken from:
123 | //https://github.com/jbavari/cordova-plugin-video-editor/
124 | - (void)getVideoInfo:(CDVInvokedUrlCommand *)command {
125 | NSString* srcVideoPath = [command.arguments objectAtIndex: 0];
126 |
127 | CDVPluginResult* pluginResult = nil;
128 | if (!srcVideoPath) {
129 | pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString: @"Path not a valid video file"];
130 | [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
131 | return;
132 | }
133 |
134 | NSURL *srcVideoUrl = [srcVideoPath rangeOfString:@"://"].location == NSNotFound
135 | ? [NSURL URLWithString:[[@"file://localhost" stringByAppendingString:srcVideoPath] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]
136 | : [NSURL URLWithString:[srcVideoPath stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
137 |
138 | AVURLAsset *srcAsset = [AVURLAsset assetWithURL: srcVideoUrl];
139 | NSString *tmpFileName = [[NSProcessInfo processInfo] globallyUniqueString];
140 |
141 | // Get App Document path
142 | NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES);
143 | NSString *appDocumentPath = [paths objectAtIndex:0];
144 |
145 | // Get Duration
146 | Float64 duration = CMTimeGetSeconds(srcAsset.duration);
147 |
148 | // Get Resolution
149 | AVAssetTrack *videoTrack = nil;
150 |
151 | // Get Filesize
152 | NSNumber *fileSize = nil;
153 | [srcVideoUrl getResourceValue:&fileSize forKey:NSURLFileSizeKey error:nil];
154 |
155 | // Generate Image
156 | UIImage *image;
157 | NSString *imagePath;
158 | if (duration == 0) {
159 | image = [UIImage imageWithData:[NSData dataWithContentsOfURL:srcVideoUrl]];
160 | image = [self rotateImage:image];
161 |
162 | imagePath = srcVideoPath;
163 | } else {
164 | AVAssetImageGenerator* generator = [AVAssetImageGenerator assetImageGeneratorWithAsset:srcAsset];
165 | generator.appliesPreferredTrackTransform = true;
166 |
167 | int frameLocation = 1;
168 | int frameTimeStart = MIN(duration, 3);
169 | CGImageRef frameRef = [generator copyCGImageAtTime:CMTimeMake(frameTimeStart,frameLocation) actualTime:nil error:nil];
170 | image = [UIImage imageWithCGImage:frameRef];
171 |
172 | // Optionally Save Image
173 | imagePath = [[appDocumentPath stringByAppendingPathComponent:tmpFileName] stringByAppendingString:@".jpg"];
174 | [UIImageJPEGRepresentation(image, 0.96f) writeToFile:imagePath atomically:YES];
175 | }
176 |
177 | NSNumber *frameRate = [NSNumber numberWithInt:0]; //overwritten if video
178 | NSNumber *height;
179 | NSNumber *width;
180 |
181 | NSArray *videoTracks = [srcAsset tracksWithMediaType:AVMediaTypeVideo];
182 | if ([videoTracks count] > 0) {
183 | videoTrack = [videoTracks objectAtIndex:0];
184 |
185 | CGSize trackDimensions = {
186 | .width = 0.0,
187 | .height = 0.0,
188 | };
189 | trackDimensions = [videoTrack naturalSize];
190 |
191 | height = [NSNumber numberWithInt:trackDimensions.height];
192 | width = [NSNumber numberWithInt:trackDimensions.width];
193 |
194 | // GET FPS
195 | frameRate = [NSNumber numberWithFloat:[videoTrack nominalFrameRate]];
196 | } else {
197 | height = [NSNumber numberWithInt:image.size.height];
198 | width = [NSNumber numberWithInt:image.size.width];
199 | }
200 |
201 | // Generate Thumbnail
202 | UIImage *thumbnail = [self scaleImageToSize: image maxSize: CGSizeMake(320, 180)];
203 | NSString *thumbnailPath = [[appDocumentPath stringByAppendingPathComponent:tmpFileName] stringByAppendingString:@"_thumb.jpg"];
204 | [UIImageJPEGRepresentation(thumbnail, 0.96f) writeToFile:thumbnailPath atomically:YES];
205 |
206 | NSDictionary *results = @{
207 | @"duration" : [NSNumber numberWithLong: ceil(duration)],
208 | @"fileSize": fileSize,
209 | @"frameRate": frameRate,
210 | @"height": height,
211 | @"image": imagePath,
212 | @"rotation": @0,
213 | @"thumbnail": thumbnailPath,
214 | @"width": width
215 | };
216 |
217 | pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:results];
218 | [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
219 | }
220 |
221 | - (UIImage *)rotateImage:(UIImage *) image {
222 | CGImageRef imgRef = image.CGImage;
223 | CGFloat width = CGImageGetWidth(imgRef);
224 | CGFloat height = CGImageGetHeight(imgRef);
225 |
226 | CGRect bounds = CGRectMake(0, 0, width, height);
227 | CGFloat scaleRatio = bounds.size.width / width;
228 |
229 | CGFloat boundHeight;
230 | CGSize imageSize = CGSizeMake(width, height);
231 | UIImageOrientation orient = image.imageOrientation;
232 | CGAffineTransform transform = CGAffineTransformIdentity;
233 |
234 | switch(orient) {
235 | case UIImageOrientationUp: //EXIF = 1
236 | transform = CGAffineTransformIdentity;
237 | break;
238 |
239 | case UIImageOrientationUpMirrored: //EXIF = 2
240 | transform = CGAffineTransformMakeTranslation(imageSize.width, 0.0);
241 | transform = CGAffineTransformScale(transform, -1.0, 1.0);
242 | break;
243 |
244 | case UIImageOrientationDown: //EXIF = 3
245 | transform = CGAffineTransformMakeTranslation(imageSize.width, imageSize.height);
246 | transform = CGAffineTransformRotate(transform, M_PI);
247 | break;
248 |
249 | case UIImageOrientationDownMirrored: //EXIF = 4
250 | transform = CGAffineTransformMakeTranslation(0.0, imageSize.height);
251 | transform = CGAffineTransformScale(transform, 1.0, -1.0);
252 | break;
253 |
254 | case UIImageOrientationLeftMirrored: //EXIF = 5
255 | boundHeight = bounds.size.height;
256 | bounds.size.height = bounds.size.width;
257 | bounds.size.width = boundHeight;
258 | transform = CGAffineTransformMakeTranslation(imageSize.height, imageSize.width);
259 | transform = CGAffineTransformScale(transform, -1.0, 1.0);
260 | transform = CGAffineTransformRotate(transform, 3.0 * M_PI / 2.0);
261 | break;
262 |
263 | case UIImageOrientationLeft: //EXIF = 6
264 | boundHeight = bounds.size.height;
265 | bounds.size.height = bounds.size.width;
266 | bounds.size.width = boundHeight;
267 | transform = CGAffineTransformMakeTranslation(0.0, imageSize.width);
268 | transform = CGAffineTransformRotate(transform, 3.0 * M_PI / 2.0);
269 | break;
270 |
271 | case UIImageOrientationRightMirrored: //EXIF = 7
272 | boundHeight = bounds.size.height;
273 | bounds.size.height = bounds.size.width;
274 | bounds.size.width = boundHeight;
275 | transform = CGAffineTransformMakeScale(-1.0, 1.0);
276 | transform = CGAffineTransformRotate(transform, M_PI / 2.0);
277 | break;
278 |
279 | case UIImageOrientationRight: //EXIF = 8
280 | boundHeight = bounds.size.height;
281 | bounds.size.height = bounds.size.width;
282 | bounds.size.width = boundHeight;
283 | transform = CGAffineTransformMakeTranslation(imageSize.height, 0.0);
284 | transform = CGAffineTransformRotate(transform, M_PI / 2.0);
285 | break;
286 |
287 | default:
288 | [NSException raise:NSInternalInconsistencyException format:@"Invalid image orientation"];
289 | }
290 |
291 | UIGraphicsBeginImageContext(bounds.size);
292 |
293 | CGContextRef context = UIGraphicsGetCurrentContext();
294 |
295 | if (orient == UIImageOrientationRight || orient == UIImageOrientationLeft) {
296 | CGContextScaleCTM(context, -scaleRatio, scaleRatio);
297 | CGContextTranslateCTM(context, -height, 0);
298 | } else {
299 | CGContextScaleCTM(context, scaleRatio, -scaleRatio);
300 | CGContextTranslateCTM(context, 0, -height);
301 | }
302 |
303 | CGContextConcatCTM(context, transform);
304 |
305 | CGContextDrawImage(UIGraphicsGetCurrentContext(), CGRectMake(0, 0, width, height), imgRef);
306 | UIImage *imageCopy = UIGraphicsGetImageFromCurrentImageContext();
307 | UIGraphicsEndImageContext();
308 |
309 | return imageCopy;
310 | }
311 | @end
312 |
--------------------------------------------------------------------------------
/www/LibraryHelper.js:
--------------------------------------------------------------------------------
1 | // LibraryHelper-cordova
2 | // https://github.com/coryjthompson/LibraryHelper-cordova
3 | var exec = require('cordova/exec');
4 |
5 | module.exports = {
6 | compressImage: function (onSuccess, onError, path, jpegCompression) {
7 | exec(onSuccess, onError, 'LibraryHelper', 'compressImage', [normalizePath(path), jpegCompression]);
8 | },
9 | getVideoInfo: function(onSuccess, onError, path) {
10 | exec(onSuccess, onError, 'LibraryHelper', 'getVideoInfo', [normalizePath(path)]);
11 | },
12 | saveImageToLibrary: function (onSuccess, onError, path, albumName) {
13 | exec(onSuccess, onError, 'LibraryHelper', 'saveImageToLibrary', [normalizePath(path), albumName]);
14 | },
15 | saveVideoToLibrary: function (onSuccess, onError, path, albumName) {
16 | exec(onSuccess, onError, 'LibraryHelper', 'saveVideoToLibrary', [normalizePath(path), albumName]);
17 | }
18 | };
19 |
20 | function normalizePath(path) {
21 | return path.indexOf('file://') === 0 ? path.slice(7) : path;
22 | }
23 |
--------------------------------------------------------------------------------