alexeichhorn / YouTubeKit

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

some shorts cannot be downloaded for some reason #62

Closed naticio closed 4 hours ago

naticio commented 5 hours ago

the short link: https://youtube.com/shorts/sabOsHyHwX0?si=kEylr4s70kQXyutl

I manipulated urls to make it work and it works for some shorts but not for others, https://youtu.be/sabOsHyHwX0?si=kEylr4s70kQXyutl https://youtu.be/sabOsHyHwX0?si=wbmkErgctMevabeX https://www.youtube.com/watch?v=kitgsNlUlsY

error: pattern (ytplayer.config\s=\s) failed: The operation couldn’t be completed. (YouTubeKit.YouTubeKitError error 1.)

  private func playAudioVideo() {
        //guard let videoURL = URL(string: youtubeURL) else { return }
        isDownloading = true

        guard let normalizedURL = normalizeYouTubeURL(youtubeURL) else {
                print("Invalid YouTube URL")
                return
            }

        print("normalized url->\(normalizedURL)")
        Task {
            do {
//                let video = YouTube(url: videoURL)
//                let streams = try await video.streams
//                
//                // Filter for a combined stream that includes both video and audio, and select the highest resolution
//                if let combinedStream = streams.filterVideoAndAudio()
////                                               .filter({ $0.subtype == "mp4" }) // Ensure MP4 format
////                                               .highestResolutionStream() { // Select highest resolution with video and audio
//                    .filter { $0.includesVideoAndAudioTrack && $0.fileExtension == .mp4 }
//                                              .highestResolutionStream()

                let stream = try await YouTube(url: normalizedURL).streams
                    .filter { $0.includesVideoAndAudioTrack}
                                        //.filter { $0.includesVideoAndAudioTrack && ($0.fileExtension == .mp4 || $0.fileExtension == .mov || $0.fileExtension == .m4a || $0.fileExtension == .mpeg)}
                                          .highestResolutionStream()

                let streamURL = stream?.url

                if streamURL != nil {
                    DispatchQueue.main.async {
                        // Set up the AVPlayer with the combined stream URL
                        //self.playerItemVideo = AVPlayerItem(url: combinedStream.url)
                        self.playerItemVideo = AVPlayerItem(url: streamURL!)

                        // Set up the AVPlayer with a single AVPlayerItem containing both audio and video
                        self.videoPlayer = AVPlayer(playerItem: self.playerItemVideo)

                        // Start playback
                        self.videoPlayer?.play()

                        // Download and save the video to the gallery
                        isDownloading = false
                        self.downloadAndSaveVideoToGallery(url: streamURL!)
                    }
                } else {
                    print("got fucking nil in stream url")
                    isDownloading = false
                }
            } catch {
                isDownloading = false
                print("Error with YouTubeKit: \(error)")
            }
        }
    }

 private func normalizeYouTubeURL(_ url: String) -> URL? {
        guard var urlComponents = URLComponents(string: url) else { return nil }

        if urlComponents.host?.contains("youtu.be") == true {
            // Extract the video ID from the path
            let videoId = urlComponents.path.replacingOccurrences(of: "/", with: "")

            // Construct a new URL in the desired format
            urlComponents.host = "www.youtube.com"
            urlComponents.path = "/watch"
            urlComponents.queryItems = [URLQueryItem(name: "v", value: videoId)]

            return urlComponents.url
        }

        if urlComponents.host?.contains("youtube.com") == true, urlComponents.path.starts(with: "/shorts/") {
            // Extract the video ID from the shorts path
            let videoId = urlComponents.path.replacingOccurrences(of: "/shorts/", with: "")

            // Convert to /watch?v= format
            urlComponents.path = "/watch"
            urlComponents.queryItems = [URLQueryItem(name: "v", value: videoId)]

            return urlComponents.url
        }

        return URL(string: url)
    }
naticio commented 5 hours ago

examples: the short download works for this short: https://youtube.com/shorts/Ek6Vs4b8O_I?si=tQBpRPY2EXXUTgcU

but doesn't work to download this short: https://youtube.com/shorts/sabOsHyHwX0?si=CZL5GpzDMBTNP3Mn

naticio commented 4 hours ago

nevermidn you need to merge the audio and video streamfor thosee cases let stream = streams.filter { stream in stream.includesAudioTrack }.highestAudioBitrateStream()