Last active
May 18, 2024 03:02
-
-
Save rsheldiii/e15bfa8f376ab64fef569784e161ff83 to your computer and use it in GitHub Desktop.
transfer your Zune favorites over to Youtube Music
This file contains 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
MIT License | |
Copyright (c) [year] [fullname] | |
Permission is hereby granted, free of charge, to any person obtaining a copy | |
of this software and associated documentation files (the "Software"), to deal | |
in the Software without restriction, including without limitation the rights | |
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
copies of the Software, and to permit persons to whom the Software is | |
furnished to do so, subject to the following conditions: | |
The above copyright notice and this permission notice shall be included in all | |
copies or substantial portions of the Software. | |
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
SOFTWARE. |
This file contains 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
import os | |
import pickle | |
from mutagen.id3 import ID3 | |
from googleapiclient.discovery import build | |
from google_auth_oauthlib.flow import InstalledAppFlow | |
from google.auth.transport.requests import Request | |
import time, json | |
# favorites class | |
class Favorites: | |
def __init__(self): | |
self.favorites = self.load() | |
def key_for_song(self, song_info): | |
return f"{song_info['artist']} - {song_info['title']} - {song_info['album']}" | |
def get_song(self, song_info): | |
return self.favorites[self.key_for_song(song_info)] | |
def set_song(self, song_info): | |
self.favorites[self.key_for_song(song_info)] = song_info | |
# save as JSON | |
def save(self): | |
with open('favorites.json', 'w') as f: | |
# save as json | |
json.dump(self.favorites, f) | |
# pickle.dump(self.favorites, f) | |
def load(self): | |
if os.path.exists('favorites.json'): | |
# rescue errors | |
with open('favorites.json', 'r') as f: | |
return json.load(f) | |
# return pickle.load(f) | |
return {} | |
def favorite_song(self, song_info): | |
new_song_info = self.get_song(song_info) | |
new_song_info['liked'] = True | |
self.set_song(new_song_info) | |
# save after favoriting | |
self.save() | |
def remove_liked_songs(self): | |
for key in list(self.favorites.keys()): | |
if self.favorites[key]['liked']: | |
del self.favorites[key] | |
self.save() | |
# boolean method to check if favorites are loaded | |
def loaded_favorites(self): | |
return self.favorites.__len__() > 0 | |
def __iter__(self): | |
return iter(self.favorites.values()) | |
def __len__(self): | |
return len(self.favorites) | |
def as_array(self): | |
return list(self.favorites.values()) | |
favorites = Favorites() | |
def scan_songs(directory): | |
for root, dirs, files in os.walk(directory): | |
for file in files: | |
path = os.path.join(root, file) | |
try: | |
if file.endswith('.mp3'): | |
audio = ID3(path) | |
if "POPM:Windows Media Player 9 Series" in audio: | |
popm = audio.getall('POPM:Windows Media Player 9 Series')[0] | |
if popm.rating >= 196: | |
song_info = { | |
'artist': audio['TPE1'].text[0] if 'TPE1' in audio else 'Unknown Artist', | |
'title': audio['TIT2'].text[0] if 'TIT2' in audio else 'Unknown Title', | |
'album': audio['TALB'].text[0] if 'TALB' in audio else 'Unknown Album', | |
'liked': False | |
} | |
favorites.set_song(song_info) | |
except Exception as e: | |
print(f"Error processing {path}: {e}") | |
def youtube_service(): | |
credentials = None | |
if os.path.exists('token.pickle'): | |
with open('token.pickle', 'rb') as token: | |
credentials = pickle.load(token) | |
if not credentials or not credentials.valid: | |
if credentials and credentials.expired and credentials.refresh_token: | |
credentials.refresh(Request()) | |
else: | |
flow = InstalledAppFlow.from_client_secrets_file( | |
'client_secrets.json', | |
scopes=['https://www.googleapis.com/auth/youtube.force-ssl'] | |
) | |
credentials = flow.run_console() | |
with open('token.pickle', 'wb') as token: | |
pickle.dump(credentials, token) | |
return build('youtube', 'v3', credentials=credentials) | |
def search_and_like_song(service, song_info): | |
search_response = service.search().list( | |
q=f"{song_info['artist']} {song_info['title']}", | |
part="snippet", | |
maxResults=1, | |
type="video" | |
).execute() | |
for search_result in search_response.get('items', []): | |
video_id = search_result['id']['videoId'] | |
service.videos().rate(id=video_id, rating='like').execute() | |
print(f"Liking {song_info['title']} by {song_info['artist']}...") | |
# favorite song in favorites | |
favorites.favorite_song(song_info) | |
# print(f"Liked {song_info['title']} by {song_info['artist']}") | |
time.sleep(1) # Manage quota | |
# Main execution logic | |
if not favorites.loaded_favorites(): | |
print('Scanning songs...') | |
music_dir = './' | |
scan_songs(music_dir) | |
favorites.save() | |
print('Starting liking songs...') | |
youtube = youtube_service() | |
# for song in favorites: | |
# if not song['liked']: | |
# search_and_like_song(youtube, song) | |
favorites.remove_liked_songs() | |
# Liking songs in batches of 65 | |
for i in range(0, len(favorites.as_array()), 65): | |
batch = favorites.as_array()[i:i+65] | |
for song in batch: | |
try: | |
search_and_like_song(youtube, song) | |
except: | |
print(f"Error liking {song['title']} by {song['artist']}") | |
print("Pausing for 1 day before next batch...") | |
time.sleep(86400) # Pause for 1 day |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment