Last active
December 2, 2020 02:21
-
-
Save ocshawn/a153eb7b7a0e669c761fd88332d37eaa to your computer and use it in GitHub Desktop.
Track data from GPM to Plex
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
#!/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