├── .github └── funding.yml ├── .gitignore ├── LICENSE ├── README.md ├── main.py ├── methods.py ├── presets_example.json ├── requirements.txt ├── screenshots └── play_dnd.gif ├── settings.json └── src └── initialize_speech.py /.github/funding.yml: -------------------------------------------------------------------------------- 1 | github: nexxeln 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | venv/ 3 | .cache 4 | __pycache__/ 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Shoubhit Dash 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 
2 | 3 | 4 |
5 |
6 | 7 | Logo 8 | 9 | 10 |

Spotify Voice Control

11 | 12 |

13 | A voice control utility for Spotify 14 | · 15 | Report Bug 16 | · 17 | Request Feature 18 |

19 |
20 | 21 |

Logo credit: soph

22 | 23 | ![play](https://github.com/nexxeln/spotify-voice-control/blob/main/screenshots/play_dnd.gif) 24 | 25 | _Demo video will be added soon_ 26 | 27 | ### Built With 28 | 29 | - [Python](https://python.org/) 30 | - [spotipy - Spotify api wrapper](https://github.com/plamere/spotipy) 31 | - [speech_recognition](https://github.com/Uberi/speech_recognition) 32 | 33 |

(^)

34 | 35 | ### Prerequisites 36 | 37 | - [Python 3.6+](https://www.python.org/downloads/) 38 | - [Spotify Premium](https://www.spotify.com/premium/) 39 | 40 |

(^)

41 | 42 | ### Installation 43 | 44 | 1. Make a application at [https://developer.spotify.com](https://developer.spotify.com) 45 | 2. Add a Redirect URI to the application and set is as `http://localhost:8888/callback` 46 | 3. Clone this repository (`git clone https://github.com/nexxeln/spotify-voice-control`) 47 | 4. Install all dependencies (`pip install -r requirements.txt`) 48 | 5. Set two environment variables, `SPOTIFY_CLIENT_ID` and `SPOTIFY_CLIENT_SECRET` 49 | 6. Authenticate by running `main.py` 50 | 7. Run `main.py` to use 51 | 52 |

(^)

53 | 54 | ### Usage 55 | 56 | 1. Have some music playing thorough Spotify in the device you want to listen in. 57 | 2. Run `main.py` 58 | 3. Say `play {track name}` to play a track (say `play random` to play a random track from your saved tracks) 59 | 4. Say `album {album name}` to play an album 60 | 5. Say `artist {artist name}` to play songs of an artist (say `artist random` to play a random artist from your followed artists) 61 | 6. Say `playlist {playlist name}` to play a playlist from Your Library 62 | 7. Say `pause` to pause the music 63 | 8. Say `resume` to resume the music 64 | 9. Say `skip` to skip to the next track 65 | 10. Say `volume {number}` to set the volume to a number between 0 and 100 66 | 11. Say `repeat` to put the current track in on repeat 67 | 12. Say `shuffle {ON/OFF}` to turn shuffle on or off 68 | 13. Say `current song` to see the current song playing. 69 | 14. Say `go back` or `back` to go back to the last played song. 70 | 15. Say `quit` to quit the program 71 | 16. Sometimes the speech_recognition library doesn't understand some names. For this there is a presets feature 72 | - Go to `settings.json` and add your own presets 73 | - See `presets_example.json` for an example 74 | - Make your own in `settings.json` 75 | - Presets can only be one word so make it meaningful 76 | - Now you can say `{preset}` to play a preset 77 | 78 | _Note: Sometimes the speech_recognition library hangs, in that case you have to force quit the program by using_ `ctrl + C` _or_ `cmd + C`_._ _Then restart the program by using_ `python main.py` 79 | 80 |

(^)

81 | 82 | ## Future plans 83 | 84 | Make the whole thing in Rust to make it a whole lot faster. 85 | 86 |

(^)

87 | 88 | 89 | 90 | ## Contributing 91 | 92 | Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**. 93 | 94 | If you have a suggestion that would make this better, please fork the repo and create a pull request. You can also simply open an issue with the tag "enhancement". 95 | Don't forget to give the project a star! Thanks again! 96 | 97 | 1. Fork the Project 98 | 2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`) 99 | 3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`) 100 | 4. Push to the Branch (`git push origin feature/AmazingFeature`) 101 | 5. Open a Pull Request 102 | 103 |

(^)

104 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | # imports 2 | import spotipy 3 | import os 4 | import dotenv 5 | import speech_recognition as sr 6 | import orjson 7 | import random 8 | from spotipy.oauth2 import SpotifyOAuth 9 | from rich import print 10 | from methods import * 11 | import asyncio 12 | from src.initialize_speech import * 13 | 14 | 15 | """ 16 | TODO(emoss): Fully implement asynchronized methods. 17 | """ 18 | 19 | 20 | # load environment variables 21 | dotenv.load_dotenv() 22 | 23 | # loading settings 24 | with open('settings.json') as f: 25 | settings = orjson.loads(f.read()) 26 | presets = settings["presets"] 27 | f.close() 28 | 29 | # create spotify object with all scopes 30 | scope = f"ugc-image-upload, user-read-playback-state, user-modify-playback-state, user-follow-modify, user-read-private, user-follow-read, user-library-modify, user-library-read, streaming, user-read-playback-position, app-remote-control, user-read-email, user-read-currently-playing, user-read-recently-played, playlist-modify-private, playlist-read-collaborative, playlist-read-private, user-top-read, playlist-modify-public" 31 | 32 | sp = spotipy.Spotify(auth_manager=SpotifyOAuth(scope=scope, client_id=os.getenv("SPOTIFY_CLIENT_ID"), client_secret=os.getenv("SPOTIFY_CLIENT_SECRET"), redirect_uri="http://localhost:8888/callback"), requests_timeout=300) 33 | 34 | # everyone likes ASCII art so why not 35 | print(f'''[spring_green3] 36 | :'######::'########:::'#######::'########:'####:'########:'##:::'##: 37 | '##... ##: ##.... ##:'##.... ##:... ##..::. ##:: ##.....::. ##:'##:: 38 | ##:::..:: ##:::: ##: ##:::: ##:::: ##::::: ##:: ##::::::::. ####::: 39 | . ######:: ########:: ##:::: ##:::: ##::::: ##:: ######:::::. ##:::: 40 | :..... ##: ##.....::: ##:::: ##:::: ##::::: ##:: ##...::::::: ##:::: 41 | '##::: ##: ##:::::::: ##:::: ##:::: ##::::: ##:: ##:::::::::: ##:::: 42 | . ######:: ##::::::::. #######::::: ##::::'####: ##:::::::::: ##:::: 43 | :......:::..::::::::::.......::::::..:::::....::..:::::::::::..::::: 44 | [/spring_green3] 45 | [hot_pink2]Voice Control for Spotify[/hot_pink2] 46 | ''') 47 | 48 | while True: 49 | ''' 50 | infinite loop to listen for commands 51 | commands: 52 | - 'play {track name}' 53 | - 'album {album name}' 54 | - 'artist {artist name}' 55 | - 'skip track' 56 | - 'previous track' 57 | ''' 58 | r = sr.Recognizer() 59 | command, audio = initialize_voice(recognizer=r) 60 | 61 | # recognize speech and using try-except to catch errors 62 | try: 63 | command = r.recognize_google(audio_data=audio).lower() 64 | except sr.UnknownValueError: 65 | print(f"[italic red]Could not understand.[/italic red]") 66 | continue 67 | 68 | print(f"[medium_purple3]{command}[/medium_purple3]") 69 | # splitting the command into separate words 70 | words = command.split() 71 | 72 | # checking if the speech recognizer recognized a command 73 | if len(words) < 1: 74 | print(f"[italic red]Could not understand.[/italic red]") 75 | continue 76 | elif len(words) == 1: 77 | for preset in presets: 78 | if words[0] == preset["preset"]: 79 | if preset["type"] == "track": 80 | name = preset["name"] 81 | uri = asyncio.run(get_track_uri(spotify=sp, name=name)) 82 | asyncio.run(play_track(spotify=sp, uri=uri)) 83 | print(f"[bold deep_sky_blue2]Playing track:[/bold deep_sky_blue2] [italic spring_green3]{name}[/italic spring_green3]") 84 | continue 85 | 86 | elif preset["type"] == "album": 87 | name = preset["name"] 88 | uri = asyncio.run(get_album_uri(spotify=sp, name=name)) 89 | asyncio.run(play_album(spotify=sp, uri=uri)) 90 | print(f"[bold deep_sky_blue2]Playing album:[/bold deep_sky_blue2] [italic spring_green3]{name}[/italic spring_green3]") 91 | continue 92 | 93 | elif preset["type"] == "artist": 94 | name = preset["name"] 95 | uri = asyncio.run(get_artist_uri(spotify=sp, name=name)) 96 | asyncio.run(play_artist(spotify=sp, uri=uri)) 97 | print(f"[bold deep_sky_blue2]Playing artist:[/bold deep_sky_blue2] [italic spring_green3]{name}[/italic spring_green3]") 98 | continue 99 | 100 | elif preset["type"] == "playlist": 101 | playlists, playlist_ids = asyncio.run(get_user_playlists(spotify=sp)) 102 | name = preset["name"] 103 | for i in range(len(playlists)): 104 | if name.lower() == playlists[i].lower(): 105 | id = playlist_ids[i] 106 | asyncio.run(play_playlist(spotify=sp, playlist_id=id)) 107 | print(f"[bold deep_sky_blue2]Playing playlist:[/bold deep_sky_blue2] [italic spring_green3]{name}[/italic spring_green3]") 108 | continue 109 | 110 | if words[0] == "next": 111 | asyncio.run(next_track(spotify=sp)) 112 | elif words[0] == "pause": 113 | asyncio.run(pause_track(spotify=sp)) 114 | elif words[0] == "resume": 115 | asyncio.run(resume_track(spotify=sp)) 116 | elif words[0] == 'back': 117 | asyncio.run(play_previous_song(spotify=sp)) 118 | elif words[0] == "quit": 119 | asyncio.run(exit_application()) 120 | break 121 | elif words[0] == "repeat": 122 | asyncio.run(repeat_track(spotify=sp)) 123 | else: 124 | print(f"[italic red]Command not recognized.[/italic red]") 125 | continue 126 | 127 | else: 128 | # here action is the command, eg: 'play', 'album' or 'artist' 129 | # and the name is the name of the track/album/artist to be played 130 | action = words[0] 131 | name = " ".join(words[1:]) 132 | 133 | # Current command actions 134 | try: 135 | if action == "current": 136 | if name == "song": 137 | """ Display the current song playing. """ 138 | track = asyncio.run(get_current_song(spotify=sp)) 139 | print(f"[bold deep_sky_blue2]Current track:[/bold deep_sky_blue2] [italic spring_green3]{track}[/italic spring_green3]") 140 | except Exception as action_exception: 141 | print(f"[italic red]Could not underst{action_exception}and.[/italic red]") 142 | 143 | # Go command actions 144 | try: 145 | if action == 'go': 146 | if name == 'back': 147 | """ Go Back to previous song. """ 148 | asyncio.run(play_previous_song(spotify=sp)) 149 | except Exception as e: 150 | print(f"[italic red]{e}[/italic red]") 151 | 152 | # try except block to catch InvaliSearchError 153 | try: 154 | if action == "play": 155 | if name == "random": 156 | tracks = asyncio.run(get_user_saved_tracks(spotify=sp)) 157 | random_track = random.choice(tracks) 158 | uri = asyncio.run(get_track_uri(spotify=sp, name=random_track)) 159 | asyncio.run(play_track(spotify=sp, uri=uri)) 160 | print(f"[bold deep_sky_blue2]Playing track:[/bold deep_sky_blue2] [italic spring_green3]{random_track}[/italic spring_green3]") 161 | else: 162 | uri = asyncio.run(get_track_uri(spotify=sp, name=name)) 163 | asyncio.run(play_track(spotify=sp, uri=uri)) 164 | print(f"[bold deep_sky_blue2]Playing track:[/bold deep_sky_blue2] [italic spring_green3]{name}[/italic spring_green3]") 165 | 166 | if action == "album": 167 | uri = asyncio.run(get_album_uri(spotify=sp, name=name)) 168 | asyncio.run(play_album(spotify=sp, uri=uri)) 169 | print(f"[bold deep_sky_blue2]Playing album:[/bold deep_sky_blue2] [italic spring_green3]{name}[/italic spring_green3]") 170 | 171 | if action == "artist": 172 | if name == "random": 173 | random_artist = random.choice(get_user_followed_artists(spotify=sp)) 174 | uri = asyncio.run(get_artist_uri(spotify=sp, name=random_artist)) 175 | asyncio.run(play_artist(spotify=sp, uri=uri)) 176 | print(f"[bold deep_sky_blue2]Playing artist:[/bold deep_sky_blue2] [italic spring_green3]{random_artist}[/italic spring_green3]") 177 | else: 178 | uri = asyncio.run(get_artist_uri(spotify=sp, name=name)) 179 | asyncio.run(play_artist(spotify=sp, uri=uri)) 180 | print(f"[bold deep_sky_blue2]Playing artist:[/bold deep_sky_blue2] [italic spring_green3]{name}[/italic spring_green3]") 181 | 182 | if action == "playlist": 183 | playlists, playlist_ids = asyncio.run(get_user_playlists(spotify=sp)) 184 | if name.lower() in playlists: 185 | for i in range(len(playlists)): 186 | if name.lower() == playlists[i].lower(): 187 | id = playlist_ids[i] 188 | asyncio.run(play_playlist(spotify=sp, playlist_id=id)) 189 | print(f"[bold deep_sky_blue2]Playing playlist:[/bold deep_sky_blue2] [italic spring_green3]{name}[/italic spring_green3]") 190 | break 191 | else: 192 | print("[italic red]Could not find playlist.[/italic red]") 193 | continue 194 | 195 | 196 | elif action == "volume": 197 | t = {'one':1,'two':2,'three':3,'four':4,'five':5,'six':6,'seven':7,'eight':8,'nine':9,'ten':10} # dictionary for volume 198 | if name in t: 199 | """ For some reason speech recognition return 1 - 10 as strings, so we need to convert them to ints.""" 200 | volume = t[name] 201 | asyncio.run(change_volume(spotify=sp, volume=volume)) 202 | print(f"[bold deep_sky_blue2]Volume set to:[/bold deep_sky_blue2] [italic spring_green3]{volume}[/italic spring_green3]") 203 | else: 204 | """ If volume is not in dictionary, tthen return it as is.""" 205 | volume = int(name) 206 | asyncio.run(change_volume(spotify=sp, volume=volume)) 207 | print(f"[bold deep_sky_blue2]Volume set to:[/bold deep_sky_blue2] [italic spring_green3]{volume}%[/italic spring_green3]") 208 | 209 | elif action == "shuffle": 210 | state = name 211 | asyncio.run(shuffle(spotify=sp, state=state)) 212 | 213 | except InvalidSearchError: 214 | print(f"[italic red]Could not find {name}. Try again.[/italic red]") 215 | 216 | print("[bold deep_sky_blue2]Goodbye![/bold deep_sky_blue2]") -------------------------------------------------------------------------------- /methods.py: -------------------------------------------------------------------------------- 1 | from rich import print 2 | from spotipy import Spotify 3 | 4 | 5 | # catching errors 6 | class InvalidSearchError(Exception): 7 | pass 8 | 9 | 10 | async def exit_application() -> None: 11 | """ 12 | exits the application 13 | """ 14 | print("[bold deep_sky_blue2]Goodbye![/bold deep_sky_blue2]") 15 | return exit() 16 | 17 | 18 | async def get_current_song(spotify: Spotify) -> str: 19 | """ 20 | Returns the name of the current song. 21 | """ 22 | if spotify.currently_playing() is None: 23 | """ If there is no song playing, return nothing is playing. """ 24 | return "Nothing is playing" 25 | song_name = spotify.currently_playing()['item']['name'] 26 | artist_name = spotify.currently_playing()['item']['artists'][0]['name'] 27 | return f"{song_name} - {artist_name}" 28 | 29 | 30 | async def play_previous_song(spotify: Spotify) -> Spotify: 31 | """ 32 | returns the previous song 33 | """ 34 | print(f"[bold deep_sky_blue2]Playing previous song![/bold deep_sky_blue2]") 35 | return spotify.previous_track() 36 | 37 | 38 | async def get_album_uri(spotify: Spotify, name: str) -> str: 39 | """ 40 | returns the uri of the album with the given name 41 | """ 42 | results = spotify.search(q=name, type="album") 43 | if len(results["albums"]["items"]) == 0: 44 | raise InvalidSearchError(f"No albums found with name: {name}") 45 | return results["albums"]["items"][0]["uri"] 46 | 47 | 48 | async def get_track_uri(spotify: Spotify, name: str) -> str: 49 | """ 50 | returns the uri of the track with the given name 51 | """ 52 | results = spotify.search(q=name, type="track") 53 | if len(results["tracks"]["items"]) == 0: 54 | raise InvalidSearchError(f"No tracks found with name: {name}") 55 | return results["tracks"]["items"][0]["uri"] 56 | 57 | 58 | async def get_artist_uri(spotify: Spotify, name: str) -> str: 59 | """ 60 | returns the uri of the artist with the given name 61 | """ 62 | results = spotify.search(q=name, type="artist") 63 | if len(results["artists"]["items"]) == 0: 64 | raise InvalidSearchError(f"No artists found with name: {name}") 65 | return results["artists"]["items"][0]["uri"] 66 | 67 | 68 | async def play_album(spotify: Spotify, uri: str) -> Spotify: 69 | """ 70 | plays the album with the given uri 71 | """ 72 | return spotify.start_playback(context_uri=uri) 73 | 74 | 75 | async def play_track(spotify: Spotify, uri: str) -> Spotify: 76 | """ 77 | plays the track with the given uri 78 | """ 79 | return spotify.start_playback(uris=[uri]) 80 | 81 | 82 | async def play_artist(spotify: Spotify, uri: str) -> Spotify: 83 | """ 84 | plays the artist with the given uri 85 | """ 86 | return spotify.start_playback(context_uri=uri) 87 | 88 | 89 | async def play_playlist(spotify: Spotify, playlist_id: str) -> Spotify: 90 | """ 91 | plays the playlist with the given id 92 | """ 93 | return spotify.start_playback(context_uri=f"spotify:playlist:{playlist_id}") 94 | 95 | 96 | async def next_track(spotify: Spotify) -> Spotify: 97 | """ 98 | skips the current track 99 | """ 100 | print(f"[bold deep_sky_blue2]Skipped![/bold deep_sky_blue2]") 101 | return spotify.next_track() 102 | 103 | 104 | async def pause_track(spotify: Spotify) -> Spotify: 105 | """ 106 | Puases the current song playing. 107 | """ 108 | try: 109 | if spotify.current_user_playing_track()['is_playing'] is True: 110 | """ If the song is playing pause it.""" 111 | print(f"[bold deep_sky_blue2]Paused![/bold deep_sky_blue2]") 112 | return spotify.pause_playback() 113 | else: 114 | """ Otherwise, inform the user that no song is currently playing.""" 115 | print(f"[italic red]No song is currently playing.[/italic red]") 116 | except Exception as pause_tracK_exception: 117 | print(f"Error"+ str(pause_tracK_exception)) 118 | 119 | 120 | async def resume_track(spotify: Spotify) -> Spotify: 121 | """ 122 | Resumes the paused song. 123 | """ 124 | try: 125 | if spotify.current_user_playing_track()['is_playing'] is False: 126 | """ If the song is paused, resume it.""" 127 | print(f"[bold deep_sky_blue2]Resumed![/bold deep_sky_blue2]") 128 | return spotify.start_playback() 129 | else: 130 | """ Otherwise, inform the user that song is currently playing.""" 131 | print(f"[italic red]Song is already playing.[/italic red]") 132 | except Exception as resume_track_exception: 133 | print(f"Error"+ str(resume_track_exception)) 134 | 135 | 136 | async def change_volume(spotify: Spotify, volume: int) -> Spotify: 137 | """ 138 | changes the volume to the given value between 1 and 100 139 | """ 140 | if volume < 0 or volume > 100: 141 | """ Removed the raise exception to allow the user to continue using the application. """ 142 | print(f"[italic red]Volume must be between 1 and 100.[/italic red]") 143 | else: 144 | return spotify.volume(volume) 145 | 146 | 147 | async def repeat_track(spotify: Spotify) -> Spotify: 148 | """ 149 | repeats the current track 150 | """ 151 | try: 152 | print(f"[bold deep_sky_blue2]Track on repeat![/bold deep_sky_blue2]") 153 | return spotify.repeat("track") 154 | except Exception as repeat_track_exception: 155 | print(f"Error"+ str(repeat_track_exception)) 156 | 157 | 158 | async def shuffle(spotify: Spotify, state: str) -> Spotify: 159 | """ 160 | shuffles the playlist 161 | """ 162 | if state == "on": 163 | print(f"[bold deep_sky_blue2]Shuffle is[/bold deep_sky_blue2] [italic spring_green3]ON[/italic spring_green3]") 164 | return spotify.shuffle(True) 165 | elif state == "off": 166 | print(f"[bold deep_sky_blue2]Shuffle is[/bold deep_sky_blue2] [italic spring_green3]OFF[/italic spring_green3]") 167 | return spotify.shuffle(False) 168 | else: 169 | raise ValueError("State must be either on or off") 170 | 171 | 172 | async def get_user_followed_artists(spotify: Spotify) -> list: 173 | """ 174 | returns a list of the users followed artists 175 | """ 176 | all_artists = [] 177 | results = spotify.current_user_followed_artists(limit=20) 178 | for i in range(results["artists"]["total"]): 179 | for j in range(len(results["artists"]["items"])): 180 | all_artists.append(results["artists"]["items"][j]["name"]) 181 | if results["artists"]["cursors"]["after"] is None: 182 | break 183 | else: 184 | after = results["artists"]["cursors"]["after"] 185 | results = spotify.current_user_followed_artists(limit=20, after=after) 186 | 187 | return all_artists 188 | 189 | 190 | async def get_user_saved_tracks(spotify: Spotify) -> list: 191 | """ 192 | returns a list of the users saved tracks 193 | """ 194 | tracks = [] 195 | offset = 0 196 | results = spotify.current_user_saved_tracks(limit=50, offset=offset) 197 | while len(results["items"]) != 0: 198 | for i in range(len(results["items"])): 199 | tracks.append(results["items"][i]["track"]["name"]) 200 | offset += 50 201 | results = spotify.current_user_saved_tracks(limit=50, offset=offset) 202 | 203 | return tracks 204 | 205 | 206 | async def get_user_playlists(spotify: Spotify) -> tuple[list, list]: 207 | """ 208 | returns a list of the users playlists 209 | """ 210 | playlists = [] 211 | playlist_ids = [] 212 | offset = 0 213 | results = spotify.current_user_playlists(limit=50, offset=offset) 214 | while len(results["items"]) != 0: 215 | for i in range(len(results["items"])): 216 | playlists.append(results["items"][i]["name"]) 217 | playlist_ids.append(results["items"][i]["id"]) 218 | offset += 50 219 | results = spotify.current_user_playlists(limit=50, offset=offset) 220 | 221 | return playlists, playlist_ids 222 | -------------------------------------------------------------------------------- /presets_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | { 4 | "name": "Powfu", 5 | "preset": "sad", 6 | "type": "artist" 7 | }, 8 | { 9 | "name": "tell me you feelings and i won't tell you mine", 10 | "preset": "favourite", 11 | "type": "album" 12 | }, 13 | { 14 | "name": "sauceintherough", 15 | "preset": "sauce", 16 | "type": "track" 17 | }, 18 | { 19 | "name": "This is glaive", 20 | "preset": "gold", 21 | "type": "playlist" 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nexxeln/spotify-voice-control/f304538a7bd0b1e5ecd8d79c588273dbd739f66b/requirements.txt -------------------------------------------------------------------------------- /screenshots/play_dnd.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nexxeln/spotify-voice-control/f304538a7bd0b1e5ecd8d79c588273dbd739f66b/screenshots/play_dnd.gif -------------------------------------------------------------------------------- /settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ] 4 | } -------------------------------------------------------------------------------- /src/initialize_speech.py: -------------------------------------------------------------------------------- 1 | import speech_recognition as sr 2 | from rich import print 3 | from rich.console import Console 4 | from rich.style import Style 5 | from typing import Union 6 | 7 | console = Console() 8 | base_style = Style.parse("cyan") 9 | 10 | 11 | def initialize_voice(recognizer: sr.Recognizer) -> Union[str, sr.AudioData]: 12 | command = None 13 | with sr.Microphone() as source: 14 | console.print(" ----- Calibrating microphone ----- ", style='bold magenta') 15 | recognizer.adjust_for_ambient_noise(source) # Adjust for ambient noise 16 | console.print("Calibration complete", style='bold green') 17 | console.print("[bold deep_sky_blue2]Ready![/bold deep_sky_blue2]") 18 | audio = recognizer.listen(source, 10, 3) # Set timeouut of listener 19 | 20 | return command, audio --------------------------------------------------------------------------------