├── .gitattributes
├── LICENSE
├── README.md
├── daso_installer_no_python.bat
├── daso_installer_python.bat
├── daso_script.ps1
├── daso_script_python.ps1
├── img
├── porcodio_1.png
└── porcodio_2.png
├── requirements.txt
└── src
├── DASO.py
├── __main__.py
├── __pycache__
└── DASO.cpython-310.pyc
└── daso.bat
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Joshua | Tommaso
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 |
DASO
3 |
4 |
5 |
6 | DASO - Stream music from YouTube directly from your terminal.
7 |
8 |
9 |
10 |

11 |
12 |
13 | ## Table of Contents
14 |
15 | - [Installation](#-installation)
16 | - [Usage](#-usage)
17 | - [Keybinds](#-keybinds)
18 | - [Important Notes](#-important-notes)
19 | - [Contributing](#-contributing)
20 | - [License](#-license)
21 |
22 | ## 🚀 Installation
23 |
24 | DASO is easy to install with its dedicated installer for Windows. Follow these steps:
25 |
26 | 1. **Choose Your Installer**:
27 | - `daso_installer_no_python.bat`: If you already have Python installed.
28 | - `daso_installer_python.bat`: If you don't have Python. [Install Python here](https://www.python.org/downloads/) in case it doesn't work.
29 | 2. **Run the Installer**: Double-click the chosen .bat file in the main folder. Follow the instructions.
30 | 3. **Installation Complete**: DASO is now ready to use!
31 |
32 | > 📝 **Note**: Currently, DASO is not available for Linux or Mac. There are no plans for these platforms at the moment.
33 |
34 | ## 🎵 Usage
35 |
36 | The DASO installer makes it so that you can run it from anywhere in your terminal. Basically, it adds a new command to your terminal, `daso`, which you can use to run like this:
37 |
38 | ```bash
39 | daso [song name]
40 | ```
41 |
42 | For example:
43 |
44 | ```bash
45 | daso never gonna give you up
46 | ```
47 |
48 | DASO uses the YouTube API to search for the song you want to listen to, so you can use any name you want, as long as it exists on YouTube. Remember tho, to provide a name, else you will get an error.
49 |
50 | ### 🎹 Keybinds
51 |
52 | DASO has some keybinds that you can use to control it while it's running. Here's a list of them:
53 |
54 | - `space` to pause/play the song
55 | - `ctrl + c` to stop the song and exit DASO
56 | - `arrow up` to increase the volume
57 | - `arrow down` to decrease the volume
58 |
59 |
60 |

61 |
62 |
63 | ## ❗ Important
64 |
65 | The neat thing about DASO is that it doesn't download the song you want to listen to, it just streams it from YouTube. Another important note is that you can't listen to Lives _for now_, I'm currently figuring out how to implement this feature. If you know how to, please feel free to [contribute](#contributing)!
66 |
67 | ## 🤝 Contributing
68 |
69 | I welcome all types of contributions, including documentation, code, tests, suggestions, etc.... Other than that, you can also open an issue if you find a bug or if you have a suggestion, I'll try to answer as soon as possible.
70 |
71 | ## 📝 License
72 |
73 | [MIT](LICENSE)
--------------------------------------------------------------------------------
/daso_installer_no_python.bat:
--------------------------------------------------------------------------------
1 | PowerShell -NoProfile -ExecutionPolicy Bypass -File daso_script.ps1
2 |
--------------------------------------------------------------------------------
/daso_installer_python.bat:
--------------------------------------------------------------------------------
1 | PowerShell -NoProfile -ExecutionPolicy Bypass -File daso_script_python.ps1
2 |
--------------------------------------------------------------------------------
/daso_script.ps1:
--------------------------------------------------------------------------------
1 | # Check if the script is running as administrator
2 | if (-NOT ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
3 | # Relaunch the script with administrator privileges
4 | $arguments = "& '" + $myinvocation.mycommand.definition + "'"
5 | Start-Process powershell -Verb runAs -ArgumentList $arguments
6 | Exit
7 | }
8 |
9 | # General output text
10 | Write-Output "Welcome to the DASO installer!"
11 |
12 | # Get the directory of the script
13 | $scriptPath = Split-Path -Parent -Path $MyInvocation.MyCommand.Definition
14 | $srcPath = Join-Path -Path $scriptPath -ChildPath "src"
15 |
16 | # Add src directory to system PATH
17 | $envPath = [System.Environment]::GetEnvironmentVariable("Path", [System.EnvironmentVariableTarget]::Machine)
18 | if (-not ($envPath.Split(';') -contains $srcPath)) {
19 | $newPath = $envPath + ";" + $srcPath
20 | [System.Environment]::SetEnvironmentVariable("Path", $newPath, [System.EnvironmentVariableTarget]::Machine)
21 | Write-Output "The directory has been added to your system PATH."
22 | } else {
23 | Write-Output "The directory is already in your system PATH."
24 | }
25 |
26 | # Prompt the user to download VLC
27 | Write-Output "To complete the installation, please download VLC Media Player."
28 | $vlcDownloadUrl = "https://www.videolan.org/vlc/download-windows.html"
29 | Start-Process "chrome.exe" $vlcDownloadUrl # This opens the URL in Google Chrome. You can change the browser if needed.
30 |
31 | Read-Host -Prompt "Press Enter to exit setup..." # Wait for user input before closing the window
32 |
--------------------------------------------------------------------------------
/daso_script_python.ps1:
--------------------------------------------------------------------------------
1 | # Check if the script is running as administrator
2 | if (-NOT ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
3 | # Relaunch the script with administrator privileges
4 | $arguments = "& '" + $myinvocation.mycommand.definition + "'"
5 | Start-Process powershell -Verb runAs -ArgumentList $arguments
6 | Exit
7 | }
8 |
9 | # General output text
10 | Write-Output "Welcome to the DASO installer!"
11 |
12 | # Check for Python installation
13 | $pythonInstalled = $false
14 | try {
15 | Start-Process python --ArgumentList '--version' -Wait -NoNewWindow
16 | $pythonInstalled = $true
17 | } catch {}
18 |
19 | if (-not $pythonInstalled) {
20 | Write-Output "Python is not installed. Starting installation..."
21 |
22 | # Specify the Python version to install
23 | $pythonVersion = "3.10.0" # Change this to what version you want, I advice 3.10.0 as I've written the program in that
24 | $installerUrl = "https://www.python.org/ftp/python/$pythonVersion/python-$pythonVersion-amd64.exe"
25 |
26 | # Download the Python installer
27 | $installerPath = "$env:TEMP\python-installer.exe"
28 | Invoke-WebRequest -Uri $installerUrl -OutFile $installerPath
29 |
30 | # Install Python silently
31 | Start-Process $installerPath -ArgumentList '/quiet InstallAllUsers=1 PrependPath=1' -Wait
32 |
33 | Write-Output "Python has been installed."
34 | }
35 |
36 | # Get the directory of the script
37 | $scriptPath = Split-Path -Parent -Path $MyInvocation.MyCommand.Definition
38 | $srcPath = Join-Path -Path $scriptPath -ChildPath "src"
39 |
40 | # Add src directory to system PATH
41 | $envPath = [System.Environment]::GetEnvironmentVariable("Path", [System.EnvironmentVariableTarget]::Machine)
42 | if (-not ($envPath.Split(';') -contains $srcPath)) {
43 | $newPath = $envPath + ";" + $srcPath
44 | [System.Environment]::SetEnvironmentVariable("Path", $newPath, [System.EnvironmentVariableTarget]::Machine)
45 | Write-Output "The directory has been added to your system PATH."
46 | } else {
47 | Write-Output "The directory is already in your system PATH."
48 | }
49 |
50 | # Prompt the user to download VLC
51 | Write-Output "To complete the installation, please download VLC Media Player."
52 | $vlcDownloadUrl = "https://www.videolan.org/vlc/download-windows.html"
53 | Start-Process "chrome.exe" $vlcDownloadUrl # This opens the URL in Google Chrome. You can change the browser if needed.
54 |
55 | Read-Host -Prompt "Press Enter to exit setup..." # Wait for user input before closing the window
56 |
--------------------------------------------------------------------------------
/img/porcodio_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JoshuaKasa/DASO/0a69c088b19a5767030d8fa54d8732969a671ae7/img/porcodio_1.png
--------------------------------------------------------------------------------
/img/porcodio_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JoshuaKasa/DASO/0a69c088b19a5767030d8fa54d8732969a671ae7/img/porcodio_2.png
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | anyio==4.2.0
2 | certifi==2024.7.4
3 | colorama==0.4.6
4 | exceptiongroup==1.2.0
5 | h11==0.14.0
6 | httpcore==1.0.2
7 | httpx==0.26.0
8 | idna==3.7
9 | keyboard==0.13.5
10 | markdown-it-py==3.0.0
11 | mdurl==0.1.2
12 | prompt-toolkit==3.0.36
13 | Pygments==2.17.2
14 | python-vlc==3.0.20123
15 | pytube==15.0.0
16 | questionary==2.0.1
17 | rich==13.7.0
18 | sniffio==1.3.0
19 | tqdm==4.66.1
20 | typing_extensions==4.9.0
21 | wcwidth==0.2.13
22 | youtube-search-python==1.6.6
--------------------------------------------------------------------------------
/src/DASO.py:
--------------------------------------------------------------------------------
1 | from pytube import YouTube
2 | from youtubesearchpython import VideosSearch
3 | import time
4 | import vlc
5 | import keyboard
6 |
7 | def format_time(seconds):
8 | """Convert seconds to MM:SS format."""
9 | minutes, seconds = divmod(int(seconds), 60)
10 | return f"{minutes:02d}:{seconds:02d}"
11 |
12 | def stream_youtube_audio(url):
13 | # Fetching the YouTube video
14 | yt = YouTube(url)
15 | audio_stream = yt.streams.filter(only_audio=True).order_by('abr').first() # Highest bitrate
16 | audio_url = audio_stream.url # Get the audio url
17 |
18 | # Using VLC to play the audio
19 | player = vlc.MediaPlayer(audio_url)
20 | player.play()
21 | time.sleep(1) # Wait for player to start
22 |
23 | is_playing = True # Variable to track play/pause state
24 | volume = 100 # Initial volume set to 100%
25 | player.audio_set_volume(volume) # Set the initial volume
26 |
27 | try:
28 | while True:
29 | time.sleep(0.1) # A shorter sleep time for more responsive keyboard interaction
30 |
31 | # Adjust volume with up/down arrows
32 | if keyboard.is_pressed('down'):
33 | volume = max(0, volume - 5) # Decrease volume, but not less than 0%
34 | player.audio_set_volume(volume)
35 | while keyboard.is_pressed('down'):
36 | time.sleep(0.1)
37 |
38 | if keyboard.is_pressed('up'):
39 | volume = min(100, volume + 5) # Increase volume, but not more than 100%
40 | player.audio_set_volume(volume)
41 | while keyboard.is_pressed('up'):
42 | time.sleep(0.1)
43 |
44 | # Check if spacebar is pressed for play/pause
45 | if keyboard.is_pressed('space'):
46 | if is_playing:
47 | player.pause()
48 | is_playing = False
49 | print("[Paused]", end='\r')
50 | else:
51 | player.play()
52 | is_playing = True
53 | print("[Playing]", end='\r')
54 | while keyboard.is_pressed('space'): # Wait until space is released
55 | time.sleep(0.1)
56 |
57 | if is_playing:
58 | # We can print over the previous line using \r and end='\r', which is the carriage return character
59 | current_time = player.get_time() // 1000
60 | total_time = yt.length
61 | print(f"[Playing] {format_time(current_time)}/{format_time(total_time)} - Volume: {volume}%", end='\r')
62 |
63 | except KeyboardInterrupt:
64 | pass # Press Ctrl+C to stop
65 | finally:
66 | print() # Ensure the next print starts on a new line
67 | player.stop() # Stop the player
68 |
69 |
70 | def search_yt_audio_from_keyword(keyword: str) -> str | dict:
71 | # Search the keyword on YouTube
72 | videosSearch = VideosSearch(keyword, limit=10)
73 | result = videosSearch.result()
74 |
75 | return result
76 |
--------------------------------------------------------------------------------
/src/__main__.py:
--------------------------------------------------------------------------------
1 | import os
2 | import sys
3 | import DASO
4 | from rich.console import Console
5 | from rich.table import Table
6 | from colorama import Fore, Style
7 | import questionary
8 | from tqdm import tqdm
9 | import time
10 |
11 | # Initialize console
12 | console = Console()
13 |
14 | def truncate_string(s: str, max_length: int) -> str:
15 | """Truncate a string to a maximum length and add '...' if truncated.
16 |
17 | Args:
18 | s (str): The string to truncate.
19 | max_length (int): The maximum length of the truncated string.
20 |
21 | Returns:
22 | str: The truncated string.
23 | """
24 | return s if len(s) <= max_length else s[:max_length-3] + '...'
25 |
26 | def main(arg: str) -> None:
27 | """Main function of the program.
28 |
29 | Args:
30 | arg (str): The search keyword.
31 | """
32 | result = DASO.search_yt_audio_from_keyword(arg)
33 | print(f"\nShowing results for '{arg}':\n")
34 |
35 | # Set fixed widths for each column
36 | title_width = 30
37 | duration_width = 10
38 | views_width = 15
39 |
40 | table = Table(show_header=True, header_style="bold magenta")
41 | table.add_column("No.", style="dim cyan", width=6)
42 | table.add_column("Title", width=title_width, overflow="ellipsis")
43 | table.add_column("Duration", justify="right", width=duration_width)
44 | table.add_column("Views", justify="right", width=views_width)
45 |
46 | for i, video in enumerate(result['result']):
47 | video_title = truncate_string(video.get('title', 'No Title'), title_width)
48 | video_duration = truncate_string(video.get('duration', 'N/A'), duration_width)
49 | video_view_count = truncate_string(video.get('viewCount', {}).get('short', 'N/A'), views_width)
50 |
51 | if video_view_count is not None and isinstance(video_view_count, str):
52 | video_view_count = video_view_count.replace('views','')
53 | else:
54 | video_view_count = 'N/A' # Default value in case of None or non-string
55 |
56 | table.add_row(str(i + 1), video_title, video_duration, video_view_count)
57 |
58 | console.print(table)
59 |
60 | questions = [
61 | {
62 | 'type': 'input',
63 | 'name': 'choice',
64 | 'message': 'Choose a video to play:',
65 | }
66 | ]
67 |
68 | choice = questionary.select(
69 | "Choose a video to play:",
70 | choices=[f"{i+1}. {video['title']}" for i, video in enumerate(result['result'])]
71 | ).ask()
72 |
73 | # Convert choice to index
74 | choice_index = int(choice.split('.')[0]) - 1
75 | url = result['result'][choice_index]['link']
76 |
77 | try:
78 | DASO.stream_youtube_audio(url)
79 | except Exception as e:
80 | print(f'An error occurred while trying to play the audio, {e}')
81 |
82 | if __name__ == '__main__':
83 | argument = ' '.join(sys.argv[1:]) # Get the arguments passed to the program
84 | if argument == '':
85 | print('You must provide a search keyword as a argument.')
86 | print('Example: DASO never gonna give you up')
87 | sys.exit(1)
88 |
89 | main(argument)
90 | sys.exit(0) # Exit without errors
91 |
--------------------------------------------------------------------------------
/src/__pycache__/DASO.cpython-310.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JoshuaKasa/DASO/0a69c088b19a5767030d8fa54d8732969a671ae7/src/__pycache__/DASO.cpython-310.pyc
--------------------------------------------------------------------------------
/src/daso.bat:
--------------------------------------------------------------------------------
1 | @echo off
2 | cd /d "%~dp0"
3 | python __main__.py %*
4 |
--------------------------------------------------------------------------------