kewlbear / YoutubeDL-iOS

Swift package of youtube_dl python module for iOS
MIT License
112 stars 35 forks source link

App crashes in release mode #19

Open Asherbinyy opened 1 month ago

Asherbinyy commented 1 month ago

The download works fine in the debug mode but, crashes when I run the app in the release. PS: I'm using the latest version. This is the code I use from package:

`import Foundation import Photos import YoutubeDL import PythonKit

@objc(YoutubeDLWrapper) class YoutubeDLWrapper: NSObject {

func downloadVideo(url: String, completion: @escaping (Result<String, Error>) -> Void) {

    if let url = URL(string: url) {
        let documentsDirectory: URL
                if #available(iOS 16.0, *) {
                    documentsDirectory = URL.documentsDirectory
                } else {
                    documentsDirectory = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
                }
                FileManager.default.changeCurrentDirectoryPath(documentsDirectory.path)
        Task {
            do {
                let path = try await self.startDownload(url: url)
                completion(.success(path))
            } catch {
                completion(.failure(error))
            }
        }
    } else {
        completion(.failure(NSError(domain: "Invalid URL", code: 400, userInfo: nil)))
    }
}

private func startDownload(url: URL) async throws -> String {
    print(#function, url)
    clearCache()
    do {
        let (info, _, _) = try await download(url: url)
        guard let path = info.flatMap({ String($0["_filename"]) }) else {
            print(#function, "no '_filename'?", info ?? "nil")
            throw NSError(domain: "Download Error", code: 500, userInfo: [NSLocalizedDescriptionKey: "Failed to get the output file path"])
        }
        let outputURL: URL
        if #available(iOS 16.0, *) {
            outputURL = URL(filePath: path)
        } else {
            outputURL = URL(fileURLWithPath: path)
        }
        print("############## finished #############")
        print("outputURL:\(outputURL)")
        export(url: outputURL)
        notify(body: "Finished")
        return outputURL.path
    } catch {
        print(#function, error)
        throw error
    }
}

private func export(url: URL) {
    PHPhotoLibrary.shared().performChanges({
        _ = PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: url)
    }) { success, error in
        print(#function, success, error ?? "")
        DispatchQueue.main.async {
            print("error:\(String(describing: error))")
        }
    }
}

private func download(url: URL) async throws -> (PythonObject?, [String], [PythonObject]) {
    let str = NSLocalizedString("Extracting info", comment: "progress description")
    print("str:\(str)")
    var info: PythonObject?
    var files = [String]()
    var formats = [PythonObject]()
    var error: String?
    let argv: [String] = (
        url.pathExtension == "mp4"
        ? ["-o", url.lastPathComponent]
        : [
            "-f", "bestvideo+bestaudio[ext=m4a]/best",
            "--merge-output-format", "mp4",
            "--postprocessor-args", "Merger+ffmpeg:-c:v h264",
            "-o", "%(title).200B.%(ext)s",
        ]
    ) + [
        "--no-check-certificates",
        url.absoluteString,
    ]
    print(#function, argv)
    try await yt_dlp(argv: argv) { dict in
        info = dict["info_dict"]
        let status = String(dict["status"]!)
        switch status {
        case "downloading":
            print("downloading")
        case "finished":
            print(#function, dict["filename"] ?? "no filename")
            files.append(String(dict["filename"]!)!)
            formats.append(info!)
        default:
            print(#function, dict)
        }
    } log: { level, message in
        print(#function, level, message)
        if level == "error" || message.hasSuffix("has already been downloaded") {
            error = message
        }
    } makeTranscodeProgressBlock: {
        let t0 = ProcessInfo.processInfo.systemUptime
        return { (progress: Double) in
            print(#function, "transcode:", progress)
            let elapsed = ProcessInfo.processInfo.systemUptime - t0
            let speed = progress / elapsed
            let ETA = (1 - progress) / speed
            guard ETA.isFinite else { return }
            print("progress:\(Int64(progress * 100))")
        }
    }
    if let error {
        throw NSError(domain: "App", code: 1, userInfo: [NSLocalizedDescriptionKey: error])
    }
    return (info, files, formats)
}

private func clearCache() {
    let fileManager = FileManager.default
    let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first!
    do {
        let fileName = try fileManager.contentsOfDirectory(atPath: paths)
        for file in fileName {
            let filePath = URL(fileURLWithPath: paths).appendingPathComponent(file).absoluteURL
            try fileManager.removeItem(at: filePath)
        }
    } catch let error {
        print(error)
    }
}

And this is what Xcode throws:

Screenshot 2024-09-05 at 6 05 50 PM

What could be the possible reason?