Compare commits

...

2 commits

5 changed files with 68 additions and 4 deletions

View file

@ -3,7 +3,7 @@ from os import environ
from corefoundationasyncio import CoreFoundationEventLoop from corefoundationasyncio import CoreFoundationEventLoop
from .cocoa import CocoaNowPlaying from .cocoa.now_playing import CocoaNowPlaying
from .mpd.listener import MpdStateListener from .mpd.listener import MpdStateListener

View file

View file

@ -6,8 +6,13 @@ from Foundation import CGSize, NSMutableDictionary
from MediaPlayer import ( from MediaPlayer import (
MPMediaItemArtwork, MPMediaItemArtwork,
MPMediaItemPropertyAlbumTitle, MPMediaItemPropertyAlbumTitle,
MPMediaItemPropertyAlbumTrackNumber,
MPMediaItemPropertyArtist, MPMediaItemPropertyArtist,
MPMediaItemPropertyArtwork, MPMediaItemPropertyArtwork,
MPMediaItemPropertyComposer,
MPMediaItemPropertyDiscNumber,
MPMediaItemPropertyGenre,
MPMediaItemPropertyPersistentID,
MPMediaItemPropertyPlaybackDuration, MPMediaItemPropertyPlaybackDuration,
MPMediaItemPropertyTitle, MPMediaItemPropertyTitle,
MPMusicPlaybackState, MPMusicPlaybackState,
@ -18,15 +23,20 @@ from MediaPlayer import (
MPNowPlayingInfoMediaTypeAudio, MPNowPlayingInfoMediaTypeAudio,
MPNowPlayingInfoMediaTypeNone, MPNowPlayingInfoMediaTypeNone,
MPNowPlayingInfoPropertyElapsedPlaybackTime, MPNowPlayingInfoPropertyElapsedPlaybackTime,
MPNowPlayingInfoPropertyExternalContentIdentifier,
MPNowPlayingInfoPropertyMediaType, MPNowPlayingInfoPropertyMediaType,
MPNowPlayingInfoPropertyPlaybackQueueCount,
MPNowPlayingInfoPropertyPlaybackQueueIndex,
MPNowPlayingInfoPropertyPlaybackRate,
MPRemoteCommandCenter, MPRemoteCommandCenter,
MPRemoteCommandEvent, MPRemoteCommandEvent,
MPRemoteCommandHandlerStatus, MPRemoteCommandHandlerStatus,
) )
from .async_tools import run_background_task from ..async_tools import run_background_task
from .player import Player from ..player import Player
from .song import PlaybackState, Song from ..song import PlaybackState, Song
from .persistent_id import song_to_persistent_id
def logo_to_ns_image() -> NSImage: def logo_to_ns_image() -> NSImage:
@ -70,12 +80,25 @@ def song_to_media_item(song: Song) -> NSMutableDictionary:
nowplaying_info = nothing_to_media_item() nowplaying_info = nothing_to_media_item()
nowplaying_info[MPNowPlayingInfoPropertyMediaType] = MPNowPlayingInfoMediaTypeAudio nowplaying_info[MPNowPlayingInfoPropertyMediaType] = MPNowPlayingInfoMediaTypeAudio
nowplaying_info[MPNowPlayingInfoPropertyElapsedPlaybackTime] = song.elapsed nowplaying_info[MPNowPlayingInfoPropertyElapsedPlaybackTime] = song.elapsed
nowplaying_info[MPNowPlayingInfoPropertyExternalContentIdentifier] = song.file
nowplaying_info[MPNowPlayingInfoPropertyPlaybackQueueCount] = song.queue_length
nowplaying_info[MPNowPlayingInfoPropertyPlaybackQueueIndex] = song.queue_index
nowplaying_info[MPMediaItemPropertyPersistentID] = song_to_persistent_id(song)
nowplaying_info[MPMediaItemPropertyTitle] = song.title nowplaying_info[MPMediaItemPropertyTitle] = song.title
nowplaying_info[MPMediaItemPropertyArtist] = song.artist nowplaying_info[MPMediaItemPropertyArtist] = song.artist
nowplaying_info[MPMediaItemPropertyAlbumTitle] = song.album nowplaying_info[MPMediaItemPropertyAlbumTitle] = song.album
nowplaying_info[MPMediaItemPropertyAlbumTrackNumber] = song.track
nowplaying_info[MPMediaItemPropertyDiscNumber] = song.disc
nowplaying_info[MPMediaItemPropertyGenre] = song.genre
nowplaying_info[MPMediaItemPropertyComposer] = song.composer
nowplaying_info[MPMediaItemPropertyPlaybackDuration] = song.duration nowplaying_info[MPMediaItemPropertyPlaybackDuration] = song.duration
# MPD can't play back music at different rates, so we just want to set it
# to 1.0 if the song is playing. (Leave it at 0.0 if the song is paused.)
if song.state == PlaybackState.play:
nowplaying_info[MPNowPlayingInfoPropertyPlaybackRate] = 1.0
if song.art: if song.art:
nowplaying_info[MPMediaItemPropertyArtwork] = ns_image_to_media_item_artwork( nowplaying_info[MPMediaItemPropertyArtwork] = ns_image_to_media_item_artwork(
data_to_ns_image(song.art) data_to_ns_image(song.art)
@ -88,6 +111,7 @@ def nothing_to_media_item() -> NSMutableDictionary:
nowplaying_info[MPNowPlayingInfoPropertyMediaType] = MPNowPlayingInfoMediaTypeNone nowplaying_info[MPNowPlayingInfoPropertyMediaType] = MPNowPlayingInfoMediaTypeNone
nowplaying_info[MPMediaItemPropertyArtwork] = MPD_LOGO nowplaying_info[MPMediaItemPropertyArtwork] = MPD_LOGO
nowplaying_info[MPMediaItemPropertyTitle] = "MPD (stopped)" nowplaying_info[MPMediaItemPropertyTitle] = "MPD (stopped)"
nowplaying_info[MPNowPlayingInfoPropertyPlaybackRate] = 0.0
return nowplaying_info return nowplaying_info

View file

@ -0,0 +1,39 @@
from hashlib import blake2b
from pathlib import Path
from typing import Final
from uuid import UUID
from ..song import Song
# The maximum size for a BLAKE2b "person" value is sixteen bytes, so we need to be concise.
HASH_PERSON_PREFIX: Final = b"mnp.mac."
TRACKID_HASH_PERSON: Final = HASH_PERSON_PREFIX + b"mb_tid"
FILE_HASH_PERSON: Final = HASH_PERSON_PREFIX + b"f"
PERSISTENT_ID_BITS: Final = 64
PERSISTENT_ID_BYTES: Final = PERSISTENT_ID_BITS // 8
def digest_trackid(trackid: UUID) -> bytes:
return blake2b(
trackid.bytes, digest_size=PERSISTENT_ID_BYTES, person=TRACKID_HASH_PERSON
).digest()
def digest_file_uri(file: Path) -> bytes:
return blake2b(
bytes(file), digest_size=PERSISTENT_ID_BYTES, person=FILE_HASH_PERSON
).digest()
# The MPMediaItemPropertyPersistentID is only 64 bits, while a UUID is 128
# bits and not all tracks will even have their MusicBrainz track ID included.
# To work around this, we compute a BLAKE2 hash from the UUID, or failing
# that from the file URI. BLAKE2 can be customised to different digest sizes,
# making it perfect for this problem.
def song_to_persistent_id(song: Song) -> int:
if song.musicbrainz_trackid:
hashed_id = digest_trackid(song.musicbrainz_trackid)
else:
hashed_id = digest_file_uri(song.file)
return int.from_bytes(hashed_id)

View file

@ -24,6 +24,7 @@ MPNowPlayingInfoPropertyMediaType: Final = "MPNowPlayingInfoPropertyMediaType"
MPNowPlayingInfoMediaTypeAudio: Final = 1 MPNowPlayingInfoMediaTypeAudio: Final = 1
MPNowPlayingInfoMediaTypeNone: Final = 0 MPNowPlayingInfoMediaTypeNone: Final = 0
MPNowPlayingInfoPropertyPlaybackRate: Final = "MPNowPlayingInfoPropertyPlaybackRate"
MPNowPlayingInfoPropertyPlaybackQueueCount: Final = ( MPNowPlayingInfoPropertyPlaybackQueueCount: Final = (
"MPNowPlayingInfoPropertyPlaybackQueueCount" "MPNowPlayingInfoPropertyPlaybackQueueCount"
) )