Skip to content

Instantly share code, notes, and snippets.

@ocshawn
Last active December 2, 2020 02:21
Show Gist options
  • Save ocshawn/a153eb7b7a0e669c761fd88332d37eaa to your computer and use it in GitHub Desktop.
Save ocshawn/a153eb7b7a0e669c761fd88332d37eaa to your computer and use it in GitHub Desktop.
Track data from GPM to Plex
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Python version 3
# Based off of LucidBrot migrate-from-google-play-music and gmusic_playlists_to_plex by Author: Blacktwin, pjft, sdlynx
# Author of this version: ocShawn
import os, sys, csv, html, requests, re
requests.packages.urllib3.disable_warnings()
from datetime import datetime
from plexapi.server import PlexServer, CONFIG
"""
README
Purpose: To add Google Play Music Takeout track data to plex
1) First get your Google Play Music out of Takeout at https://takeout.google.com/
Requires: plexapi, requests
2) Install requirements Run: `python -m pip install --upgrade pip plexapi requests`
3) Change below:
TRACKS_PATH = path to google takeout Tracks folder
PLEX_URL = url to plex server
PLEX_TOKEN = See https://support.plex.tv/articles/204059436-finding-an-authentication-token-x-plex-token/ for how to get
MUSIC_LIBRARY_NAME = name of plex music library
4) Run Script: `python trackDataToPlex.py > trackDataToPlexOut.txt`
After script runs view trackDataToPlexOut.txt file towards the bottom missed tracks will be listed
5) Done, PLEX party time
"""
# Note that path settings are relative to the current working directory if you don't specify absolute paths.
# Path to "Takeout / Google Play Music / Playlists" as obtained from takeout.google.com
TRACKS_PATH = os.path.normpath('E:\Takeout\Google Play Music\Tracks')
# plex server info
PLEX_URL = 'http://192.168.0.100:32400/'
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)
PLEX_MUSIC_LIBRARY = plex.library.section(MUSIC_LIBRARY_NAME)
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)
def compare(ggmusic, pmusic):
"""
Parameters
----------
ggmusic (dict): Contains track data from Google Music
pmusic (object): Plex item found from search
Returns
-------
pmusic (object): Matched Plex item
"""
title = str(ggmusic['title'].encode('ascii', 'ignore'))
duration = int(ggmusic['duration_ms'])
# Check if track duration match
if isinstance(duration, int) and isinstance(pmusic.duration, int):
if round_down(duration, 1000) == round_down(pmusic.duration, 1000):
return [pmusic]
# Lastly, check if title matches
if title == pmusic.title:
return [pmusic]
# check if title matches raw
elif ggmusic['title'] == pmusic.title:
return [pmusic]
# check if title matches raw trimmed caseless all special carecters removed
elif re.sub('[^A-Za-z0-9]+', '', ggmusic['title']).lower() == re.sub('[^A-Za-z0-9]+', '', pmusic.title).lower():
return [pmusic]
def updatePLEXTrackInfo(ggmusic, pmusic):
"""
Parameters
----------
ggmusic (dict): Contains track data from Google Music
pmusic (object): Plex item found from search
Adds google music info to plex
"""
# if played increase play count by one
if ggmusic['play_count'] != '0':
# manually query
key = '/:/scrobble?key=%s&identifier=com.plexapp.plugins.library' % pmusic[0].ratingKey
pmusic[0]._server.query(key)
print("Marking Played: {}".format(pmusic[0].title.encode('utf-8')))
# if rated add rating
if ggmusic['rating'] != '0':
ratingMath = float(ggmusic['rating'])*2
pmusic[0].edit(**{'userRating.value':ratingMath})
print("Updating: {} to rating: {}".format(pmusic[0].title.encode('utf-8'), ratingMath))
def main():
startTime=datetime.now()
print("Considering any tracks in {}".format(TRACKS_PATH))
print("Considering any tracks in {}".format(TRACKS_PATH), file=sys.stderr)
print("Collecting tracks...\n")
# only grab csv files
song_csvs = [ f.path for f in os.scandir(TRACKS_PATH) if f.is_file() and f.name.split('.')[-1].lower() == 'csv' ]
song_infos_unsorted = []
for song_csv in song_csvs:
try:
with open(song_csv, encoding="utf-8") as csvfile:
rows = csv.reader(csvfile)
for title, album, artist, duration_ms, rating, play_count, removed in rows:
if (title.strip() == 'Title') and (artist.strip() == 'Artist') and (album.strip() == 'Album'):
# skip headline
continue
if removed:
# skip removed
continue
# clean up google's htmlencoding mess
title = html.unescape(title)
album = html.unescape(album)
artist = html.unescape(artist)
print("Reading GPM {} by {}.".format(title.encode('utf-8'), artist.encode('utf-8')))
song_info = {'title': title, 'album': album, 'artist': artist, 'rating': rating, 'play_count': play_count, 'duration_ms': duration_ms}
song_infos_unsorted.append(song_info)
except UnicodeEncodeError as e:
print("INFO: Skipping file {} due to Unicode Reading Error.".format(song_csv))
print("Done getting all track data from files RunTime: {}".format(datetime.now() - startTime))
print("Done getting all track data from files RunTime: {}".format(datetime.now() - startTime), file=sys.stderr)
tracksUpdated = 0
print("Total Tracks: {}".format(len(song_infos_unsorted)), file=sys.stderr)
# Go through tracks in Google Music
for ggmusic in song_infos_unsorted:
if tracksUpdated % 50 == 0:
print("Remaining:{}".format(len(song_infos_unsorted) - tracksUpdated), file=sys.stderr)
trackFound = False
title = str(ggmusic['title'])
album = str(ggmusic['album'])
artist = str(ggmusic['artist'])
# Search Plex for Album title and Track title
albumTrackSearch = PLEX_MUSIC_LIBRARY.searchTracks(
**{'album.title': album, 'track.title': title})
# Check results
if len(albumTrackSearch) == 1:
updatePLEXTrackInfo(ggmusic, albumTrackSearch)
tracksUpdated += 1
trackFound = True
if len(albumTrackSearch) > 1:
for pmusic in albumTrackSearch:
albumTrackFound = compare(ggmusic, pmusic)
if albumTrackFound:
updatePLEXTrackInfo(ggmusic, albumTrackFound)
tracksUpdated += 1
trackFound = True
break
# Nothing found from Album title and Track title
if not trackFound:
# Search Plex for Track title
trackSearch = PLEX_MUSIC_LIBRARY.searchTracks(
**{'track.title': title})
if len(trackSearch) == 1:
updatePLEXTrackInfo(ggmusic, trackSearch)
tracksUpdated += 1
trackFound = True
if len(trackSearch) > 1:
for pmusic in trackSearch:
trackFound = compare(ggmusic, pmusic)
if trackFound:
updatePLEXTrackInfo(ggmusic, trackFound)
tracksUpdated += 1
trackFound = True
break
# Nothing found from Track title
if not trackFound:
# Search Plex for Artist
artistSearch = PLEX_MUSIC_LIBRARY.searchTracks(
**{'artist.title': artist})
for pmusic in artistSearch:
artistFound = compare(ggmusic, pmusic)
if artistFound:
updatePLEXTrackInfo(ggmusic, artistFound)
tracksUpdated += 1
trackFound = True
break
# last shot to find a match
if not trackFound:
# Use Artist search
for pmusic in artistSearch:
toMatchA = re.sub('[^A-Za-z0-9]+', '', title).lower()
toMatchB = re.sub('[^A-Za-z0-9]+', '', pmusic.title).lower()
if toMatchA in toMatchB or toMatchB in toMatchA:
updatePLEXTrackInfo(ggmusic, [pmusic])
tracksUpdated += 1
trackFound = True
break
if not trackFound:
print(u"Could not find in Plex:\n\t{} - {} - {}".format(artist.encode('utf-8'), album.encode('utf-8'), title.encode('utf-8')))
print("Google Music: {} tracks. {} tracks were found in Plex.".format(len(song_infos_unsorted), tracksUpdated))
print("Google Music: {} tracks. {} tracks were found in Plex.".format(len(song_infos_unsorted), tracksUpdated), file=sys.stderr)
print("Total RunTime: {}".format(datetime.now() - startTime))
print("Total RunTime: {}".format(datetime.now() - startTime), file=sys.stderr)
if __name__ == '__main__':
if len(sys.argv) > 1:
if sys.argv[1] == 'help':
print("hello. Specify some things in the source file with the CAPS LOCKED variables!")
print("If you're running this in Windows CMD, you might need to `set PYTHONIOENCODING=utf-8` first.")
print("It is probably advisable to pipe the stdout into a file so that the important messages from STDERR surface clearly.")
exit(0)
# always:
main()
print("Done", file=sys.stderr)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment