├── .gitignore ├── logo.png ├── colors.py ├── requirements.txt ├── LICENSE ├── README.md └── main.py /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HaseebKhalid1507/AudioLine/HEAD/logo.png -------------------------------------------------------------------------------- /colors.py: -------------------------------------------------------------------------------- 1 | class bcolors: 2 | HEADER = '\033[95m' 3 | OKBLUE = '\033[94m' 4 | OKCYAN = '\033[96m' 5 | OKGREEN = '\033[92m' 6 | WARNING = '\033[93m' 7 | FAIL = '\033[91m' 8 | ENDC = '\033[0m' 9 | BOLD = '\033[1m' 10 | UNDERLINE = '\033[4m' -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # Automatically generated by https://github.com/damnever/pigar. 2 | 3 | # AudioLine/main.py: 5 4 | inquirer == 2.9.1 5 | 6 | # AudioLine/main.py: 1 7 | pafy == 0.5.5 8 | 9 | youtube-dl == 2020.12.2 10 | 11 | # AudioLine/main.py: 2 12 | python_vlc == 3.0.12118 13 | 14 | # AudioLine/main.py: 8 15 | rich == 10.3.0 16 | 17 | # AudioLine/main.py: 9 18 | youtube_search_python == 1.4.5 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Haseeb Khalid 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 | [![Forks][forks-shield]][forks-url] 2 | [![Stargazers][stars-shield]][stars-url] 3 | [![Issues][issues-shield]][issues-url] 4 | [![LinkedIn][linkedin-shield]][linkedin-url] 5 | 6 | 7 | 8 | 9 |
10 |

11 | 12 | Logo 13 | 14 | 15 |

AudioLine

16 | 17 |

18 | A lightweight Youtube audio player for your terminal 19 |
20 | Explore the docs » 21 |
22 |
23 | View Demo 24 | · 25 | Report Bug 26 | · 27 | Request Feature 28 | · 29 | Send a Pull Request 30 |

31 |

32 | 33 | 34 | ## About The Project 35 | 36 | This python script allows you to play audio scraped from youtube straight from your terminal. The objective here is to keep the player as simple and lightweight as possible. 37 | 38 | Features: 39 | * Playing audio with URL 40 | * Playing audio thorugh search 41 | * Autoplay option 42 | 43 | This project is currently nowhere near finished, so I'll be adding more features in the future. You may also suggest changes by forking this repo and creating a pull request or opening an issue. 44 | 45 | ### Built With 46 | This section should list any major frameworks that you built your project using. Leave any add-ons/plugins for the acknowledgements section. Here are a few examples. 47 | * [youtube-dl](https://pypi.org/project/youtube_dl/) 48 | * [Pafy](https://pypi.org/project/pafy/) 49 | * [python-vlc](https://pypi.org/project/python-vlc/) 50 | * [Rich](https://pypi.org/project/rich/) 51 | * [youtube_search_python](https://pypi.org/project/youtube-search-python/) 52 | 53 | 54 | 55 | 56 | ## Getting Started 57 | 58 | 59 | ### Prerequisites 60 | 61 | This script requires Python and VLC to run 62 | 63 | ### Installation 64 | 65 | 1. Clone the repo 66 | ```sh 67 | git clone https://github.com/HaseebKhalid1507/AudioLine 68 | ``` 69 | 2. Install requirements.txt 70 | ```sh 71 | pip install -r requirements.txt 72 | ``` 73 | 74 | 75 | 76 | ## Usage 77 | 78 | You can now run the script with python3. 79 | ```sh 80 | python3 main.py 81 | ``` 82 | 83 | 84 | 85 | ## 🤝 Contributing 86 | 87 | Contributions are what make the open source community such an amazing place to be learn, inspire, and create. Any contributions you make are **extremely appreciated**. 88 | 89 | 1. Fork the Project 90 | 2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`) 91 | 3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`) 92 | 4. Push to the Branch (`git push origin feature/AmazingFeature`) 93 | 5. Open a Pull Request 94 | 95 | 96 | 97 | 98 | ## 📝 License 99 | 100 | Distributed under the MIT License. See `LICENSE` for more information. 101 | 102 | 103 | 104 | 105 | ## 📫 Contact 106 | 107 | Haseeb Khalid - [@HaseebK1507](https://twitter.com/HaseebK1507) - haseebkhalid1507@gmail.com 108 | 109 | Project Link: [https://github.com/HaseebKhalid1507/AudioLine](https://github.com/HaseebKhalid1507/AudioLine) 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | [forks-shield]: https://img.shields.io/github/forks/HaseebKhalid1507/AudioLine?style=for-the-badge 118 | [forks-url]: https://github.com/HaseebKhalid1507/AudioLine/network/members 119 | [stars-shield]: https://img.shields.io/github/stars/HaseebKhalid1507/AudioLine?style=for-the-badge 120 | [stars-url]: https://github.com/HaseebKhalid1507/AudioLine/stargazers 121 | [issues-shield]: https://img.shields.io/github/issues/HaseebKhalid1507/AudioLine?style=for-the-badge 122 | [issues-url]: https://github.com/HaseebKhalid1507/AudioLine/issues 123 | [linkedin-shield]: https://img.shields.io/badge/-LinkedIn-black.svg?style=flat-square&logo=linkedin&colorB=555 124 | [linkedin-url]: https://www.linkedin.com/in/haseeb-khalid-954147197/ 125 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import pafy 2 | import vlc 3 | import urllib.request 4 | import re 5 | import inquirer 6 | import sys 7 | from time import sleep 8 | from rich.progress import track, Progress 9 | from youtubesearchpython import PlaylistsSearch 10 | from os import system 11 | from colors import bcolors 12 | 13 | 14 | def isWindows(): 15 | if sys.platform == "win32": 16 | return True 17 | elif sys.platform == "linux" or sys.platform == "linux2": 18 | return False 19 | else: 20 | raise OSError("Unsupported Operating System! [WIN/LINUX ONLY]") 21 | 22 | 23 | def cls(): 24 | if isWindows(): 25 | system("cls") 26 | else: 27 | system("clear") 28 | 29 | 30 | class AudioLine: 31 | """ The Main Object for the AudioLine Process to Utilize. """ 32 | 33 | def __init__(self): 34 | self.played = [] 35 | self.video_ids = [] 36 | self.media = None 37 | 38 | # FUNCTION TO PLAY AUDIO 39 | def playvideo(self, url): 40 | 41 | # ADD URL TO LIST OF PLAYED SONGS 42 | self.played.append(url) 43 | 44 | # INITIALIZE VIDEO 45 | video = pafy.new(url) 46 | audio = video.getbestaudio() 47 | self.media = vlc.MediaPlayer(audio.url) 48 | 49 | # PRINT VIDEO DETAILS 50 | print(f"\n{'-' * 70}\n") 51 | 52 | print(bcolors.HEADER + "Now Playing: " + bcolors.OKCYAN + video.title) 53 | print(bcolors.HEADER + "\nViews: " + 54 | bcolors.OKCYAN + f"{video.viewcount:,d}", end="") 55 | print(bcolors.HEADER + "\t\tDuration: " + 56 | bcolors.OKCYAN + video.duration) 57 | print(bcolors.WARNING + "\nPress 'CTRL+C' to Skip Song!\n" + bcolors.ENDC) 58 | 59 | with Progress(transient=True) as prog: 60 | song_play = prog.add_task( 61 | "[green]Playing Song", total=video.length) 62 | 63 | # START PLAYER 64 | self.media.play() 65 | 66 | while self.media.is_playing() == False: 67 | pass 68 | 69 | while self.media.is_playing(): 70 | sleep(1) 71 | prog.update(song_play, advance=1) 72 | 73 | print(bcolors.OKGREEN + "DONE PLAYING %s!" % video.title) 74 | 75 | # FUNCTION TO KEEP PLAYING SONGS 76 | def autoplay(self, url): 77 | # PLAY CURRENT VIDEO 78 | self.playvideo(url) 79 | 80 | self.video_ids = [] 81 | video_ids_dupes = [] 82 | # CHANGE CURRENT VIDEO TO NEXT ONE 83 | html = urllib.request.urlopen(url) 84 | video_ids_dupes = re.findall( 85 | r"watch\?v=(\S{11})", html.read().decode()) 86 | 87 | # REMOVE DUPLICATES FROM LIST 88 | for i in video_ids_dupes: 89 | if "https://www.youtube.com/watch?v=" + i not in self.played: 90 | self.autoplay("https://www.youtube.com/watch?v=" + i) 91 | # RECURSION PAGMAN 92 | 93 | # FUNCTION TO TAKE FIRST VIDEO FROM YOUTUBE SEARCH PAGE 94 | 95 | def search_youtube(self, search: str): 96 | # Check if the given search term is a valid youtube video link 97 | if(search.startswith("https://www.youtube.com")): 98 | res = urllib.request.urlopen(search) 99 | 100 | if(res.getcode() == 200): 101 | return search 102 | 103 | search_url = "https://www.youtube.com/results?search_query={}" + \ 104 | search.replace(" ", "+") 105 | 106 | html = urllib.request.urlopen(search_url) 107 | video_ids = re.findall(r"watch\?v=(\S{11})", html.read().decode()) 108 | url = ("https://www.youtube.com/watch?v=" + video_ids[0]) 109 | return url 110 | 111 | # FUNCTION TO PLAY VIDEOS IN A PLAYLIST 112 | def play_playlist(self, url): 113 | html = urllib.request.urlopen(url) 114 | self.video_ids = re.findall( 115 | r"watch\?v=(\S{11})", html.read().decode()) 116 | 117 | for i in self.video_ids: 118 | self.playvideo("https://www.youtube.com/watch?v=" + i) 119 | 120 | # FUNCTION TO TAKE FIRST PLAYLIST FROM YOUTUBE SEARCH PAGE 121 | def search_playlist(self, search): 122 | playlistsSearch = PlaylistsSearch(search, limit=1) 123 | self.play_playlist(playlistsSearch.result()["result"][0]["link"]) 124 | 125 | # MAIN SCRIPT FUNCTION 126 | def menu(self): 127 | 128 | cls() 129 | 130 | print(bcolors.HEADER + "") 131 | print("\t █████╗ ██╗ ██╗██████╗ ██╗ ██████╗ ██╗ ██╗███╗ ██╗███████╗") 132 | print("\t██╔══██╗██║ ██║██╔══██╗██║██╔═══██╗██║ ██║████╗ ██║██╔════╝") 133 | print("\t███████║██║ ██║██║ ██║██║██║ ██║██║ ██║██╔██╗ ██║█████╗ ") 134 | print("\t██╔══██║██║ ██║██║ ██║██║██║ ██║██║ ██║██║╚██╗██║██╔══╝ ") 135 | print("\t██║ ██║╚██████╔╝██████╔╝██║╚██████╔╝███████╗██║██║ ╚████║███████╗") 136 | print("\t╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═════╝ ╚══════╝╚═╝╚═╝ ╚═══╝╚══════╝", 137 | bcolors.ENDC, "\n") 138 | 139 | questions = [ 140 | inquirer.List('option', 141 | message="Choose An Option", 142 | choices=[ 143 | 'Video Search/URL', 'Video Search/URL (Autoplay)', 'Playlist URL', 'Playlist Search', 'Exit'], 144 | ), 145 | ] 146 | 147 | flag = inquirer.prompt(questions)["option"] 148 | 149 | # ENTER CHOICE 150 | if flag == "Video Search/URL": 151 | # INPUT SEARCH TERM 152 | search = input(bcolors.OKCYAN + 153 | "Enter Search Term/URL: " + bcolors.ENDC) 154 | print(bcolors.ENDC, end="") 155 | # search = "Stephen - play me like a violin" # For bug fixing 156 | url = self.search_youtube(search) 157 | try: 158 | self.playvideo(url) 159 | except KeyboardInterrupt: 160 | self.media.stop() 161 | del self.media 162 | # Terminate the program on KeyBoardInterrupt 163 | exit() 164 | 165 | elif flag == "Video Search/URL (Autoplay)": 166 | # INPUT SEARCH TERM 167 | search = input(bcolors.OKCYAN + 168 | "Enter Search Term/URL: " + bcolors.ENDC) 169 | # search = "off the grid" # For bug fixing 170 | url = self.search_youtube(search) 171 | try: 172 | self.autoplay(url) 173 | except KeyboardInterrupt: 174 | self.media.stop() 175 | del self.media 176 | self.video_ids.clear() 177 | exit() 178 | 179 | elif flag == 'Playlist URL': 180 | # INPUT PLAYLIST URL 181 | url = input(bcolors.OKCYAN + "Enter Playlist URL: " + bcolors.ENDC) 182 | 183 | try: 184 | self.play_playlist(url) 185 | except KeyboardInterrupt: 186 | self.media.stop() 187 | del self.media 188 | self.video_ids.clear() 189 | exit() 190 | 191 | elif flag == 'Playlist Search': 192 | # INPUT SEARCH TERM 193 | search = input(bcolors.OKCYAN + 194 | "Enter Search Term: " + bcolors.ENDC) 195 | 196 | try: 197 | self.search_playlist(search) 198 | except KeyboardInterrupt: 199 | self.media.stop() 200 | del self.media 201 | self.video_ids.clear() 202 | exit() 203 | 204 | elif flag == 'Exit': 205 | exit() 206 | 207 | 208 | if __name__ == '__main__': 209 | al = AudioLine() 210 | try: 211 | al.menu() 212 | except: 213 | print(bcolors.FAIL + "Exiting...") 214 | --------------------------------------------------------------------------------