Sleepytime
A Spotify wind-down routine for macOS. The sleepytime Bash script uses
launchd to start two playlists each night, then fade out and pause playback.
It controls the Spotify desktop app through AppleScript.
Schedule
The current defaults, in the Mac's local time, are:
| Time | Cue | Action |
|---|---|---|
| 00:15 | winddown |
Play the Wind Down playlist with repeat enabled. |
| 00:45 | bedtime |
Play the Bed Time playlist with repeat enabled. |
| 01:00 | fadeout |
Fade Spotify's volume to zero over 60 seconds, then pause. |
The schedule runs daily. Playlist cues open Spotify in the background if needed and replace the current playback. The fade affects Spotify's own volume and restores its previous level after pausing. If Spotify is closed or not playing, the fade cue does nothing.
Cues have a 10-minute grace period after their scheduled time. A delayed cue outside that window is skipped, so waking the Mac much later does not start a missed playlist. The script does not arrange to wake the Mac.
Setup
You need macOS, the Spotify desktop app installed and signed in, and a logged-in graphical user session. The script uses macOS command-line tools; it requires no additional packages or Spotify API credentials.
-
Keep this directory somewhere permanent. The installed jobs store the absolute path to the script.
-
Edit the configuration near the top of sleepytime if you want different playlists, times, or fade settings (see below).
-
From this directory, run a test while you can respond to a macOS permission prompt:
./sleepytime testThis immediately starts the wind-down playlist through
launchd. Allow the request to control Spotify if it appears. Check that playback starts and inspect the log output; the command returning alone does not prove the cue succeeded. -
Enable the nightly schedule:
./sleepytime on ./sleepytime status
Run these commands as your normal user, without sudo.
Configuration
Settings are constants near the top of sleepytime:
| Setting | Default | Purpose |
|---|---|---|
WINDDOWN_PLAYLIST |
spotify:playlist:37i9dQZF1FwRDZZVSbIBqz |
First playlist. |
BEDTIME_PLAYLIST |
spotify:playlist:37i9dQZF1FwPghOHCcNTIB |
Second playlist. |
WINDDOWN_AT |
00:15 |
First playlist's start time. |
BEDTIME_AT |
00:45 |
Second playlist's start time. |
FADEOUT_AT |
01:00 |
Fade start time. |
FADE_SECONDS |
60 |
Fade duration in seconds. |
GRACE_MINUTES |
10 |
How late a scheduled cue may run. |
Use 24-hour HH:MM times. These settings are edited in the script, rather than
passed as command-line options or environment variables. Rerun ./sleepytime on
after changing schedule times or moving the script to update the installed jobs.
Getting playlist URIs
If you already have playlists in mind, you can right-click on the playlist, go
to "Share", and select "Copy link to playlist". Then take the ID after
/playlist/, discard any query string beginning with ?, and prefix the ID
with spotify:playlist:.
For example:
https://open.spotify.com/playlist/37i9dQZF1FwRDZZVSbIBqz?si=example
becomes
spotify:playlist:37i9dQZF1FwRDZZVSbIBqz
Paste that URI into WINDDOWN_PLAYLIST or BEDTIME_PLAYLIST, retaining the
quotes.
Auto-creating playlists
If you would like fresh playlists generated every week, do the following:
- Go to your library in Spotify and click "+ Create"
- Select "Prompted Playlist"
- Use the "Set updates" button at the bottom to specify how often you would like the list regenerated (e.g. weekly on Mondays)
- Add a prompt to indicate the sort of playlist you want generated.
- Click "Generate playlist"
The prompts used for this script are:
Late Night Wind Down
Mellow, warm music for the start of a late-night wind-down routine. Draw on artists and styles I already listen to, but only their calmest, slowest songs. Downtempo, soft acoustic, gentle electronic, slow jazz. Low energy, no loud choruses, no sudden drops or big builds, consistent volume from track to track. Vocals are fine if they're soft. Should feel familiar and comforting, like the evening is ending.
Pre-Sleep Minimal Ambient
Very quiet, slow ambient music for the time between finishing winding down and getting into bed. Mostly instrumental, no drums or percussion, no vocals, nothing with a strong beat or melody that grabs attention. Soft pads, gentle piano, slow drones, sparse textures. Noticeably calmer and emptier than a normal chill playlist. Consistent low volume throughout.
Commands
Run these from this directory:
| Command | Effect |
|---|---|
./sleepytime on |
Write and load all three scheduled jobs, replacing previous definitions. |
./sleepytime off |
Unload and remove all three scheduled jobs. |
./sleepytime status |
Show whether each scheduled job is loaded and print the log path. |
./sleepytime test [CUE] |
Run a cue immediately through a temporary launchd job; defaults to winddown. |
./sleepytime cue CUE |
Run a cue directly, subject to its scheduled time and grace period. |
./sleepytime cue CUE --force |
Run a cue directly regardless of the current time. |
CUE must be winddown, bedtime, or fadeout. Both test and forced cues
perform the real playback action. For example, ./sleepytime test fadeout fades
and pauses whatever Spotify is currently playing.
off disables future scheduled cues; it does not pause Spotify or delete the
log. Playlist cues enable repeat and do not restore the previous repeat setting.
Logs and troubleshooting
All scheduled and test cues write standard output and errors to:
~/Library/Logs/sleepytime.log
After the first test or scheduled run, follow the log with:
tail -f ~/Library/Logs/sleepytime.log
The three job files live at
~/Library/LaunchAgents/local.sleepytime.{winddown,bedtime,fadeout}.plist.
- If playback fails, open Spotify, confirm it is signed in and can play the
configured playlist, then rerun
./sleepytime test winddown. Check the log for AppleScript errors and allow macOS permission to control Spotify. - If a cue is logged as
skipped, it ran outside its grace period. Usetestto try it immediately. - If a job is off, or runs at an old time after editing the script, rerun
./sleepytime on.statusreports whether jobs are loaded and displays the script's configured times; it does not verify installed times or playback.