Skip to content

Instantly share code, notes, and snippets.

@rsheldiii
Last active May 18, 2024 03:02
Show Gist options
  • Save rsheldiii/e15bfa8f376ab64fef569784e161ff83 to your computer and use it in GitHub Desktop.
Save rsheldiii/e15bfa8f376ab64fef569784e161ff83 to your computer and use it in GitHub Desktop.
transfer your Zune favorites over to Youtube Music
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.
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