SwiftUI | YouTubeの動画ファイルのURLを抽出する方法

音声・動画

SwiftUIでYouTubeの動画ファイルのURLを抽出する方法を説明する。

抽出結果イメージ

YouTubeのHTMLソースに記述されている動画や音声のURLやその付帯情報を抽出する。

  • 1行目のitagは動画や音声ファイルのフォーマットを示す指標
  • 2行目のhttps://rr〜が動画や音声ファイルのURL
[[["itag", "22"], 
  ["url", "https://rr3---sn-ogul7n7d.googlevideo.com/videoplayback?expire=1664108400&ei=EPMvY-qcBv-D2roPjPCXqA4&ip=240b%3Ac010%3A470%3A5c5f%3A51be%3A2186%3A997a%3Aa45a&id=o-AM3IKyXixUPe4CyEsIZcQjS_H0S4weXLiBZjeignsCRF&itag=22&source=youtube&requiressl=yes&mh=_R&mm=31%2C26&mn=sn-ogul7n7d%2Csn-3pm7kn7l&ms=au%2Conr&mv=m&mvi=3&pl=52&initcwndbps=566250&vprv=1&mime=video%2Fmp4&ns=Jb3DWB1wzPUnAT0OcHSVHoMI&cnr=14&ratebypass=yes&dur=20.665&lmt=1662988701380030&mt=1664086623&fvip=5&fexp=24001373%2C24007246&c=MWEB&txp=6211224&n=-D0q_km1Cd8NYY5M1&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cvprv%2Cmime%2Cns%2Ccnr%2Cratebypass%2Cdur%2Clmt&sig=AOq0QJ8wRQIhAKLa9uyDB46APqqdzwiUZwm7fH13v82JP8oBgWFTVTRIAiBXK9D7zN_AWJaVGg09DVHvMnWeQl-ysDSNFxIdPRMqvw%3D%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&lsig=AG3C_xAwRAIgID1o0eF9QjS-d5tfj9l-xukN7CEKPZtEc0vBPDivV9UCIHPK4ABr5Tp9ACWpHXpBrj9b9F1ltSEYd1Sr67HlNISg"], 
  ["mimeType", "video/mp4; "], 
  ["codecs", "avc1.64001F_mp4a.40.2"], 
  ["bitrate", "1358628"], 
  ["width", "1280"], 
  ["height", "620"], 
  ["lastModified", "1662988701380030"], 
  ["quality", "hd720"], 
  ["fps", "30"], 
  ["qualityLabel", "720p"], 
  ["projectionType", "RECTANGULAR"], 
  ["audioQuality", "AUDIO_QUALITY_MEDIUM"], 
  ["approxDurationMs", "20665"], 
  ["audioSampleRate", "44100"], 
  ["audioChannels", "2}"]], 
 [["itag", "18"], ["url", "https://rr3---sn-ogul7n7d.google...
   ...

具体例

新規swiftファイルを作成し任意の名前を付ける。ここではYouTube.swiftと名付けた。

YouTube.swiftにコードを記述する。

import SwiftUI

struct YouTube {
    
    let subFunc = SubFunctions()
    
    func サムネイル抽出(YouTubeのURL: String) -> String {
        let id = YouTubeのURL.split(separator: "=")[1]
//        let thumbnailStart         = "http://img.youtube.com/vi/\(id)/1.jpg"  // 120x90
//        let thumbnailMiddle        = "http://img.youtube.com/vi/\(id)/2.jpg"  // 120x90
//        let thumbnailEnd           = "http://img.youtube.com/vi/\(id)/3.jpg"  // 120x90
//        let thumbnailHqdefault     = "http://img.youtube.com/vi/\(id)/hqdefault.jpg"  // 480x360
//        let thumbnailMqdefault     = "http://img.youtube.com/vi/\(id)/mqdefault.jpg"  // 320x180
//        let thumbnailDefault       = "http://img.youtube.com/vi/\(id)/default.jpg"  // 120x90
//        let thumbnailSddefault     = "http://img.youtube.com/vi/\(id)/sddefault.jpg"  // 640x480
        let thumbnailMaxresdefault = "http://img.youtube.com/vi/\(id)/maxresdefault.jpg"  // 1920x1080
        
        return thumbnailMaxresdefault

    }

    func タイトル抽出(YouTubeのURL: String) -> String {
        let HTMLソース: String = subFunc.URLをHTMLソースに変換(url: YouTubeのURL)
        if HTMLソース == "error" {
            return ""
        }
        let HTMLソースデコード後 = subFunc.特殊文字をデコード(source: HTMLソース)
        let タイトル = タイトルを抽出(source: HTMLソースデコード後)
        
        func タイトルを抽出(source: String) -> String {
            let str1 = "playerOverlayVideoDetailsRenderer:{title:{runs:[{text:"
            let str2 = "}]},subtitle"
            let result = source
                .replacingOccurrences(of: str1, with: "\\")  // str1 -> \
                .replacingOccurrences(of: str2, with: "\\")  // str2 -> \
                .split(separator: "\\")[1]  // \で分割
            return String(result)
        }
        return タイトル
    }
    
    func 動画抽出(YouTubeのURL: String) -> String {
        let 全ファイルのURLリスト = subFunc.URLリスト抽出(YouTubeのURL: YouTubeのURL)
        let mp4動画URL_360p: String = extractUrl(fileList: 全ファイルのURLリスト, itag: 18)  // 360p mp4 video & audio
        let mp4動画URL_720p: String = extractUrl(fileList: 全ファイルのURLリスト, itag: 22)  // 720p mp4 video & audio
        let mp4動画URL_1080p: String = extractUrl(fileList: 全ファイルのURLリスト, itag: 37)  // 1080p mp4 video & audio
        var mp4動画URL_最高品質: String {
            if mp4動画URL_1080p != "no url" {
                return mp4動画URL_1080p
            } else if mp4動画URL_720p != "no url" {
                return mp4動画URL_720p
            } else if mp4動画URL_360p != "no url" {
                return mp4動画URL_360p
            } else {
                return "no video url"
            }
        }
        
        func extractUrl(fileList: [[[String]]], itag: Int) -> String {
            var result: String = "no url"
            for item in fileList {
                if item[0][1] == String(itag) {
                    result = item[1][1]
                }
            }
            return result
        }
        
        return mp4動画URL_最高品質
    }

    func 音声抽出(YouTubeのURL: String) -> String {
        let 全ファイルのURLリスト = subFunc.URLリスト抽出(YouTubeのURL: YouTubeのURL)
        let m4a音声URL_128k: String = extractUrl(fileList: 全ファイルのURLリスト, itag: 140)  // 128k m4a audio
        let m4a音声URL_256k: String = extractUrl(fileList: 全ファイルのURLリスト, itag: 141)  // 256k m4a audio
        var m4a音声URL_最高品質: String {
            if m4a音声URL_256k != "no url" {
                return m4a音声URL_256k
            } else if m4a音声URL_128k != "no url" {
                return m4a音声URL_128k
            } else {
                return "no audio url"
            }
        }
        
        func extractUrl(fileList: [[[String]]], itag: Int) -> String {
            var result: String = "no url"
            for item in fileList {
                if item[0][1] == String(itag) {
                    result = item[1][1]
                }
            }
            return result
        }
        return m4a音声URL_最高品質
    }

}


struct SubFunctions {
    func 特殊文字をデコード(source: String) -> String {
        return source
            .replacingOccurrences(of: "\\x22", with: "\"")  // \x22 -> "
            .replacingOccurrences(of: "\\x27", with: "'")  // \x27 -> '
            .replacingOccurrences(of: "\\x3d", with: "=")  // \x3d -> =
            .replacingOccurrences(of: "\\x5b", with: "[")  // \x5b -> [
            .replacingOccurrences(of: "\\x5d", with: "]")  // \x5d -> ]
            .replacingOccurrences(of: "\\x7b", with: "{")  // \x7b -> {
            .replacingOccurrences(of: "\\x7d", with: "}")  // \x7d -> }
            .replacingOccurrences(of: "\\u0026", with: "&")  // \u0026 -> &
            .replacingOccurrences(of: "\\u0027", with: "'")  // \u0027 -> '
            .replacingOccurrences(of: "\\u003c", with: "<")  // \u003c -> <
            .replacingOccurrences(of: "\\u003d", with: "=")  // \u003d -> =
            .replacingOccurrences(of: "\\u003e", with: ">")  // \u003e -> >
            .replacingOccurrences(of: "\\/", with: "/")  // \/ -> /
            .replacingOccurrences(of: "\\&", with: "&")  // \& -> &
            .replacingOccurrences(of: "codecs=\\", with: ",codecs:")  // codecs=\ -> ,codecs:
            .replacingOccurrences(of: ", mp4a", with: "_mp4a")  // , mp4a\ -> _mp4a
            .replacingOccurrences(of: "\\\"", with: "")  // \" -> none
            .replacingOccurrences(of: "\\\\", with: "\\")  // \\ -> \
            .replacingOccurrences(of: "\\n", with: "[kaigyo]")  // \n -> [kaigyo]
            .replacingOccurrences(of: "\"", with: "")  // " -> none
            .replacingOccurrences(of: "\\", with: "")  // \ -> none
    }
    
    func パーセント文字をデコード(source: String) -> String {
        return source
            .replacingOccurrences(of: "%2C", with: ",")  // %2C -> ,
            .replacingOccurrences(of: "%3A", with: ":")  // %3A -> :
            .replacingOccurrences(of: "%3D", with: "=")  // %3D -> =
    }
    
    func URLをHTMLソースに変換(url: String) -> String {
        guard
            let url = URL(string: url) else {
            print("YouTubeのURLがエラー")
            return "error"
        }
        do {
            return try String(contentsOf: url, encoding: String.Encoding.utf8)
        }
        catch {
            return "error"
        }
    }

    func URLリスト抽出(YouTubeのURL: String) -> [[[String]]] {
        let HTMLソース: String = URLをHTMLソースに変換(url: YouTubeのURL)
        if HTMLソース == "error" {
            return [[[]]]
        }
        let HTMLソースデコード後 = 特殊文字をデコード(source: HTMLソース)
        let 粗く分割した結果 = 粗く分割(source: HTMLソースデコード後)
        let 動画や音声 = 動画や音声を含む要素を抽出(source: 粗く分割した結果)
        let コンマで分割した結果 = コンマで分割(source: 動画や音声)
        let 最終結果 = コロンで分割(source: コンマで分割した結果)

        func 粗く分割(source: String) -> Array<Substring> {
            return source.split(separator: "{")
        }

        func 動画や音声を含む要素を抽出(source: Array<Substring>) -> Array<Substring> {
            return source.filter({ $0.contains("googlevideo") })
        }

        func コンマで分割(source: Array<Substring>) -> [Array<Substring>] {
            let result = source.map {
                $0.split(separator: ",")
            }
            return result
        }

        func コロンで分割(source: [Array<Substring>]) -> [[[String]]] {
            let 仮で置換した結果 = 仮で置換(source: source)
            let コロンで分割した結果 = コロンで分割(source: 仮で置換した結果)
            let 置換し直した結果 = 置換し直す(source: コロンで分割した結果)
            
            func 仮で置換 (source: [Array<Substring>]) -> [[String]] {
                let result = source.map {
                    $0.map {
                        $0.replacingOccurrences(of: "https://", with: "temp//")
                    }
                }
                return result
            }

            func コロンで分割(source: [[String]]) -> [[Array<Substring>]] {
                let result = source.map {
                    $0.map {
                        $0.split(separator: ":")
                    }
                }
                return result
            }

            func 置換し直す (source: [[Array<Substring>]]) -> [[[String]]] {
                let result = source.map {
                    $0.map {
                        $0.map {
                            $0.replacingOccurrences(of: "temp//", with: "https://")
                        }
                    }
                }
                return result
            }

            return 置換し直した結果

        }

        return 最終結果

    }
   
}

ContentView.swiftでYouTube.swiftを使う。

import SwiftUI

struct ContentView: View {
    let url = "https://www.youtube.com/watch?v=Nptp-ogd3aw"
    let youTube = YouTube()
    
    var body: some View {
        Button("ボタンをタップ") {
            let タイトル = youTube.タイトル抽出(YouTubeのURL: url)
            let サムネURL = youTube.サムネイル抽出(YouTubeのURL: url)
            let 動画URL = youTube.動画抽出(YouTubeのURL: url)
            print(タイトル)
            print(サムネURL)
            print(動画URL)
        }
        .buttonStyle(.borderedProminent)
    }
}

まとめ

SwiftUIでYouTubeの動画ファイルのURLを抽出する方法を説明した。

コメント

タイトルとURLをコピーしました