spytifyd records; the Spytify app and spytify-cli talk to it over a local socket.
spytifyd & # logs the recorded application on its first line
spytify-cli info # versions and the startup checks
spytify-cli set encode.format=flac output.dir=~/Music/Recordings
spytify-cli start # recording starts with the next track
spytify-cli status
spytify-cli tail -f # follow the log
spytify-cli stop # the current track finishes first; --now stops at once
spytify-cli keep # saves the last track cut short, as "<title> (partial)"
spytify-cli |
What it does |
info |
versions, recorded application, startup checks |
start [--single-track] / stop [--now] / status |
the recording session |
set key=value... / get [key...] / reset [prefix] |
settings, validated by the recorder |
tail [-n N] [-f] / events |
log lines / every event as JSON lines |
control play|pause|next|previous|restart |
transport; restart plays the current track again from 0:00 |
launch |
opens the application if it is not running |
keep [id] |
saves the last track cut short (skipped, stopped with --now, or moved along its track bar) as “title (partial)”, until the next track is saved |
service install|uninstall|status |
runs spytifyd at login and again after a crash (Linux, macOS) |
shutdown |
stops the recorder |
spytifyd --once --set output.dir=/srv/rec records one session without a socket, until the application stops or stays idle. spytifyd --fake timeline.json plays a scripted timeline through a built-in fake player, to try everything without an application.
The recorded application is set with environment variables, read when the recorder starts, from the environment or a .env file: next to the binary, in the working folder, or in your configuration folder (~/.config/spytify/.env, ~/Library/Application Support/spytify/.env on macOS, %APPDATA%\spytify\.env on Windows). The app’s Player settings write that last one.
| Variable |
Used for |
Default |
SPYTIFY_TARGET_PROCESS |
the application’s name, or a comma list for several packagings |
the build’s default |
SPYTIFY_TARGET_MPRIS |
its D-Bus name (Linux) |
org.mpris.MediaPlayer2.<name> |
SPYTIFY_TARGET_BUNDLE_ID |
its bundle id (macOS) |
read from the installed app |
SPYTIFY_TARGET_DURATION_UNIT |
ms or s, the unit of its AppleScript track length (macOS) |
guessed |
SPYTIFY_TARGET_LAUNCH |
a command that opens it |
the system’s own way |
SPYTIFY_TAP |
the capture helper spytify-tap (macOS, Windows) |
next to spytifyd, then PATH |
SPYTIFY_CONFIG |
the settings file |
config.toml in the configuration folder |
SPYTIFY_SOCKET |
the recorder’s socket |
in the runtime or state folder |
SPYTIFY_FFMPEG |
the ffmpeg binary |
ffmpeg on PATH, then next to spytifyd and in the usual install folders |
Snap and Flatpak builds of an application can run under another process name, and expose MPRIS only through their sandbox. List every process name in SPYTIFY_TARGET_PROCESS. If spytify-cli info finds the process but no MPRIS name, check the sandbox’s D-Bus permissions (snap connections <snap>, flatpak info --show-permissions <app>).
Everything else is a setting. Clients of their own speak the protocol.
Clients speak newline-delimited JSON-RPC 2.0, protocol version 1, on the recorder’s socket, and on TCP with spytifyd --listen 127.0.0.1:7811. Actions are requests; events are notifications sent to every client. Go clients import github.com/spytify/spytify/pkg/proto; the same contract is in pkg/proto/schema.json.
{"jsonrpc":"2.0","id":7,"method":"settings.set","params":{"values":{"encode.format":"flac"}}}
{"jsonrpc":"2.0","id":7,"result":{"applied":["encode.format"],"rejected":{}}}
{"jsonrpc":"2.0","method":"settings.changed","params":{"values":{"encode.format":"flac"}}}
Actions
| Method |
Params |
Result |
Does |
core.info |
none |
CoreInfoResult |
versions, resolved target and capability checks; first call on connect |
session.start |
SessionStartParams |
SessionStartResult |
fails if the target process is not running or ffmpeg is missing |
session.stop |
SessionStopParams |
SessionStopResult |
lets the current track finish, unless now; a track ended by now can be saved with track.keep |
session.status |
none |
SessionStatusResult |
snapshot for late-joining clients |
settings.get |
SettingsGetParams |
SettingsValues |
every setting when keys is empty |
settings.set |
SettingsSetParams |
SettingsSetResult |
validated, persisted, then settings.changed fires |
settings.reset |
SettingsResetParams |
Empty |
back to defaults, all keys or those under a prefix |
player.control |
PlayerControlParams |
Empty |
play, pause, next, previous, restart (from 0:00) through the player’s local interface |
player.launch |
none |
Empty |
opens the player if it is not running: SPYTIFY_TARGET_LAUNCH, else the OS’s way |
log.tail |
LogTailParams |
LogTailResult |
last N log lines |
events.recent |
none |
EventsRecentResult |
the last 200 events, for a client that connects mid-session |
track.keep |
TrackKeepParams |
Empty |
saves a keepable take after all, as “ (partial)"; fails once it is no longer keepable |
core.shutdown |
none |
Empty |
stops the session first |
Events
Types
CoreInfoResult
| Field |
Type |
version |
string |
protocol |
integer |
target |
TargetInfo |
platform |
string |
checks |
array of Check |
warnings |
array of string, optional |
Empty
An empty object.
ErrorEvent
| Field |
Type |
code |
string |
msg |
string |
fatal |
boolean |
EventsRecentResult
| Field |
Type |
events |
array of Event |
LevelEvent
| Field |
Type |
peak_dbfs |
number |
rms_dbfs |
number |
LogEvent
| Field |
Type |
level |
string |
msg |
string |
ts |
string |
LogTailParams
LogTailResult
PlayerControlParams
SessionStartParams
| Field |
Type |
single_track |
boolean, optional |
SessionStartResult
| Field |
Type |
session_id |
string |
SessionStateEvent
SessionStatusResult
| Field |
Type |
state |
string |
session_id |
string, optional |
track |
Track, optional |
counters |
Counters |
SessionStopParams
| Field |
Type |
now |
boolean, optional |
SessionStopResult
| Field |
Type |
recorded |
integer |
skipped |
integer |
stopping |
boolean, optional |
SettingsGetParams
| Field |
Type |
keys |
array of string, optional |
SettingsResetParams
| Field |
Type |
prefix |
string, optional |
SettingsSetParams
| Field |
Type |
values |
object of any |
SettingsSetResult
| Field |
Type |
applied |
array of string |
rejected |
object of string |
SettingsValues
| Field |
Type |
values |
object of any |
TargetStateEvent
| Field |
Type |
running |
boolean |
pid |
integer, optional |
TrackFinishedEvent
| Field |
Type |
id |
string |
counter |
integer, optional |
path |
string |
duration_ms |
integer |
bytes |
integer |
tagged |
boolean |
tagged_by |
string, optional |
timings |
FinishTimings, optional |
partial |
boolean, optional |
TrackKeepParams
| Field |
Type |
id |
string, optional |
TrackKeepableEvent
| Field |
Type |
id |
string |
keepable |
boolean |
TrackSkippedEvent
| Field |
Type |
id |
string |
reason |
string |
TrackStartedEvent
| Field |
Type |
id |
string |
artist |
string |
title |
string |
album |
string, optional |
length_ms |
integer |
cover_url |
string, optional |
source |
AudioSource, optional |
cut |
TrackCut, optional |
AudioSource
| Field |
Type |
sample_rate |
integer |
channels |
integer |
Check
| Field |
Type |
name |
string |
ok |
boolean |
detail |
string, optional |
Counters
| Field |
Type |
recorded |
integer |
skipped |
integer |
ads |
integer |
bytes |
integer |
overruns |
integer |
Event
| Field |
Type |
method |
string |
params |
any JSON |
FinishTimings
| Field |
Type |
format |
string |
trim_ms |
integer |
cover_ms |
integer |
encode_ms |
integer |
tag_ms |
integer |
total_ms |
integer |
TargetInfo
| Field |
Type |
process |
string |
mpris |
string, optional |
bundle_id |
string, optional |
launch |
string, optional |
Track
| Field |
Type |
id |
string |
artist |
string |
title |
string |
album |
string, optional |
length_ms |
integer |
cover_url |
string, optional |
TrackCut
| Field |
Type |
kind |
string |
by |
string |
offset_ms |
integer |