# python spotify grabber # requirements: pygetwindow from pip and fmedia in $path import pygetwindow import re import time import subprocess import threading import logging import string import unicodedata import sys import os # configuration logging.basicConfig( level=logging.DEBUG, format='%(asctime)s %(levelname)-8s %(message)s' ) rec_path = os.path.join('.', 'rec') fmedia_path = os.path.join('fmedia', 'fmedia.exe') window_monitor_interval = .2 window_regex = '^Spotify .+' metadata_regex = '^Spotify - (?P.+) ยท (?P<artist>.+)' # end configuration valid_filename_chars = "-_.() %s%s" % (string.ascii_letters, string.digits) counter = 0 is_recording = False def get_default_device(): device_list = subprocess.run([ fmedia_path, '--list-dev' ], capture_output=True ) playback_devices = device_list.stdout.decode('utf-8') playback_devices = playback_devices.split('Loopback:')[1].split('Capture:')[0] playback_devices = playback_devices.split('\n') for device in playback_devices: if re.search('- Default$', device): logging.debug('found default output device: {device}'.format(device=device)) device_number = re.match('^device #([0-9]+)', device).group(1) break return device_number def clean_filename(filename, whitelist=valid_filename_chars): # keep only valid ascii chars cleaned_filename = unicodedata.normalize('NFKD', filename).encode('ASCII', 'ignore').decode() # keep only whitelisted chars cleaned_filename = ''.join(c for c in cleaned_filename if c in whitelist) return cleaned_filename def start_rec(artist: str, title: str): def rec_subprocess(): filename = os.path.join( rec_path, f'{counter:03d}. {clean_filename(artist)} - {clean_filename(title)}.mp3' ) logging.debug(f'recording to {filename}') command = [ fmedia_path, '--record', f'--dev-loopback={loopback_dev}', f'--out={filename}', '--mpeg-quality=2', f'--meta=artist={artist};title={title};tracknumber={counter}', '--globcmd=listen' ] logging.debug(f'starting recording subprocess: {" ".join(command)}') subprocess.run(command, check=True) global counter global is_recording counter += 1 is_recording = True logging.debug('starting subprocess') thread = threading.Thread(target=rec_subprocess) thread.start() def stop_rec(): subprocess.run([fmedia_path, '--globcmd=stop']) global is_recording is_recording = False logging.debug('stopped recording') def watch_window(): old_title = '' logging.debug('started window watcher') while True: win_titles = pygetwindow.getAllTitles() for win_title in win_titles: if re.search(window_regex, win_title): if win_title != old_title: logging.debug(f'window title changed to {win_title}') if is_recording: stop_rec() # if changed title matches the metadata regex it is assumed there is a song playing if re.match(metadata_regex, win_title): # read metadata from window title metadata = re.match(metadata_regex, win_title) title = metadata.group('title') artist = metadata.group('artist') logging.debug(f'got metadata ({title}) by ({artist})') logging.debug('start recording now') start_rec(title=title, artist=artist) old_title = win_title time.sleep(window_monitor_interval) try: loopback_dev = get_default_device() number_input = input('enter number to start counting filenames from (or press enter for 1): ') if number_input != '': try: counter = int(number_input) - 1 except ValueError: logging.warning(f'input "{number_input}" is a invalid number input') watch_window() except KeyboardInterrupt: logging.info('got CTRL+C - trying to clean up my mess') if is_recording: stop_rec() logging.info('Bye Bye') sys.exit(0)