mirror of
https://github.com/nuttylmao/nutty.gg.git
synced 2026-09-18 19:50:58 -04:00
• Renamed "Base64Image" to "Thumbnail:
• Minor CSS bug fixes • Fully documented python script
This commit is contained in:
@@ -39,6 +39,7 @@
|
|||||||
"label": "Font Size",
|
"label": "Font Size",
|
||||||
"description": "",
|
"description": "",
|
||||||
"type": "number",
|
"type": "number",
|
||||||
|
"min": 0,
|
||||||
"defaultValue": 20,
|
"defaultValue": 20,
|
||||||
"group": "Appearance"
|
"group": "Appearance"
|
||||||
},
|
},
|
||||||
@@ -47,6 +48,7 @@
|
|||||||
"label": "Max Width",
|
"label": "Max Width",
|
||||||
"description": "Set to 0 to utilize full browser width",
|
"description": "Set to 0 to utilize full browser width",
|
||||||
"type": "number",
|
"type": "number",
|
||||||
|
"min": 0,
|
||||||
"defaultValue": 500,
|
"defaultValue": 500,
|
||||||
"group": "Appearance"
|
"group": "Appearance"
|
||||||
},
|
},
|
||||||
|
|||||||
+104
-10
@@ -42,44 +42,133 @@ async def get_all_media_info():
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
# Instantiate the SMTC manager -> This allows use to "talk" to the Windows Media API
|
||||||
manager = await SMTC.request_async()
|
manager = await SMTC.request_async()
|
||||||
|
|
||||||
|
# If it returns null, then no media is playing, or something fucked up and I have no
|
||||||
|
# idea what to do, so just return an empty session list
|
||||||
if not manager:
|
if not manager:
|
||||||
return {"current_session_id": None, "sessions": []}
|
return {"current_session_id": None, "sessions": []}
|
||||||
|
|
||||||
|
# This is the session for the current media player -> Whatever Windows deems is "in focus"
|
||||||
|
# will be the current session.
|
||||||
|
# We will store the SourceAppUserModelId, which we all add to the final payload.
|
||||||
|
# For all available properties/methods/events, see the official docs:
|
||||||
|
# https://learn.microsoft.com/en-us/uwp/api/windows.media.control.globalsystemmediatransportcontrolssession?view=winrt-28000
|
||||||
current_focused = manager.get_current_session()
|
current_focused = manager.get_current_session()
|
||||||
current_session_id = current_focused.source_app_user_model_id if current_focused else None
|
current_session_id = current_focused.source_app_user_model_id if current_focused else None
|
||||||
|
|
||||||
|
# We will also get all sessions, not just the current session.
|
||||||
|
# This will provide the client with all the necessary info if they want to target just
|
||||||
|
# one application.
|
||||||
all_sessions = manager.get_sessions()
|
all_sessions = manager.get_sessions()
|
||||||
sessions_list = []
|
sessions_list = []
|
||||||
|
|
||||||
|
# We will not iterate over all the sessions, and grab all the available info
|
||||||
for session in all_sessions:
|
for session in all_sessions:
|
||||||
|
# Get all the available info for each session object:
|
||||||
|
# For all available properties/methods/events, see the official docs:
|
||||||
|
# https://learn.microsoft.com/en-us/uwp/api/windows.media.control.globalsystemmediatransportcontrolssession?view=winrt-28000
|
||||||
app_id = session.source_app_user_model_id
|
app_id = session.source_app_user_model_id
|
||||||
raw_playback = session.get_playback_info()
|
raw_playback = session.get_playback_info()
|
||||||
raw_timeline = session.get_timeline_properties()
|
raw_timeline = session.get_timeline_properties()
|
||||||
raw_media = await session.try_get_media_properties_async()
|
raw_media = await session.try_get_media_properties_async()
|
||||||
|
|
||||||
|
# Playback info
|
||||||
|
# https://learn.microsoft.com/en-us/uwp/api/windows.media.control.globalsystemmediatransportcontrolssessionplaybackinfo?view=winrt-28000
|
||||||
playback_data = {
|
playback_data = {
|
||||||
|
# AutoRepeatMode: Specifies the repeat mode of the session.
|
||||||
|
"AutoRepeatMode": raw_playback.auto_repeat_mode.value if (raw_playback and raw_playback.auto_repeat_mode) else 0,
|
||||||
|
|
||||||
|
# IsShuffleActive: Specifies whether the session is currently playing content in a shuffled order.
|
||||||
|
"IsShuffleActive": raw_playback.is_shuffle_active if raw_playback else False,
|
||||||
|
|
||||||
|
# PlaybackRate: The rate at which playback is happening (e.g., 1.0 is normal speed).
|
||||||
|
"PlaybackRate": raw_playback.playback_rate if raw_playback else 1.0,
|
||||||
|
|
||||||
|
# PlaybackStatus: The current playback state of the session (e.g., Playing, Paused).
|
||||||
"PlaybackStatus": raw_playback.playback_status.value if (raw_playback and raw_playback.playback_status) else 0,
|
"PlaybackStatus": raw_playback.playback_status.value if (raw_playback and raw_playback.playback_status) else 0,
|
||||||
"PlaybackType": raw_playback.playback_type.value if (raw_playback and raw_playback.playback_type) else 0,
|
|
||||||
|
# PlaybackType: Specifies what type of content the session has (e.g., Music, Video).
|
||||||
|
"PlaybackType": raw_playback.playback_type.value if (raw_playback and raw_playback.playback_type) else 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Timeline properties
|
||||||
|
# https://learn.microsoft.com/en-us/uwp/api/windows.media.control.globalsystemmediatransportcontrolssessiontimelineproperties?view=winrt-28000
|
||||||
timeline_data = {
|
timeline_data = {
|
||||||
"Position": int(raw_timeline.position.total_seconds() * 1000) if raw_timeline.position else 0,
|
# EndTime: The end timestamp of the current media item.
|
||||||
"EndTime": int(raw_timeline.end_time.total_seconds() * 1000) if raw_timeline.end_time else 0,
|
"EndTime": int(raw_timeline.end_time.total_seconds() * 1000) if raw_timeline.end_time else 0,
|
||||||
"LastUpdatedTime": str(raw_timeline.last_updated_time) if raw_timeline.last_updated_time else None
|
|
||||||
|
# LastUpdatedTime: The UTC time at which the timeline properties were last updated.
|
||||||
|
"LastUpdatedTime": str(raw_timeline.last_updated_time) if raw_timeline.last_updated_time else None,
|
||||||
|
|
||||||
|
# MaxSeekTime: The furthest timestamp at which the content can currently seek to.
|
||||||
|
"MaxSeekTime": int(raw_timeline.max_seek_time.total_seconds() * 1000) if raw_timeline.max_seek_time else 0,
|
||||||
|
|
||||||
|
# MinSeekTime: The earliest timestamp at which the current media item can currently seek to.
|
||||||
|
"MinSeekTime": int(raw_timeline.min_seek_time.total_seconds() * 1000) if raw_timeline.min_seek_time else 0,
|
||||||
|
|
||||||
|
# Position: The playback position, current as of LastUpdatedTime.
|
||||||
|
"Position": int(raw_timeline.position.total_seconds() * 1000) if raw_timeline.position else 0,
|
||||||
|
|
||||||
|
# StartTime: The starting timestamp of the current media item.
|
||||||
|
"StartTime": int(raw_timeline.start_time.total_seconds() * 1000) if raw_timeline.start_time else 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Media properties
|
||||||
|
# https://learn.microsoft.com/en-us/uwp/api/windows.media.control.globalsystemmediatransportcontrolssessionmediaproperties?view=winrt-28000
|
||||||
|
media_data = {
|
||||||
|
# Title: The title of the track.
|
||||||
|
"Title": raw_media.title if raw_media else "Unknown",
|
||||||
|
|
||||||
|
# Artist: The name of the artist.
|
||||||
|
"Artist": raw_media.artist if raw_media else "Unknown",
|
||||||
|
|
||||||
|
# AlbumTitle: The title of the album.
|
||||||
|
"AlbumTitle": raw_media.album_title if raw_media else "Unknown",
|
||||||
|
|
||||||
|
# AlbumArtist: The artist associated with the album.
|
||||||
|
"AlbumArtist": raw_media.album_artist if raw_media else "Unknown",
|
||||||
|
|
||||||
|
# TrackNumber: The track number within the album.
|
||||||
|
"TrackNumber": raw_media.track_number if raw_media else 0,
|
||||||
|
|
||||||
|
# AlbumTrackCount: The total number of tracks on the album.
|
||||||
|
"AlbumTrackCount": raw_media.album_track_count if raw_media else 0,
|
||||||
|
|
||||||
|
# Genres: The list of genres associated with the track.
|
||||||
|
"Genres": list(raw_media.genres) if raw_media else [],
|
||||||
|
|
||||||
|
# Subtitle: Any subtitle information (common in podcasts/videos).
|
||||||
|
"Subtitle": raw_media.subtitle if raw_media else "",
|
||||||
|
|
||||||
|
# Thumbnail: Our cached thumbnail representation.
|
||||||
|
"Thumbnail": None
|
||||||
|
}
|
||||||
|
|
||||||
|
# Add the album art (i.e. "Thumbnail") as a Base64 string.
|
||||||
|
# We will cache this so we don't have to read it every single call.
|
||||||
|
# Use the track title and artist as the key -> {title} - {artist}
|
||||||
|
|
||||||
|
# Build the key
|
||||||
title = raw_media.title if raw_media else "Unknown"
|
title = raw_media.title if raw_media else "Unknown"
|
||||||
artist = raw_media.artist if raw_media else "Unknown"
|
artist = raw_media.artist if raw_media else "Unknown"
|
||||||
track_key = f"{title} - {artist}"
|
track_key = f"{title} - {artist}"
|
||||||
|
|
||||||
media_data = {"Title": title, "Artist": artist, "Base64Image": None}
|
|
||||||
|
|
||||||
if app_id in ARTWORK_CACHE and ARTWORK_CACHE[app_id]["track_key"] == track_key:
|
|
||||||
media_data["Base64Image"] = ARTWORK_CACHE[app_id]["base64"]
|
|
||||||
|
|
||||||
|
# Check if the album art is cached.
|
||||||
|
# If yes:
|
||||||
|
# - Retrieve it from the cache.
|
||||||
|
# - Add it to the payload
|
||||||
|
if app_id in ARTWORK_CACHE and ARTWORK_CACHE[app_id]["track_key"] == track_key:
|
||||||
|
media_data["Thumbnail"] = ARTWORK_CACHE[app_id]["base64"]
|
||||||
|
|
||||||
|
# If not
|
||||||
|
# - Read the Base64 string
|
||||||
|
# - Cache the Base64 string
|
||||||
|
# - Add it to the payload
|
||||||
elif raw_media and raw_media.thumbnail:
|
elif raw_media and raw_media.thumbnail:
|
||||||
try:
|
try:
|
||||||
|
# Read the Base64 string
|
||||||
stream_ref = raw_media.thumbnail
|
stream_ref = raw_media.thumbnail
|
||||||
stream = await stream_ref.open_read_async()
|
stream = await stream_ref.open_read_async()
|
||||||
reader = DataReader(stream.get_input_stream_at(0))
|
reader = DataReader(stream.get_input_stream_at(0))
|
||||||
@@ -90,18 +179,23 @@ async def get_all_media_info():
|
|||||||
img = Image.open(io.BytesIO(buffer))
|
img = Image.open(io.BytesIO(buffer))
|
||||||
base64_art = f"data:image/png;base64,{base64.b64encode(buffer).decode('utf-8')}"
|
base64_art = f"data:image/png;base64,{base64.b64encode(buffer).decode('utf-8')}"
|
||||||
|
|
||||||
|
# Cache the Base64 string
|
||||||
ARTWORK_CACHE[app_id] = {"track_key": track_key, "base64": base64_art}
|
ARTWORK_CACHE[app_id] = {"track_key": track_key, "base64": base64_art}
|
||||||
media_data["Base64Image"] = base64_art
|
|
||||||
|
# Add it to the payload
|
||||||
|
media_data["Thumbnail"] = base64_art
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# Finally, assemble all the data into a session object, and add it to the session list
|
||||||
sessions_list.append({
|
sessions_list.append({
|
||||||
"source_app_id": app_id,
|
"source_app_id": app_id,
|
||||||
"playback_info": playback_data,
|
"playback_info": playback_data,
|
||||||
"timeline_properties": timeline_data,
|
"timeline_properties": timeline_data,
|
||||||
"media_properties": media_data
|
"media_properties": media_data
|
||||||
})
|
})
|
||||||
|
|
||||||
|
# Build the final payload
|
||||||
return {"current_session_id": current_session_id, "sessions": sessions_list}
|
return {"current_session_id": current_session_id, "sessions": sessions_list}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return {"current_session_id": None, "sessions": [], "error": str(e)}
|
return {"current_session_id": None, "sessions": [], "error": str(e)}
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ async function UpdatePlayerState(data) {
|
|||||||
const timelineProps = targetSession.timeline_properties;
|
const timelineProps = targetSession.timeline_properties;
|
||||||
|
|
||||||
// Calcualte an accent color
|
// Calcualte an accent color
|
||||||
const accentColorPalette = await GetAccentPalette(mediaProps.Base64Image);
|
const accentColorPalette = await GetAccentPalette(mediaProps.Thumbnail);
|
||||||
|
|
||||||
// 1. Check if playback status has changed and update visibility accordingly
|
// 1. Check if playback status has changed and update visibility accordingly
|
||||||
if (playbackInfo.PlaybackStatus !== CurrentPlaybackStatus) {
|
if (playbackInfo.PlaybackStatus !== CurrentPlaybackStatus) {
|
||||||
@@ -168,7 +168,7 @@ async function ChangeTrack(mediaProps, tintColor) {
|
|||||||
artistLabel.innerText = swapArtistTrack ? mediaProps.Title : mediaProps.Artist;
|
artistLabel.innerText = swapArtistTrack ? mediaProps.Title : mediaProps.Artist;
|
||||||
|
|
||||||
// Extract the image source string (use fallback if Windows has no art)
|
// Extract the image source string (use fallback if Windows has no art)
|
||||||
const newArtUrl = mediaProps.Base64Image;
|
const newArtUrl = mediaProps.Thumbnail;
|
||||||
|
|
||||||
// Set the image
|
// Set the image
|
||||||
backgroundLayer.style.backgroundImage = `url('${newArtUrl}')`;
|
backgroundLayer.style.backgroundImage = `url('${newArtUrl}')`;
|
||||||
|
|||||||
@@ -32,12 +32,12 @@
|
|||||||
transition: opacity 0.5s ease;
|
transition: opacity 0.5s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
#art-layer {
|
#album-art-layer {
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
transition: all 0.5s ease;
|
transition: all 0.5s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
#art-transition-layer {
|
#album-art-transition-layer {
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,7 +82,7 @@
|
|||||||
position: relative; /* Keeps it in the document flow */
|
position: relative; /* Keeps it in the document flow */
|
||||||
z-index: 2; /* Sits above the background-layer */
|
z-index: 2; /* Sits above the background-layer */
|
||||||
|
|
||||||
padding: 1.5em 1.25em;
|
padding: 2em 1.75em;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 0.3em;
|
gap: 0.3em;
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ async function UpdatePlayerState(data) {
|
|||||||
const timelineProps = targetSession.timeline_properties;
|
const timelineProps = targetSession.timeline_properties;
|
||||||
|
|
||||||
// Calcualte an accent color
|
// Calcualte an accent color
|
||||||
const accentColorPalette = await GetAccentPalette(mediaProps.Base64Image);
|
const accentColorPalette = await GetAccentPalette(mediaProps.Thumbnail);
|
||||||
|
|
||||||
// 1. Check if playback status has changed and update visibility accordingly
|
// 1. Check if playback status has changed and update visibility accordingly
|
||||||
if (playbackInfo.PlaybackStatus !== CurrentPlaybackStatus) {
|
if (playbackInfo.PlaybackStatus !== CurrentPlaybackStatus) {
|
||||||
|
|||||||
@@ -165,7 +165,7 @@ async function ChangeTrack(mediaProps) {
|
|||||||
artistLabel.innerText = swapArtistTrack ? mediaProps.Title : mediaProps.Artist;
|
artistLabel.innerText = swapArtistTrack ? mediaProps.Title : mediaProps.Artist;
|
||||||
|
|
||||||
// Extract the image source string (use fallback if Windows has no art)
|
// Extract the image source string (use fallback if Windows has no art)
|
||||||
const newArtUrl = mediaProps.Base64Image;
|
const newArtUrl = mediaProps.Thumbnail;
|
||||||
// const accent = mediaProps.AccentColor || "#ffffff";
|
// const accent = mediaProps.AccentColor || "#ffffff";
|
||||||
|
|
||||||
// Set the image
|
// Set the image
|
||||||
|
|||||||
@@ -32,12 +32,12 @@
|
|||||||
transition: opacity 0.5s ease;
|
transition: opacity 0.5s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
#art-layer {
|
#album-art-layer {
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
transition: all 0.5s ease;
|
transition: all 0.5s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
#art-transition-layer {
|
#album-art-transition-layer {
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ async function UpdatePlayerState(data) {
|
|||||||
const timelineProps = targetSession.timeline_properties;
|
const timelineProps = targetSession.timeline_properties;
|
||||||
|
|
||||||
// Calcualte an accent color
|
// Calcualte an accent color
|
||||||
const accentColorPalette = await GetAccentPalette(mediaProps.Base64Image);
|
const accentColorPalette = await GetAccentPalette(mediaProps.Thumbnail);
|
||||||
|
|
||||||
// 1. Check if playback status has changed and update visibility accordingly
|
// 1. Check if playback status has changed and update visibility accordingly
|
||||||
if (playbackInfo.PlaybackStatus !== CurrentPlaybackStatus) {
|
if (playbackInfo.PlaybackStatus !== CurrentPlaybackStatus) {
|
||||||
@@ -168,7 +168,7 @@ async function ChangeTrack(mediaProps, tintColor) {
|
|||||||
artistLabel.innerText = swapArtistTrack ? mediaProps.Title : mediaProps.Artist;
|
artistLabel.innerText = swapArtistTrack ? mediaProps.Title : mediaProps.Artist;
|
||||||
|
|
||||||
// Extract the image source string (use fallback if Windows has no art)
|
// Extract the image source string (use fallback if Windows has no art)
|
||||||
const newArtUrl = mediaProps.Base64Image;
|
const newArtUrl = mediaProps.Thumbnail;
|
||||||
|
|
||||||
// Set the image
|
// Set the image
|
||||||
backgroundLayer.style.backgroundImage = `url('${newArtUrl}')`;
|
backgroundLayer.style.backgroundImage = `url('${newArtUrl}')`;
|
||||||
|
|||||||
@@ -31,12 +31,12 @@
|
|||||||
transition: opacity 0.5s ease;
|
transition: opacity 0.5s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
#art-layer {
|
#album-art-layer {
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
transition: all 0.5s ease;
|
transition: all 0.5s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
#art-transition-layer {
|
#album-art-transition-layer {
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user