瀏覽代碼

合并多个视频方法

jsonwang 3 年之前
父節點
當前提交
ce04b40b8f

+ 457 - 0
BFFramework/Classes/PQGPUImage/akfilters/Tools/NXFileManager.h

@@ -0,0 +1,457 @@
+//
+//  NXFFileManager.h
+//  NXlib
+//
+//  Created by AK on 15/8/30.
+//  Copyright (c) 2015年 AK. All rights reserved.
+//
+
+/*
+
+ 本类功能, 文件操作.(ios, mac)
+
+ 一,iOS目录结构说明
+ 1,沙盒目录结构
+ ├── Documents - 存储用户数据或其它应该定期备份的
+ ├── Library
+ │   ├── Caches -
+ 用于存放应用程序专用的支持文件,保存应用程序再次启动过程中需要的信息
+ │   │   └── Snapshots
+ │   │       └── com.youyouxingyuan.re
+ │   │           ├── A85B73F0-26A8-44E4-A761-446CAB8DAB38@2x.png
+ │   │           └── BFAD5885-B767-4320-9A4B-555EC881C50D@2x.png
+ │   └── Preferences - 偏好设置文件 NSUserDefaults 保存的数据
+ └── tmp - 这个目录用于存放临时文件,保存应用程序再次启动过程中不需要的信息
+
+ 2,在iOS8之后,应用每一次重启,沙盒路径都动态的发生了变化但不用担心数据问题,苹果会把你上一个路径中的数据转移到你新的路径中。你上一个路径也会被苹果毫无保留的删除,只保留最新的路径。
+
+
+ */
+//文件操作
+#import <Foundation/Foundation.h>
+#import <UIKit/UIKit.h>
+
+@interface NXFileManager : NSObject<NSFileManagerDelegate>
+
+#pragma mark - 常用路径
+
+/**
+ *  app bundle path 每次运行会改变
+ *  //mobile/Containers/Bundle/Application/84C95A20-21A7-4E39-AFFC-E63FE5586BDA/YOSticker.app>
+ *  //mobile/Containers/Bundle/Application/3BC8A0E0-0177-400A-AECE-6B940F8F2DF6/YOSticker.app>
+ *
+ *  @return mainBundle
+ */
++ (NSBundle *)getMainBundle;
+
++ (NSString *)getMainBundleRes;
+
+/**
+ *  获取Document path
+ *
+ *  @return Document path
+ */
++ (NSString *)getDocumentDir;
+
+/**
+ *  获取Cache path
+ *
+ *  @return Cache path
+ */
++ (NSString *)getCacheDir;
+
+/**
+ *  获取temp path
+ *
+ *  @return temp 路径
+ */
++ (NSString *)getTmpDir;
+
+/**
+ *  得到文件所在目录
+ *
+ *  @param filePath 文件路径
+ *
+ *  @return 文件所在目录
+ */
++ (NSString *)deletingLastPathComponent:(NSString *)filePath;
+
+/**
+ *  校验目录路径是否有效  如果目录不存在 会自动创建
+ *
+ *  @param dir 目录路径
+ *
+ *  @return 目录路径
+ */
++ (NSString *)validateDir:(NSString *)dir;
+
+/**
+ *  文件路径校验是否存在
+ *
+ *  @param filePath 文件路径
+ *
+ *  @return YES 存在
+ */
++ (BOOL)validateFile:(NSString *)filePath;
+
+/**
+ *  通过文件名返回Documents 目录下的全路径
+ *
+ *  @param filename 文件名
+ *
+ *  @return 返回Doc下的全路径
+ */
++ (NSString *)getPathForDocuments:(NSString *)filename;
+
+/**
+ *  通过文件名返回Documents 目录下的全路径
+ *
+ *  @param filename 文件名
+ *  @param dir      目录名可以是多层目录如"aaa/bbb"
+ *
+ *  @return 全路径
+ */
++ (NSString *)getPathForDocuments:(NSString *)filename inDir:(NSString *)dir;
+
+#pragma mark -  get文件的属性
+/*
+     NSFileAppendOnly: 文件是否只读
+     NSFileBusy: 文件是否繁忙
+     NSFileCreationDate: 文件创建日期
+     NSFileOwnerAccountName: 文件所有者的名字
+     NSFileGroupOwnerAccountName: 文件所有组的名字
+     NSFileDeviceIdentifier: 文件所在驱动器的标示符
+     NSFileExtensionHidden: 文件后缀是否隐藏
+     NSFileGroupOwnerAccountID: 文件所有组的group ID
+     NSFileHFSCreatorCode: 文件的HFS创建者的代码
+     NSFileHFSTypeCode: 文件的HFS类型代码
+     NSFileImmutable: 文件是否可以改变
+     NSFileModificationDate: 文件修改日期
+     NSFileOwnerAccountID: 文件所有者的ID
+     NSFilePosixPermissions: 文件的Posix权限
+     NSFileReferenceCount: 文件的链接数量
+     NSFileSize: 文件的字节
+     NSFileSystemFileNumber: 文件在文件系统的文件数量
+     NSFileType: 文件类型
+     NSDirectoryEnumerationSkipsSubdirectoryDescendants:
+   浅层的枚举,不会枚举子目录
+     NSDirectoryEnumerationSkipsPackageDescendants: 不会扫描pakages的内容
+     NSDirectoryEnumerationSkipsHiddenFile: 不会扫描隐藏文件
+ */
+
+/**
+ *   取文件的所有属性
+ *
+ *  @param path 文件path
+ *
+ *  @return 文件的所有属性(MAC)
+ */
++ (NSDictionary *)attributesOfItemAtPath:(NSString *)path;
+
+/**
+ *  取文件的所有属性 &error
+ *
+ *  @param path  文件path
+ *  @param error &error
+ *
+ *  @return 文件的所有属性
+ */
++ (NSDictionary *)attributesOfItemAtPath:(NSString *)path error:(NSError **)error;
+/**
+ *  取文件属性
+ *
+ *  @param path 文件目录
+ *  @param key  属性KEY
+ *
+ *  @return 属性 value
+ */
++ (id)attributeOfItemAtPath:(NSString *)path forKey:(NSString *)key;
+
+/**
+ *  取文件属性
+ *
+ *  @param path  文件目录
+ *  @param key   属性KEY
+ *  @param error &error
+ *
+ *  @return 属性 value
+ */
++ (id)attributeOfItemAtPath:(NSString *)path forKey:(NSString *)key error:(NSError **)error;
+
+#pragma mark - 文件C-M-D-L操作
+/**
+ *  复制一个文件到指定目录
+ *
+ *  @param path 原目录
+ *  @param toPath 目标目录
+ *
+ *  @return 操作结果
+ */
++ (BOOL)copyItemAtPath:(NSString *)path toPath:(NSString *)toPath;
+
++ (BOOL)copyItemAtPath:(NSString *)path toPath:(NSString *)path error:(NSError **)error;
+
+/**
+ *  创建文件夹
+ *
+ *  @param path
+ *
+ *  @return 创建文件夹结果
+ */
+
+/**
+ 创建文件所在目录 如~/aaaa/xxxx.txt 生成的为 文件夹 ~/aaaa/
+
+ @param path 文件所在目录
+ @return 是否创建成功
+ */
++ (BOOL)createDirectoriesForFileAtPath:(NSString *)path;
++ (BOOL)createDirectoriesForFileAtPath:(NSString *)path error:(NSError **)error;
+
+
+/**
+ 创建目录 如~/aaaa/xxxx 生成的为 文件夹 ~/aaaa/xxxx/   (注意 即使指定xxxx的类型 生成的也是文件夹)
+
+ @param path 目录
+ @return 是否创建成功
+ */
++ (BOOL)createDirectoriesForPath:(NSString *)path;
++ (BOOL)createDirectoriesForPath:(NSString *)path error:(NSError **)error;
+
+
+/**
+ 
+ 创建指定文件 如~/aaaa/xxxx.txt 生成的为 文件 ~/aaaa/xxxx.txt   (注意 即使不指定xxxx的类型 生成的也是文件)
+ @param path 文件路径
+ @return 是否创建成功
+ */
++ (BOOL)createFileAtPath:(NSString *)path;
++ (BOOL)createFileAtPath:(NSString *)path error:(NSError **)error;
+
++ (BOOL)createFileAtPath:(NSString *)path withContent:(NSObject *)content;
++ (BOOL)createFileAtPath:(NSString *)path withContent:(NSObject *)content error:(NSError **)error;
+
+#pragma mark -
+/**
+ *  取文件的创建时间
+ *
+ *  @param path 文件路径
+ *
+ *  @return 时间
+ */
++ (NSDate *)creationDateOfItemAtPath:(NSString *)path;
++ (NSDate *)creationDateOfItemAtPath:(NSString *)path error:(NSError **)error;
+
+/**
+ *  清空Caches目录
+ *
+ *  @return 清空结果
+ */
++ (BOOL)emptyCachesDirectory;
+/**
+ *  清空Temporary目录
+ *
+ *  @return 清空结果
+ */
++ (BOOL)emptyTemporaryDirectory;
+
+/**
+ *  判断文件是否存在
+ *
+ *  @param path 文件路径
+ *
+ *  @return 存在 返回YES
+ */
++ (BOOL)existsItemAtPath:(NSString *)path;
+
+/**
+ *  判断路径是不是目录 类型
+ *
+ *  @param path 路径
+ *
+ *  @return YES 是目录
+ */
++ (BOOL)isDirectoryItemAtPath:(NSString *)path;
++ (BOOL)isDirectoryItemAtPath:(NSString *)path error:(NSError **)error;
+
++ (BOOL)isEmptyItemAtPath:(NSString *)path;
++ (BOOL)isEmptyItemAtPath:(NSString *)path error:(NSError **)error;
+
+/**
+ *  判断路径是不是一个文件类型
+ *
+ *  @param path 路径
+ *
+ *  @return YES 是文件
+ */
++ (BOOL)isFileItemAtPath:(NSString *)path;
++ (BOOL)isFileItemAtPath:(NSString *)path error:(NSError **)error;
+
+// 判断某个路径下文件是否可执行
++ (BOOL)isExecutableItemAtPath:(NSString *)path;
+// 判断某个路径下文件是否可读
++ (BOOL)isReadableItemAtPath:(NSString *)path;
+// 判断某个路径下文件是否可写
++ (BOOL)isWritableItemAtPath:(NSString *)path;
+
+/**
+ *  获取某个路径下的所有子路径
+ *
+ *  @param path 路径
+ *
+ *  @return 子路径数组
+ */
++ (NSArray *)listDirectoriesInDirectoryAtPath:(NSString *)path;
++ (NSArray *)listDirectoriesInDirectoryAtPath:(NSString *)path deep:(BOOL)deep;
+
++ (NSArray *)listFilesInDirectoryAtPath:(NSString *)path;
++ (NSArray *)listFilesInDirectoryAtPath:(NSString *)path deep:(BOOL)deep;
+
++ (NSArray *)listFilesInDirectoryAtPath:(NSString *)path withExtension:(NSString *)extension;
++ (NSArray *)listFilesInDirectoryAtPath:(NSString *)path withExtension:(NSString *)extension deep:(BOOL)deep;
+
++ (NSArray *)listFilesInDirectoryAtPath:(NSString *)path withPrefix:(NSString *)prefix;
++ (NSArray *)listFilesInDirectoryAtPath:(NSString *)path withPrefix:(NSString *)prefix deep:(BOOL)deep;
+
++ (NSArray *)listFilesInDirectoryAtPath:(NSString *)path withSuffix:(NSString *)suffix;
++ (NSArray *)listFilesInDirectoryAtPath:(NSString *)path withSuffix:(NSString *)suffix deep:(BOOL)deep;
+
++ (NSArray *)listItemsInDirectoryAtPath:(NSString *)path deep:(BOOL)deep;
+
+/**
+ *  移动某个路径下文件到另一个路径
+ *
+ *  @param path 路径
+ *  @param toPath 目标路径
+ *  @return 是否移动成功
+ */
++ (BOOL)moveItemAtPath:(NSString *)path toPath:(NSString *)toPath;
++ (BOOL)moveItemAtPath:(NSString *)path toPath:(NSString *)toPath error:(NSError **)error;
+
+// location of application support files
++ (NSString *)pathForApplicationSupportDirectory;
++ (NSString *)pathForApplicationSupportDirectoryWithPath:(NSString *)path;
+
+// location of discardable cache files (Library/Caches)
++ (NSString *)pathForCachesDirectory;
++ (NSString *)pathForCachesDirectoryWithPath:(NSString *)path;
+
+// documents (Documents)
++ (NSString *)pathForDocumentsDirectory;
++ (NSString *)pathForDocumentsDirectoryWithPath:(NSString *)path;
+
+// various documentation, support, and configuration files, resources (Library)
++ (NSString *)pathForLibraryDirectory;
++ (NSString *)pathForLibraryDirectoryWithPath:(NSString *)path;
+
+// 沙盒路径
++ (NSString *)pathForMainBundleDirectory;
++ (NSString *)pathForMainBundleDirectoryWithPath:(NSString *)path;
+
+// plist 文件路径
++ (NSString *)pathForPlistNamed:(NSString *)name;
+
+// 临时文件路径
++ (NSString *)pathForTemporaryDirectory;
++ (NSString *)pathForTemporaryDirectoryWithPath:(NSString *)path;
+
+#pragma mark - 以不同的形式读写文件的内容
+// 读取指定路径中的文件内容
++ (NSString *)readFileAtPath:(NSString *)path;
++ (NSString *)readFileAtPath:(NSString *)path error:(NSError **)error;
+
++ (NSArray *)readFileAtPathAsArray:(NSString *)path;
+
++ (NSObject *)readFileAtPathAsCustomModel:(NSString *)path;
+
++ (NSData *)readFileAtPathAsData:(NSString *)path;
++ (NSData *)readFileAtPathAsData:(NSString *)path error:(NSError **)error;
+
++ (NSDictionary *)readFileAtPathAsDictionary:(NSString *)path;
+
++ (UIImage *)readFileAtPathAsImage:(NSString *)path;
++ (UIImage *)readFileAtPathAsImage:(NSString *)path error:(NSError **)error;
+
++ (UIImageView *)readFileAtPathAsImageView:(NSString *)path;
++ (UIImageView *)readFileAtPathAsImageView:(NSString *)path error:(NSError **)error;
+
++ (NSJSONSerialization *)readFileAtPathAsJSON:(NSString *)path;
++ (NSJSONSerialization *)readFileAtPathAsJSON:(NSString *)path error:(NSError **)error;
+
++ (NSMutableArray *)readFileAtPathAsMutableArray:(NSString *)path;
+
++ (NSMutableData *)readFileAtPathAsMutableData:(NSString *)path;
++ (NSMutableData *)readFileAtPathAsMutableData:(NSString *)path error:(NSError **)error;
+
++ (NSMutableDictionary *)readFileAtPathAsMutableDictionary:(NSString *)path;
+
++ (NSString *)readFileAtPathAsString:(NSString *)path;
++ (NSString *)readFileAtPathAsString:(NSString *)path error:(NSError **)error;
+
+// 删除指定路径的文件/目录
++ (BOOL)removeFilesInDirectoryAtPath:(NSString *)path;
++ (BOOL)removeFilesInDirectoryAtPath:(NSString *)path error:(NSError **)error;
+
++ (BOOL)removeFilesInDirectoryAtPath:(NSString *)path withExtension:(NSString *)extension;
++ (BOOL)removeFilesInDirectoryAtPath:(NSString *)path withExtension:(NSString *)extension error:(NSError **)error;
+
++ (BOOL)removeFilesInDirectoryAtPath:(NSString *)path withPrefix:(NSString *)prefix;
++ (BOOL)removeFilesInDirectoryAtPath:(NSString *)path withPrefix:(NSString *)prefix error:(NSError **)error;
+
++ (BOOL)removeFilesInDirectoryAtPath:(NSString *)path withSuffix:(NSString *)suffix;
++ (BOOL)removeFilesInDirectoryAtPath:(NSString *)path withSuffix:(NSString *)suffix error:(NSError **)error;
+
++ (BOOL)removeItemsInDirectoryAtPath:(NSString *)path;
++ (BOOL)removeItemsInDirectoryAtPath:(NSString *)path error:(NSError **)error;
+
++ (BOOL)removeItemAtPath:(NSString *)path;
++ (BOOL)removeItemAtPath:(NSString *)path error:(NSError **)error;
+
+// 对指定路径的文件/目录重新命名
++ (BOOL)renameItemAtPath:(NSString *)path withName:(NSString *)name;
++ (BOOL)renameItemAtPath:(NSString *)path withName:(NSString *)name error:(NSError **)error;
+
+#pragma mark - 文件 size
++ (NSString *)sizeFormatted:(NSNumber *)size;
+
++ (NSString *)sizeFormattedOfDirectoryAtPath:(NSString *)path;
++ (NSString *)sizeFormattedOfDirectoryAtPath:(NSString *)path error:(NSError **)error;
+
++ (NSString *)sizeFormattedOfFileAtPath:(NSString *)path;
++ (NSString *)sizeFormattedOfFileAtPath:(NSString *)path error:(NSError **)error;
+
++ (NSString *)sizeFormattedOfItemAtPath:(NSString *)path;
++ (NSString *)sizeFormattedOfItemAtPath:(NSString *)path error:(NSError **)error;
+
++ (NSNumber *)sizeOfDirectoryAtPath:(NSString *)path;
++ (NSNumber *)sizeOfDirectoryAtPath:(NSString *)path error:(NSError **)error;
+
++ (NSNumber *)sizeOfFileAtPath:(NSString *)path;
++ (NSNumber *)sizeOfFileAtPath:(NSString *)path error:(NSError **)error;
+
++ (NSNumber *)sizeOfItemAtPath:(NSString *)path;
++ (NSNumber *)sizeOfItemAtPath:(NSString *)path error:(NSError **)error;
+
+#pragma mark -
+//将文件路径转化为url
++ (NSURL *)urlForItemAtPath:(NSString *)path;
+
+#pragma mark -
+//将文件写入指定路径
++ (BOOL)writeFileAtPath:(NSString *)path content:(NSObject *)content;
++ (BOOL)writeFileAtPath:(NSString *)path content:(NSObject *)content error:(NSError **)error;
+#pragma mark -
+// 获取指定路径的 图像 元数据,EXIF数据,TIFF数据
++ (NSDictionary *)metadataOfImageAtPath:(NSString *)path;
++ (NSDictionary *)exifDataOfImageAtPath:(NSString *)path;
++ (NSDictionary *)tiffDataOfImageAtPath:(NSString *)path;
+
+#pragma mark -
+// 读写xattr
++ (NSDictionary *)xattrOfItemAtPath:(NSString *)path;
++ (NSString *)xattrOfItemAtPath:(NSString *)path getValueForKey:(NSString *)key;
++ (BOOL)xattrOfItemAtPath:(NSString *)path hasValueForKey:(NSString *)key;
++ (BOOL)xattrOfItemAtPath:(NSString *)path removeValueForKey:(NSString *)key;
++ (BOOL)xattrOfItemAtPath:(NSString *)path setValue:(NSString *)value forKey:(NSString *)key;
+
+@end

+ 1041 - 0
BFFramework/Classes/PQGPUImage/akfilters/Tools/NXFileManager.m

@@ -0,0 +1,1041 @@
+//
+//  NXFFileManager.m
+//  NXlib
+//
+//  Created by AK on 15/8/30.
+//  Copyright (c) 2015年 AK. All rights reserved.
+//
+
+#import "NXFileManager.h"
+
+#import <ImageIO/ImageIO.h>
+#import <sys/xattr.h>
+
+@implementation NXFileManager
+
++ (NSBundle *)getMainBundle;
+{
+    return [NSBundle mainBundle];
+}
+
++ (NSString *)getMainBundleRes { return [[NSBundle mainBundle] resourcePath]; }
++ (NSString *)getDocumentDir
+{
+    NSString *documentDirectory =
+        [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
+    return documentDirectory;
+}
+
++ (NSString *)getCacheDir
+{
+    NSString *cacheDirectory =
+        [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0];
+    return cacheDirectory;
+}
+
++ (NSString *)getTmpDir { return NSTemporaryDirectory(); }
+
++ (NSString *)getPathForDocuments:(NSString *)filename
+{
+    return [[self getDocumentDir] stringByAppendingPathComponent:filename];
+}
++ (NSString *)getPathForDocuments:(NSString *)filename inDir:(NSString *)dir
+{
+    return [[self getDirectoryForDocuments:dir] stringByAppendingPathComponent:filename];
+}
+
++ (NSString *)getDirectoryForDocuments:(NSString *)dir
+{
+    NSString *dirPath = [[self getDocumentDir] stringByAppendingPathComponent:dir];
+    BOOL isDir = NO;
+    BOOL isCreated = [[NSFileManager defaultManager] fileExistsAtPath:dirPath isDirectory:&isDir];
+    if (isCreated == NO || isDir == NO)
+    {
+        NSError *error = nil;
+        BOOL success = [[NSFileManager defaultManager] createDirectoryAtPath:dirPath
+                                                 withIntermediateDirectories:YES
+                                                                  attributes:nil
+                                                                       error:&error];
+        if (success == NO) NSLog(@"create dir error: %@", error.debugDescription);
+    }
+    return dirPath;
+}
+
++ (NSString *)deletingLastPathComponent:(NSString *)filePath { return [filePath stringByDeletingLastPathComponent]; }
++ (NSArray *)scanFilesInDirectory:(NSString *)directoryPath
+{
+    NSFileManager *fileManager = [NSFileManager defaultManager];
+    NSError *error = nil;
+    // fileList便是包含有该文件夹下所有文件的文件名及文件夹名的数组
+    NSArray *fileList = [fileManager contentsOfDirectoryAtPath:directoryPath error:&error];
+    NSLog(@"路径==%@,fileList%@", directoryPath, fileList);
+    return fileList;
+}
+
++ (NSString *)validateDir:(NSString *)dir
+{
+    BOOL isDir = NO;
+    NSFileManager *fileManager = [NSFileManager defaultManager];
+    BOOL existed = [fileManager fileExistsAtPath:dir isDirectory:&isDir];
+    if (!(isDir == YES && existed == YES))
+    {
+        [self createDirectoriesForPath:dir];
+    }
+    return dir;
+}
+
++ (BOOL)validateFile:(NSString *)filePath { return [[NSFileManager defaultManager] fileExistsAtPath:filePath]; }
++ (NSArray *)pathComponent:(NSString *)url { return [url componentsSeparatedByString:@"/"]; }
++ (NSMutableArray *)absoluteDirectories
+{
+    static NSMutableArray *directories = nil;
+    static dispatch_once_t token;
+
+    dispatch_once(&token, ^{
+
+        directories =
+            [NSMutableArray arrayWithObjects:[self pathForApplicationSupportDirectory], [self pathForCachesDirectory],
+                                             [self pathForDocumentsDirectory], [self pathForLibraryDirectory],
+                                             [self pathForMainBundleDirectory], [self pathForTemporaryDirectory], nil];
+
+        [directories sortUsingComparator:^NSComparisonResult(id obj1, id obj2) {
+
+            return (((NSString *)obj1).length > ((NSString *)obj2).length) ? 0 : 1;
+
+        }];
+    });
+
+    return directories;
+}
+
++ (NSString *)absoluteDirectoryForPath:(NSString *)path
+{
+    [self assertPath:path];
+
+    if ([path isEqualToString:@"/"])
+    {
+        return nil;
+    }
+
+    NSMutableArray *directories = [self absoluteDirectories];
+
+    for (NSString *directory in directories)
+    {
+        NSRange indexOfDirectoryInPath = [path rangeOfString:directory];
+
+        if (indexOfDirectoryInPath.location == 0)
+        {
+            return directory;
+        }
+    }
+
+    return nil;
+}
+
++ (NSString *)absolutePath:(NSString *)path
+{
+    [self assertPath:path];
+
+    NSString *defaultDirectory = [self absoluteDirectoryForPath:path];
+
+    if (defaultDirectory != nil)
+    {
+        return path;
+    }
+    else
+    {
+        return [self pathForDocumentsDirectoryWithPath:path];
+    }
+}
+
++ (void)assertPath:(NSString *)path
+{
+    NSAssert(path != nil, @"Invalid path. Path cannot be nil.");
+    NSAssert(![path isEqualToString:@""], @"Invalid path. Path cannot be empty string.");
+}
+
++ (id)attributeOfItemAtPath:(NSString *)path forKey:(NSString *)key
+{
+    return [[self attributesOfItemAtPath:path] objectForKey:key];
+}
+
++ (id)attributeOfItemAtPath:(NSString *)path forKey:(NSString *)key error:(NSError **)error
+{
+    return [[self attributesOfItemAtPath:path error:error] objectForKey:key];
+}
+
++ (NSDictionary *)attributesOfItemAtPath:(NSString *)path { return [self attributesOfItemAtPath:path error:nil]; }
++ (NSDictionary *)attributesOfItemAtPath:(NSString *)path error:(NSError **)error
+{
+    return [[NSFileManager defaultManager] attributesOfItemAtPath:[self absolutePath:path] error:error];
+}
+
++ (BOOL)copyItemAtPath:(NSString *)path toPath:(NSString *)toPath
+{
+    return [self copyItemAtPath:path toPath:toPath error:nil];
+}
+
++ (BOOL)copyItemAtPath:(NSString *)path toPath:(NSString *)toPath error:(NSError **)error
+{
+    return ([self createDirectoriesForFileAtPath:toPath error:error] &&
+            [[NSFileManager defaultManager] copyItemAtPath:[self absolutePath:path]
+                                                    toPath:[self absolutePath:toPath]
+                                                     error:error]);
+}
+
++ (BOOL)createDirectoriesForFileAtPath:(NSString *)path { return [self createDirectoriesForFileAtPath:path error:nil]; }
++ (BOOL)createDirectoriesForFileAtPath:(NSString *)path error:(NSError **)error
+{
+    NSString *pathLastChar = [path substringFromIndex:(path.length - 1)];
+
+    if ([pathLastChar isEqualToString:@"/"])
+    {
+        [NSException raise:@"Invalid path" format:@"file path can't have a trailing '/'."];
+
+        return NO;
+    }
+
+    return [self createDirectoriesForPath:[[self absolutePath:path] stringByDeletingLastPathComponent] error:error];
+}
+
++ (BOOL)createDirectoriesForPath:(NSString *)path { return [self createDirectoriesForPath:path error:nil]; }
++ (BOOL)createDirectoriesForPath:(NSString *)path error:(NSError **)error
+{
+    return [[NSFileManager defaultManager] createDirectoryAtPath:[self absolutePath:path]
+                                     withIntermediateDirectories:YES
+                                                      attributes:nil
+                                                           error:error];
+}
+
++ (BOOL)createFileAtPath:(NSString *)path { return [self createFileAtPath:path withContent:nil error:nil]; }
++ (BOOL)createFileAtPath:(NSString *)path error:(NSError **)error
+{
+    return [self createFileAtPath:path withContent:nil error:error];
+}
+
++ (BOOL)createFileAtPath:(NSString *)path withContent:(NSObject *)content
+{
+    return [self createFileAtPath:path withContent:content error:nil];
+}
+
++ (BOOL)createFileAtPath:(NSString *)path withContent:(NSObject *)content error:(NSError **)error
+{
+    if (![self existsItemAtPath:path] && [self createDirectoriesForFileAtPath:path error:error])
+    {
+        [[NSFileManager defaultManager] createFileAtPath:[self absolutePath:path] contents:nil attributes:nil];
+
+        if (content != nil)
+        {
+            [self writeFileAtPath:path content:content error:error];
+        }
+
+        return (error == nil);
+    }
+
+    return NO;
+}
+
++ (NSDate *)creationDateOfItemAtPath:(NSString *)path { return [self creationDateOfItemAtPath:path error:nil]; }
++ (NSDate *)creationDateOfItemAtPath:(NSString *)path error:(NSError **)error
+{
+    return (NSDate *)[self attributeOfItemAtPath:path forKey:NSFileCreationDate error:error];
+}
+
++ (BOOL)emptyCachesDirectory { return [self removeFilesInDirectoryAtPath:[self pathForCachesDirectory]]; }
++ (BOOL)emptyTemporaryDirectory { return [self removeFilesInDirectoryAtPath:[self pathForTemporaryDirectory]]; }
++ (BOOL)existsItemAtPath:(NSString *)path
+{
+    return [[NSFileManager defaultManager] fileExistsAtPath:[self absolutePath:path]];
+}
+
++ (BOOL)isDirectoryItemAtPath:(NSString *)path { return [self isDirectoryItemAtPath:path error:nil]; }
++ (BOOL)isDirectoryItemAtPath:(NSString *)path error:(NSError **)error
+{
+    return ([self attributeOfItemAtPath:path forKey:NSFileType error:error] == NSFileTypeDirectory);
+}
+
++ (BOOL)isEmptyItemAtPath:(NSString *)path { return [self isEmptyItemAtPath:path error:nil]; }
++ (BOOL)isEmptyItemAtPath:(NSString *)path error:(NSError **)error
+{
+    return ([self isFileItemAtPath:path error:error] && ([[self sizeOfItemAtPath:path error:error] intValue] == 0)) ||
+           ([self isDirectoryItemAtPath:path error:error] &&
+            ([[self listItemsInDirectoryAtPath:path deep:NO] count] == 0));
+}
+
++ (BOOL)isFileItemAtPath:(NSString *)path { return [self isFileItemAtPath:path error:nil]; }
++ (BOOL)isFileItemAtPath:(NSString *)path error:(NSError **)error
+{
+    return ([self attributeOfItemAtPath:path forKey:NSFileType error:error] == NSFileTypeRegular);
+}
+
++ (BOOL)isExecutableItemAtPath:(NSString *)path
+{
+    return [[NSFileManager defaultManager] isExecutableFileAtPath:[self absolutePath:path]];
+}
+
++ (BOOL)isReadableItemAtPath:(NSString *)path
+{
+    return [[NSFileManager defaultManager] isReadableFileAtPath:[self absolutePath:path]];
+}
+
++ (BOOL)isWritableItemAtPath:(NSString *)path
+{
+    return [[NSFileManager defaultManager] isWritableFileAtPath:[self absolutePath:path]];
+}
+
++ (NSArray *)listDirectoriesInDirectoryAtPath:(NSString *)path
+{
+    return [self listDirectoriesInDirectoryAtPath:path deep:NO];
+}
+
++ (NSArray *)listDirectoriesInDirectoryAtPath:(NSString *)path deep:(BOOL)deep
+{
+    NSArray *subpaths = [self listItemsInDirectoryAtPath:path deep:deep];
+
+    return [subpaths
+        filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
+
+            NSString *subpath = (NSString *)evaluatedObject;
+
+            return [self isDirectoryItemAtPath:subpath];
+        }]];
+}
+
++ (NSArray *)listFilesInDirectoryAtPath:(NSString *)path { return [self listFilesInDirectoryAtPath:path deep:NO]; }
++ (NSArray *)listFilesInDirectoryAtPath:(NSString *)path deep:(BOOL)deep
+{
+    NSArray *subpaths = [self listItemsInDirectoryAtPath:path deep:deep];
+
+    return [subpaths
+        filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
+
+            NSString *subpath = (NSString *)evaluatedObject;
+
+            return [self isFileItemAtPath:subpath];
+        }]];
+}
+
++ (NSArray *)listFilesInDirectoryAtPath:(NSString *)path withExtension:(NSString *)extension
+{
+    return [self listFilesInDirectoryAtPath:path withExtension:extension deep:NO];
+}
+
++ (NSArray *)listFilesInDirectoryAtPath:(NSString *)path withExtension:(NSString *)extension deep:(BOOL)deep
+{
+    NSArray *subpaths = [self listFilesInDirectoryAtPath:path deep:deep];
+
+    return [subpaths
+        filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
+
+            NSString *subpath = (NSString *)evaluatedObject;
+            NSString *subpathExtension = [[subpath pathExtension] lowercaseString];
+            NSString *filterExtension =
+                [[extension lowercaseString] stringByReplacingOccurrencesOfString:@"." withString:@""];
+
+            return [subpathExtension isEqualToString:filterExtension];
+        }]];
+}
+
++ (NSArray *)listFilesInDirectoryAtPath:(NSString *)path withPrefix:(NSString *)prefix
+{
+    return [self listFilesInDirectoryAtPath:path withPrefix:prefix deep:NO];
+}
+
++ (NSArray *)listFilesInDirectoryAtPath:(NSString *)path withPrefix:(NSString *)prefix deep:(BOOL)deep
+{
+    NSArray *subpaths = [self listFilesInDirectoryAtPath:path deep:deep];
+
+    return [subpaths
+        filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
+
+            NSString *subpath = (NSString *)evaluatedObject;
+
+            return ([subpath hasPrefix:prefix] || [subpath isEqualToString:prefix]);
+        }]];
+}
+
++ (NSArray *)listFilesInDirectoryAtPath:(NSString *)path withSuffix:(NSString *)suffix
+{
+    return [self listFilesInDirectoryAtPath:path withSuffix:suffix deep:NO];
+}
+
++ (NSArray *)listFilesInDirectoryAtPath:(NSString *)path withSuffix:(NSString *)suffix deep:(BOOL)deep
+{
+    NSArray *subpaths = [self listFilesInDirectoryAtPath:path deep:deep];
+
+    return [subpaths
+        filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
+
+            NSString *subpath = (NSString *)evaluatedObject;
+            NSString *subpathName = [subpath stringByDeletingPathExtension];
+
+            return ([subpath hasSuffix:suffix] || [subpath isEqualToString:suffix] || [subpathName hasSuffix:suffix] ||
+                    [subpathName isEqualToString:suffix]);
+        }]];
+}
+
++ (NSArray *)listItemsInDirectoryAtPath:(NSString *)path deep:(BOOL)deep
+{
+    NSString *absolutePath = [self absolutePath:path];
+    NSArray *relativeSubpaths =
+        (deep ? [[NSFileManager defaultManager] subpathsOfDirectoryAtPath:absolutePath error:nil]
+              : [[NSFileManager defaultManager] contentsOfDirectoryAtPath:absolutePath error:nil]);
+
+    NSMutableArray *absoluteSubpaths = [[NSMutableArray alloc] init];
+
+    for (NSString *relativeSubpath in relativeSubpaths)
+    {
+        NSString *absoluteSubpath = [absolutePath stringByAppendingPathComponent:relativeSubpath];
+        [absoluteSubpaths addObject:absoluteSubpath];
+    }
+
+    return [NSArray arrayWithArray:absoluteSubpaths];
+}
+
++ (BOOL)moveItemAtPath:(NSString *)path toPath:(NSString *)toPath
+{
+    return [self moveItemAtPath:path toPath:toPath error:nil];
+}
+
++ (BOOL)moveItemAtPath:(NSString *)path toPath:(NSString *)toPath error:(NSError **)error
+{
+    return ([self createDirectoriesForFileAtPath:toPath error:error] &&
+            [[NSFileManager defaultManager] moveItemAtPath:[self absolutePath:path]
+                                                    toPath:[self absolutePath:toPath]
+                                                     error:error]);
+}
+
++ (NSString *)pathForApplicationSupportDirectory
+{
+    static NSString *path = nil;
+    static dispatch_once_t token;
+
+    dispatch_once(&token, ^{
+
+        NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
+
+        path = [paths lastObject];
+    });
+
+    return path;
+}
+
++ (NSString *)pathForApplicationSupportDirectoryWithPath:(NSString *)path
+{
+    return [[NXFileManager pathForApplicationSupportDirectory] stringByAppendingPathComponent:path];
+}
+
++ (NSString *)pathForCachesDirectory
+{
+    static NSString *path = nil;
+    static dispatch_once_t token;
+
+    dispatch_once(&token, ^{
+
+        NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
+
+        path = [paths lastObject];
+    });
+
+    return path;
+}
+
++ (NSString *)pathForCachesDirectoryWithPath:(NSString *)path
+{
+    return [[NXFileManager pathForCachesDirectory] stringByAppendingPathComponent:path];
+}
+
++ (NSString *)pathForDocumentsDirectory
+{
+    static NSString *path = nil;
+    static dispatch_once_t token;
+
+    dispatch_once(&token, ^{
+
+        NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
+
+        path = [paths lastObject];
+    });
+
+    return path;
+}
+
++ (NSString *)pathForDocumentsDirectoryWithPath:(NSString *)path
+{
+    return [[NXFileManager pathForDocumentsDirectory] stringByAppendingPathComponent:path];
+}
+
++ (NSString *)pathForLibraryDirectory
+{
+    static NSString *path = nil;
+    static dispatch_once_t token;
+
+    dispatch_once(&token, ^{
+
+        NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
+
+        path = [paths lastObject];
+    });
+
+    return path;
+}
+
++ (NSString *)pathForLibraryDirectoryWithPath:(NSString *)path
+{
+    return [[NXFileManager pathForLibraryDirectory] stringByAppendingPathComponent:path];
+}
+
++ (NSString *)pathForMainBundleDirectory { return [NSBundle mainBundle].resourcePath; }
++ (NSString *)pathForMainBundleDirectoryWithPath:(NSString *)path
+{
+    return [[NXFileManager pathForMainBundleDirectory] stringByAppendingPathComponent:path];
+}
+
++ (NSString *)pathForPlistNamed:(NSString *)name
+{
+    NSString *nameExtension = [name pathExtension];
+    NSString *plistExtension = @"plist";
+
+    if ([nameExtension isEqualToString:@""])
+    {
+        name = [name stringByAppendingPathExtension:plistExtension];
+    }
+
+    return [self pathForMainBundleDirectoryWithPath:name];
+}
+
++ (NSString *)pathForTemporaryDirectory
+{
+    static NSString *path = nil;
+    static dispatch_once_t token;
+
+    dispatch_once(&token, ^{
+
+        path = NSTemporaryDirectory();
+    });
+
+    return path;
+}
+
++ (NSString *)pathForTemporaryDirectoryWithPath:(NSString *)path
+{
+    return [[NXFileManager pathForTemporaryDirectory] stringByAppendingPathComponent:path];
+}
+
++ (NSString *)readFileAtPath:(NSString *)path { return [self readFileAtPathAsString:path error:nil]; }
++ (NSString *)readFileAtPath:(NSString *)path error:(NSError **)error
+{
+    return [self readFileAtPathAsString:path error:error];
+}
+
++ (NSArray *)readFileAtPathAsArray:(NSString *)path
+{
+    return [NSArray arrayWithContentsOfFile:[self absolutePath:path]];
+}
+
++ (NSObject *)readFileAtPathAsCustomModel:(NSString *)path
+{
+    return [NSKeyedUnarchiver unarchiveObjectWithFile:[self absolutePath:path]];
+}
+
++ (NSData *)readFileAtPathAsData:(NSString *)path { return [self readFileAtPathAsData:path error:nil]; }
++ (NSData *)readFileAtPathAsData:(NSString *)path error:(NSError **)error
+{
+    return [NSData dataWithContentsOfFile:[self absolutePath:path] options:NSDataReadingMapped error:error];
+}
+
++ (NSDictionary *)readFileAtPathAsDictionary:(NSString *)path
+{
+    return [NSDictionary dictionaryWithContentsOfFile:[self absolutePath:path]];
+}
+
++ (UIImage *)readFileAtPathAsImage:(NSString *)path { return [self readFileAtPathAsImage:path error:nil]; }
++ (UIImage *)readFileAtPathAsImage:(NSString *)path error:(NSError **)error
+{
+    NSData *data = [self readFileAtPathAsData:path error:error];
+
+    if (error == nil)
+    {
+        return [UIImage imageWithData:data];
+    }
+
+    return nil;
+}
+
++ (UIImageView *)readFileAtPathAsImageView:(NSString *)path { return [self readFileAtPathAsImageView:path error:nil]; }
++ (UIImageView *)readFileAtPathAsImageView:(NSString *)path error:(NSError **)error
+{
+    UIImage *image = [self readFileAtPathAsImage:path error:error];
+
+    if (error == nil)
+    {
+        UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
+        [imageView sizeToFit];
+        return imageView;
+    }
+
+    return nil;
+}
+
++ (NSJSONSerialization *)readFileAtPathAsJSON:(NSString *)path { return [self readFileAtPathAsJSON:path error:nil]; }
++ (NSJSONSerialization *)readFileAtPathAsJSON:(NSString *)path error:(NSError **)error
+{
+    NSData *data = [self readFileAtPathAsData:path error:error];
+
+    if (error == nil)
+    {
+        NSJSONSerialization *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:error];
+
+        if ([NSJSONSerialization isValidJSONObject:json])
+        {
+            return json;
+        }
+    }
+
+    return nil;
+}
+
++ (NSMutableArray *)readFileAtPathAsMutableArray:(NSString *)path
+{
+    return [NSMutableArray arrayWithContentsOfFile:[self absolutePath:path]];
+}
+
++ (NSMutableData *)readFileAtPathAsMutableData:(NSString *)path
+{
+    return [self readFileAtPathAsMutableData:path error:nil];
+}
+
++ (NSMutableData *)readFileAtPathAsMutableData:(NSString *)path error:(NSError **)error
+{
+    return [NSMutableData dataWithContentsOfFile:[self absolutePath:path] options:NSDataReadingMapped error:error];
+}
+
++ (NSMutableDictionary *)readFileAtPathAsMutableDictionary:(NSString *)path
+{
+    return [NSMutableDictionary dictionaryWithContentsOfFile:[self absolutePath:path]];
+}
+
++ (NSString *)readFileAtPathAsString:(NSString *)path { return [self readFileAtPath:path error:nil]; }
++ (NSString *)readFileAtPathAsString:(NSString *)path error:(NSError **)error
+{
+    return [NSString stringWithContentsOfFile:[self absolutePath:path] encoding:NSUTF8StringEncoding error:error];
+}
+
++ (BOOL)removeFilesInDirectoryAtPath:(NSString *)path
+{
+    return [self removeItemsAtPaths:[self listFilesInDirectoryAtPath:path] error:nil];
+}
+
++ (BOOL)removeFilesInDirectoryAtPath:(NSString *)path error:(NSError **)error
+{
+    return [self removeItemsAtPaths:[self listFilesInDirectoryAtPath:path] error:error];
+}
+
++ (BOOL)removeFilesInDirectoryAtPath:(NSString *)path withExtension:(NSString *)extension
+{
+    return [self removeItemsAtPaths:[self listFilesInDirectoryAtPath:path withExtension:extension] error:nil];
+}
+
++ (BOOL)removeFilesInDirectoryAtPath:(NSString *)path withExtension:(NSString *)extension error:(NSError **)error
+{
+    return [self removeItemsAtPaths:[self listFilesInDirectoryAtPath:path withExtension:extension] error:error];
+}
+
++ (BOOL)removeFilesInDirectoryAtPath:(NSString *)path withPrefix:(NSString *)prefix
+{
+    return [self removeItemsAtPaths:[self listFilesInDirectoryAtPath:path withPrefix:prefix] error:nil];
+}
+
++ (BOOL)removeFilesInDirectoryAtPath:(NSString *)path withPrefix:(NSString *)prefix error:(NSError **)error
+{
+    return [self removeItemsAtPaths:[self listFilesInDirectoryAtPath:path withPrefix:prefix] error:error];
+}
+
++ (BOOL)removeFilesInDirectoryAtPath:(NSString *)path withSuffix:(NSString *)suffix
+{
+    return [self removeItemsAtPaths:[self listFilesInDirectoryAtPath:path withSuffix:suffix] error:nil];
+}
+
++ (BOOL)removeFilesInDirectoryAtPath:(NSString *)path withSuffix:(NSString *)suffix error:(NSError **)error
+{
+    return [self removeItemsAtPaths:[self listFilesInDirectoryAtPath:path withSuffix:suffix] error:error];
+}
+
++ (BOOL)removeItemsInDirectoryAtPath:(NSString *)path { return [self removeItemsInDirectoryAtPath:path error:nil]; }
++ (BOOL)removeItemsInDirectoryAtPath:(NSString *)path error:(NSError **)error
+{
+    return [self removeItemsAtPaths:[self listItemsInDirectoryAtPath:path deep:NO] error:error];
+}
+
++ (BOOL)removeItemAtPath:(NSString *)path { return [self removeItemAtPath:path error:nil]; }
++ (BOOL)removeItemAtPath:(NSString *)path error:(NSError **)error
+{
+    return [[NSFileManager defaultManager] removeItemAtPath:[self absolutePath:path] error:error];
+}
+
++ (BOOL)removeItemsAtPaths:(NSArray *)paths { return [self removeItemsAtPaths:paths error:nil]; }
++ (BOOL)removeItemsAtPaths:(NSArray *)paths error:(NSError **)error
+{
+    BOOL success = YES;
+
+    for (NSString *path in paths)
+    {
+        success &= [self removeItemAtPath:[self absolutePath:path] error:error];
+    }
+
+    return success;
+}
+
++ (BOOL)renameItemAtPath:(NSString *)path withName:(NSString *)name
+{
+    return [self renameItemAtPath:path withName:name error:nil];
+}
+
++ (BOOL)renameItemAtPath:(NSString *)path withName:(NSString *)name error:(NSError **)error
+{
+    NSRange indexOfSlash = [name rangeOfString:@"/"];
+
+    if (indexOfSlash.location < name.length)
+    {
+        [NSException raise:@"Invalid name" format:@"file name can't contain a '/'."];
+
+        return NO;
+    }
+
+    return [self moveItemAtPath:path
+                         toPath:[[[self absolutePath:path] stringByDeletingLastPathComponent]
+                                    stringByAppendingPathComponent:name]
+                          error:error];
+}
+
++ (NSString *)sizeFormatted:(NSNumber *)size
+{
+    // TODO if OS X 10.8 or iOS 6
+    // return [NSByteCountFormatter stringFromByteCount:[size intValue]
+    // countStyle:NSByteCountFormatterCountStyleFile];
+
+    double convertedValue = [size doubleValue];
+    int multiplyFactor = 0;
+
+    NSArray *tokens = @[ @"bytes", @"KB", @"MB", @"GB", @"TB" ];
+
+    while (convertedValue > 1024)
+    {
+        convertedValue /= 1024;
+
+        multiplyFactor++;
+    }
+
+    NSString *sizeFormat = ((multiplyFactor > 1) ? @"%4.2f %@" : @"%4.0f %@");
+
+    return [NSString stringWithFormat:sizeFormat, convertedValue, tokens[multiplyFactor]];
+}
+
++ (NSString *)sizeFormattedOfDirectoryAtPath:(NSString *)path
+{
+    return [self sizeFormattedOfDirectoryAtPath:path error:nil];
+}
+
++ (NSString *)sizeFormattedOfDirectoryAtPath:(NSString *)path error:(NSError **)error
+{
+    NSNumber *size = [self sizeOfDirectoryAtPath:path error:error];
+
+    if (size != nil && error == nil)
+    {
+        return [self sizeFormatted:size];
+    }
+
+    return nil;
+}
+
++ (NSString *)sizeFormattedOfFileAtPath:(NSString *)path { return [self sizeFormattedOfFileAtPath:path error:nil]; }
++ (NSString *)sizeFormattedOfFileAtPath:(NSString *)path error:(NSError **)error
+{
+    NSNumber *size = [self sizeOfFileAtPath:path error:error];
+
+    if (size != nil && error == nil)
+    {
+        return [self sizeFormatted:size];
+    }
+
+    return nil;
+}
+
++ (NSString *)sizeFormattedOfItemAtPath:(NSString *)path { return [self sizeFormattedOfItemAtPath:path error:nil]; }
++ (NSString *)sizeFormattedOfItemAtPath:(NSString *)path error:(NSError **)error
+{
+    NSNumber *size = [self sizeOfItemAtPath:path error:error];
+
+    if (size != nil && error == nil)
+    {
+        return [self sizeFormatted:size];
+    }
+
+    return nil;
+}
+
++ (NSNumber *)sizeOfDirectoryAtPath:(NSString *)path { return [self sizeOfDirectoryAtPath:path error:nil]; }
++ (NSNumber *)sizeOfDirectoryAtPath:(NSString *)path error:(NSError **)error
+{
+    if ([self isDirectoryItemAtPath:path error:error])
+    {
+        if (error == nil)
+        {
+            NSNumber *size = [self sizeOfItemAtPath:path error:error];
+            double sizeValue = [size doubleValue];
+
+            if (error == nil)
+            {
+                NSArray *subpaths = [self listItemsInDirectoryAtPath:path deep:YES];
+                NSUInteger subpathsCount = [subpaths count];
+
+                for (NSUInteger i = 0; i < subpathsCount; i++)
+                {
+                    NSString *subpath = [subpaths objectAtIndex:i];
+                    NSNumber *subpathSize = [self sizeOfItemAtPath:subpath error:error];
+
+                    if (error == nil)
+                    {
+                        sizeValue += [subpathSize doubleValue];
+                    }
+                    else
+                    {
+                        return nil;
+                    }
+                }
+
+                return [NSNumber numberWithDouble:sizeValue];
+            }
+        }
+    }
+
+    return nil;
+}
+
++ (NSNumber *)sizeOfFileAtPath:(NSString *)path { return [self sizeOfFileAtPath:path error:nil]; }
++ (NSNumber *)sizeOfFileAtPath:(NSString *)path error:(NSError **)error
+{
+    if ([self isFileItemAtPath:path error:error])
+    {
+        if (error == nil)
+        {
+            return [self sizeOfItemAtPath:path error:error];
+        }
+    }
+
+    return nil;
+}
+
++ (NSNumber *)sizeOfItemAtPath:(NSString *)path { return [self sizeOfItemAtPath:path error:nil]; }
++ (NSNumber *)sizeOfItemAtPath:(NSString *)path error:(NSError **)error
+{
+    return (NSNumber *)[self attributeOfItemAtPath:path forKey:NSFileSize error:error];
+}
+
++ (NSURL *)urlForItemAtPath:(NSString *)path { return [NSURL fileURLWithPath:[self absolutePath:path]]; }
++ (BOOL)writeFileAtPath:(NSString *)path content:(NSObject *)content
+{
+    return [self writeFileAtPath:path content:content error:nil];
+}
+
++ (BOOL)writeFileAtPath:(NSString *)path content:(NSObject *)content error:(NSError **)error
+{
+    if (content == nil)
+    {
+        [NSException raise:@"Invalid content" format:@"content can't be nil."];
+    }
+
+    [self createFileAtPath:path withContent:nil error:error];
+
+    NSString *absolutePath = [self absolutePath:path];
+
+    if ([content isKindOfClass:[NSMutableArray class]])
+    {
+        [((NSMutableArray *)content) writeToFile:absolutePath atomically:YES];
+    }
+    else if ([content isKindOfClass:[NSArray class]])
+    {
+        [((NSArray *)content) writeToFile:absolutePath atomically:YES];
+    }
+    else if ([content isKindOfClass:[NSMutableData class]])
+    {
+        [((NSMutableData *)content) writeToFile:absolutePath atomically:YES];
+    }
+    else if ([content isKindOfClass:[NSData class]])
+    {
+        [((NSData *)content) writeToFile:absolutePath atomically:YES];
+    }
+    else if ([content isKindOfClass:[NSMutableDictionary class]])
+    {
+        [((NSMutableDictionary *)content) writeToFile:absolutePath atomically:YES];
+    }
+    else if ([content isKindOfClass:[NSDictionary class]])
+    {
+        [((NSDictionary *)content) writeToFile:absolutePath atomically:YES];
+    }
+    else if ([content isKindOfClass:[NSJSONSerialization class]])
+    {
+        [((NSDictionary *)content) writeToFile:absolutePath atomically:YES];
+    }
+    else if ([content isKindOfClass:[NSMutableString class]])
+    {
+        [[((NSString *)content) dataUsingEncoding:NSUTF8StringEncoding] writeToFile:absolutePath atomically:YES];
+    }
+    else if ([content isKindOfClass:[NSString class]])
+    {
+        [[((NSString *)content) dataUsingEncoding:NSUTF8StringEncoding] writeToFile:absolutePath atomically:YES];
+    }
+    else if ([content isKindOfClass:[UIImage class]])
+    {
+        [UIImagePNGRepresentation((UIImage *)content) writeToFile:absolutePath atomically:YES];
+    }
+    else if ([content isKindOfClass:[UIImageView class]])
+    {
+        return [self writeFileAtPath:absolutePath content:((UIImageView *)content).image error:error];
+    }
+    else if ([content conformsToProtocol:@protocol(NSCoding)])
+    {
+        [NSKeyedArchiver archiveRootObject:content toFile:absolutePath];
+    }
+    else
+    {
+        [NSException raise:@"Invalid content type"
+                    format:@"content of type %@ is not handled.", NSStringFromClass([content class])];
+
+        return NO;
+    }
+
+    return YES;
+}
+
++ (NSDictionary *)metadataOfImageAtPath:(NSString *)path
+{
+    if ([self isFileItemAtPath:path])
+    {
+        // http://blog.depicus.com/getting-exif-data-from-images-on-ios/
+
+        NSURL *url = [self urlForItemAtPath:path];
+        CGImageSourceRef sourceRef = CGImageSourceCreateWithURL((CFURLRef)url, NULL);
+        NSDictionary *metadata =
+            (NSDictionary *)CFBridgingRelease(CGImageSourceCopyPropertiesAtIndex(sourceRef, 0, NULL));
+
+        return metadata;
+    }
+
+    return nil;
+}
+
++ (NSDictionary *)exifDataOfImageAtPath:(NSString *)path
+{
+    NSDictionary *metadata = [self metadataOfImageAtPath:path];
+
+    if (metadata)
+    {
+        return [metadata objectForKey:(NSString *)kCGImagePropertyExifDictionary];
+    }
+
+    return nil;
+}
+
++ (NSDictionary *)tiffDataOfImageAtPath:(NSString *)path
+{
+    NSDictionary *metadata = [self metadataOfImageAtPath:path];
+
+    if (metadata)
+    {
+        return [metadata objectForKey:(NSString *)kCGImagePropertyTIFFDictionary];
+    }
+
+    return nil;
+}
+
++ (NSDictionary *)xattrOfItemAtPath:(NSString *)path
+{
+    NSMutableDictionary *values = [[NSMutableDictionary alloc] init];
+
+    const char *upath = [path UTF8String];
+
+    ssize_t ukeysSize = listxattr(upath, NULL, 0, 0);
+
+    if (ukeysSize > 0)
+    {
+        char *ukeys = malloc(ukeysSize);
+
+        ukeysSize = listxattr(upath, ukeys, ukeysSize, 0);
+
+        NSUInteger keyOffset = 0;
+        NSString *key;
+        NSString *value;
+
+        while (keyOffset < ukeysSize)
+        {
+            key = [NSString stringWithUTF8String:(keyOffset + ukeys)];
+            keyOffset += ([key length] + 1);
+
+            value = [self xattrOfItemAtPath:path getValueForKey:key];
+            [values setObject:value forKey:key];
+        }
+
+        free(ukeys);
+    }
+
+    return [NSDictionary dictionaryWithObjects:[values allKeys] forKeys:[values allValues]];
+}
+
++ (NSString *)xattrOfItemAtPath:(NSString *)path getValueForKey:(NSString *)key
+{
+    NSString *value = nil;
+
+    const char *ukey = [key UTF8String];
+    const char *upath = [path UTF8String];
+
+    ssize_t uvalueSize = getxattr(upath, ukey, NULL, 0, 0, 0);
+
+    if (uvalueSize > -1)
+    {
+        if (uvalueSize == 0)
+        {
+            value = @"";
+        }
+        else
+        {
+            char *uvalue = malloc(uvalueSize);
+
+            if (uvalue)
+            {
+                getxattr(upath, ukey, uvalue, uvalueSize, 0, 0);
+                uvalue[uvalueSize] = '\0';
+                value = [NSString stringWithUTF8String:uvalue];
+                free(uvalue);
+            }
+        }
+    }
+
+    return value;
+}
+
++ (BOOL)xattrOfItemAtPath:(NSString *)path hasValueForKey:(NSString *)key
+{
+    return ([self xattrOfItemAtPath:path getValueForKey:key] != nil);
+}
+
++ (BOOL)xattrOfItemAtPath:(NSString *)path removeValueForKey:(NSString *)key
+{
+    int result = removexattr([path UTF8String], [key UTF8String], 0);
+
+    return (result == 0);
+}
+
++ (BOOL)xattrOfItemAtPath:(NSString *)path setValue:(NSString *)value forKey:(NSString *)key
+{
+    if (value == nil)
+    {
+        return NO;
+    }
+
+    int result = setxattr([path UTF8String], [key UTF8String], [value UTF8String], [value length], 0, 0);
+
+    return (result == 0);
+}
+
+@end

+ 53 - 0
BFFramework/Classes/PQGPUImage/akfilters/Tools/NXVideoMerge.h

@@ -0,0 +1,53 @@
+//
+//  NXVideoRecorder.h
+//  Philm
+//
+//  Created by AK on 2017/2/16.
+//  Copyright © 2017年 yoyo. All rights reserved.
+//
+/**
+ *  功能 :将多段视频合并成一个MP4视频
+ *
+ */
+#import <Foundation/Foundation.h>
+
+//通用 callback
+typedef void(^NXGenericCallback)(BOOL success, id result);
+
+
+//合成进度
+typedef void (^NXProgressHandler)(double progress);
+@interface NXVideoMerge : NSObject
+{
+ 
+}
+
+//合成进度 ,回调会回到主线
+@property (nonatomic, copy) NXProgressHandler progressHandler;
+
+
+/**
+ *  合成多段视频
+ *
+ *  @param fileURLArray 包含所有视频分段的文件URL数组,必须是[NSURL fileURLWithString:...]得到的
+ *  @param renderSize   输出视频的宽高
+ *  @param finishBlock  生成视频结果回调 返回的是NSURL类型 并回到主线
+ */
+- (void)mergeAndExportVideosWithFileURLs:(NSArray<NSURL *> *)fileURLArray
+                              renderSize:(CGSize)renderSize
+                             finishBlock:(NXGenericCallback)finishBlock;
+
+/**
+ *  合成多段视频
+ *
+ *  @param fileURLArray 包含所有视频分段的文件URL数组,必须是[NSURL fileURLWithString:...]得到的
+ *  @param renderSize   输出视频的宽高
+ *  @param savePath     输出视频MP4的文件路径 delault is ~/Library/Caches/NXExportVideo.mp4
+ *  @param finishBlock  生成视频结果回调 返回的是NSURL类型 并回到主线
+ */
+- (void)mergeAndExportVideosWithFileURLs:(NSArray<NSURL *> *)fileURLArray
+                              renderSize:(CGSize)renderSize
+                                savePath:(NSString *)savePath
+                             finishBlock:(NXGenericCallback)finishBlock;
+@end
+

+ 343 - 0
BFFramework/Classes/PQGPUImage/akfilters/Tools/NXVideoMerge.m

@@ -0,0 +1,343 @@
+//
+//  NXVideoRecorder.m
+//  Philm
+//
+//  Created by AK on 2017/2/16.
+//  Copyright © 2017年 yoyo. All rights reserved.
+//
+
+//合并输出视频的文件名
+#define NXExportVideoName @"NXExportVideo.mp4"
+
+//视频叠加 http://www.theappguruz.com/blog/ios-overlap-multiple-videos
+
+#import "NXVideoMerge.h"
+#import "NXFileManager.h"
+#import <AVFoundation/AVFoundation.h>
+
+static void *ExportProcess = &ExportProcess;
+
+@interface NXVideoMerge ()
+{
+    AVAssetExportSession *exporterSession;
+    NSTimer *exportProgressTimer;  //监听导出的进度
+}
+
+@end
+
+@implementation NXVideoMerge
+
+- (void)applyVideoEffectsToComposition:(AVMutableVideoComposition *)composition size:(CGSize)size
+{
+    // 在子类中实现本方法可以自定义显示layer
+}
+
+- (void)mergeAndExportVideosWithFileURLs:(NSArray<NSURL *> *)fileURLArray
+                              renderSize:(CGSize)renderSize
+                             finishBlock:(NXGenericCallback)finishBlock;
+{
+    [self mergeAndExportVideosWithFileURLs:fileURLArray renderSize:renderSize savePath:@"" finishBlock:finishBlock];
+}
+
+- (void)mergeAndExportVideosWithFileURLs:(NSArray<NSURL *> *)fileURLArray
+                              renderSize:(CGSize)renderSize
+                                savePath:(NSString *)savePath
+                             finishBlock:(NXGenericCallback)finishBlock
+{
+    NSLog(@"合成视频个数:  %zd", fileURLArray.count);
+
+    NSMutableArray *layerInstructionArray = [[NSMutableArray alloc] init];
+    AVMutableComposition *mixComposition = [[AVMutableComposition alloc] init];
+
+    CMTime totalDuration = kCMTimeZero;
+
+    for (int i = 0; i < [fileURLArray count]; i++)
+    {
+        // 1 准备 asset & AssetTrack
+        NSURL *videoURL = [fileURLArray objectAtIndex:i];
+        AVAsset *asset = [AVAsset assetWithURL:videoURL];
+
+        if (!asset)
+        {
+            NSLog(@"视频文件 %@ 读取失败", [videoURL absoluteString]);
+            continue;
+        }
+
+        NSLog(@"视频idx %d path:%@ duration: %f timescale%d", i, videoURL.absoluteString,
+              CMTimeGetSeconds(asset.duration), asset.duration.timescale);
+
+        //如果视频没有视频数据不处理本视频,视频轨道总数
+        NSArray *assetVideoTracks = [asset tracksWithMediaType:AVMediaTypeVideo];
+        //音频轨道总数
+        NSArray *assetAudioTracks = [asset tracksWithMediaType:AVMediaTypeAudio];
+
+        if (assetVideoTracks.count == 0)
+        {
+            NSLog(@"此视频没有视频轨信息!!!!!");
+            continue;
+        }
+
+        NSLog(@"声音轨道数: %lu", (unsigned long)assetAudioTracks.count);
+
+        AVAssetTrack *videoAssetTrack = [assetVideoTracks objectAtIndex:0];
+
+        //加载指定声音文件 test
+        /*
+          NSString *auidoPath1 = [[NSBundle mainBundle] pathForResource:@"music" ofType:@"mp3"];
+          AVURLAsset *audioAsset = [AVURLAsset assetWithURL:[NSURL fileURLWithPath:auidoPath1]];
+         AVAssetTrack *audioAssetTrack = [[audioAsset1 tracksWithMediaType:AVMediaTypeAudio] firstObject];
+
+        */
+
+        // 2,合并音频数据 ,如果有声音数据才进行声音的合并,否则会CRASH
+        if (assetAudioTracks.count > 0)
+        {
+            // audio track
+            NSError *error = nil;
+
+            AVMutableCompositionTrack *audioTrack =
+                [mixComposition addMutableTrackWithMediaType:AVMediaTypeAudio
+                                            preferredTrackID:kCMPersistentTrackID_Invalid];
+
+            [audioTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, asset.duration)
+                                ofTrack:[assetAudioTracks objectAtIndex:0]
+                                 atTime:totalDuration
+                                  error:&error];
+            if (error)
+            {
+                NSLog(@"插入声音数据失败! %@", error);
+            }
+        }
+        else
+        {
+            NSLog(@"没有声音数据 %@", asset);
+        }
+
+        AVMutableCompositionTrack *videoTrack =
+            [mixComposition addMutableTrackWithMediaType:AVMediaTypeVideo
+                                        preferredTrackID:kCMPersistentTrackID_Invalid];
+
+        // 3,合并视频数据 video track
+        if (assetVideoTracks.count > 0)
+        {
+            NSError *error = nil;
+
+            [videoTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, asset.duration)
+                                ofTrack:videoAssetTrack
+                                 atTime:totalDuration
+                                  error:&error];
+
+            if (error)
+            {
+                NSLog(@"videoTrack insertTime error: %@", error);
+            }
+        }
+        else
+        {
+            NSLog(@"没有视频数据 %@", asset);
+        }
+
+        // fix orientationissue
+        AVMutableVideoCompositionLayerInstruction *layerInstruciton =
+            [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:videoTrack];
+
+        totalDuration = CMTimeAdd(totalDuration, asset.duration);
+        // set other layerTransform? 如多屏指定大小显示
+        /*
+        CGAffineTransform SecondScale = CGAffineTransformMakeScale(0.5f,0.5f);
+        CGAffineTransform SecondMove = CGAffineTransformMakeTranslation(0,0);
+        [layerInstruciton setTransform:CGAffineTransformConcat(SecondScale,SecondMove) atTime:kCMTimeZero];
+        */
+        
+        //XXXX 全屏合成有问题先不进行Transform
+        CGAffineTransform layerTransform = [self applayAfftransform:videoAssetTrack renderSize:renderSize isCut:YES];
+        
+        [layerInstruciton setTransform:layerTransform atTime:kCMTimeZero];
+
+        [layerInstruciton setOpacity:0.0 atTime:totalDuration];
+
+        // data
+        [layerInstructionArray addObject:layerInstruciton];
+    }
+
+    // LayerInstruction's count is 0
+    if (layerInstructionArray.count == 0)
+    {
+        if (finishBlock)
+        {
+            finishBlock(NO, @"视频数据都为空!!!");
+        }
+        return;
+    }
+
+    //导出视频
+    [self exportVideoWithsavePath:savePath
+                        timeRange:CMTimeRangeMake(kCMTimeZero, totalDuration)
+                layerInstructions:layerInstructionArray
+                       renderSize:renderSize
+                   mixComposition:mixComposition
+                      finishBlock:finishBlock];
+}
+
+- (void)exportVideoWithsavePath:(NSString *)savePath
+                      timeRange:(CMTimeRange)timeRange
+              layerInstructions:(NSArray<AVVideoCompositionLayerInstruction *> *)layerInstructions
+                     renderSize:(CGSize)renderSize
+                 mixComposition:(AVMutableComposition *)mixComposition
+                    finishBlock:(NXGenericCallback)finishBlock
+
+{
+    //设置输出文件路径
+    savePath =
+        savePath.length > 0 ? savePath : [[NXFileManager getCacheDir] stringByAppendingPathComponent:NXExportVideoName];
+    //如果文件存在 删除老数据
+    unlink([savePath UTF8String]);
+
+    AVMutableVideoCompositionInstruction *mainInstruciton =
+        [AVMutableVideoCompositionInstruction videoCompositionInstruction];
+
+    mainInstruciton.timeRange = timeRange;
+    mainInstruciton.layerInstructions = layerInstructions;
+
+    AVMutableVideoComposition *mainCompositionInst = [AVMutableVideoComposition videoComposition];
+    mainCompositionInst.instructions = @[ mainInstruciton ];
+    mainCompositionInst.frameDuration = CMTimeMake(1, 30);
+    mainCompositionInst.renderSize = renderSize;
+
+    [self applyVideoEffectsToComposition:mainCompositionInst size:renderSize];
+
+    exporterSession =
+        [[AVAssetExportSession alloc] initWithAsset:mixComposition presetName:AVAssetExportPresetHighestQuality];
+    exporterSession.videoComposition = mainCompositionInst;
+    exporterSession.outputURL = [NSURL fileURLWithPath:savePath];
+    exporterSession.outputFileType = AVFileTypeMPEG4;
+
+    exporterSession.shouldOptimizeForNetworkUse = YES;
+    NSLog(@"支持的文件格式 %@", [exporterSession supportedFileTypes]);
+
+    exportProgressTimer = [NSTimer scheduledTimerWithTimeInterval:1 / 60.
+                                                           target:self
+                                                         selector:@selector(updateExportDisplay)
+                                                         userInfo:nil
+                                                          repeats:YES];
+
+    [exporterSession exportAsynchronouslyWithCompletionHandler:^{
+
+        dispatch_async(dispatch_get_main_queue(), ^{
+
+            AVAsset *asset = [AVAsset assetWithURL:[NSURL fileURLWithPath:savePath]];
+            NSLog(@"导出视频结果 %ld 路径: %@ 时长 %f", (long)exporterSession.status, savePath,
+                  CMTimeGetSeconds(asset.duration));
+            //导出完成且时长不为0
+            if (exporterSession.status == AVAssetExportSessionStatusCompleted && CMTimeGetSeconds(asset.duration) != 0)
+            {
+
+                if (finishBlock)
+                {
+                    finishBlock(YES, [NSURL fileURLWithPath:savePath]);
+                }
+            }
+            else
+            {
+                NSLog(@"处理视频失败 %ld", (long)exporterSession.status);
+                //合成视频失败时不在发进度block
+                [exportProgressTimer invalidate];
+
+                if (finishBlock)
+                {
+                    finishBlock(NO, @"");
+                }
+            }
+        });
+
+    }];
+}
+
+//处理进度
+- (void)updateExportDisplay
+{
+    NSLog(@"导出进度 progress %f", exporterSession.progress);
+    if (exporterSession.progress < 1)
+    {
+        //回到主线程
+        dispatch_async(dispatch_get_main_queue(), ^{
+
+            if (_progressHandler)
+            {
+                _progressHandler(exporterSession.progress);
+                _progressHandler = nil;
+            }
+        });
+    }
+    else
+    {
+        [exportProgressTimer invalidate];
+    }
+}
+
+
+- (CGAffineTransform )applayAfftransform:(AVAssetTrack *)videoAssetTrack renderSize:(CGSize)renderSize isCut:(BOOL)cut{
+
+    CGAffineTransform layerTransform = CGAffineTransformIdentity;
+    double rate = 1.0f;
+    CGPoint point = CGPointZero;
+    [self computeRate: &rate targetPoint:&point renderSize:renderSize vidoSize:videoAssetTrack.naturalSize cut:cut];
+    layerTransform = CGAffineTransformMake(videoAssetTrack.preferredTransform.a,
+                                           videoAssetTrack.preferredTransform.b,
+                                           videoAssetTrack.preferredTransform.c,
+                                           videoAssetTrack.preferredTransform.d,
+                                           videoAssetTrack.preferredTransform.tx * rate,
+                                           videoAssetTrack.preferredTransform.ty * rate);
+    layerTransform = CGAffineTransformScale(layerTransform, rate, rate);  //放缩,解决前后摄像结果大小不对称
+    layerTransform = CGAffineTransformConcat(
+                                             layerTransform,
+                                             CGAffineTransformMake(1, 0, 0, 1, -point.x * rate , -point.y * rate));
+    return layerTransform;
+}
+- (void)computeRate:(double *)rate targetPoint:(CGPoint *) point renderSize:(CGSize)renderSize vidoSize:(CGSize)videoSize cut:(BOOL) cut{
+
+    if (cut) {
+        //按短边充满画布
+        if (videoSize.width >= videoSize.height)
+        {
+            *rate = renderSize.height / videoSize.height;
+            //视频实际宽度比画布要宽
+            if(renderSize.width < *rate *videoSize.width )
+            {
+                (*point).x = (videoSize.width - videoSize.height)/2.0f;
+            }
+            
+        } else {
+            
+            *rate = renderSize.width / videoSize.width;
+            //视频实际高度比画布要高
+            if(renderSize.height < *rate *videoSize.height){
+                
+                (*point).y = (videoSize.height - videoSize.width)/2.0f;
+            }
+        }
+        
+    } else {
+        
+        //不足部分留黑边
+        if(videoSize.width >= videoSize.height){
+            
+            *rate = renderSize.width / videoSize.width;
+            
+            if(renderSize.height > *rate * videoSize.height){
+                
+                (*point).y = (videoSize.height - videoSize.width)/2.0f;
+            }
+        } else {
+            
+            *rate = renderSize.height / videoSize.height;
+            
+            if (renderSize.width > *rate * videoSize.width) {
+                
+                (*point).x = (videoSize.width - videoSize.height) /2.0f;
+            }
+        }
+    }
+}
+@end
+