2020-07-17 18:47:12 +00:00
|
|
|
#!/usr/bin/env python
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
|
|
"""
|
|
|
|
Description: Pull Playlists from Google Music and create Playlist in Plex
|
2020-09-27 07:41:51 +00:00
|
|
|
Author: Blacktwin, pjft, sdlynx
|
2020-07-17 18:47:12 +00:00
|
|
|
Requires: gmusicapi, plexapi, requests
|
|
|
|
|
|
|
|
|
|
|
|
Example:
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
from plexapi.server import PlexServer, CONFIG
|
|
|
|
from gmusicapi import Mobileclient
|
|
|
|
|
|
|
|
import requests
|
|
|
|
requests.packages.urllib3.disable_warnings()
|
|
|
|
|
|
|
|
PLEX_URL = ''
|
|
|
|
PLEX_TOKEN = ''
|
|
|
|
MUSIC_LIBRARY_NAME = 'Music'
|
|
|
|
|
|
|
|
## CODE BELOW ##
|
|
|
|
|
|
|
|
if not PLEX_URL:
|
|
|
|
PLEX_URL = CONFIG.data['auth'].get('server_baseurl')
|
|
|
|
if not PLEX_TOKEN:
|
|
|
|
PLEX_TOKEN = CONFIG.data['auth'].get('server_token')
|
|
|
|
|
|
|
|
# Connect to Plex Server
|
|
|
|
sess = requests.Session()
|
|
|
|
sess.verify = False
|
|
|
|
plex = PlexServer(PLEX_URL, PLEX_TOKEN, session=sess)
|
|
|
|
|
|
|
|
# Connect to Google Music, if not authorized prompt to authorize
|
|
|
|
# See https://unofficial-google-music-api.readthedocs.io/en/latest/reference/mobileclient.html
|
|
|
|
# for more information
|
|
|
|
mc = Mobileclient()
|
|
|
|
if not mc.oauth_login(device_id=Mobileclient.FROM_MAC_ADDRESS):
|
|
|
|
mc.perform_oauth()
|
Adjust code from feedback
Thanks for the feedback. Once again, first incursion in python - unsure if this is what you expected on the dictionary front.
Also, added the declaration/instancing of GGMUSICLIST to after the authentication part because it'd fail miserably if we weren't authenticated, I imagine?
As for the behavior, I have a free account, all playlists created by myself, mostly uploaded tracks, some purchased. This seemed to be the way it'd work for me. A simple way I got to troubleshoot it was to run
print("Playlist length: {}".format(len(mc.get_shared_playlist_contents(shareToken))))
print("Playlist SOURCE length: {}".format(len(pl['tracks'])))
and in many cases there was a difference there (the SOURCE would always be the same number or larger than the one from get_shared_playlist_contents(shareToken), and the correct number from what I could tell).
Hope this helps, but once again, your mileage may vary. Just wanted to share in case it'd help.
2020-09-15 19:45:01 +00:00
|
|
|
GGMUSICLIST = mc.get_all_songs()
|
2020-09-27 07:41:51 +00:00
|
|
|
PLEX_MUSIC_LIBRARY = plex.library.section(MUSIC_LIBRARY_NAME)
|
2020-07-17 18:47:12 +00:00
|
|
|
|
|
|
|
def round_down(num, divisor):
|
|
|
|
"""
|
|
|
|
Parameters
|
|
|
|
----------
|
|
|
|
num (int,str): Number to round down
|
|
|
|
divisor (int): Rounding digit
|
|
|
|
|
|
|
|
Returns
|
|
|
|
-------
|
|
|
|
Rounded down int
|
|
|
|
"""
|
|
|
|
num = int(num)
|
|
|
|
return num - (num%divisor)
|
|
|
|
|
|
|
|
|
Adjust code from feedback
Thanks for the feedback. Once again, first incursion in python - unsure if this is what you expected on the dictionary front.
Also, added the declaration/instancing of GGMUSICLIST to after the authentication part because it'd fail miserably if we weren't authenticated, I imagine?
As for the behavior, I have a free account, all playlists created by myself, mostly uploaded tracks, some purchased. This seemed to be the way it'd work for me. A simple way I got to troubleshoot it was to run
print("Playlist length: {}".format(len(mc.get_shared_playlist_contents(shareToken))))
print("Playlist SOURCE length: {}".format(len(pl['tracks'])))
and in many cases there was a difference there (the SOURCE would always be the same number or larger than the one from get_shared_playlist_contents(shareToken), and the correct number from what I could tell).
Hope this helps, but once again, your mileage may vary. Just wanted to share in case it'd help.
2020-09-15 19:45:01 +00:00
|
|
|
def compare(ggmusic, pmusic):
|
2020-07-17 18:47:12 +00:00
|
|
|
"""
|
|
|
|
Parameters
|
|
|
|
----------
|
Adjust code from feedback
Thanks for the feedback. Once again, first incursion in python - unsure if this is what you expected on the dictionary front.
Also, added the declaration/instancing of GGMUSICLIST to after the authentication part because it'd fail miserably if we weren't authenticated, I imagine?
As for the behavior, I have a free account, all playlists created by myself, mostly uploaded tracks, some purchased. This seemed to be the way it'd work for me. A simple way I got to troubleshoot it was to run
print("Playlist length: {}".format(len(mc.get_shared_playlist_contents(shareToken))))
print("Playlist SOURCE length: {}".format(len(pl['tracks'])))
and in many cases there was a difference there (the SOURCE would always be the same number or larger than the one from get_shared_playlist_contents(shareToken), and the correct number from what I could tell).
Hope this helps, but once again, your mileage may vary. Just wanted to share in case it'd help.
2020-09-15 19:45:01 +00:00
|
|
|
ggmusic (dict): Contains track data from Google Music
|
2020-07-17 18:47:12 +00:00
|
|
|
pmusic (object): Plex item found from search
|
|
|
|
|
|
|
|
Returns
|
|
|
|
-------
|
|
|
|
pmusic (object): Matched Plex item
|
|
|
|
"""
|
Adjust code from feedback
Thanks for the feedback. Once again, first incursion in python - unsure if this is what you expected on the dictionary front.
Also, added the declaration/instancing of GGMUSICLIST to after the authentication part because it'd fail miserably if we weren't authenticated, I imagine?
As for the behavior, I have a free account, all playlists created by myself, mostly uploaded tracks, some purchased. This seemed to be the way it'd work for me. A simple way I got to troubleshoot it was to run
print("Playlist length: {}".format(len(mc.get_shared_playlist_contents(shareToken))))
print("Playlist SOURCE length: {}".format(len(pl['tracks'])))
and in many cases there was a difference there (the SOURCE would always be the same number or larger than the one from get_shared_playlist_contents(shareToken), and the correct number from what I could tell).
Hope this helps, but once again, your mileage may vary. Just wanted to share in case it'd help.
2020-09-15 19:45:01 +00:00
|
|
|
title = str(ggmusic['title'].encode('ascii', 'ignore'))
|
|
|
|
album = str(ggmusic['album'].encode('ascii', 'ignore'))
|
|
|
|
tracknum = int(ggmusic['trackNumber'])
|
|
|
|
duration = int(ggmusic['durationMillis'])
|
2020-07-17 18:47:12 +00:00
|
|
|
|
|
|
|
# Check if track numbers match
|
|
|
|
if int(pmusic.index) == int(tracknum):
|
|
|
|
return [pmusic]
|
|
|
|
# If not track number, check track title and album title
|
|
|
|
elif title == pmusic.title and (album == pmusic.parentTitle or
|
|
|
|
album.startswith(pmusic.parentTitle)):
|
|
|
|
return [pmusic]
|
|
|
|
# Check if track duration match
|
|
|
|
elif round_down(duration, 1000) == round_down(pmusic.duration, 1000):
|
|
|
|
return [pmusic]
|
|
|
|
# Lastly, check if title matches
|
|
|
|
elif title == pmusic.title:
|
|
|
|
return [pmusic]
|
|
|
|
|
Adjust code from feedback
Thanks for the feedback. Once again, first incursion in python - unsure if this is what you expected on the dictionary front.
Also, added the declaration/instancing of GGMUSICLIST to after the authentication part because it'd fail miserably if we weren't authenticated, I imagine?
As for the behavior, I have a free account, all playlists created by myself, mostly uploaded tracks, some purchased. This seemed to be the way it'd work for me. A simple way I got to troubleshoot it was to run
print("Playlist length: {}".format(len(mc.get_shared_playlist_contents(shareToken))))
print("Playlist SOURCE length: {}".format(len(pl['tracks'])))
and in many cases there was a difference there (the SOURCE would always be the same number or larger than the one from get_shared_playlist_contents(shareToken), and the correct number from what I could tell).
Hope this helps, but once again, your mileage may vary. Just wanted to share in case it'd help.
2020-09-15 19:45:01 +00:00
|
|
|
def get_ggmusic(trackId):
|
|
|
|
for ggmusic in GGMUSICLIST:
|
|
|
|
if ggmusic['id'] == trackId:
|
|
|
|
return ggmusic
|
2020-07-17 18:47:12 +00:00
|
|
|
|
|
|
|
def main():
|
|
|
|
for pl in mc.get_all_user_playlist_contents():
|
|
|
|
playlistName = pl['name']
|
|
|
|
# Check for existing Plex Playlists, skip if exists
|
|
|
|
if playlistName in [x.title for x in plex.playlists()]:
|
|
|
|
print("Playlist: ({}) already available, skipping...".format(playlistName))
|
|
|
|
else:
|
|
|
|
playlistContent = []
|
|
|
|
shareToken = pl['shareToken']
|
|
|
|
# Go through tracks in Google Music Playlist
|
Adjust code from feedback
Thanks for the feedback. Once again, first incursion in python - unsure if this is what you expected on the dictionary front.
Also, added the declaration/instancing of GGMUSICLIST to after the authentication part because it'd fail miserably if we weren't authenticated, I imagine?
As for the behavior, I have a free account, all playlists created by myself, mostly uploaded tracks, some purchased. This seemed to be the way it'd work for me. A simple way I got to troubleshoot it was to run
print("Playlist length: {}".format(len(mc.get_shared_playlist_contents(shareToken))))
print("Playlist SOURCE length: {}".format(len(pl['tracks'])))
and in many cases there was a difference there (the SOURCE would always be the same number or larger than the one from get_shared_playlist_contents(shareToken), and the correct number from what I could tell).
Hope this helps, but once again, your mileage may vary. Just wanted to share in case it'd help.
2020-09-15 19:45:01 +00:00
|
|
|
for ggmusicTrackInfo in pl['tracks']:
|
|
|
|
ggmusic = get_ggmusic(ggmusicTrackInfo['trackId'])
|
|
|
|
title = str(ggmusic['title'])
|
|
|
|
album = str(ggmusic['album'])
|
|
|
|
artist = str(ggmusic['artist'])
|
2020-07-17 18:47:12 +00:00
|
|
|
# Search Plex for Album title and Track title
|
2020-09-27 07:41:51 +00:00
|
|
|
albumTrackSearch = PLEX_MUSIC_LIBRARY.searchTracks(
|
2020-07-17 18:47:12 +00:00
|
|
|
**{'album.title': album, 'track.title': title})
|
|
|
|
# Check results
|
|
|
|
if len(albumTrackSearch) == 1:
|
|
|
|
playlistContent += albumTrackSearch
|
|
|
|
if len(albumTrackSearch) > 1:
|
|
|
|
for pmusic in albumTrackSearch:
|
Adjust code from feedback
Thanks for the feedback. Once again, first incursion in python - unsure if this is what you expected on the dictionary front.
Also, added the declaration/instancing of GGMUSICLIST to after the authentication part because it'd fail miserably if we weren't authenticated, I imagine?
As for the behavior, I have a free account, all playlists created by myself, mostly uploaded tracks, some purchased. This seemed to be the way it'd work for me. A simple way I got to troubleshoot it was to run
print("Playlist length: {}".format(len(mc.get_shared_playlist_contents(shareToken))))
print("Playlist SOURCE length: {}".format(len(pl['tracks'])))
and in many cases there was a difference there (the SOURCE would always be the same number or larger than the one from get_shared_playlist_contents(shareToken), and the correct number from what I could tell).
Hope this helps, but once again, your mileage may vary. Just wanted to share in case it'd help.
2020-09-15 19:45:01 +00:00
|
|
|
albumTrackFound = compare(ggmusic, pmusic)
|
2020-07-17 18:47:12 +00:00
|
|
|
if albumTrackFound:
|
|
|
|
playlistContent += albumTrackFound
|
|
|
|
break
|
|
|
|
# Nothing found from Album title and Track title
|
|
|
|
if not albumTrackSearch or len(albumTrackSearch) == 0:
|
|
|
|
# Search Plex for Track title
|
2020-09-27 07:41:51 +00:00
|
|
|
trackSearch = PLEX_MUSIC_LIBRARY.searchTracks(
|
2020-07-17 18:47:12 +00:00
|
|
|
**{'track.title': title})
|
|
|
|
if len(trackSearch) == 1:
|
|
|
|
playlistContent += trackSearch
|
|
|
|
if len(trackSearch) > 1:
|
|
|
|
for pmusic in trackSearch:
|
Adjust code from feedback
Thanks for the feedback. Once again, first incursion in python - unsure if this is what you expected on the dictionary front.
Also, added the declaration/instancing of GGMUSICLIST to after the authentication part because it'd fail miserably if we weren't authenticated, I imagine?
As for the behavior, I have a free account, all playlists created by myself, mostly uploaded tracks, some purchased. This seemed to be the way it'd work for me. A simple way I got to troubleshoot it was to run
print("Playlist length: {}".format(len(mc.get_shared_playlist_contents(shareToken))))
print("Playlist SOURCE length: {}".format(len(pl['tracks'])))
and in many cases there was a difference there (the SOURCE would always be the same number or larger than the one from get_shared_playlist_contents(shareToken), and the correct number from what I could tell).
Hope this helps, but once again, your mileage may vary. Just wanted to share in case it'd help.
2020-09-15 19:45:01 +00:00
|
|
|
trackFound = compare(ggmusic, pmusic)
|
2020-07-17 18:47:12 +00:00
|
|
|
if trackFound:
|
|
|
|
playlistContent += trackFound
|
|
|
|
break
|
|
|
|
# Nothing found from Track title
|
|
|
|
if not trackSearch or len(trackSearch) == 0:
|
|
|
|
# Search Plex for Artist
|
2020-09-27 07:41:51 +00:00
|
|
|
artistSearch = PLEX_MUSIC_LIBRARY.searchTracks(
|
2020-07-17 18:47:12 +00:00
|
|
|
**{'artist.title': artist})
|
|
|
|
for pmusic in artistSearch:
|
Adjust code from feedback
Thanks for the feedback. Once again, first incursion in python - unsure if this is what you expected on the dictionary front.
Also, added the declaration/instancing of GGMUSICLIST to after the authentication part because it'd fail miserably if we weren't authenticated, I imagine?
As for the behavior, I have a free account, all playlists created by myself, mostly uploaded tracks, some purchased. This seemed to be the way it'd work for me. A simple way I got to troubleshoot it was to run
print("Playlist length: {}".format(len(mc.get_shared_playlist_contents(shareToken))))
print("Playlist SOURCE length: {}".format(len(pl['tracks'])))
and in many cases there was a difference there (the SOURCE would always be the same number or larger than the one from get_shared_playlist_contents(shareToken), and the correct number from what I could tell).
Hope this helps, but once again, your mileage may vary. Just wanted to share in case it'd help.
2020-09-15 19:45:01 +00:00
|
|
|
artistFound = compare(ggmusic, pmusic)
|
2020-07-17 18:47:12 +00:00
|
|
|
if artistFound:
|
|
|
|
playlistContent += artistFound
|
|
|
|
break
|
|
|
|
if not artistSearch or len(artistSearch) == 0:
|
|
|
|
print(u"Could not find in Plex:\n\t{} - {} {}".format(artist, album, title))
|
2020-09-28 12:09:55 +00:00
|
|
|
if len(playlistContent) != 0:
|
|
|
|
print("Adding Playlist: {}".format(playlistName))
|
|
|
|
print("Google Music Playlist: {}, has {} tracks. {} tracks were added to Plex.".format(
|
|
|
|
playlistName, len(pl['tracks']), len(playlistContent)))
|
|
|
|
plex.createPlaylist(playlistName, playlistContent)
|
|
|
|
else:
|
|
|
|
print("Could not find any matching tracks in Plex for {}".format(playlistName))
|
2020-07-17 18:47:12 +00:00
|
|
|
|
|
|
|
main()
|