This repository has been archived by the owner on Dec 21, 2022. It is now read-only.
forked from claythearc/iTunesRPC
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathitunesrpc.py
461 lines (389 loc) · 17.2 KB
/
itunesrpc.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
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
# encoding: utf-8
# iTuneRPC-Remastered
# github.com/bildsben/iTunesRPC-Remastered
import ast # used for secure evaluation of strings from server; ty Neko#0013
import os
import platform # for log info
import sys # exit at end of program: for compatibility with PyInstaller
import time
import psutil # for log info
import pypresence
import win32com.client
import module.connect_to_server as networking
import module.itunesrpc_window.main as itrpc_window
from module.itrpc_logging import log_message
from module.systray.traybar import SysTrayIcon
# DEFINITIONS
def push_playing(o, DiscordRPC, dict, last_pos, paused_track, moved_playhead):
paused = False
# Get all relevant information
# TRACK INFO
track = o.CurrentTrack.Name
# OTHER INFO
artist = o.CurrentTrack.Artist
album = o.CurrentTrack.Album
# SAVE DATA TO FILE, FOR WINDOW
curr = str({"song": track, "artist": artist, "album": album})
with open(
"current_song_info", "w", encoding="utf-8"
) as current: # save with context manager to allow for encoding= variable.
current.write(curr)
# MODIFY TRACK TO HAVE PAUSED IF PAUSED ON APPLE MUSIC
if paused_track:
track = "[PAUSED] " + track
file_path = os.getcwd() + "\\temporary.png"
o.CurrentTrack.Artwork.Item(1).SaveArtworkToFile(file_path)
artwork_url = networking.get("temporary.png", domain, track, artist, album)
artwork_url = ast.literal_eval(str(artwork_url))
artwork_url = str(artwork_url[1]) + str(artwork_url[2])
artwork_url = "https://" + domain + "/itrpc/" + artwork_url
# os.remove(file_path)
# log_message INFO
log_message("Track: " + track)
log_message("Artist: " + artist)
log_message("Album: " + album)
log_message("Artwork URL: " + str(artwork_url))
pause_button = f"https://{domain}/itrpc/pause.png"
play_button = f"https://{domain}/itrpc/play.png"
# timestamps for computing how far into the song we are
if paused_track is False:
starttime = int(time.time()) - o.PlayerPosition
endtime = int(time.time()) + (o.CurrentTrack.Duration - o.PlayerPosition)
try:
if moved_playhead:
DiscordRPC.clear() # get rid of the current status: the left count won't refresh otherwise.
time.sleep(0.1)
# if we don't pause for a tiny amount the .update will send, and discord will
# forget the .clear command.
if paused_track is True:
if paused is not True:
DiscordRPC.update(
details=track,
state=artist,
large_image=artwork_url,
large_text=album,
small_image=pause_button,
small_text="Paused on Apple Music",
buttons=buttons,
)
paused = True
else:
if last_pos is not False:
DiscordRPC.update(
details=track,
state=artist,
start=starttime,
end=endtime,
large_image=artwork_url,
large_text=album,
small_image=play_button,
small_text="Playing on Apple Music",
buttons=buttons,
)
else:
last_pos = o.CurrentTrack.Duration - o.PlayerPosition
except Exception:
# Discord is closed if we error here.
# Let's re open it.
log_message("..........................................")
log_message(". Discord is closed... .")
log_message(". Attempting to open it .")
log_message(". Waiting global_pause+3 seconds before .")
log_message(". continuing. .")
log_message("..........................................")
DiscordRPC = False
opened = False
while DiscordRPC is False:
try:
DiscordRPC = pypresence.Presence(secret, pipe=0)
DiscordRPC.connect()
log_message("Hooked to Discord.")
except Exception:
if not opened:
os.system(discord_command)
log_message("..........................................")
log_message(". Discord is closed... .")
log_message(". Attempting to open it .")
log_message(". Waiting global_pause+3 seconds before .")
log_message(". continuing. .")
log_message("..........................................")
opened = True
time.sleep(global_pause + 3)
continue
# Now we have re opened Discord, let's post the message to Discord.
time.sleep(global_pause)
# Since we may have had Discord closed for a while, we need to update our items.
# Let's re call this definition, as it ensures we get the most recent values.
# We can send our original arguments to this. It isn't a massive deal.
DiscordRPC, track, artist, album, last_pos, paused = push_playing(
o, DiscordRPC, dict, last_pos, paused_track, moved_playhead
)
# Finally, regardless of what happened, let's return all our values.
return (DiscordRPC, track, artist, album, last_pos, paused)
# SYSTRAY DEFINITIONS
# window definitions defined at start of program.
def exit_program(systray):
global shutdown_systray
shutdown_systray = True
def toggle_rpc(systray):
global toggled
toggled = not toggled
time.sleep(global_pause + 1)
DiscordRPC.clear()
if __name__ == "__main__":
# CONFIGURATION FILES
f = open("config", "r")
config = ast.literal_eval(f.read()) # returns dict
f.close()
# FIX FOR WINDOW
# this fix sets current song to not playing, artist to nothing and album to nothing.
curr = str({"song": "Not Playing", "artist": "", "album": ""})
x = open("current_song_info", "w")
x.write(curr)
x.close()
# CONSTS/VARS
domain = config["domain"]
global_pause = 5 # set this higher if you get rate limited often by discord servers (recommended: 5)
if config["slow_mode"] is True:
global_pause += 5
try:
import secret
app_ID = secret.return_secret()
del secret
secret = app_ID
del app_ID
except Exception:
import module.itunesrpc_window.error_no_secret as ens # error no secret (E.001.NS)
ens.get_logger(log_message)
ens.start()
sys.exit()
discord_command = (
os.getenv("LOCALAPPDATA").replace(" ", "\ ")
+ "\\Discord\\"
+ config["discord_command"]
)
shutdown_systray = False
buttons = [
{
"label": "View on GitHub",
"url": "https://github.com/bildsben/iTunesRPC-Remastered",
}
]
itrpc_window.get_logger(log_message) # send the logger instance
# main cannot access the logger otherwise.
itrpc_window.send_logger()
# this sends the logger to window_test
# the window can be opened by requesting the window in the system tray
# show brief message telling user about this.
if config["show_msg"] is True:
itrpc_window.start_welcome()
# LOGGING
# first start: clean log messages and dump systeminfo
# none of this is automatically uploaded, but it should be
# sent within error reports on GitHub, as it may help determine
# your issue
os.remove("log") # delete the old log file.
log_message("Starting system log dump.")
log_message("Machine Architecture: " + platform.machine())
log_message("Machine Version:" + platform.version())
log_message("Machine Platform: " + platform.platform())
log_message("Machine Unix Name: " + str(platform.uname()))
log_message("OS Type: " + platform.system())
log_message("Processor: " + platform.processor())
log_message(
"Total RAM: " + str(round(psutil.virtual_memory().total / (1024.0**3))) + " GB"
)
log_message("End of system logs.\n")
log_message("Starting iTunesRPC logs.")
# SYSTRAY MENU OPTIONS AND MAKING THE ICON
menu_options = (
("Show Window", None, itrpc_window.start),
("Toggle Rich Presence", None, toggle_rpc),
("Shutdown iTunesRPC Safely", None, exit_program),
)
systray = SysTrayIcon("icon.ico", "iTunesRPC", menu_options)
systray.start()
log_message("Started Systray icon.")
# GETTING THE ITUNES COM CONNECTION
o = win32com.client.gencache.EnsureDispatch(
"iTunes.Application"
) # connect to the COM of iTunes.Application
log_message("Hooked to iTunes COM.")
# NOTE: win32com.client.gencache.EnsureDispatch will force open the application if not already open
# CONNECTING TO DISCORD
DiscordRPC = False
opened = False
while DiscordRPC is False:
if shutdown_systray:
log_message("No connection to DiscordRPC, so not closing.")
systray.shutdown()
log_message("Shutdown the Systray icon.")
quit("Shutdown the Python program.")
try:
DiscordRPC = pypresence.Presence(secret, pipe=0)
DiscordRPC.connect()
log_message("Hooked to Discord.")
except Exception:
if not opened:
os.system(discord_command)
log_message("..........................................")
log_message(". Discord is closed... .")
log_message(". Waiting for it to open before starting .")
log_message(". Waiting global_pause+3 seconds before .")
log_message(". continuing. .")
log_message("..........................................")
opened = True
time.sleep(
global_pause + 3
) # takes a while to open discord on lower end hardware so account for that here
continue
# GET LAST POSITION OF TRACK
stopped = True
while stopped:
try:
last_pos = o.CurrentTrack.Duration - o.PlayerPosition
# LAST TRACK = THE TRACK THAT IS CURRENTLY PLAYED. IT MAKES SENSE IN
# CODE AS LAST_TRACK IS THE TRACK PLAYED {global_pause} SECONDS AGO
last_track = o.CurrentTrack.Name
track = o.CurrentTrack.Name
stopped = False
except Exception:
DiscordRPC.clear()
o = win32com.client.gencache.EnsureDispatch("iTunes.Application")
log_message("..........................................")
log_message(". iTunes is not playing anything... .")
log_message(". Waiting for it to play before starting .")
log_message(". Waiting 10 seconds. .")
log_message("..........................................")
time.sleep(10)
# LOOP VARIABLES
special_push = False # this is used to determine if another function has already pushed
# to RPC, as we don't want to repeat for loads of different items.
stopped = False # track stopped (iTunes has no track selected)
paused = False # track paused (iTunes has a track selected)
first_run = True # first ran the program.
shutdown_systray = False # shutdown the program from the systray
running = True # run var
skipped = False # skipping song.
toggled = True # showing RP?
# LOOP
while running:
if toggled:
if first_run:
last_pos = o.CurrentTrack.Duration - o.PlayerPosition
time.sleep(global_pause)
first_run = False
try:
placeholder = o.CurrentTrack.Name # try to get current track name
except Exception:
stopped = True
if stopped is False:
log_message("------------------")
# update the last track to be the variable that was playing 5 seconds ago and
# get the new current track and store it as track
last_track = track
track = o.CurrentTrack.Name
log_message("Last Track: " + last_track)
log_message("Current Track: " + track)
log_message("Last Playhead Position: " + str(last_pos))
log_message(
"Current Playhead Position: "
+ str((o.CurrentTrack.Duration - o.PlayerPosition))
)
log_message(
"Position Difference: "
+ str(last_pos - (o.CurrentTrack.Duration - o.PlayerPosition))
)
log_message("Pushing the following info...")
if last_track != track: # if we changed tracks.
special_push = True
skipped = True
log_message("Changed track. Getting regular fetch from push_playing.")
DiscordRPC, track, artist, album, last_pos, paused = push_playing(
o, DiscordRPC, dict, last_pos, False, False
)
if not paused or not skipped:
if (
last_pos - (o.CurrentTrack.Duration - o.PlayerPosition)
< global_pause - 1
and last_pos - (o.CurrentTrack.Duration - o.PlayerPosition) >= 0
):
special_push = True
# we are paused
log_message("Paused. Sending pause message to RPC.")
DiscordRPC, track, artist, album, last_pos, paused = push_playing(
o, DiscordRPC, dict, last_pos, True, False
)
else:
paused = False
if (
(last_pos - (o.CurrentTrack.Duration - o.PlayerPosition) < 0)
or (
last_pos - (o.CurrentTrack.Duration - o.PlayerPosition)
> global_pause + 1
)
) and last_track == track:
# we have rewound or fast forwarded within the song. let's make sure we account for that when calling push_playing
# this could also happen when a new song has started. that is why last_track == track is in this if statement
log_message(
"Track position moved over global_pause value, likely skipped forward/backward in the song."
)
special_push = True
DiscordRPC, track, artist, album, last_pos, paused = push_playing(
o, DiscordRPC, dict, last_pos, False, True
)
if special_push is False:
DiscordRPC, track, artist, album, last_pos, paused = push_playing(
o, DiscordRPC, dict, last_pos, False, False
)
else:
skipped = False
special_push = False
# get the last position of the track. used for pause
last_pos = o.CurrentTrack.Duration - o.PlayerPosition
time.sleep(global_pause)
else:
DiscordRPC.clear()
try:
del o
except Exception:
pass
o = win32com.client.gencache.EnsureDispatch("iTunes.Application")
log_message("..........................................")
log_message(". iTunes is not playing anything... .")
log_message(". Waiting for it to play before starting .")
log_message(". Waiting 10 seconds. .")
log_message("..........................................")
time.sleep(10)
stopped = False
try:
track = o.CurrentTrack.Name
except Exception as e:
log_message(e)
stopped = True
if shutdown_systray:
running = False
log_message("------------------")
log_message("Shutting down.")
else:
log_message("RPC is toggled off. Not showing status.")
log_message("Waiting 1 second to check if toggled is enabled.")
time.sleep(1)
# SHUTDOWN
DiscordRPC.close()
log_message("Closed connection to DiscordRPC.")
systray.shutdown()
log_message("Shutdown the Systray icon.")
o.Quit()
log_message("Closed iTunes connection.")
p = open("config", "r")
prev = ast.literal_eval(p.read())
p.close()
prev["gui_window_isOpen"] = False
update = open("config", "w")
update.write(str(prev))
update.close()
log_message(
"Set GUI isOpen to False to ensure we don't get hanging on next open."
) # If this value is left as true, on the next launch of the app, it is possible that the window will freeze.
sys.exit(log_message("Shutdown application."))