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を抽出する方法を説明した。
コメント