Skip to content

Instantly share code, notes, and snippets.

@noahbroyles
Last active November 13, 2025 00:13
Show Gist options
  • Select an option

  • Save noahbroyles/77c0c7827d52fa051b0660e8bc2917f2 to your computer and use it in GitHub Desktop.

Select an option

Save noahbroyles/77c0c7827d52fa051b0660e8bc2917f2 to your computer and use it in GitHub Desktop.
Search YouTube Music for videos from any search phrase
# AUTHOR: Noah Broyles
# DESCRIPTION: Uses the YouTube Music search API to find videoURLs, titles, artists, and thumbnail images for any songs searched for.
#
# Last Working: Nov 12, 2025
import re
import json
import requests
from typing import Dict, List
def extract_videos(search_data: Dict) -> List[Dict]:
"""
AI slop function which extracts video information from the YouTube Music search API response.
Returns a list of dictionaries containing the following keys:
title: The song title
artist: The song artist
length: The track length
videoId: The YouTube video ID
thumbnailURL: The video thumbnail URL
videoURL: The YouTube video URL
"""
time_re = re.compile(r"\b\d{1,2}:\d{2}(?::\d{2})?\b")
results = []
def get(obj, path, default=None):
cur = obj
for key in path:
if isinstance(cur, dict):
cur = cur.get(key, default)
elif isinstance(cur, list) and isinstance(key, int):
if 0 <= key < len(cur):
cur = cur[key]
else:
return default
else:
return default
return cur
tabs = get(search_data, ["contents", "tabbedSearchResultsRenderer", "tabs"], [])
for tab in tabs:
sections = get(tab, ["tabRenderer", "content", "sectionListRenderer", "contents"], [])
if not sections:
continue
# -------------------------------
# 1) Top card: musicCardShelfRenderer
# -------------------------------
for section in sections:
card = section.get("musicCardShelfRenderer")
if not card:
continue
title = get(card, ["title", "runs", 0, "text"])
video_id = get(card, ["onTap", "watchEndpoint", "videoId"])
if not (title and video_id):
continue
subtitle_runs = get(card, ["subtitle", "runs"], []) or []
artist = None
length = None
for run in subtitle_runs:
txt = run.get("text", "").strip()
if not txt or txt == "•":
continue
m = time_re.search(txt)
if m:
length = m.group(0)
continue
if txt.lower() != "video" and "views" not in txt.lower():
artist = txt
if not length:
label = get(card, ["subtitle", "accessibility", "accessibilityData", "label"], "") or ""
m = time_re.search(label)
if m:
length = m.group(0)
thumbs = get(card, ["thumbnail", "musicThumbnailRenderer", "thumbnail", "thumbnails"], []) or []
thumb_url = thumbs[0]["url"] if thumbs else None
results.append({
"title": title,
"artist": artist,
"length": length,
"videoId": video_id,
"thumbnailURL": thumb_url,
"videoURL": f"https://youtu.be/{video_id}",
})
# -------------------------------
# 2) List results: musicShelfRenderer
# -------------------------------
for section in sections:
shelf = section.get("musicShelfRenderer")
if not shelf:
continue
for item in shelf.get("contents", []):
r = item.get("musicResponsiveListItemRenderer")
if not r:
continue
title = get(
r,
["flexColumns", 0, "musicResponsiveListItemFlexColumnRenderer", "text", "runs", 0, "text"]
)
if not title:
continue
video_id = get(r, ["playlistItemData", "videoId"])
if not video_id:
video_id = get(
r,
["flexColumns", 0, "musicResponsiveListItemFlexColumnRenderer", "text", "runs", 0, "navigationEndpoint", "watchEndpoint", "videoId"]
)
if not video_id:
# not a real video entry, skip that clown
continue
subtitle_runs = get(
r,
["flexColumns", 1, "musicResponsiveListItemFlexColumnRenderer", "text", "runs"],
[]
) or []
artist = None
length = None
for run in subtitle_runs:
txt = run.get("text", "").strip()
if not txt or txt == "•":
continue
m = time_re.search(txt)
if m:
length = m.group(0)
continue
if txt.lower() != "video" and "views" not in txt.lower():
artist = txt
if not length:
label = get(
r,
["flexColumns", 1, "musicResponsiveListItemFlexColumnRenderer", "text", "accessibility", "accessibilityData", "label"],
""
) or ""
m = time_re.search(label)
if m:
length = m.group(0)
thumbs = get(
r,
["thumbnail", "musicThumbnailRenderer", "thumbnail", "thumbnails"],
[]
) or []
thumb_url = thumbs[0]["url"] if thumbs else None
results.append({
"title": title,
"artist": artist,
"length": length,
"videoId": video_id,
"thumbnailURL": thumb_url,
"videoURL": f"https://youtu.be/{video_id}",
})
return results
def get_videos(search_query: str):
"""
Search YouTube music API for songs.
:param search_query: The query to search for.
"""
API_HEADERS = {
"accept": "*/*",
"accept-language": "en-US,en;q=0.5",
"cache-control": "no-cache",
"content-type": "application/json",
"pragma": "no-cache",
"priority": "u=1, i",
"sec-ch-ua": "\"Chromium\";v=\"142\", \"Google Chrome\";v=\"142\", \"Not_A Brand\";v=\"99\"",
"sec-ch-ua-arch": "\"x86\"",
"sec-ch-ua-bitness": "\"64\"",
"sec-ch-ua-full-version-list": "\"Chromium\";v=\"142.0.0.0\", \"Google Chrome\";v=\"142.0.0.0\", \"Not_A Brand\";v=\"99.0.0.0\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-model": "\"\"",
"sec-ch-ua-platform": "\"Linux\"",
"sec-ch-ua-platform-version": "\"6.17.7\"",
"sec-ch-ua-wow64": "?0",
"sec-fetch-dest": "empty",
"sec-fetch-mode": "same-origin",
"sec-fetch-site": "same-origin",
"sec-gpc": "1",
"x-youtube-bootstrap-logged-in": "false",
"x-youtube-client-name": "67",
"x-youtube-client-version": "1.20251110.03.00"
}
request_body = {
"context": {
"client": {
"hl": "en",
"gl": "US",
"deviceMake": "",
"deviceModel": "",
"visitorData": "CgtYWmtKUkhpNVZSSSiBjdTIBjIKCgJVUxIEGgAgGmLfAgrcAjEzLllUPXF4Z0JoMi1ERXVFb2lqNThFOTdLUTBJNExDZWt2YXRkd1NRa1Y5Q013dmtGU3Q4TkFGTllSWkFqUDl3NkcwU2lqcEJnZ3lzSUZDRE1vZTBGNGVqT0dfQ0dCbGdaZmlQTlVlUlhOVFU0RlZuUmZOcjU0Q0dqcmZkejVUQjJtVll0eWRjdWFwb3NjWHhnMm5faVlJTUhSZ1g2UFM4Q3J6d3lYelVyLUhtbVVWREdqWUdqeF9aT19rc3lXajgxNW9qOUpLNVcyWm1kdl9kZ0pSeGotWjU0SmhHdl85b1NmOHI2R2hMeTRpZkFmS2hGc3pSWHRMVXNTN0J6SG4xQUgxR25JMzhDekRuZldEaGxmaW5kNVN4MjNxUzdJeXdzMTJBQTU0Y3JveUpoMFdDWDZrbU85OG55ZGFiUEFKQ00tQzVndGdjc1VvNm95YkhIOERpcXppRTVnUQ%3D%3D",
"userAgent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36,gzip(gfe)",
"clientName": "WEB_REMIX",
"clientVersion": "1.20251110.03.00",
"osName": "X11",
"osVersion": "",
"originalUrl": "https://music.youtube.com/",
"screenPixelDensity": 1,
"platform": "DESKTOP",
"clientFormFactor": "UNKNOWN_FORM_FACTOR",
"screenDensityFloat": 1.3333333730697632,
"userInterfaceTheme": "USER_INTERFACE_THEME_DARK",
"timeZone": "America/New_York",
"browserName": "Chrome",
"browserVersion": "142.0.0.0",
"acceptHeader": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
"screenWidthPoints": 2066,
"screenHeightPoints": 1505,
"utcOffsetMinutes": -300,
"musicAppInfo": {
"pwaInstallabilityStatus": "PWA_INSTALLABILITY_STATUS_CAN_BE_INSTALLED",
"webDisplayMode": "WEB_DISPLAY_MODE_BROWSER",
"storeDigitalGoodsApiSupportStatus": {
"playStoreDigitalGoodsApiSupportStatus": "DIGITAL_GOODS_API_SUPPORT_STATUS_UNSUPPORTED"
}
}
},
"user": {
"lockedSafetyMode": False
},
"request": {
"useSsl": True,
"consistencyTokenJars": [],
"internalExperimentFlags": []
},
},
"query": search_query,
"suggestStats": {
"validationStatus": "VALID",
"parameterValidationStatus": "VALID_PARAMETERS",
"clientName": "youtube-music",
"searchMethod": "ENTER_KEY",
"inputMethods": [
"KEYBOARD"
],
"originalQuery": search_query,
"availableSuggestions": [
{
"index": 0,
"type": 0
},
{
"index": 1,
"type": 0
},
{
"index": 2,
"type": 0
},
{
"index": 3,
"type": 0
},
{
"index": 4,
"type": 0
},
{
"index": 5,
"type": 0
},
{
"index": 6,
"type": 46
}
],
"zeroPrefixEnabled": True,
"firstEditTimeMsec": 1977,
"lastEditTimeMsec": 8156
},
"inlineSettingStatus": "INLINE_SETTING_STATUS_ON"
}
def _get_api_response():
return requests.post(f"https://music.youtube.com/youtubei/v1/search?prettyPrint=false", headers=API_HEADERS, data=json.dumps(request_body)).json()
response = _get_api_response()
return extract_videos(response)
if __name__ == '__main__':
print(get_videos('let her go passenger'))
"""
[{'title': 'Let Her Go (Live from The Factory Theatre, Sydney)', 'artist': 'Passenger', 'length': '4:15', 'videoId': 'RBumgq5yVrA', 'thumbnailURL': 'https://i.ytimg.com/vi/RBumgq5yVrA/sddefault.jpg?sqp=-oaymwEWCJADEOEBIAQqCghqEJQEGHgg6AJIWg&rs=AMzJL3nUQb83j7wsH9zUc_WNf-ZFxqkkMg', 'videoURL': 'https://youtu.be/RBumgq5yVrA'}, {'title': 'Let Her Go', 'artist': 'Passenger', 'length': None, 'videoId': 'xsvuOkYIRoI', 'thumbnailURL': 'https://lh3.googleusercontent.com/Rme0SKKm-cStCzJFIdZD6kbJX9VABQGWDS7MPSlf2py-JbIpnRMmuF1E-_dkiJf2dsUDxMEdqXuaHfM=w60-h60-l90-rj', 'videoURL': 'https://youtu.be/xsvuOkYIRoI'}, {'title': 'Let Her Go', 'artist': 'Passenger', 'length': None, 'videoId': '1Tgu8aK0omo', 'thumbnailURL': 'https://lh3.googleusercontent.com/9va_O0B2rTWjHJsA00aHVyhfoJddCh-QCUxMvFKa-2B9VpI4b-Yhifn3JnBTPK8yUFurutOcq4ks2ro=w60-h60-l90-rj', 'videoURL': 'https://youtu.be/1Tgu8aK0omo'}, {'title': 'Let Her Go (Anniversary Edition) (feat. Ed Sheeran)', 'artist': 'Passenger', 'length': None, 'videoId': '8a1eG-Y0u6Y', 'thumbnailURL': 'https://lh3.googleusercontent.com/lyvbmnz40KV5VS-8LOceMKdWLmj0XzR9MsNM38gEl0N_WOs1QrF2Ukqla5h27o8Ox3FqcWqk4PerYncZJg=w60-h60-l90-rj', 'videoURL': 'https://youtu.be/8a1eG-Y0u6Y'}, {'title': 'Let Her Go (Acoustic)', 'artist': 'Passenger', 'length': None, 'videoId': '2On8YN3WbxE', 'thumbnailURL': 'https://lh3.googleusercontent.com/Rme0SKKm-cStCzJFIdZD6kbJX9VABQGWDS7MPSlf2py-JbIpnRMmuF1E-_dkiJf2dsUDxMEdqXuaHfM=w60-h60-l90-rj', 'videoURL': 'https://youtu.be/2On8YN3WbxE'}, {'title': 'Let Her Go [Anniversary Edition] (feat. Ed Sheeran)', 'artist': 'Passenger', 'length': None, 'videoId': 'HTcL9WkB_wg', 'thumbnailURL': 'https://i.ytimg.com/vi/HTcL9WkB_wg/sddefault.jpg?sqp=-oaymwEWCJADEOEBIAQqCghqEJQEGHgg6AJIWg&rs=AMzJL3kjiF9RkmkPLN0VJpJrYjv0mbRBWw', 'videoURL': 'https://youtu.be/HTcL9WkB_wg'}, {'title': 'Let Her Go', 'artist': 'Jon Young', 'length': None, 'videoId': 'ipWa2sl1lq0', 'thumbnailURL': 'https://lh3.googleusercontent.com/a9BgoVIauloZenNrGsi8kvHRX-rOiiKuNJeFcMhfnRkHSbp7K622WL1o0xBhoIuhqvVwfwsl6Y0YCw0=w60-h60-l90-rj', 'videoURL': 'https://youtu.be/ipWa2sl1lq0'}, {'title': 'Passenger - Let Her Go (Lyrics)', 'artist': 'Dan Music', 'length': None, 'videoId': 'JLdjXD_QfU0', 'thumbnailURL': 'https://i.ytimg.com/vi/JLdjXD_QfU0/sddefault.jpg?sqp=-oaymwEWCJADEOEBIAQqCghqEJQEGHgg6AJIWg&rs=AMzJL3mMEGaviNAfyQixQ7TFpj2yDFWhCw', 'videoURL': 'https://youtu.be/JLdjXD_QfU0'}, {'title': 'LET HER GO - PASSENGER (Soul Blues Version) | AI Cover', 'artist': 'Soul Blues Covers', 'length': None, 'videoId': 'C1G1aVthe68', 'thumbnailURL': 'https://i.ytimg.com/vi/C1G1aVthe68/sddefault.jpg?sqp=-oaymwEWCJADEOEBIAQqCghqEJQEGHgg6AJIWg&rs=AMzJL3la9jKKDvFFIvNacXjg3Rxkxt5eww', 'videoURL': 'https://youtu.be/C1G1aVthe68'}, {'title': 'How To Let Go | Buddhism In English', 'artist': 'The Inner Guide Q&A', 'length': None, 'videoId': '2GYkT6dlUDo', 'thumbnailURL': 'https://i.ytimg.com/vi/2GYkT6dlUDo/hqdefault.jpg?sqp=-oaymwEWCOADEI4CIAQqCggAEOADGC0guwJIWg&rs=AMzJL3nFSy79VdVBZ59g87c3T-OCkWDqug', 'videoURL': 'https://youtu.be/2GYkT6dlUDo'}, {'title': 'Music for the Existentialist: Passenger, Noah Kahan, and... Paul Tillich?', 'artist': 'Some Joyful Noises', 'length': None, 'videoId': 'bYCNbU3mrUM', 'thumbnailURL': 'https://i.ytimg.com/vi/bYCNbU3mrUM/hqdefault.jpg?sqp=-oaymwEWCOADEI4CIAQqCggAEOADGC0guwJIWg&rs=AMzJL3kc3LTRbvOjHGMGIAGbtQ6wEyJ5hg', 'videoURL': 'https://youtu.be/bYCNbU3mrUM'}, {'title': 'She sat in his front seat. He told me to sit in the back with his friend—froze when he saw us kiss.', 'artist': 'Love Diary', 'length': None, 'videoId': 'bq0ZSeMLdYw', 'thumbnailURL': 'https://i.ytimg.com/vi/bq0ZSeMLdYw/hqdefault.jpg?sqp=-oaymwEWCOADEI4CIAQqCggAEOADGC0guwJIWg&rs=AMzJL3n6Llgwny3FodDSV1mp1Br638QprQ', 'videoURL': 'https://youtu.be/bq0ZSeMLdYw'}]
"""
@noahbroyles
Copy link
Author

Updated to work as of November 12th, 2025.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment