Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I get the title of the currently playing media in windows 10 with python

Whenever audio is playing in windows 10, whether it is from Spotify, Firefox, or a game. When you turn the volume, windows has a thing in the corner that says the song artist, title, and what app is playing like the photo below (sometimes it only says what app is playing sound if a game is playing the sound)

enter image description here

I want to somehow get that data with python. My end goal, is to mute an application if it is playing something I don't like, such as an advertisement.

like image 260
Alexander Avatar asked Nov 25 '20 19:11

Alexander


2 Answers

Turns out this is possible without a workaround and by accessing this info directly using the Windows Runtime API (winrt).

All code shown uses Python 3 and the winrt library installed via pip

Collecting Media/'Now Playing' information

The following code allows for you to collect a dictionary of the media information available to Windows using the winrt wrapper for the Windows Runtime API. It does not rely on a window's title/application name changing as in the other answers here.

import asyncio

from winrt.windows.media.control import \
    GlobalSystemMediaTransportControlsSessionManager as MediaManager


async def get_media_info():
    sessions = await MediaManager.request_async()

    # This source_app_user_model_id check and if statement is optional
    # Use it if you want to only get a certain player/program's media
    # (e.g. only chrome.exe's media not any other program's).

    # To get the ID, use a breakpoint() to run sessions.get_current_session()
    # while the media you want to get is playing.
    # Then set TARGET_ID to the string this call returns.

    current_session = sessions.get_current_session()
    if current_session:  # there needs to be a media session running
        if current_session.source_app_user_model_id == TARGET_ID:
            info = await current_session.try_get_media_properties_async()

            # song_attr[0] != '_' ignores system attributes
            info_dict = {song_attr: info.__getattribute__(song_attr) for song_attr in dir(info) if song_attr[0] != '_'}

            # converts winrt vector to list
            info_dict['genres'] = list(info_dict['genres'])

            return info_dict

    # It could be possible to select a program from a list of current
    # available ones. I just haven't implemented this here for my use case.
    # See references for more information.
    raise Exception('TARGET_PROGRAM is not the current media session')


if __name__ == '__main__':
    current_media_info = asyncio.run(get_media_info())

current_media_info will be a dictionary in the following format and information can then be accessed as required within the program:

{
    'album_artist': str,
    'album_title': str,
    'album_track_count': int, 
    'artist': str,
    'genres': list,
    'playback_type': int,
    'subtitle': str, 
    'thumbnail': 
        <_winrt_Windows_Storage_Streams.IRandomAccessStreamReference object at ?>, 
    'title': str,
    'track_number': int,
}

Controlling Media

As the OP says that their end goal is to control media, this should be possible with the same libraries. See here for more information possibly (I didn't need this in my case):

  • Microsoft WinRT Docs - Windows.Media.Control - GlobalSystemMediaTransportControlsSession class (e.g. await current_session.try_pause_async())

(Getting Media thumbnail)

It is in fact possible to also 'scrape' the album art/media thumbnail (displayed on the right in the OP's screenshot) of the media currently playing (although the OP didn't ask for this but someone might want to do it):

from winrt.windows.storage.streams import \
    DataReader, Buffer, InputStreamOptions


async def read_stream_into_buffer(stream_ref, buffer):
    readable_stream = await stream_ref.open_read_async()
    readable_stream.read_async(buffer, buffer.capacity, InputStreamOptions.READ_AHEAD)


# create the current_media_info dict with the earlier code first
thumb_stream_ref = current_media_info['thumbnail']

# 5MB (5 million byte) buffer - thumbnail unlikely to be larger
thumb_read_buffer = Buffer(5000000)

# copies data from data stream reference into buffer created above
asyncio.run(read_stream_into_buffer(thumb_stream_ref, thumb_read_buffer))

# reads data (as bytes) from buffer
buffer_reader = DataReader.from_buffer(thumb_read_buffer)
byte_buffer = buffer_reader.read_bytes(thumb_read_buffer.length)

with open('media_thumb.jpg', 'wb+') as fobj:
    fobj.write(bytearray(byte_buffer))

This will save a media_thumb.jpg to the current working directory (cwd) which can then be used elsewhere for whatever.

Docs & References:

  • Stackoverflow - The main 'inspiration' for this Python answer; originally in C#
  • Github xlang - Using Windows RT API with xlang and Python
  • Github xlang - Async usage for IAsync* interface methods
  • Microsoft WinRT Docs - Windows.Media.Control - TryGetMediaPropertiesAsync()
  • Microsoft WinRT Docs - Windows.Storage.Streams - IBuffer Interface

Potentially chose from multiple available media streams?

Please note that I haven't tested or tried this and is merely a pointer for anyone who may want to experiment:

  • Microsoft WinRT Docs - Windows.Media.Control - GetSessions() (would be accessed with sessions.get_sessions() above)

As opposed to current use of

  • Microsoft WinRT Docs - Windows.Media.Control - GetCurrentSession() (accessed with sessions.get_current_session() above)
like image 168
tameTNT Avatar answered Oct 16 '22 15:10

tameTNT


I am getting the titles of the windows to get the song information. Usually, the application name is displayed in the title, but when it is playing a song, the song name is shown. Here is a function that returns a list of all the window titles.

from __future__ import print_function
import ctypes
def get_titles(): 
    EnumWindows = ctypes.windll.user32.EnumWindows
    EnumWindowsProc = ctypes.WINFUNCTYPE(ctypes.c_bool, ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_int))
    GetWindowText = ctypes.windll.user32.GetWindowTextW
    GetWindowTextLength = ctypes.windll.user32.GetWindowTextLengthW
    IsWindowVisible = ctypes.windll.user32.IsWindowVisible
    
    titles = []
    def foreach_window(hwnd, lParam):
        if IsWindowVisible(hwnd):
            length = GetWindowTextLength(hwnd)
            buff = ctypes.create_unicode_buffer(length + 1)
            GetWindowText(hwnd, buff, length + 1)
            titles.append(buff.value)
        return True
    EnumWindows(EnumWindowsProc(foreach_window), 0)
    return titles
like image 38
Alexander Avatar answered Oct 16 '22 14:10

Alexander