Description:

This little program fixes some issues I came across when using the Google Play Music Manager to upload my iTunes playlists. I use Google Play Music for an online-version of my local iTunes media library. The Music Manager offers some functionality to upload and transfer your iTunes playlists to Google Play Music. Unfortunately the Music Manager messed up the playlists, always adding entries to the playlists which resulted in huge playlists containing a lot of duplicates of the songs.

The Playlist Fixer fixes this issues using a brute force approach: deleting the contents of every playlist and again adding all entries. To get access to Google Play Music, the Playlist Fixer uses the gmusicapi from Simon Weber. To export the local iTunes playlists I used the iTunes Export program by Eric Daugherty.

Please note that this is just a hobby project and I won’t take any responsibility for possible damage of your hard- or software or your Google Music library. You use it on your own risk. Furthermore this is still a work in progress and will be further developed in the future.

How to install:

  • Install python 3.X
  • Install Java Runtime Environment
  • Run the command line tool (cmd.exe) and type in:
pip install gmusicapi
pip install colorama
  • Copy the files of the PlaylistFixer to C:\GoogleMusicPlaylistFixer\

How to use:

The PlaylistFixer just tries to fix the playlist and won’t upload any files (yet). Therefore the steps to fix your playlists are:

  • Use the official Google Play Music Manager to upload your MP3 music library to Google Music so that the MP3 files in your playlists are already on the Google servers (But turn off the Auto-Start function, just use the Music Manager to upload your media library).
  • In Google Play Music, for every iTunes playlist you want to upload, create a playlist in Google Music with the same name
    • Important!: Note that all special characters (like „_“, „!“, etc.) in your iTunes playlist names will be converted to „_“. Therefore the names of your Google Music playlists should have „_“ instead of the special characters in your iTunes playlist names. You just have to do this once.
  • Temporarily turn off the synchronisation on all your devices running the Google Play Music App (most likely your smartphone) (otherwise the app will delete your offline MP3s and you re-download all of your music).
  • Now run the PlaylistFixer with opening the command line and type in:
Python C:\GoogleMusicPlaylistFixer\PlaylistFixer.py
  • The PlaylistFixer will now fill all Google Music playlists he can assign to one of your iTunes playlists with the right tracks.

Download:

Download GoogleMusicPlaylistFixer on GitHub

Source Code:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import print_function, division, absolute_import, unicode_literals
from future import standard_library
standard_library.install_aliases()
from builtins import *  # noqa
from getpass import getpass
 
from gmusicapi import Mobileclient
 
from subprocess import check_call
from mutagen.mp3 import EasyMP3 as MP3
import os
from os.path import expanduser
from colorama import Fore, Back, init, Style
 
 
# //////////////// Helper functions ////////////////
# //////////////////////////////////////////////////
 
def ask_for_credentials():
    """Make an instance of the api and attempts to login with it.
    Returns the authenticated api.
    """
 
    # We're not going to upload anything, so the Mobileclient is what we want.
    api = Mobileclient(debug_logging=False)
 
    logged_in = False
    attempts = 0
 
    try:
        logged_in = api.login("YourEmail", "YourPassword", Mobileclient.FROM_MAC_ADDRESS)
    except:
        print(Fore.YELLOW + "Error logging you in!")
 
    #while not logged_in and attempts < 3:
    #    email = input('Email: ')
    #    password = getpass()
 
    #    logged_in = api.login(email, password, Mobileclient.FROM_MAC_ADDRESS)
    #    attempts += 1
 
    return api
 
def loadLocalPlaylist(filepath):
    """Loads the playlist file in the given directory, giving back a list of MP3 file paths."""
    mp3List = list()
    file = open(filepath, encoding="utf-8", mode='r')
    for line in file:
        line = line.replace('\\','/').strip()
        if os.path.exists(line):
            mp3List.append(line)
        #else:
        #    print("Couldn't locate " + line + " in " + filepath)
 
    if len(mp3List)==0:
        print(Fore.RED + "~~ ERROR Corrupt playlist file! " + filepath)
 
    return mp3List
 
def exportiTunesPlaylists(exportPath):
    print("Calling iTunesExport ...")
    print("-----------------------------------------------------")
    try:
        check_call(['java', '-jar', 'C:\GoogleMusicPlaylistFixer\iTunesExport\itunesexport.jar', "-outputDir="+exportPath])
    except:
        print("Problem exporting iTunes Playlists! Please install iTunesExport Console from http://www.ericdaugherty.com/dev/itunesexport/")
    print("-----------------------------------------------------")
 
def giveTrackID(artist, title):
    """Gives the ID of the (last) track matching the given artists and song title. Returns 0 if no ID is found."""
    id = "0"
    for song in library:
        #print (song['durationmillis'])
        if song['title'] == title and song['artist'] == artist:
            id = song['id']
    return id
 
def givePlaylistID(name):
    """Gives the ID of the (last) playlist matching the given name. Returns 0 if no ID is found."""
    id = "0"
    for pl in playlistsContent:
        if pl['name'] == name:
            id = pl['id']
    return id
 
def clearAllPlaylists():
    """Deletes the content of every Google Music playlist."""
    for pl in playlistsContent:
        trackList=list()
        for track in pl['tracks']:
            trackList.append(track['id'])
        mc.remove_entries_from_playlist(trackList)
 
def fillPlaylists():
    """Appends the tracks to the right playlists based on the local playlist files. Assumes empty Google Music playlists. Fills only already existing Google Music playlists, doesn't create new playlists."""
    i = 0
    for pl in localPlaylists:
 
        plID = givePlaylistID(localPlaylistNames[i])
        if plID == "0":
                print(Fore.YELLOW + "~~ Couldn't get ID for Playlist " + localPlaylistNames[i] + " - skipping")
                i=i+1
                continue
 
        print("--------------------------------------------")
        print("/// Processing Playlist " + localPlaylistNames[i])
        ids=list()
 
        for song in pl:
            try:
                id3info = MP3(song)
            except:
                print(Fore.YELLOW + "~~ ERROR reading ID3 tag for " + song + " - skipping")
                continue
            #print(id3info['length'])
            try:
                id = giveTrackID(id3info['artist'][0], id3info['title'][0])
            except:
                print(Fore.YELLOW + "~~ ERROR retreaving Google Music ID for " + song + " - skipping")
                continue
            if len(ids) >= 1000:
                print(Fore.YELLOW + "Playlist has more than 1000 songs - skipping the rest")
                break
            if id == "0":
                print(Fore.YELLOW + "~~ ERROR retreaving Google Music ID for " + song + " - skipping")
                continue
            else:
                ids.append(id)
            
        mc.add_songs_to_playlist(plID, ids)
        i=i+1
        print("Done.")
        print()
 
def askUserToProceed(exportPath):
    while True:
        print(Fore.RED + 'The content of your online playlists is about to be deleted. They will be replaced by the files in ' + exportPath + ".\n Please check if the names of the playlists in the directory match the online playlists.") 
        answer = input('Proceed? (Y/N)')
        print()
        if(answer == "Y" or answer == "y" or answer == "Yes" or answer == "yes"):
            return True
        if(answer == "N" or answer == "n" or answer == "No" or answer == "no"):
            return False
 
 
# //////////////////////////////////////////////////
# //////////////////////////////////////////////////
 
def main():
    """Fixes the Google Music playlists if they differ from the local iTunes playlists."""
 
    init(autoreset=True)
 
    print()
    print(Style.BRIGHT + '##########################################################')
    print(Style.BRIGHT + "############# GoogleMusic PlaylistFixer V0.1 #############")
    print(Style.BRIGHT + '##########################################################')
    print("(c) 2016 by Valentin Kraft, www.valentinkraft.de")
    print()
 
    # Start mobile client
    global mc
    mc = ask_for_credentials()
    if not mc.is_authenticated():
        print("Sorry, those credentials weren't accepted.")
        return
    print('Successfully logged in.')
    print()
 
    # Scanning local media
    print(Style.BRIGHT + "/// Scanning local iTunes playlists. //////////////")
    print(Style.BRIGHT + '///////////////////////////////////////////////////')
   
    global localPlaylists
    localPlaylists=list()
    global localPlaylistNames
    localPlaylistNames=list()
 
    userPath = expanduser("~")
    exportPath = userPath + "\\GoogleMusicPlaylistFixerExport"
 
    exportiTunesPlaylists(exportPath)
 
    for file in os.listdir(exportPath):
        localPlaylists.append(loadLocalPlaylist(exportPath+"\\"+file))
        localPlaylistNames.append(file.split(".")[0])
    print(Fore.GREEN + "Done. Sucessfully processed " + str(len(localPlaylists)) + " iTunes playlists.")
    print()
 
    # Scanning Google Music playlists
    print(Style.BRIGHT + "/// Scanning Google Music playlists. //////////////")
    print(Style.BRIGHT + '///////////////////////////////////////////////////')
    print('Loading library... Please wait!')
    
    global library
    library = mc.get_all_songs()
    global playlists
    playlists = mc.get_all_playlists()
    global playlistsContent
    playlistsContent = mc.get_all_user_playlist_contents()
 
    print(Fore.GREEN + 'Done.')
    print()
 
    # Consistency check
    if len(library) == 0 or len(playlists) == 0 or len(playlistsContent) == 0 or len(localPlaylists) == 0:
        print(Fore.RED + "~~ ERROR retrieving Google Music library data - Aborting")
        return
 
    # Ask user for permission
    proceed = askUserToProceed(exportPath)
    if(proceed == False):
        print("Aborting.")
        return
                
    # Fix Google Music library
    print(Style.BRIGHT + "/// Fixing library. ///////////////////////////////")
    print(Style.BRIGHT + '///////////////////////////////////////////////////')
    clearAllPlaylists()
    fillPlaylists()
   
    #TODO: delete m3u files?
 
    # End
    mc.logout()
    print()
    print(Fore.LIGHTGREEN_EX + '___________ Finished!')
    print()
 
 
if __name__ == '__main__':
    main()

 

Add a comment

*Please complete all fields correctly

Related Blogs