如何使用 AVFoundation 修剪视频
Posted
技术标签:
【中文标题】如何使用 AVFoundation 修剪视频【英文标题】:How to trim the video using AVFoundation 【发布时间】:2010-12-14 13:35:07 【问题描述】:我可以使用 AVFoundation 或 UIImagePickerController 录制视频。但是我无法将视频从某一秒修剪到另一个特定的持续时间/时间。谁能帮帮我。
谢谢, 湿婆克里希纳。
【问题讨论】:
在寻找修剪现有视频的内容时发现了这个问题。修剪捕获的视频相对简单。但是让那个修剪窗口出现似乎让我望而却步。希望我的回答对您有所帮助。 【参考方案1】:您可以让 UIImagePickerController 启用修剪
UIImagePickerController *videoRecorder = [[UIImagePickerController alloc]init];
NSArray *sourceTypes = [UIImagePickerController availableMediaTypesForSourceType:videoRecorder.sourceType];
NSLog(@"Available types for source as camera = %@", sourceTypes);
if (![sourceTypes containsObject:(NSString*)kUTTypeMovie] )
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:nil
message:@"Device Not Supported for video Recording." delegate:self
cancelButtonTitle:@"Yes"
otherButtonTitles:@"No",nil];
[alert show];
[alert release];
return;
videoRecorder.allowsEditing = YES;
不幸的是,当你从 imagePickerController 回来后,你不得不手动转换视频。
-(void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
if ([self.popoverLibraryBrowser isPopoverVisible])
[self.popoverLibraryBrowser dismissPopoverAnimated:YES];
NSString *type = [info objectForKey:UIImagePickerControllerMediaType];
if ([type isEqualToString:(NSString *)kUTTypeVideo] ||
[type isEqualToString:(NSString *)kUTTypeMovie]) // movie != video
NSURL *videoURL = [info objectForKey:UIImagePickerControllerMediaURL];
NSNumber *start = [info objectForKey:@"_UIImagePickerControllerVideoEditingStart"];
NSNumber *end = [info objectForKey:@"_UIImagePickerControllerVideoEditingEnd"];
// if start and end are nil then clipping was not used.
// You should use the entire video.
int startMilliseconds = ([start doubleValue] * 1000);
int endMilliseconds = ([end doubleValue] * 1000);
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSFileManager *manager = [NSFileManager defaultManager];
NSString *outputURL = [documentsDirectory stringByAppendingPathComponent:@"output"] ;
[manager createDirectoryAtPath:outputURL withIntermediateDirectories:YES attributes:nil error:nil];
outputURL = [outputURL stringByAppendingPathComponent:@"output.mp4"];
// Remove Existing File
[manager removeItemAtPath:outputURL error:nil];
//[self loadAssetFromFile:videoURL];
[self.recorder dismissModalViewControllerAnimated:YES];
AVURLAsset *videoAsset = [AVURLAsset URLAssetWithURL:videoURL options:nil];
AVAssetExportSession *exportSession = [[AVAssetExportSession alloc] initWithAsset:videoAsset presetName:AVAssetExportPresetHighestQuality];
exportSession.outputURL = [NSURL fileURLWithPath:outputURL];
exportSession.outputFileType = AVFileTypeQuickTimeMovie;
CMTimeRange timeRange = CMTimeRangeMake(CMTimeMake(startMilliseconds, 1000), CMTimeMake(endMilliseconds - startMilliseconds, 1000));
exportSession.timeRange = timeRange;
[exportSession exportAsynchronouslyWithCompletionHandler:^
switch (exportSession.status)
case AVAssetExportSessionStatusCompleted:
// Custom method to import the Exported Video
[self loadAssetFromFile:exportSession.outputURL];
break;
case AVAssetExportSessionStatusFailed:
//
NSLog(@"Failed:%@",exportSession.error);
break;
case AVAssetExportSessionStatusCancelled:
//
NSLog(@"Canceled:%@",exportSession.error);
break;
default:
break;
];
//NSData *videoData = [NSData dataWithContentsOfURL:videoURL];
//NSString *videoStoragePath;//Set your video storage path to this variable
//[videoData writeToFile:videoStoragePath atomically:YES];
//You can store the path of the saved video file in sqlite/coredata here.
【讨论】:
感谢来源。当我使用“*sourceTypes”作为“UIImagePickerControllerSourceTypeCamera”时,我得到“*开始和*结束时间”。但是当我使用“*sourceTypes”作为“UIImagePickerControllerSourceTypeSavedPhotosAlbum”时,我没有得到“开始和结束”时间。问题是什么?如何获得“开始和结束”时间? 您是否让他们能够“剪辑”视频?填充编辑开始和结束的部分是剪辑。如果您不剪辑视频,则这些值将保留为 nil,这反过来意味着应该跳过剪辑并使用整个视频。 请参考这里的日志pastebin.com/uMU1k61T。我允许用户从缩略图中选择帧。没问题,如果我编辑“相机录制的视频”并且当我尝试从相册加载的视频中选择帧时会出现问题。 @karthi 使用UIImagePickerControllerSourceTypeSavedPhotosAlbum
时,您从[info objectForKey:UIImagePickerControllerMediaURL];
获得的url 引用了修剪后的视频文件,因此不需要开始和结束时间。我知道,因为我从[info objectForKey:UIImagePickerControllerMediaURL];
获得了 url 的持续时间并将其与原始视频持续时间进行比较。 How to get the duration of video
您应该检查类型是否符合电影:UTTypeConformsTo((__bridge CFStringRef)mediaType, kUTTypeMovie) != 0
,而不是检查电影或视频。电影和视频也是如此。【参考方案2】:
上述的 Swift 版本
import UIKit
import AVFoundation
import MobileCoreServices
func pickVideo()
if UIImagePickerController.isSourceTypeAvailable(.Camera)
let videoRecorder = UIImagePickerController()
videoRecorder.sourceType = .Camera
videoRecorder.mediaTypes = [kUTTypeMovie as String]
videoRecorder.allowsEditing = true
videoRecorder.delegate = self
presentViewController(videoRecorder, animated: true, completion: nil)
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject])
picker.dismissViewControllerAnimated(true, completion: nil)
let manager = NSFileManager.defaultManager()
guard let documentDirectory = try? manager.URLForDirectory(.DocumentDirectory, inDomain: .UserDomainMask, appropriateForURL: nil, create: true) else return
guard let mediaType = info[UIImagePickerControllerMediaType] as? String else return
guard let url = info[UIImagePickerControllerMediaURL] as? NSURL else return
if mediaType == kUTTypeMovie as String || mediaType == kUTTypeVideo as String
let asset = AVAsset(URL: url)
let length = Float(asset.duration.value) / Float(asset.duration.timescale)
print("video length: \(length) seconds")
let start = info["_UIImagePickerControllerVideoEditingStart"] as? Float
let end = info["_UIImagePickerControllerVideoEditingEnd"] as? Float
var outputURL = documentDirectory.URLByAppendingPathComponent("output")
do
try manager.createDirectoryAtURL(outputURL, withIntermediateDirectories: true, attributes: nil)
outputURL = outputURL.URLByAppendingPathComponent("output.mp4")
catch let error
print(error)
//Remove existing file
_ = try? manager.removeItemAtURL(outputURL)
guard let exportSession = AVAssetExportSession(asset: asset, presetName: AVAssetExportPresetHighestQuality) else return
exportSession.outputURL = outputURL
exportSession.outputFileType = AVFileTypeMPEG4
let startTime = CMTime(seconds: Double(start ?? 0), preferredTimescale: 1000)
let endTime = CMTime(seconds: Double(end ?? length), preferredTimescale: 1000)
let timeRange = CMTimeRange(start: startTime, end: endTime)
exportSession.timeRange = timeRange
exportSession.exportAsynchronouslyWithCompletionHandler
switch exportSession.status
case .Completed:
print("exported at \(outputURL)")
case .Failed:
print("failed \(exportSession.error)")
case .Cancelled:
print("cancelled \(exportSession.error)")
default: break
【讨论】:
此解决方案对某些视频失败。有什么建议吗? 作为程序员,您应该知道“失败”不是描述错误/问题的恰当方式 云视频失败了,我调试并弄清楚了。我说失败了,因为我无法得到一个场景,所以想问你,如果你遇到了这样的奇怪问题。无论如何,我已经解决了这个问题。下次请和任何开发者好好谈谈。【参考方案3】:swift 4 的最佳解决方案,我找到了there。我确实根据自己的需要对其进行了修复,但它确实非常清晰和方便。
代码:
import AVFoundation
import Foundation
extension FileManager
func removeFileIfNecessary(at url: URL) throws
guard fileExists(atPath: url.path) else
return
do
try removeItem(at: url)
catch let error
throw TrimError("Couldn't remove existing destination file: \(error)")
struct TrimError: Error
let description: String
let underlyingError: Error?
init(_ description: String, underlyingError: Error? = nil)
self.description = "TrimVideo: " + description
self.underlyingError = underlyingError
extension AVMutableComposition
convenience init(asset: AVAsset)
self.init()
for track in asset.tracks
addMutableTrack(withMediaType: track.mediaType, preferredTrackID: track.trackID)
func trim(timeOffStart: Double)
let duration = CMTime(seconds: timeOffStart, preferredTimescale: 1)
let timeRange = CMTimeRange(start: kCMTimeZero, duration: duration)
for track in tracks
track.removeTimeRange(timeRange)
removeTimeRange(timeRange)
extension AVAsset
func assetByTrimming(timeOffStart: Double) throws -> AVAsset
let duration = CMTime(seconds: timeOffStart, preferredTimescale: 1)
let timeRange = CMTimeRange(start: kCMTimeZero, duration: duration)
let composition = AVMutableComposition()
do
for track in tracks
let compositionTrack = composition.addMutableTrack(withMediaType: track.mediaType, preferredTrackID: track.trackID)
try compositionTrack?.insertTimeRange(timeRange, of: track, at: kCMTimeZero)
catch let error
throw TrimError("error during composition", underlyingError: error)
return composition
func export(to destination: URL) throws
guard let exportSession = AVAssetExportSession(asset: self, presetName: AVAssetExportPresetPassthrough) else
throw TrimError("Could not create an export session")
exportSession.outputURL = destination
exportSession.outputFileType = AVFileType.m4v
exportSession.shouldOptimizeForNetworkUse = true
let group = DispatchGroup()
group.enter()
try FileManager.default.removeFileIfNecessary(at: destination)
exportSession.exportAsynchronously
group.leave()
group.wait()
if let error = exportSession.error
throw TrimError("error during export", underlyingError: error)
func time(_ operation: () throws -> ()) rethrows
let start = Date()
try operation()
let end = Date().timeIntervalSince(start)
print(end)
let sourceURL = URL(fileURLWithPath: CommandLine.arguments[1])
let destinationURL = URL(fileURLWithPath: CommandLine.arguments[2])
do
try time
let asset = AVURLAsset(url: sourceURL)
let trimmedAsset = try asset.assetByTrimming(timeOffStart: 1.0)
try trimmedAsset.export(to: destinationURL)
catch let error
print("? \(error)")
【讨论】:
【参考方案4】:您应该在 setMediaTypes 数组中添加 kUTTypeMovie,它会起作用。
【讨论】:
以上是关于如何使用 AVFoundation 修剪视频的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 AVFoundation 为您的视频添加不同图像和不同 CMTimes 的水印
如何防止使用 AVFoundation 录制视频中断当前正在播放的任何全局音频(Swift)?