-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathcli.py
More file actions
188 lines (158 loc) · 6 KB
/
Copy pathcli.py
File metadata and controls
188 lines (158 loc) · 6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
"""
cli.py
------
Headless CLI mode for Spotify AI DJ.
Invoked automatically by main.py when arguments are passed:
dj "play some dark techno"
python main.py "relaxing lo-fi"
Prints coloured status output to the terminal and exits when done.
No GUI is launched. Safe to run over SSH or in scripts.
"""
import sys
from brain import get_vibe_params, get_playlist_vibe_params
from config import is_configured, load_config
from spotify_client import SpotifyClient
# ------------------------------------------------------------------
# Colour helpers (auto-disabled when output is not a terminal,
# e.g. when piped into a script or log file)
# ------------------------------------------------------------------
if sys.stdout.isatty():
_RESET = "\033[0m"
_BOLD = "\033[1m"
_GREEN = "\033[32m"
_YELLOW = "\033[33m"
_RED = "\033[31m"
_CYAN = "\033[36m"
else:
_RESET = _BOLD = _GREEN = _YELLOW = _RED = _CYAN = ""
def _info(msg: str) -> None: print(f"{_CYAN}{_BOLD}[*]{_RESET} {msg}")
def _success(msg: str) -> None: print(f"{_GREEN}{_BOLD}[+]{_RESET} {msg}")
def _warn(msg: str) -> None: print(f"{_YELLOW}{_BOLD}[!]{_RESET} {msg}")
def _error(msg: str) -> None: print(f"{_RED}{_BOLD}[x]{_RESET} {msg}")
# Persists between CLI calls within the same process (i.e. not between
# separate dj invocations — use the GUI for multi-session continue).
_cli_spotify_client: SpotifyClient | None = None
def _get_cli_client() -> SpotifyClient:
global _cli_spotify_client
if _cli_spotify_client is None:
_cli_spotify_client = SpotifyClient()
return _cli_spotify_client
def run_cli(request: str, is_continue: bool = False) -> int:
"""
Execute a music request from the terminal.
Args:
request: Natural language music request, e.g. "dark techno"
is_continue: If True, generate fresh queries that avoid previous ones
Returns:
Exit code - 0 for success, 1 for any error.
"""
if not is_configured():
_error("No Gemini API key found.")
_warn(
"Run the app in GUI mode first to complete setup:\n"
" python main.py\n"
"Or set your key directly:\n"
" python main.py --set-key YOUR_KEY_HERE"
)
return 1
config = load_config()
api_key = config.get("gemini_api_key", "")
local_only = config.get("local_ai_only", False)
client = _get_cli_client()
import re as _re
playlist_url = _re.search(
r"(https?://open\.spotify\.com/playlist/[A-Za-z0-9]+|spotify:playlist:[A-Za-z0-9]+)",
request or ""
)
# Step 1 - AI generates search queries
if is_continue:
if not client.last_request:
_error("Nothing playing yet — run a normal request first.")
return 1
_info(f'Continuing: "{client.last_request}"')
try:
from brain import get_continue_params
directives = get_continue_params(client.last_request, client.last_queries, api_key, local_only=local_only)
except Exception as e:
_error(f"AI error: {e}")
return 1
playlist_tracks = None
elif playlist_url:
_info("Playlist URL detected — fetching tracks...")
try:
playlist_tracks = client.get_playlist_tracks(playlist_url.group(0))
_info(f"Fetched {len(playlist_tracks)} tracks from playlist")
except Exception as e:
_error(f"Playlist error: {e}")
return 1
user_intent = _re.sub(r"https?://\S+|spotify:\S+", "", request).strip()
client.last_request = request
try:
directives = get_playlist_vibe_params(playlist_tracks, user_intent, api_key, local_only=local_only)
except Exception as e:
_error(f"AI error: {e}")
return 1
else:
playlist_tracks = None
_info(f'Request: "{request}"')
client.last_request = request
try:
directives = get_vibe_params(request, api_key, local_only=local_only)
except Exception as e:
_error(f"AI error: {e}")
return 1
_info(f"AI: {directives.reasoning}")
_info(f"Queries ({len(directives.queries)}): {directives.queries}")
_info(f"Target queue: {directives.queue_size} tracks")
# Step 2 - Search Spotify and start playback
try:
if playlist_tracks is not None:
result = client.search_and_play_mixed(playlist_tracks, directives)
else:
result = client.search_and_play(directives)
except Exception as e:
_error(f"Spotify error: {e}")
return 1
if result.success:
_success(f"Now playing: {_BOLD}{result.first_track}{_RESET}")
_info(f"{result.track_count} tracks queued from {result.queries_run} searches")
return 0
else:
_error(result.message)
return 1
def run_set_key(key: str) -> int:
"""
Save a Gemini API key from the command line without opening the GUI.
Called when the user runs: python main.py --set-key AIza...
"""
from config import save_config
key = key.strip()
if len(key) < 20:
_error("That doesn't look like a valid key.")
return 1
config = load_config()
config["gemini_api_key"] = key
save_config(config)
_success("Gemini API key saved.")
_info("You can now use the CLI: dj \"your request\"")
return 0
def print_help() -> None:
"""Print CLI usage information."""
print(f"""
{_BOLD}Spotify AI DJ{_RESET}
{_BOLD}GUI mode{_RESET} (no arguments):
python main.py
dj
{_BOLD}CLI mode{_RESET} (play immediately from terminal):
dj "dark techno"
dj "relaxing lo-fi for studying"
python main.py "90s hip hop"
{_BOLD}Continue playing (fresh tracks, same vibe){_RESET}:
dj --continue
{_BOLD}First-time setup from terminal{_RESET} (skips the GUI setup screen):
python main.py --set-key YOUR_GEMINI_API_KEY
dj --set-key YOUR_GEMINI_API_KEY
{_BOLD}Options{_RESET}:
--set-key KEY Save a Gemini API key without opening the GUI
--help, -h Show this message
""")