Last active
November 13, 2025 00:13
-
-
Save noahbroyles/77c0c7827d52fa051b0660e8bc2917f2 to your computer and use it in GitHub Desktop.
Search YouTube Music for videos from any search phrase
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # 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'}] | |
| """ |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Updated to work as of November 12th, 2025.