2022-01-27 21:57:08 +01:00
|
|
|
# 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
|
2022-01-30 02:43:26 +01:00
|
|
|
import os
|
2022-01-27 21:57:08 +01:00
|
|
|
|
|
|
|
# configuration
|
|
|
|
logging.basicConfig(
|
|
|
|
level=logging.DEBUG,
|
|
|
|
format='%(asctime)s %(levelname)-8s %(message)s'
|
|
|
|
)
|
2022-01-30 02:43:26 +01:00
|
|
|
|
|
|
|
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<title>.+) · (?P<artist>.+)'
|
|
|
|
|
2022-01-27 21:57:08 +01:00
|
|
|
# end configuration
|
|
|
|
|
|
|
|
valid_filename_chars = "-_.() %s%s" % (string.ascii_letters, string.digits)
|
2022-01-30 02:43:26 +01:00
|
|
|
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
|
|
|
|
|
2022-01-27 21:57:08 +01:00
|
|
|
|
|
|
|
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
|
|
|
|
|
2022-01-30 02:43:26 +01:00
|
|
|
|
|
|
|
def start_rec(artist: str, title: str):
|
2022-01-27 21:57:08 +01:00
|
|
|
def rec_subprocess():
|
2022-01-30 02:43:26 +01:00
|
|
|
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)}')
|
|
|
|
|
2022-01-30 11:57:46 +01:00
|
|
|
subprocess.run(command, check=True)
|
2022-01-27 21:57:08 +01:00
|
|
|
|
|
|
|
global counter
|
2022-01-30 02:43:26 +01:00
|
|
|
global is_recording
|
2022-01-27 21:57:08 +01:00
|
|
|
counter += 1
|
2022-01-30 02:43:26 +01:00
|
|
|
is_recording = True
|
|
|
|
|
|
|
|
logging.debug('starting subprocess')
|
2022-01-27 21:57:08 +01:00
|
|
|
thread = threading.Thread(target=rec_subprocess)
|
|
|
|
thread.start()
|
|
|
|
|
2022-01-30 02:43:26 +01:00
|
|
|
|
2022-01-27 21:57:08 +01:00
|
|
|
def stop_rec():
|
2022-01-30 02:43:26 +01:00
|
|
|
subprocess.run([fmedia_path, '--globcmd=stop'])
|
|
|
|
global is_recording
|
|
|
|
is_recording = False
|
2022-01-27 21:57:08 +01:00
|
|
|
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:
|
2022-01-30 02:43:26 +01:00
|
|
|
if re.search(window_regex, win_title):
|
2022-01-27 21:57:08 +01:00
|
|
|
if win_title != old_title:
|
2022-01-30 02:43:26 +01:00
|
|
|
logging.debug(f'window title changed to {win_title}')
|
2022-01-27 21:57:08 +01:00
|
|
|
|
2022-01-30 02:43:26 +01:00
|
|
|
if is_recording:
|
2022-01-27 21:57:08 +01:00
|
|
|
stop_rec()
|
2022-01-30 02:43:26 +01:00
|
|
|
|
|
|
|
# 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)
|
|
|
|
|
2022-01-27 21:57:08 +01:00
|
|
|
old_title = win_title
|
|
|
|
|
2022-01-30 02:43:26 +01:00
|
|
|
time.sleep(window_monitor_interval)
|
|
|
|
|
|
|
|
|
2022-01-27 21:57:08 +01:00
|
|
|
try:
|
2022-01-30 02:43:26 +01:00
|
|
|
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')
|
|
|
|
|
2022-01-27 21:57:08 +01:00
|
|
|
watch_window()
|
2022-01-30 02:43:26 +01:00
|
|
|
|
2022-01-27 21:57:08 +01:00
|
|
|
except KeyboardInterrupt:
|
|
|
|
logging.info('got CTRL+C - trying to clean up my mess')
|
2022-01-30 02:43:26 +01:00
|
|
|
if is_recording:
|
|
|
|
stop_rec()
|
2022-01-27 21:57:08 +01:00
|
|
|
logging.info('Bye Bye')
|
|
|
|
sys.exit(0)
|