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
Copy Markdown
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