cloudspin 0.1.0__tar.gz → 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. cloudspin-0.2.0/.github/workflows/ci.yml +52 -0
  2. {cloudspin-0.1.0 → cloudspin-0.2.0}/PKG-INFO +76 -42
  3. {cloudspin-0.1.0 → cloudspin-0.2.0}/README.md +73 -41
  4. {cloudspin-0.1.0 → cloudspin-0.2.0}/packaging/aur/PKGBUILD +3 -4
  5. {cloudspin-0.1.0 → cloudspin-0.2.0}/packaging/npm/package.json +1 -1
  6. {cloudspin-0.1.0 → cloudspin-0.2.0}/pyproject.toml +2 -0
  7. {cloudspin-0.1.0 → cloudspin-0.2.0}/src/cloudspin/__init__.py +1 -1
  8. {cloudspin-0.1.0 → cloudspin-0.2.0}/src/cloudspin/app.py +89 -15
  9. {cloudspin-0.1.0 → cloudspin-0.2.0}/src/cloudspin/cli.py +62 -14
  10. {cloudspin-0.1.0 → cloudspin-0.2.0}/src/cloudspin/config.py +4 -0
  11. cloudspin-0.2.0/src/cloudspin/links.py +166 -0
  12. {cloudspin-0.1.0 → cloudspin-0.2.0}/src/cloudspin/mood.py +91 -34
  13. cloudspin-0.2.0/src/cloudspin/mpvget.py +119 -0
  14. {cloudspin-0.1.0 → cloudspin-0.2.0}/src/cloudspin/player.py +87 -14
  15. {cloudspin-0.1.0 → cloudspin-0.2.0}/src/cloudspin/screens.py +2 -2
  16. {cloudspin-0.1.0 → cloudspin-0.2.0}/src/cloudspin/widgets.py +3 -2
  17. cloudspin-0.2.0/tests/conftest.py +13 -0
  18. cloudspin-0.2.0/tests/test_links.py +75 -0
  19. cloudspin-0.2.0/tests/test_network.py +40 -0
  20. {cloudspin-0.1.0 → cloudspin-0.2.0}/tests/test_player.py +2 -2
  21. cloudspin-0.2.0/tests/test_real_mpv.py +113 -0
  22. cloudspin-0.1.0/.github/workflows/ci.yml +0 -23
  23. cloudspin-0.1.0/tests/conftest.py +0 -6
  24. {cloudspin-0.1.0 → cloudspin-0.2.0}/.github/workflows/release.yml +0 -0
  25. {cloudspin-0.1.0 → cloudspin-0.2.0}/.gitignore +0 -0
  26. {cloudspin-0.1.0 → cloudspin-0.2.0}/LICENSE +0 -0
  27. {cloudspin-0.1.0 → cloudspin-0.2.0}/docs/evening.png +0 -0
  28. {cloudspin-0.1.0 → cloudspin-0.2.0}/docs/night.png +0 -0
  29. {cloudspin-0.1.0 → cloudspin-0.2.0}/docs/storm.png +0 -0
  30. {cloudspin-0.1.0 → cloudspin-0.2.0}/packaging/npm/README.md +0 -0
  31. {cloudspin-0.1.0 → cloudspin-0.2.0}/packaging/npm/bin/cloudspin.js +0 -0
  32. {cloudspin-0.1.0 → cloudspin-0.2.0}/scripts/screenshot.py +0 -0
  33. {cloudspin-0.1.0 → cloudspin-0.2.0}/src/cloudspin/__main__.py +0 -0
  34. {cloudspin-0.1.0 → cloudspin-0.2.0}/src/cloudspin/canvas.py +0 -0
  35. {cloudspin-0.1.0 → cloudspin-0.2.0}/src/cloudspin/color.py +0 -0
  36. {cloudspin-0.1.0 → cloudspin-0.2.0}/src/cloudspin/library.py +0 -0
  37. {cloudspin-0.1.0 → cloudspin-0.2.0}/src/cloudspin/sky.py +0 -0
  38. {cloudspin-0.1.0 → cloudspin-0.2.0}/src/cloudspin/songlook.py +0 -0
  39. {cloudspin-0.1.0 → cloudspin-0.2.0}/src/cloudspin/spectrum.py +0 -0
  40. {cloudspin-0.1.0 → cloudspin-0.2.0}/src/cloudspin/themes.py +0 -0
  41. {cloudspin-0.1.0 → cloudspin-0.2.0}/src/cloudspin/vinyl.py +0 -0
  42. {cloudspin-0.1.0 → cloudspin-0.2.0}/src/cloudspin/weather.py +0 -0
  43. {cloudspin-0.1.0 → cloudspin-0.2.0}/tests/fake_mpv.py +0 -0
  44. {cloudspin-0.1.0 → cloudspin-0.2.0}/tests/fixtures/forecast.json +0 -0
  45. {cloudspin-0.1.0 → cloudspin-0.2.0}/tests/synth.py +0 -0
  46. {cloudspin-0.1.0 → cloudspin-0.2.0}/tests/test_app.py +0 -0
  47. {cloudspin-0.1.0 → cloudspin-0.2.0}/tests/test_mood.py +0 -0
  48. {cloudspin-0.1.0 → cloudspin-0.2.0}/tests/test_sky.py +0 -0
  49. {cloudspin-0.1.0 → cloudspin-0.2.0}/tests/test_weather.py +0 -0
@@ -0,0 +1,52 @@
1
+ name: Tests
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ${{ matrix.os }}
11
+ strategy:
12
+ fail-fast: false
13
+ matrix:
14
+ os: [ubuntu-latest]
15
+ python: ["3.11", "3.12", "3.13"]
16
+ include:
17
+ - os: windows-latest
18
+ python: "3.12"
19
+ - os: macos-latest
20
+ python: "3.12"
21
+ env:
22
+ # On Windows the tests download mpv the same way a first run does.
23
+ CLOUDSPIN_TEST_DOWNLOAD: "1"
24
+ PYTHONUTF8: "1"
25
+ steps:
26
+ - uses: actions/checkout@v4
27
+ - uses: actions/setup-python@v5
28
+ with:
29
+ python-version: ${{ matrix.python }}
30
+ - name: Install mpv and ffmpeg (Linux)
31
+ if: runner.os == 'Linux'
32
+ run: sudo apt-get update && sudo apt-get install -y --no-install-recommends mpv ffmpeg
33
+ - name: Install mpv and ffmpeg (macOS)
34
+ if: runner.os == 'macOS'
35
+ run: brew install mpv ffmpeg
36
+ - run: pip install -e ".[dev]"
37
+ - run: pytest -q -rs
38
+
39
+ network:
40
+ # Real Spotify and YouTube. Informational: their servers can refuse CI machines.
41
+ runs-on: ubuntu-latest
42
+ continue-on-error: true
43
+ steps:
44
+ - uses: actions/checkout@v4
45
+ - uses: actions/setup-python@v5
46
+ with:
47
+ python-version: "3.12"
48
+ - run: sudo apt-get update && sudo apt-get install -y --no-install-recommends mpv
49
+ - run: pip install -e ".[dev]"
50
+ - run: pytest -q -rs -s tests/test_network.py
51
+ env:
52
+ CLOUDSPIN_TEST_NETWORK: "1"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: cloudspin
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: A terminal music player that lives under a living sky: spinning vinyl, real weather, animated scenes.
5
5
  Project-URL: Homepage, https://github.com/itsHarshDabas/Cloudspin
6
6
  Project-URL: Issues, https://github.com/itsHarshDabas/Cloudspin/issues
@@ -12,6 +12,7 @@ Classifier: Development Status :: 3 - Alpha
12
12
  Classifier: Environment :: Console
13
13
  Classifier: Intended Audience :: End Users/Desktop
14
14
  Classifier: Operating System :: MacOS
15
+ Classifier: Operating System :: Microsoft :: Windows
15
16
  Classifier: Operating System :: POSIX :: Linux
16
17
  Classifier: Programming Language :: Python :: 3
17
18
  Classifier: Programming Language :: Python :: 3.11
@@ -22,6 +23,7 @@ Requires-Python: >=3.11
22
23
  Requires-Dist: mutagen>=1.47
23
24
  Requires-Dist: pillow>=10.0
24
25
  Requires-Dist: textual>=8.0
26
+ Requires-Dist: yt-dlp
25
27
  Provides-Extra: dev
26
28
  Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
27
29
  Requires-Dist: pytest>=8; extra == 'dev'
@@ -43,7 +45,8 @@ synthwave turns the stars on.
43
45
  ## What it does
44
46
 
45
47
  - Plays local files, folders, radio streams and links through `mpv`.
46
- - Paste a link while it's running (press `o`) and it starts playing.
48
+ - Opens YouTube, YouTube Music and Spotify playlists as a queue.
49
+ - Paste a link or a playlist while it's running (press `o`) and it starts playing.
47
50
  - Shows live weather and a six-day forecast from [Open-Meteo](https://open-meteo.com) (free, no API key).
48
51
  - Draws an animated sky: sun, moon with the real phase, stars, drifting clouds, rain, snow, fog and lightning.
49
52
  - Picks the sky to suit the song, or follows the real weather, or stays on a scene you pin.
@@ -53,26 +56,28 @@ synthwave turns the stars on.
53
56
 
54
57
  ## Install
55
58
 
56
- Cloudspin is not on PyPI, the AUR or npm yet. Until it is, install it from this repository:
59
+ Pick whichever you already have:
57
60
 
58
61
  ```sh
59
- pipx install git+https://github.com/itsHarshDabas/Cloudspin
60
- # or
61
- uv tool install git+https://github.com/itsHarshDabas/Cloudspin
62
+ pipx install cloudspin # Python
63
+ uv tool install cloudspin # uv
64
+ npm install -g cloudspin # npm (a launcher; it runs the Python app through uv or pipx)
62
65
  ```
63
66
 
64
- You also need `mpv` for sound. Two more tools are optional:
67
+ Or try it without installing: `uvx cloudspin --demo` or `npx cloudspin --demo`.
65
68
 
66
- | Tool | Why |
67
- | --- | --- |
68
- | `mpv` | Plays the music. Without it Cloudspin runs silently. |
69
- | `ffmpeg` | Lets Cloudspin listen to songs that have no genre tag, to guess a sky. |
70
- | `yt-dlp` | Lets mpv play YouTube, SoundCloud and similar links. |
69
+ Works on Linux, macOS and Windows, with Python 3.11 or newer and a truecolor
70
+ terminal (Windows Terminal, kitty, foot, Alacritty, WezTerm, Ghostty, iTerm2
71
+ and most others).
72
+
73
+ ### What comes with it
71
74
 
72
- On Arch: `sudo pacman -S mpv ffmpeg yt-dlp`.
75
+ | Tool | What it's for | How you get it |
76
+ | --- | --- | --- |
77
+ | `yt-dlp` | YouTube, SoundCloud and similar links | Installed with Cloudspin |
78
+ | `mpv` | Plays the sound | **Windows:** downloaded automatically the first time you start Cloudspin (about 30 MB, kept in its cache folder). **Linux and macOS:** from your package manager, e.g. `sudo pacman -S mpv`, `sudo apt install mpv`, `brew install mpv` |
73
79
 
74
- Needs Python 3.11 or newer, Linux or macOS, and a terminal with truecolor
75
- (kitty, foot, Alacritty, WezTerm, Ghostty, iTerm2 and most others).
80
+ Without mpv Cloudspin still runs, silently, and tells you the command to install it.
76
81
 
77
82
  ## Use
78
83
 
@@ -81,10 +86,14 @@ cloudspin # plays ~/Music
81
86
  cloudspin ~/Music/phonk # a folder
82
87
  cloudspin song.flac other.mp3 # files
83
88
  cloudspin https://example.com/stream.mp3 # a link
89
+ cloudspin "https://music.youtube.com/playlist?list=..." # a YouTube Music playlist
90
+ cloudspin "https://open.spotify.com/playlist/..." # a Spotify playlist
84
91
  cloudspin --city "New Delhi" # set the city once; it is remembered
85
92
  cloudspin --demo # look around without any music or sound
86
93
  ```
87
94
 
95
+ No music folder? Start `cloudspin`, press `o` and paste a link.
96
+
88
97
  ### Keys
89
98
 
90
99
  | Key | Does |
@@ -103,24 +112,43 @@ cloudspin --demo # look around without any music or sound
103
112
  | `?` | Show the keys |
104
113
  | `q` | Quit |
105
114
 
115
+ ## Playlists
116
+
117
+ Paste a playlist link with `o`, pass it on the command line, or set it as
118
+ `music` in the settings so Cloudspin opens with it every time.
119
+
120
+ - **YouTube, YouTube Music, SoundCloud** and other sites yt-dlp knows play
121
+ straight from the site.
122
+ - **Spotify** works differently. Spotify's audio only plays inside its own
123
+ apps, so Cloudspin reads the list of songs from the public playlist page and
124
+ plays each one from the closest match on YouTube. Usually that is the same
125
+ recording; now and then it is a live or alternate version. The playlist has
126
+ to be public, and long ones are read up to the first 50 to 100 songs.
127
+
106
128
  ## How the sky picks a scene
107
129
 
108
- With the sky set to follow the song (the default), Cloudspin looks for a
109
- scene in this order and uses the first one it finds:
110
-
111
- 1. **Your rules.** Words you map to scenes in the config file.
112
- 2. **The tags.** The genre tag, then sky words in the title or album.
113
- `phonk`, `metal` and `dubstep` storm; `lofi`, `jazz` and `acoustic` get the
114
- evening; `synthwave` and `r&b` get the night; `blues` rains; `dream pop`
115
- fogs over; `christmas` snows; `pop` and `dance` get a sunny day. A song
116
- called "Midnight Rain" needs no tag at all.
117
- 3. **A short listen.** If `ffmpeg` is installed, 30 seconds of the song are
118
- measured for loudness, tempo, bass and brightness. Loud, fast and
119
- bass-heavy becomes a storm; quiet and slow becomes night.
130
+ With the sky set to follow the song (the default), Cloudspin decides like this:
131
+
132
+ 1. **Your rules.** Words you map to scenes in the config file always win.
133
+ 2. **How the song sounds.** Thirty seconds are decoded in the background and
134
+ measured for rhythm (attacks per second, tempo), brightness, bass weight
135
+ and loudness. Busy, bright and bass-heavy becomes a storm; slow and soft
136
+ becomes evening or night. This works for links as well as files, and each
137
+ song is only measured once.
138
+ 3. **The words**, as a first guess while the listening happens and as the
139
+ answer when listening isn't possible: the genre tag, then sky words in the
140
+ title or album. `phonk`, `hoodtrap` and `metal` storm; `lofi` and `jazz`
141
+ get the evening; `synthwave` gets the night; "Midnight Rain" needs no tag.
120
142
  4. **The real weather**, if nothing above says anything.
121
143
 
122
144
  The caption in the top right corner of the sky always says which scene is
123
- showing and why, for example `Thunderstorm, tagged Drift phonk`.
145
+ showing and why, for example `Thunderstorm, hard-hitting and heavy on bass`.
146
+
147
+ To see what Cloudspin hears in a song, and report one it gets wrong:
148
+
149
+ ```sh
150
+ cloudspin --listen "song.mp3"
151
+ ```
124
152
 
125
153
  The scenes are `storm`, `rain`, `evening`, `night`, `day`, `dawn`, `snow`,
126
154
  `fog` and `overcast`.
@@ -134,9 +162,10 @@ city = "New Delhi"
134
162
  units = "metric" # or "imperial"
135
163
  sky = "song" # "song", "weather", or a scene name to pin
136
164
  colors = "sky" # "sky", "cover", a built-in theme, or one of yours
137
- music = "~/Music"
165
+ music = "~/Music" # a folder, or a playlist link
138
166
  volume = 70
139
167
  fps = 24 # lower this on a slow terminal or over SSH
168
+ listen = true # false: choose the sky from tags and titles only
140
169
 
141
170
  # Send particular artists, albums or words to a scene.
142
171
  [moods]
@@ -158,26 +187,29 @@ Built-in themes: `sky`, `cover`, `amber`, `orchid`, `lagoon`, `moss`, `rose`, `m
158
187
 
159
188
  - **The bars are generative.** The spectrum moves with the play state, the
160
189
  volume and a tempo chosen per song. It is not a live analysis of the audio.
161
- - **The listen is a guess.** Step 3 above is a heuristic that has only been
162
- checked against synthetic test audio. Tags and your own rules are exact;
163
- use `[moods]` when it gets a song wrong.
164
- - **Links need mpv.** Plain audio URLs and radio streams work with mpv alone.
165
- Sites like YouTube also need `yt-dlp`.
166
- - **Windows is not supported** except through WSL: playback control uses a
167
- Unix socket.
168
- - **MPRIS** (media keys, `playerctl`, Waybar) works if you have the
190
+ - **The listening is a rule of thumb.** It has been checked against synthetic
191
+ test audio, not a real music library, so expect some wrong skies. Use
192
+ `[moods]` to pin an artist or word to a scene, and `cloudspin --listen` to
193
+ see the numbers behind a choice.
194
+ - **Links** play through mpv, with the bundled yt-dlp handling sites like
195
+ YouTube. Sites change often; if a link stops working, update with
196
+ `pipx upgrade cloudspin` (or `uv tool upgrade cloudspin`) to get a newer yt-dlp.
197
+ - **Windows** uses the mpv that Cloudspin downloads for itself unless `mpv` is
198
+ already on your PATH.
199
+ - **MPRIS** (media keys, `playerctl`, Waybar) works on Linux if you have the
169
200
  `mpv-mpris` plugin installed for mpv; Cloudspin loads your normal mpv config.
170
201
 
171
- ## Packaging status
202
+ ## Releases
172
203
 
173
204
  | Channel | Status |
174
205
  | --- | --- |
175
- | Git (`pipx`, `uv`) | Works today |
176
- | PyPI and npm | Published by `.github/workflows/release.yml` when a `v*` tag is pushed. Setup steps are at the top of that file. |
177
- | AUR | `packaging/aur/PKGBUILD` is written; it builds from the PyPI release |
206
+ | [PyPI](https://pypi.org/project/cloudspin/) | Published |
207
+ | [npm](https://www.npmjs.com/package/cloudspin) | Published (launcher for the Python app) |
208
+ | AUR | `packaging/aur/PKGBUILD` is written, not submitted yet |
178
209
  | Homebrew | Not started |
179
210
 
180
- The npm package is a small launcher that runs the Python app through `uv` or `pipx`.
211
+ Pushing a `v*` tag runs `.github/workflows/release.yml`, which tests, then
212
+ publishes to PyPI and npm.
181
213
 
182
214
  ## Develop
183
215
 
@@ -199,6 +231,8 @@ The code is small and split by job:
199
231
  | `mood.py` | Choosing a scene for a song |
200
232
  | `weather.py` | Open-Meteo and sun and moon maths |
201
233
  | `player.py` | mpv control and the queue |
234
+ | `links.py` | Reading YouTube and Spotify playlists |
235
+ | `mpvget.py` | Finding mpv and yt-dlp; the Windows mpv download |
202
236
  | `widgets.py`, `app.py` | The interface |
203
237
 
204
238
  ## Licence
@@ -14,7 +14,8 @@ synthwave turns the stars on.
14
14
  ## What it does
15
15
 
16
16
  - Plays local files, folders, radio streams and links through `mpv`.
17
- - Paste a link while it's running (press `o`) and it starts playing.
17
+ - Opens YouTube, YouTube Music and Spotify playlists as a queue.
18
+ - Paste a link or a playlist while it's running (press `o`) and it starts playing.
18
19
  - Shows live weather and a six-day forecast from [Open-Meteo](https://open-meteo.com) (free, no API key).
19
20
  - Draws an animated sky: sun, moon with the real phase, stars, drifting clouds, rain, snow, fog and lightning.
20
21
  - Picks the sky to suit the song, or follows the real weather, or stays on a scene you pin.
@@ -24,26 +25,28 @@ synthwave turns the stars on.
24
25
 
25
26
  ## Install
26
27
 
27
- Cloudspin is not on PyPI, the AUR or npm yet. Until it is, install it from this repository:
28
+ Pick whichever you already have:
28
29
 
29
30
  ```sh
30
- pipx install git+https://github.com/itsHarshDabas/Cloudspin
31
- # or
32
- uv tool install git+https://github.com/itsHarshDabas/Cloudspin
31
+ pipx install cloudspin # Python
32
+ uv tool install cloudspin # uv
33
+ npm install -g cloudspin # npm (a launcher; it runs the Python app through uv or pipx)
33
34
  ```
34
35
 
35
- You also need `mpv` for sound. Two more tools are optional:
36
+ Or try it without installing: `uvx cloudspin --demo` or `npx cloudspin --demo`.
36
37
 
37
- | Tool | Why |
38
- | --- | --- |
39
- | `mpv` | Plays the music. Without it Cloudspin runs silently. |
40
- | `ffmpeg` | Lets Cloudspin listen to songs that have no genre tag, to guess a sky. |
41
- | `yt-dlp` | Lets mpv play YouTube, SoundCloud and similar links. |
38
+ Works on Linux, macOS and Windows, with Python 3.11 or newer and a truecolor
39
+ terminal (Windows Terminal, kitty, foot, Alacritty, WezTerm, Ghostty, iTerm2
40
+ and most others).
41
+
42
+ ### What comes with it
42
43
 
43
- On Arch: `sudo pacman -S mpv ffmpeg yt-dlp`.
44
+ | Tool | What it's for | How you get it |
45
+ | --- | --- | --- |
46
+ | `yt-dlp` | YouTube, SoundCloud and similar links | Installed with Cloudspin |
47
+ | `mpv` | Plays the sound | **Windows:** downloaded automatically the first time you start Cloudspin (about 30 MB, kept in its cache folder). **Linux and macOS:** from your package manager, e.g. `sudo pacman -S mpv`, `sudo apt install mpv`, `brew install mpv` |
44
48
 
45
- Needs Python 3.11 or newer, Linux or macOS, and a terminal with truecolor
46
- (kitty, foot, Alacritty, WezTerm, Ghostty, iTerm2 and most others).
49
+ Without mpv Cloudspin still runs, silently, and tells you the command to install it.
47
50
 
48
51
  ## Use
49
52
 
@@ -52,10 +55,14 @@ cloudspin # plays ~/Music
52
55
  cloudspin ~/Music/phonk # a folder
53
56
  cloudspin song.flac other.mp3 # files
54
57
  cloudspin https://example.com/stream.mp3 # a link
58
+ cloudspin "https://music.youtube.com/playlist?list=..." # a YouTube Music playlist
59
+ cloudspin "https://open.spotify.com/playlist/..." # a Spotify playlist
55
60
  cloudspin --city "New Delhi" # set the city once; it is remembered
56
61
  cloudspin --demo # look around without any music or sound
57
62
  ```
58
63
 
64
+ No music folder? Start `cloudspin`, press `o` and paste a link.
65
+
59
66
  ### Keys
60
67
 
61
68
  | Key | Does |
@@ -74,24 +81,43 @@ cloudspin --demo # look around without any music or sound
74
81
  | `?` | Show the keys |
75
82
  | `q` | Quit |
76
83
 
84
+ ## Playlists
85
+
86
+ Paste a playlist link with `o`, pass it on the command line, or set it as
87
+ `music` in the settings so Cloudspin opens with it every time.
88
+
89
+ - **YouTube, YouTube Music, SoundCloud** and other sites yt-dlp knows play
90
+ straight from the site.
91
+ - **Spotify** works differently. Spotify's audio only plays inside its own
92
+ apps, so Cloudspin reads the list of songs from the public playlist page and
93
+ plays each one from the closest match on YouTube. Usually that is the same
94
+ recording; now and then it is a live or alternate version. The playlist has
95
+ to be public, and long ones are read up to the first 50 to 100 songs.
96
+
77
97
  ## How the sky picks a scene
78
98
 
79
- With the sky set to follow the song (the default), Cloudspin looks for a
80
- scene in this order and uses the first one it finds:
81
-
82
- 1. **Your rules.** Words you map to scenes in the config file.
83
- 2. **The tags.** The genre tag, then sky words in the title or album.
84
- `phonk`, `metal` and `dubstep` storm; `lofi`, `jazz` and `acoustic` get the
85
- evening; `synthwave` and `r&b` get the night; `blues` rains; `dream pop`
86
- fogs over; `christmas` snows; `pop` and `dance` get a sunny day. A song
87
- called "Midnight Rain" needs no tag at all.
88
- 3. **A short listen.** If `ffmpeg` is installed, 30 seconds of the song are
89
- measured for loudness, tempo, bass and brightness. Loud, fast and
90
- bass-heavy becomes a storm; quiet and slow becomes night.
99
+ With the sky set to follow the song (the default), Cloudspin decides like this:
100
+
101
+ 1. **Your rules.** Words you map to scenes in the config file always win.
102
+ 2. **How the song sounds.** Thirty seconds are decoded in the background and
103
+ measured for rhythm (attacks per second, tempo), brightness, bass weight
104
+ and loudness. Busy, bright and bass-heavy becomes a storm; slow and soft
105
+ becomes evening or night. This works for links as well as files, and each
106
+ song is only measured once.
107
+ 3. **The words**, as a first guess while the listening happens and as the
108
+ answer when listening isn't possible: the genre tag, then sky words in the
109
+ title or album. `phonk`, `hoodtrap` and `metal` storm; `lofi` and `jazz`
110
+ get the evening; `synthwave` gets the night; "Midnight Rain" needs no tag.
91
111
  4. **The real weather**, if nothing above says anything.
92
112
 
93
113
  The caption in the top right corner of the sky always says which scene is
94
- showing and why, for example `Thunderstorm, tagged Drift phonk`.
114
+ showing and why, for example `Thunderstorm, hard-hitting and heavy on bass`.
115
+
116
+ To see what Cloudspin hears in a song, and report one it gets wrong:
117
+
118
+ ```sh
119
+ cloudspin --listen "song.mp3"
120
+ ```
95
121
 
96
122
  The scenes are `storm`, `rain`, `evening`, `night`, `day`, `dawn`, `snow`,
97
123
  `fog` and `overcast`.
@@ -105,9 +131,10 @@ city = "New Delhi"
105
131
  units = "metric" # or "imperial"
106
132
  sky = "song" # "song", "weather", or a scene name to pin
107
133
  colors = "sky" # "sky", "cover", a built-in theme, or one of yours
108
- music = "~/Music"
134
+ music = "~/Music" # a folder, or a playlist link
109
135
  volume = 70
110
136
  fps = 24 # lower this on a slow terminal or over SSH
137
+ listen = true # false: choose the sky from tags and titles only
111
138
 
112
139
  # Send particular artists, albums or words to a scene.
113
140
  [moods]
@@ -129,26 +156,29 @@ Built-in themes: `sky`, `cover`, `amber`, `orchid`, `lagoon`, `moss`, `rose`, `m
129
156
 
130
157
  - **The bars are generative.** The spectrum moves with the play state, the
131
158
  volume and a tempo chosen per song. It is not a live analysis of the audio.
132
- - **The listen is a guess.** Step 3 above is a heuristic that has only been
133
- checked against synthetic test audio. Tags and your own rules are exact;
134
- use `[moods]` when it gets a song wrong.
135
- - **Links need mpv.** Plain audio URLs and radio streams work with mpv alone.
136
- Sites like YouTube also need `yt-dlp`.
137
- - **Windows is not supported** except through WSL: playback control uses a
138
- Unix socket.
139
- - **MPRIS** (media keys, `playerctl`, Waybar) works if you have the
159
+ - **The listening is a rule of thumb.** It has been checked against synthetic
160
+ test audio, not a real music library, so expect some wrong skies. Use
161
+ `[moods]` to pin an artist or word to a scene, and `cloudspin --listen` to
162
+ see the numbers behind a choice.
163
+ - **Links** play through mpv, with the bundled yt-dlp handling sites like
164
+ YouTube. Sites change often; if a link stops working, update with
165
+ `pipx upgrade cloudspin` (or `uv tool upgrade cloudspin`) to get a newer yt-dlp.
166
+ - **Windows** uses the mpv that Cloudspin downloads for itself unless `mpv` is
167
+ already on your PATH.
168
+ - **MPRIS** (media keys, `playerctl`, Waybar) works on Linux if you have the
140
169
  `mpv-mpris` plugin installed for mpv; Cloudspin loads your normal mpv config.
141
170
 
142
- ## Packaging status
171
+ ## Releases
143
172
 
144
173
  | Channel | Status |
145
174
  | --- | --- |
146
- | Git (`pipx`, `uv`) | Works today |
147
- | PyPI and npm | Published by `.github/workflows/release.yml` when a `v*` tag is pushed. Setup steps are at the top of that file. |
148
- | AUR | `packaging/aur/PKGBUILD` is written; it builds from the PyPI release |
175
+ | [PyPI](https://pypi.org/project/cloudspin/) | Published |
176
+ | [npm](https://www.npmjs.com/package/cloudspin) | Published (launcher for the Python app) |
177
+ | AUR | `packaging/aur/PKGBUILD` is written, not submitted yet |
149
178
  | Homebrew | Not started |
150
179
 
151
- The npm package is a small launcher that runs the Python app through `uv` or `pipx`.
180
+ Pushing a `v*` tag runs `.github/workflows/release.yml`, which tests, then
181
+ publishes to PyPI and npm.
152
182
 
153
183
  ## Develop
154
184
 
@@ -170,6 +200,8 @@ The code is small and split by job:
170
200
  | `mood.py` | Choosing a scene for a song |
171
201
  | `weather.py` | Open-Meteo and sun and moon maths |
172
202
  | `player.py` | mpv control and the queue |
203
+ | `links.py` | Reading YouTube and Spotify playlists |
204
+ | `mpvget.py` | Finding mpv and yt-dlp; the Windows mpv download |
173
205
  | `widgets.py`, `app.py` | The interface |
174
206
 
175
207
  ## Licence
@@ -3,15 +3,14 @@
3
3
  # Not on the AUR yet. This builds from the PyPI release, so publish to PyPI
4
4
  # first, then run `updpkgsums` here to fill in the checksum.
5
5
  pkgname=cloudspin
6
- pkgver=0.1.0
6
+ pkgver=0.2.0
7
7
  pkgrel=1
8
8
  pkgdesc="A terminal music player under a living sky: spinning vinyl, real weather, scenes that follow the song"
9
9
  arch=('any')
10
10
  url="https://github.com/itsHarshDabas/Cloudspin"
11
11
  license=('MIT')
12
- depends=('python' 'python-textual' 'python-mutagen' 'python-pillow' 'mpv')
13
- optdepends=('ffmpeg: guess a sky for songs with no genre tag'
14
- 'yt-dlp: play YouTube and similar links')
12
+ depends=('python' 'python-textual' 'python-mutagen' 'python-pillow' 'yt-dlp' 'mpv')
13
+ optdepends=('ffmpeg: guess a sky for songs with no genre tag')
15
14
  makedepends=('python-build' 'python-installer' 'python-hatchling')
16
15
  source=("https://files.pythonhosted.org/packages/source/c/cloudspin/cloudspin-$pkgver.tar.gz")
17
16
  sha256sums=('SKIP')
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cloudspin",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Launcher for Cloudspin, a terminal music player under a living sky. Runs the Python app through uv or pipx.",
5
5
  "bin": {
6
6
  "cloudspin": "bin/cloudspin.js"
@@ -18,6 +18,7 @@ classifiers = [
18
18
  "Intended Audience :: End Users/Desktop",
19
19
  "Operating System :: POSIX :: Linux",
20
20
  "Operating System :: MacOS",
21
+ "Operating System :: Microsoft :: Windows",
21
22
  "Programming Language :: Python :: 3",
22
23
  "Programming Language :: Python :: 3.11",
23
24
  "Programming Language :: Python :: 3.12",
@@ -28,6 +29,7 @@ dependencies = [
28
29
  "textual>=8.0",
29
30
  "mutagen>=1.47",
30
31
  "pillow>=10.0",
32
+ "yt-dlp",
31
33
  ]
32
34
 
33
35
  [project.optional-dependencies]
@@ -1,3 +1,3 @@
1
1
  """Cloudspin: a terminal music player that lives under a living sky."""
2
2
 
3
- __version__ = "0.1.0"
3
+ __version__ = "0.2.0"