alexeichhorn / YouTubeKit

YouTube video and audio extractor for iOS, watchOS, visionOS, tvOS and macOS
MIT License
209 stars 47 forks source link

QUESTION: How to get the middle of audio? #58

Open yoondj98 opened 1 month ago

yoondj98 commented 1 month ago

Hello, Alex.

I asked about extracting part of audio to you, and I solved that problem. Thank You!

But, got something wrong about getting middle part of audio.

I tried

components.queryItems?.append(URLQueryItem(name: "range", value: "\(contentStart)-\(contentEnd)"))

this to get part of audio from youtube video. Although, I successed for get 0:00~0:50 audio easily, I failed to get 2:00~2:50 audio.(Exactly, I got Audio Data, but it couldn;'t played with AVAudioPlayer...)

I can't understand why from 0:00 can be played, and others are impossible...

I always got this Error..πŸ˜‚πŸ˜‚

error   NSError domain: "NSOSStatusErrorDomain" - code: 1954115647  0x000000030342d0e0
alexeichhorn commented 1 month ago

Are you putting the url that you get from this directly into AVPlayer? And what do you put in contentStart and contentEnd in the example where it doesn't work?

yoondj98 commented 1 month ago

I use Your library to make audio URL(like https://rr6---~~~) from Youtube URL(ex) https://youtu.be/pVrcTpriJto?si=TNssoKuae3dvnIkn).

Then I get some part of audio with this code

components.queryItems?.append(URLQueryItem(name: "range", value: "\(contentStart)-\(contentEnd)"))
let fileName = generateFileName(from: audioURL)
            let destinationURL = documentsDirectory.appendingPathComponent("new222322323")
            print("destinationURL.path", destinationURL.path)

            // 이미 파일이 μ‘΄μž¬ν•˜λŠ”μ§€ 확인
            if !fileManager.fileExists(atPath: destinationURL.path) {
                do {
                    let (totalBytes, _) = try await fetchFileMetadata(from: audioURL)
                    print("totalBytes", totalBytes)

                    let bytesPerSecond = Double(totalBytes) / 21600
                    let startByte = Int(bytesPerSecond * 0)  // start byte
                    let endByte = Int(bytesPerSecond * 1000)      // end byte

                    // URLRequest 생성
                    var request = URLRequest(url: audioURL) // ex) audioURL= https://rr6--~~
                    request.addValue("bytes=\(startByte)-\(endByte)", forHTTPHeaderField: "Range")
                    let (data, response) = try await URLSession.shared.data(for: request)
                    // λ‹€μš΄λ‘œλ“œν•œ 데이터λ₯Ό λ‘œμ»¬μ— μ €μž₯
                    try data.write(to: destinationURL) // destinationURL is my device file URL where downloaded file exists

                } catch {
                    return .failure(.copyFailed(error))
                }
            }

-> I put in the byte of the initial time and finish time

// HTTP HEAD μš”μ²­μœΌλ‘œ 메타데이터λ₯Ό κ°€μ Έμ˜€λŠ” ν•¨μˆ˜
    private func fetchFileMetadata(from url: URL) async throws -> (totalBytes: Int, totalDuration: Double?) {
        var request = URLRequest(url: url)
        request.httpMethod = "HEAD"  // HEAD μš”μ²­μœΌλ‘œ 파일 λ©”νƒ€λ°μ΄ν„°λ§Œ κ°€μ Έμ˜΄

        let (_, response) = try await URLSession.shared.data(for: request)

        if let httpResponse = response as? HTTPURLResponse,
           let contentLength = httpResponse.value(forHTTPHeaderField: "Content-Length"),
           let totalBytes = Int(contentLength) {

            let totalDuration: Double? = httpResponse.value(forHTTPHeaderField: "X-Content-Duration").flatMap(Double.init)
            return (totalBytes, totalDuration)
        } else {
            throw URLError(.badServerResponse)
        }
    }

When I put let startByte = Int(bytesPerSecond 0), It works. But, If I put let startByte = Int(bytesPerSecond 10), It doesn't works.

alexeichhorn commented 1 month ago

So an audio file is, very simply put, set up like this: First bytes in the file is metadata that defines the codec etc for a player to know how to play this file, after that you have actual content. So when you only retrieve data from a point that is not 0, you basically remove the metadata, which is crucial.

Want do you want to achieve? Play the audio file from a certain position?

yoondj98 commented 1 month ago

Yeah right! Getting whole of the file needs too long time to wait and request too much of LTE or 5G data usage, so I want to get it only parts of audio like 0:00 ~ 0:10, 1:00 ~ 1:10, 2:00 ~ 2:10,... Then I will use these certain positions to play in the App.

yoondj98 commented 1 month ago

@alexeichhorn Do you need anything I have to provide more information?

alexeichhorn commented 1 month ago

If you plug in the unmodified url straight into AVPlayer (no manual downloading) and seek directly to the desired starting position, it shouldn't load unnecessary parts of the file. Or did you try this already?

yoondj98 commented 1 month ago

I tried that and I couldn't play it.πŸ₯²πŸ₯²

Then you mean if I need to play the part of audio File, I have to download all data of the file?

And if it's long time to download, use this code several times

var request = URLRequest(url: externalURL)
                    request.addValue("bytes=\(startByte)-\(endByte)", forHTTPHeaderField: "Range")
                    var (data, response) = try await URLSession.shared.data(for: request)

to get each part(ex)0:00~2:00, 2:00~4:00, 4:00~6:00,...) of the audio data. Then append all of the audio data for using in player??

alexeichhorn commented 4 weeks ago

How did you try it? Works for me. I have an audio app and I'm using AVPlayer (might not work on AVAudioPlayer). See this:

let audioStream = try await YouTube(url: URL(string: "https://www.youtube.com/watch?v=xyz")!)
                .streams
                .filterAudioOnly()
                .filter { $0.isNativelyPlayable }
                .highestAudioBitrateStream()!
let playerItemAudio = AVPlayerItem(url: audioStream.url)