如何使用 AVFoundation 修剪视频

发布于 2024-10-07 09:27:23 字数 117 浏览 1 评论 0原文

我可以使用 AVFoundation 或 UIImagePickerController 录制视频。但我无法将视频从某一特定秒修剪到另一个特定持续时间/时间。任何人都可以帮助我吗?

谢谢, 湿婆克里希纳。

Iam able to record the video by using AVFoundation or UIImagePickerController. But i am unable to trim the video from one particular second to another particular duration/time. Can any one help me.

Thanks,
Siva Krishna.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

鸩远一方 2024-10-14 09:27:23

您可以让 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.
    }
}

You can have the UIImagePickerController enable trimming

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;

Unfortunately after you get back from the imagePickerController, You are forced to convert the video manually.

-(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.
    }
}
乖乖哒 2024-10-14 09:27:23

上述的 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
            }
        }
    }
}

Swift version of above

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
            }
        }
    }
}
給妳壹絲溫柔 2024-10-14 09:27:23

swift 4的最佳解决方案,我找到了那里。我确实根据我的需要修复了它,但它确实很清晰和方便。

代码:

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("

The best solution for swift 4, i have found there. I did fixes it for my needs, but it's really clear and convenience.

The code:

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)")
}
}
月竹挽风 2024-10-14 09:27:23

您应该将 kUTTypeMovie 添加到 setMediaTypes 数组中,它将起作用。

you should add kUTTypeMovie in the setMediaTypes array and it will work.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文