def test_setup(self): headers = YTMusic.setup(config['auth']['headers_file'], config['auth']['headers_raw']) self.assertGreaterEqual(len(headers), 2) headers_raw = config['auth']['headers_raw'].split('\n') with unittest.mock.patch('builtins.input', side_effect=(headers_raw + [EOFError()])): headers = YTMusic.setup(config['auth']['headers_file']) self.assertGreaterEqual(len(headers), 2)
def on_start(self): if self.auth: self.api = YTMusic(self._ytmusicapi_auth_json) else: self.api = YTMusic() if self._auto_playlist_refresh_rate: self._auto_playlist_refresh_timer = RepeatingTimer( self._refresh_auto_playlists, self._auto_playlist_refresh_rate) self._auto_playlist_refresh_timer.start() self._youtube_player_refresh_timer = RepeatingTimer( self._refresh_youtube_player, self._youtube_player_refresh_rate) self._youtube_player_refresh_timer.start()
def run(self, args, config): from ytmusicapi.ytmusic import YTMusic filepath = input( "Enter the path where you want to save auth.json [default=current dir]: " ) if not filepath: filepath = os.getcwd() path = Path(filepath + '/auth.json') print('Using "' + str(path) + '"') if (path.exists()): print("File already exists!") return 1 print( "Open Youtube Music, open developer tools (F12), go to Network tab," ) print( "right click on a POST request and choose \"Copy request headers\"." ) print("Then paste (CTRL+SHIFT+V) them here and press CTRL+D.") try: print(YTMusic.setup(filepath=str(path))) except Exception: logger.exception("YoutubeMusic setup failed") return 1 print("Authentication JSON data saved to {}".format(str(path))) print('') print('Update your mopidy.conf to reflect the new auth file:') print(' [youtubemusic]') print(' enabled=true') print(' auth_json=' + str(path)) return 0
def test_setup(self): headers = YTMusic.setup(config['auth']['headers_file'], config['auth']['headers_raw']) self.assertGreaterEqual(len(headers), 2) with unittest.mock.patch('builtins.input', return_value=config['auth']['headers_raw']): self.assertGreaterEqual(len(headers), 2)
def __init__(self, config, audio): super().__init__() self.config = config self.audio = audio self.uri_schemes = ["ytmusic"] self.auth = False self._auto_playlist_refresh_rate = ( config["ytmusic"]["auto_playlist_refresh"] * 60 ) self._auto_playlist_refresh_timer = None self._youtube_player_refresh_rate = ( config["ytmusic"]["youtube_player_refresh"] * 60 ) self._youtube_player_refresh_timer = None self.playlist_item_limit = config["ytmusic"]["playlist_item_limit"] self.subscribed_artist_limit = config["ytmusic"][ "subscribed_artist_limit" ] self.history = config["ytmusic"]["enable_history"] self.liked_songs = config["ytmusic"]["enable_liked_songs"] self.mood_genre = config["ytmusic"]["enable_mood_genre"] self.stream_preference = config["ytmusic"]["stream_preference"] self.verify_track_url = config["ytmusic"]["verify_track_url"] if config["ytmusic"]["auth_json"]: self._ytmusicapi_auth_json = config["ytmusic"]["auth_json"] self.auth = True if self.auth: self.api = YTMusic(self._ytmusicapi_auth_json) else: self.api = YTMusic() self.playback = YTMusicPlaybackProvider(audio=audio, backend=self) self.library = YTMusicLibraryProvider(backend=self) if self.auth: self.playlists = YTMusicPlaylistsProvider(backend=self)
def next(self) -> MusicSource: """Loads up the next song in the queue""" try: music_source = self.queue.next() except IndexError: if self.radio and self.watch_playlist: if len(self.watch_playlist["tracks"]) < 3: current_music = self.queue.current() logging.warning( f"Regenerating malformed watch_playlist using id {current_music.info['id']}" ) ytmusic = YTMusic() self.watch_playlist = ytmusic.get_watch_playlist( videoId=current_music.info["id"] ) if 2 <= len(self.watch_playlist["tracks"]) - 1: track_no = random.randint( 2, len(self.watch_playlist["tracks"]) - 1 ) track = self.watch_playlist["tracks"][track_no] base_url = "https://www.youtube.com/watch?v=" normalized_url = base_url + track["videoId"] asyncio.run_coroutine_threadsafe( self.play_youtube(normalized_url), self.client.loop ) return else: logging.error( "Watch playlist is empty or malformed after second try, stopping radio mode." ) logging.info("No more music to play. Stopping...") self.stop() return None else: asyncio.run_coroutine_threadsafe( self.play(music_source), self.client.loop ) return music_source
def run(self, args, config): from ytmusicapi.ytmusic import YTMusic path = config["ytmusic"]["auth_json"] if not path: logger.error("auth_json path not defined in config") return 1 print('Updating credentials in "' + str(path) + '"') print( "Open Youtube Music, open developer tools (F12), go to Network tab," ) print( 'right click on a POST request and choose "Copy request headers".') print("Then paste (CTRL+SHIFT+V) them here and press CTRL+D.") try: print(YTMusic.setup(filepath=str(path))) except Exception: logger.exception("YTMusic setup failed") return 1 print("Authentication JSON data saved to {}".format(str(path))) return 0
def __init__(self, config, audio): super().__init__() self.config = config self.audio = audio self.uri_schemes = ["ytm"] from youtube_dl import YoutubeDL from ytmusicapi.ytmusic import YTMusic global YDL YDL = YoutubeDL({ "format": "bestaudio/m4a/mp3/ogg/best", "proxy": httpclient.format_proxy(self.config["proxy"]), "nocheckcertificate": True, "cachedir": False, }) global API API = YTMusic(config["ytmusic"]["auth_json"]) self.playback = YouTubePlaybackProvider(audio=audio, backend=self) self.library = YouTubeLibraryProvider(backend=self) self.playlists = YouTubePlaylistsProvider(backend=self)
def run(self, args, config): from ytmusicapi.ytmusic import YTMusic filepath = input("Enter the path where you want to save auth.json:") path = Path(filepath) if (path.exists()): print("File already exists!") return 1 print( "Open Youtube Music, open developer tools (F12), go to Network tab," ) print( "right click on a POST request and choose \"Copy request headers\"." ) print("Then paste (CTRL+SHIFT+V) them here and press CTRL+D.") try: print(YTMusic.setup(filepath=str(path))) except Exception: logger.exception("YTMusic setup failed") return 1 print("auth.json saved to {}".format(str(path))) return 0
def test_setup(self): YTMusic.setup(config['auth']['headers_file'], config['auth']['headers_raw'])
import unittest import configparser import sys sys.path.insert(0, '..') from ytmusicapi.ytmusic import YTMusic # noqa: E402 config = configparser.RawConfigParser() config.read('./test.cfg', 'utf-8') youtube = YTMusic() youtube_auth = YTMusic(config['auth']['headers_file']) youtube_brand = YTMusic(config['auth']['headers'], config['auth']['brand_account']) class TestYTMusic(unittest.TestCase): def test_init(self): self.assertRaises(Exception, YTMusic, "{}") def test_setup(self): YTMusic.setup(config['auth']['headers_file'], config['auth']['headers_raw']) ############### # BROWSING ############### def test_search(self): query = "Oasis Wonderwall" self.assertRaises(Exception, youtube_auth.search, query, "song") results = youtube.search(query)
class YTMusicBackend(pykka.ThreadingActor, backend.Backend, YTMusicScrobbleListener): def __init__(self, config, audio): super().__init__() self.config = config self.audio = audio self.uri_schemes = ["ytmusic"] self.auth = False self._auto_playlist_refresh_rate = ( config["ytmusic"]["auto_playlist_refresh"] * 60) self._auto_playlist_refresh_timer = None self._youtube_player_refresh_rate = ( config["ytmusic"]["youtube_player_refresh"] * 60) self._youtube_player_refresh_timer = None self.playlist_item_limit = config["ytmusic"]["playlist_item_limit"] self.subscribed_artist_limit = config["ytmusic"][ "subscribed_artist_limit"] self.history = config["ytmusic"]["enable_history"] self.liked_songs = config["ytmusic"]["enable_liked_songs"] self.mood_genre = config["ytmusic"]["enable_mood_genre"] self.stream_preference = config["ytmusic"]["stream_preference"] self.verify_track_url = config["ytmusic"]["verify_track_url"] if config["ytmusic"]["auth_json"]: self._ytmusicapi_auth_json = config["ytmusic"]["auth_json"] self.auth = True if self.auth: self.api = YTMusic(self._ytmusicapi_auth_json) else: self.api = YTMusic() self.playback = YTMusicPlaybackProvider(audio=audio, backend=self) self.library = YTMusicLibraryProvider(backend=self) if self.auth: self.playlists = YTMusicPlaylistsProvider(backend=self) def on_start(self): if self._auto_playlist_refresh_rate: self._auto_playlist_refresh_timer = RepeatingTimer( self._refresh_auto_playlists, self._auto_playlist_refresh_rate) self._auto_playlist_refresh_timer.start() self._youtube_player_refresh_timer = RepeatingTimer( self._refresh_youtube_player, self._youtube_player_refresh_rate) self._youtube_player_refresh_timer.start() def on_stop(self): if self._auto_playlist_refresh_timer: self._auto_playlist_refresh_timer.cancel() self._auto_playlist_refresh_timer = None if self._youtube_player_refresh_timer: self._youtube_player_refresh_timer.cancel() self._youtube_player_refresh_timer = None def _refresh_youtube_player(self): t0 = time.time() self.playback.Youtube_Player_URL = self._get_youtube_player() t = time.time() - t0 logger.debug("YTMusic Player URL refreshed in %.2fs", t) def _get_youtube_player(self): # Refresh our js player URL so YDL can decode the signature correctly. response = requests.get( "https://music.youtube.com", headers=self.api.headers, proxies=self.api.proxies, ) m = re.search(r'jsUrl"\s*:\s*"([^"]+)"', response.text) if m: url = m.group(1) logger.info("YTMusic updated player URL to %s", url) return url else: logger.error("YTMusic unable to extract player URL.") return None def _refresh_auto_playlists(self): t0 = time.time() self._get_auto_playlists() t = time.time() - t0 logger.info("YTMusic Auto Playlists refreshed in %.2fs", t) def _get_auto_playlists(self): try: logger.debug("YTMusic loading auto playlists") response = self.api._send_request("browse", {}) tab = nav(response, SINGLE_COLUMN_TAB) browse = parse_auto_playlists(nav(tab, SECTION_LIST)) if "continuations" in tab["sectionListRenderer"]: request_func = lambda additionalParams: self.api._send_request( "browse", {}, additionalParams) parse_func = lambda contents: parse_auto_playlists(contents) browse.extend( get_continuations( tab["sectionListRenderer"], "sectionListContinuation", 100, request_func, parse_func, )) # Delete empty sections for i in range(len(browse) - 1, 0, -1): if len(browse[i]["items"]) == 0: browse.pop(i) logger.info("YTMusic loaded %d auto playlists sections", len(browse)) self.library.ytbrowse = browse except Exception: logger.exception("YTMusic failed to load auto playlists") return None def scrobble_track(self, bId): # Called through YTMusicScrobbleListener # Let YTMusic know we're playing this track so it will be added to our history. endpoint = "https://www.youtube.com/get_video_info" params = { "video_id": bId, "hl": self.api.language, "el": "detailpage", "c": "WEB_REMIX", "cver": "0.1", } response = requests.get(endpoint, params, headers=self.api.headers, proxies=self.api.proxies) text = parse_qs(response.text) player_response = json.loads(text["player_response"][0]) trackurl = re.sub( r"plid=", "list=", player_response["playbackTracking"]["videostatsPlaybackUrl"] ["baseUrl"], ) CPN_ALPHABET = ( "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_") params = { "cpn": "".join((CPN_ALPHABET[random.randint(0, 256) & 63] for _ in range(0, 16))), "referrer": "https://music.youtube.com", "cbr": text["cbr"][0], "cbrver": text["cbrver"][0], "c": text["c"][0], "cver": text["cver"][0], "cos": text["cos"][0], "cosver": text["cosver"][0], "cr": text["cr"][0], "ver": 2, } tr = requests.get( trackurl, params=params, headers=self.api.headers, proxies=self.api.proxies, ) logger.debug("%d code from '%s'", tr.status_code, tr.url)
import unittest import configparser import sys sys.path.insert(0, '..') from ytmusicapi.ytmusic import YTMusic # noqa: E402 config = configparser.RawConfigParser() config.read('./test.cfg', 'utf-8') youtube = YTMusic(requests_session=False) youtube_auth = YTMusic(config['auth']['headers_file']) youtube_brand = YTMusic(config['auth']['headers'], config['auth']['brand_account']) class TestYTMusic(unittest.TestCase): def test_init(self): self.assertRaises(Exception, YTMusic, "{}") def test_setup(self): YTMusic.setup(config['auth']['headers_file'], config['auth']['headers_raw']) ############### # BROWSING ############### def test_search(self): query = "Oasis Wonderwall" self.assertRaises(Exception, youtube_auth.search, query, "song") results = youtube.search(query)
def download_playlist(dir, link, list_name, append_pl=False): if list_name is None: list_name = "playlist" if list_name == "": list_name = "playlist" dir_base = dir folder = 'mp3-songs' directory = '{}/{}'.format(dir_base, folder) exists = os.path.exists(directory) i = -1 while exists: i += 1 exists = os.path.exists(directory + str(i)) if i >= 0: folder += str(i) directory += str(i) os.mkdir(directory) print("Beginning download for playlist: {}\nDownloading from link: {}".format(list_name, link)) if link.find("music.") < 0: proc = Process(target=download_yt_direct, args=(link, directory, dir_base, folder, list_name, append_pl)) proc.start() return proc playlistId = link[link.find("list=")+5:] playlistId_find = playlistId.find("&") if playlistId_find > 0: playlistId = playlistId[:playlistId_find] youtube = YTMusic() playlist_for_count = youtube.get_playlist(playlistId=playlistId, limit=1) playlist = youtube.get_playlist(playlistId=playlistId, limit=playlist_for_count['trackCount']) log_str = "--- WHILE OBTAINING PLAYLIST ---\n" log_str_orig_len = len(log_str) for track in playlist['tracks']: videoId = track['videoId'] if videoId is None: log_str += "NOT FOUND: {}.\n".format(track['artists'][0]['name'] + " - " + track['title']) query = "" for artist in track['artists']: query += artist['name'] + " " query += track['title'] search_list = youtube.search(query=query, filter='songs', limit=1, ignore_spelling=True) if len(search_list) == 0 or search_list[0]['videoId'] is None: query = track['artists'][0]['name'] + " " + track['title'] search_list = youtube.search(query=query, filter='songs', limit=1, ignore_spelling=True) if len(search_list) == 0 or search_list[0]['videoId'] is None: query = track['title'] search_list = youtube.search(query=query, filter='songs', limit=1, ignore_spelling=True) if len(search_list) == 0 or search_list[0]['videoId'] is None: search_list = youtube.search(query=query, filter='songs', limit=1, ignore_spelling=False) if len(search_list) > 0 and search_list[0]['videoId'] is not None: track['videoId'] = search_list[0]['videoId'] track['artists'] = search_list[0]['artists'] track['title'] = search_list[0]['title'] videoId = track['videoId'] log_str += "REPLACEMENT FOUND: {}.\n".format(track['artists'][0]['name'] + " - " + track['title']) else: log_str += "NO REPLACEMENT FOUND.\n" if len(log_str) == log_str_orig_len: log_str = "" proc = Process(target=download_tracks, args=(playlist, directory, dir_base, folder, list_name, log_str, append_pl)) proc.start() return proc
#!/usr/bin/env python from ytmusicapi.ytmusic import YTMusic songLimit = 100000 ytm = YTMusic('headers_auth.json') songList = ytm.get_library_upload_songs(limit=songLimit) for sl in songList: print(str(sl))
import unittest from ytmusicapi.ytmusic import YTMusic youtube = YTMusic() youtube_auth = YTMusic('../headers_auth.json') class TestYTMusic(unittest.TestCase): def test_setup(self): YTMusic.setup() def test_get_playlists(self): playlists = youtube_auth.get_playlists() self.assertGreater(len(playlists), 0) def test_get_foreign_playlist(self): songs = youtube.get_playlist_items("PLw-VjHDlEOgs658kAHR_LAaILBXb-s6Q5") self.assertEqual(len(songs), 200) def test_get_owned_playlist(self): songs = youtube_auth.get_playlist_items('PLQwVIlKxHM6oCeKLTsUx_w9CmWjHKdg68') self.assertEqual(len(songs), 64) def test_get_history(self): songs = youtube_auth.get_history() self.assertGreater(len(songs), 0) def test_search(self): results = youtube_auth.search("Oasis Wonderwall") self.assertGreater(len(results), 0)
def test_setup(self): YTMusic.setup()
def setUpClass(cls): cls.yt = YTMusic(requests_session=False) cls.yt_auth = YTMusic(config['auth']['headers_file']) cls.yt_brand = YTMusic(config['auth']['headers'], config['auth']['brand_account'])
songDict['album'] = normalizeUnicode(songDict['album']) songDict['filename'] = normalizeUnicode(songFile) return songDict ### ################### ### Main ### playlist_file = sys.argv[1] # authenticate w Youtube Music ytm = YTMusic('headers_auth.json') # figure out which playlist files we need to create (need_to_create_name, need_to_read_file) = getPlayListFiles(playlist_file) ### find each song in each new playlist, lookup song_ids in Youtube Music, add them to list, create new playlist cnt = 0 while (cnt <= len(need_to_read_file)) and (need_to_read_file[cnt] != 0 and os.path.isfile( need_to_read_file[cnt])): songCount = 0 pl_songs = [] # read local playlist print("reading playlist : " + str(need_to_read_file[cnt])) with open(need_to_read_file[cnt], 'r', errors='replace') as f: line = f.readline()
from ytmusicapi.ytmusic import YTMusic ytm = YTMusic('headers_auth.json') #results = ytm.search('kind', filter='uploads', limit=20) results = ytm.get_library_playlists() print(results)
#!/usr/bin/env python from ytmusicapi.ytmusic import YTMusic ytm = YTMusic('headers_auth.json') searchList = list() searchList.append("Lee Moses - Hey Joe") searchList.append("Joe Bataan - CALL MY NAME") searchList.append("Creep") searchList.append("H.E.R. - Fight For You") searchList.append("Ugly Casanova - Barnacles") for searchFor in searchList: print("Querying YTMusic for : " + searchFor) songDict = ytm.search(searchFor, 'uploads', limit=40) print("Result : ") print(str(songDict))