-
Notifications
You must be signed in to change notification settings - Fork 0
/
PPP.py
256 lines (196 loc) · 10.6 KB
/
PPP.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
#!/usr/bin/python3
## PPP (Plex Playlist Pusher) v2.1.3
# Synchronises playlists between local files (.m3u) and Plex playlists.
# If there are differences between local and Plex playlists, both will be merged and duplicates deleted; meaning tracks
# can be added on one and updated on both... but must be deleted on BOTH to remove completely (the same goes for new playlists).
# XDGFX 2019
# 17/03/19 Started working on script
# 19/03/19 Original v2.0 Release
# 20/03/19 v2.1 Updated to use tempfile module temporary directory
# 22/03/19 v2.1.1 General improvements and bug fixes
# 23/03/19 v2.1.2 Fixed v2.1 and v2.1.1 releases, no longer using tempfile
# 30/03/19 v2.1.3 Added timestamp and improved character support
# Uses GNU General Public License
# VARIABLES GO HERE
server_url = '192.168.1.96:32400' # The url to your server (may be localhost)
plex_token = 'P6X4rFdFhcTssbA6pXoT' # Where do I find this? Here: bit.ly/2p7RtOu
local_playlists = '/mnt/Playlists' # Path (relative to the machine you're running this on)
install_directory = 'D:\\Media\\PPP' # The path to this PPP folder; as seen by your Plex machine (must be accessible by your Plex machine, but you can run this program on any machine on the network)
section_id = '11' # The media section containing all your music (Google 'find Plex section ID')
# Use only if your local playlists and plex playlists have different directories (e.g. if they are not from the same machine) and so have different paths at the start
# If your directories include backslashes (they probably will) you need to escape it with a second backslash... 'D:\' becomes 'D:\\'
# Leave blank ('') if your directories are the same
local_prepend = '\\\\uluru\\Uluru\\'
plex_prepend = 'D:\\'
# NOTES: If you have used the Plex Playlist PUSH api in the past, existing playlists will be duplicated when running this program.
# This is because the playlist .m3u files are located in a different place. Delete the old playlists from Plex and run PPP again.
#
# USE AT YOUR OWN RISK: I TAKE NO RESPONSIBILITY IF YOU BREAK SOMETHING AND YOU DON'T HAVE BACKUPS!
#---- No need to edit below here ----
print("\nPPP Copyright (C) 2019 XDGFX \nThis program comes with ABSOLUTELY NO WARRANTY. \nThis is free software, and you are welcome to redistribute it \nunder certain conditions\n")
print(('I\'ll ignore \"' + local_prepend + '\" from local playlists and \"' + plex_prepend + '\" from Plex playlists\n'))
# Import modules
from xml.dom import minidom # for xml
import urllib.request # for xml
from urllib.request import urlopen # for xml
import shutil # for deleting files
import os # for folder and file management
import io # character encoding
from collections import OrderedDict # url ordering
import requests # HTTP POST requests
from datetime import datetime # for timestamp
print('Running PPP at ' + str(datetime.now().replace(microsecond=0)) + '\n')
if not plex_token:
print('ERROR: Hmm... looks like you haven\'t set your variables! Do this by editing getPlaylists.py with a text editor')
raise SystemExit
if os.path.isdir('tmp/'):
try:
shutil.rmtree('tmp/')
except:
print('RIP: I couldn\'t remove tmp folder. Try deleting manually?')
raise SystemExit
if not os.path.isdir('local_backups') and not os.path.isfile('NOBACKUPS'):
os.makedirs('local_backups')
try:
os.makedirs('tmp/')
os.makedirs('tmp/plex/')
os.makedirs('tmp/local/')
os.makedirs('tmp/merged/')
if not os.path.isfile('NOBACKUPS'):
i = 1
while os.path.exists('local_backups/%s' % i):
i += 1
if i > 10:
size = sum(os.path.getsize(os.path.join(dirpath,filename)) for dirpath, dirnames, filenames in os.walk('local_backups') for filename in filenames ) / 1024 / 1024
print('You have 10+ backups in your install directory, taking up %sMB of space... You might want to remove some old ones.\n' % round(size, 2))
print("Alternatively, create an empty file 'NOBACKUPS' in your PPP/ directory")
# Backup local playlists
try:
print('Backing up local playlists...\n')
shutil.copytree(local_playlists, 'local_backups/%s' % i)
print(('Backed up local playlists to local_backups/%s/\n' % i))
except shutil.Error as e:
print(('Directory not copied. Error: %s' % e))
except OSError as e:
print(('Directory not copied. Error: %s' % e))
else:
print('Not backing up local playlists. If this was NOT intentional, exit the program immediately\n')
except:
print('OH NO: Couldn\'t make tmp directories... check your permissions or make sure you don\'t have them open elsewhere')
raise SystemExit
# Get list of playlist IDs
url = 'http://' + str(server_url) + '/playlists/?X-Plex-Token=' + str(plex_token)
print(('Getting playlists from ' + str(url) + '\n'))
dom = minidom.parse(urlopen(url)) # Parse the data
# Extract key from each Playlist item as long as it's not a smart playlist
key = dom.getElementsByTagName('Playlist')
key = [items.attributes['key'].value for items in key if items.attributes['smart'].value == "0"]
# Copies Plex playlists to tmp/plex/ folder
for item in key:
url = 'http://' + str(server_url) + str(item) + '?X-Plex-Token=' + str(plex_token)
print(('Accessing ' + str(url)))
dom = minidom.parse(urlopen(url)) # Parse the data (for each playlist)
# Get title of playlist
title = dom.getElementsByTagName('MediaContainer')
title = [items.attributes['title'].value for items in title] # Extract playlist title
print(('Saving Plex playlist: ' + str(title[0]) + '\n'))
# Get each track and save to file
file = io.open('tmp/plex/' + str(title[0]) + '.m3u', 'w+', encoding = 'utf8')
path = dom.getElementsByTagName('Part')
path = [items.attributes['file'].value for items in path] # Extract disk path to music file
# Writes music path to .m3u file, repeating for each track
for i in range(len(path)):
file.write(path[i] + '\n')
file.close()
# Copies local playlists to tmp/local/ folder
for root, dirs, files in os.walk(local_playlists):
for file in files:
file_path = os.path.join(root, file)
if file.endswith('.m3u'):
print(('Copying local playlist: ' + file_path))
shutil.copy2(file_path, 'tmp/local/')
# Checks for unique playlists to tmp/plex/, and copies them to tmp/merged/
for filename in os.listdir('tmp/plex/'):
if not os.path.isfile(os.path.join('tmp/local/', filename)):
print(('Found new Plex playlist: ' + filename))
plex_tracks = io.open(os.path.join('tmp/plex/', filename), 'r', encoding = 'utf8').read().splitlines()
os.remove(os.path.join('tmp/plex/', filename))
file = io.open('tmp/merged/' + filename, 'w+', encoding = 'utf8')
for i in range(len(plex_tracks)):
plex_tracks[i] = plex_tracks[i].strip(plex_prepend) # Strips plex_prepend
file.write(plex_tracks[i] + '\n')
# Checks for unique playlists to tmp/local/, and copies them to tmp/merged/
for filename in os.listdir('tmp/local/'):
if not os.path.isfile(os.path.join('tmp/plex/', filename)):
print(('Found new local playlist: ' + filename))
local_tracks = io.open(os.path.join('tmp/local/', filename), 'r', encoding = 'utf8').read().splitlines()
os.remove(os.path.join('tmp/local/', filename))
file = io.open('tmp/merged/' + filename, 'w+', encoding = 'utf8')
for i in range(len(local_tracks)):
if not local_tracks[i].startswith('#'): #Skips m3u tags beginning with #
local_tracks[i] = local_tracks[i].strip(local_prepend) # Strips local_prepend
file.write(local_tracks[i] + '\n')
file.close()
# Merges playlists from tmp/local/ and tmp/plex/ and puts the output in tmp/merged
for filename in os.listdir('tmp/local/'):
print(('Merging: ' + filename))
local_tracks = io.open(os.path.join('tmp/local/', filename), 'r', encoding = 'utf8').read().splitlines()
for i in range(len(local_tracks)): # Strips local_prepend
local_tracks[i] = local_tracks[i].strip(local_prepend)
plex_tracks = io.open(os.path.join('tmp/plex/', filename), 'r', encoding = 'utf8').read().splitlines()
for i in range(len(plex_tracks)): # Strips plex_prepend
plex_tracks[i] = plex_tracks[i].strip(plex_prepend)
file = io.open(os.path.join('tmp/merged/', filename), 'w+', encoding='utf8')
for line in local_tracks: # Writes local_tracks to merged playlist
if not line.startswith('#'): # Skips m3u tags beginning with #
file.write(line + '\n')
if line in plex_tracks: # Remove duplicates
plex_tracks.remove(line)
for line in plex_tracks: # Writes plex_tracks to merged playlist
file.write(line + '\n')
file.close()
# Copy merged playlists back into tmp/plex/ and tmp/local/ with prepends re-added
for filename in os.listdir('tmp/merged/'):
new_tracks = io.open(os.path.join('tmp/merged/', filename), 'r+', encoding = 'utf8').read().splitlines()
plex_tracks = []
local_tracks = []
for i in range(len(new_tracks)): # Re-adds prepends and writes to files
plex_tracks.append(plex_prepend + new_tracks[i])
local_tracks.append(local_prepend + new_tracks[i])
file = io.open(os.path.join('tmp/local/', filename), 'w+', encoding='utf8')
for line in local_tracks: # Writes local_tracks to merged playlist
file.write(line + '\n')
file.close()
file = io.open(os.path.join('tmp/plex/', filename), 'w+', encoding='utf8')
for line in plex_tracks: # Writes local_tracks to merged playlist
file.write(line + '\n')
file.close()
# POST new playlists to Plex
url = 'http://' + server_url + '/playlists/upload?'
headers = {'cache-control': "no-cache"}
for filename in os.listdir('tmp/plex/'):
print('Sending updated playlist to Plex: ' + filename)
current_playlist = install_directory + '\\tmp\\plex\\' + filename
querystring = urllib.parse.urlencode(OrderedDict([("sectionID", section_id), ("path", current_playlist), ("X-Plex-Token", plex_token)]))
response = requests.post(url, data = "", headers = headers, params = querystring)
print(response.text) # Should return nothing but if there's an issue there may be an error shown
# Copy updated local playlists back to local_playlists
for root, dirs, files in os.walk(local_playlists):
for file in files:
if file.endswith('.m3u'):
print('Copying updated playlist to local playlists: ' + file)
file_path = os.path.join(root, file)
if os.path.isfile('tmp/local/' + file):
shutil.copy2('tmp/local/' + file, file_path)
os.remove('tmp/local/' + file)
else:
print('FAIL: A playlist from local_playlists was there earlier and now it isn\'t. I am very confused.')
raise SystemExit
for filename in os.listdir('tmp/local/'):
shutil.copy2('tmp/local/' + filename, local_playlists)
try:
shutil.rmtree('tmp/')
print('Complete!\n')
except shutil.Error as e:
print('Program complete, but I had trouble cleaning tmp/ directory. Check it\'s not open somewhere else \n Error: %s' % e)
raise SystemExit