├── 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 | --------------------------------------------------------------------------------