├── .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 |
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 |
--------------------------------------------------------------------------------