Skip to content

Commit 6baad61

Browse files
Add files via upload
1 parent 9dfa78d commit 6baad61

3 files changed

Lines changed: 260 additions & 0 deletions

File tree

main.py

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
from pygame import mixer
2+
import random
3+
import asyncio
4+
import os
5+
from youtube_downloader import url_download, search_and_download
6+
7+
# Initialize the mixer
8+
mixer.init()
9+
10+
# Function to get all .mp3 files in a directory
11+
async def get_music_files(directory):
12+
try:
13+
return [os.path.join(directory, file) for file in os.listdir(directory) if file.endswith(".mp3")]
14+
except FileNotFoundError:
15+
print(f"Directory {directory} not found.")
16+
return []
17+
18+
# Used directories for different playlists
19+
directories = {
20+
"oldies": './assets/oldies',
21+
"democrazy": './assets/democrazy',
22+
"heavy": './assets/heavy',
23+
"downloads": './assets/downloads'
24+
}
25+
26+
# Check and create directories if they don't exist
27+
for directory in directories.values():
28+
os.makedirs(directory, exist_ok=True)
29+
30+
# Function to get all playlists
31+
async def get_playlists(directories):
32+
playlists = {name: await get_music_files(directory) for name, directory in directories.items()}
33+
playlists["all"] = sum(playlists.values(), [])
34+
return playlists
35+
36+
# Function to update the downloads playlist
37+
async def update_downloads_playlist(playlists):
38+
playlists["downloads"] = await get_music_files(directories["downloads"])
39+
playlists["all"] = sum(playlists.values(), [])
40+
print("Updated downloads playlist:", [os.path.basename(file) for file in playlists["downloads"]])
41+
42+
# Function to choose a playlist
43+
def choice(playlists):
44+
while True:
45+
for name, files in playlists.items():
46+
print(f"{name} = {[os.path.basename(file) for file in files]}")
47+
48+
user_choice = input("What playlist do you want to play: ").strip().lower()
49+
if user_choice in playlists and playlists[user_choice]:
50+
return playlists[user_choice]
51+
else:
52+
print("Playlist is empty or invalid. Please choose a different playlist.")
53+
54+
# Function to play music from the chosen playlist
55+
async def music_play(playlist, playlists):
56+
if not playlist:
57+
print("The playlist is empty.")
58+
return
59+
60+
volume = 0.5
61+
current_index = 0
62+
63+
while True:
64+
print(f"Loading file: {playlist[current_index]}")
65+
mixer.music.load(playlist[current_index])
66+
mixer.music.play()
67+
mixer.music.set_volume(volume)
68+
69+
while True:
70+
# Print options for user to control the music player
71+
print("Press 'p' to pause, 'r' to resume")
72+
print("Press '-' or '+' to lower or raise volume")
73+
print("Press 'c' to change playlist")
74+
print("Press 'n' to skip to the next song")
75+
print("Press 'b' to go back to the previous song")
76+
print("Press 's' to shuffle the playlist")
77+
print("Press 'e' to exit the program")
78+
print("Press 'd' to download music from YouTube")
79+
option = input(" ").strip().lower()
80+
81+
# Handle user input for controlling the music player
82+
if option in ['p', 'pause']:
83+
mixer.music.pause()
84+
elif option in ['r', 'resume']:
85+
mixer.music.unpause()
86+
elif option in ['e', 'exit']:
87+
mixer.music.stop()
88+
return
89+
elif option == '-':
90+
volume = max(0, volume - 0.1)
91+
mixer.music.set_volume(volume)
92+
elif option == '+':
93+
volume = min(1, volume + 0.1)
94+
mixer.music.set_volume(volume)
95+
elif option in ['c', 'change']:
96+
playlist = choice(playlists)
97+
if not playlist:
98+
print("The new playlist is empty.")
99+
continue
100+
current_index = 0
101+
break
102+
elif option in ['n', 'next']:
103+
current_index = (current_index + 1) % len(playlist)
104+
break
105+
elif option in ['b', 'back']:
106+
current_index = (current_index - 1) % len(playlist)
107+
break
108+
elif option in ['s', 'shuffle']:
109+
random.shuffle(playlist)
110+
current_index = 0
111+
break
112+
elif option in ['d', 'download']:
113+
mixer.music.stop()
114+
use = input("Download from URL (Y or N): ").strip().lower()
115+
if use in ["y", "yes"]:
116+
url = input("Enter the URL of the video (n to break): ")
117+
if url.lower() not in ["n", "no"]:
118+
try:
119+
await url_download(url)
120+
except Exception as e:
121+
print(f"Error downloading from URL: {e}")
122+
else:
123+
try:
124+
await search_and_download()
125+
except Exception as e:
126+
print(f"Error searching and downloading: {e}")
127+
await update_downloads_playlist(playlists)
128+
playlist = playlists["downloads"]
129+
if not playlist:
130+
print("The updated playlist is empty.")
131+
continue
132+
current_index = 0
133+
134+
# Main function to start the music player
135+
async def main():
136+
playlists = await get_playlists(directories)
137+
playlist = choice(playlists)
138+
await music_play(playlist, playlists)
139+
140+
if __name__ == "__main__":
141+
asyncio.run(main())

readme.txt

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
### How to Install FFmpeg on Windows
2+
3+
1. Download FFmpeg:
4+
- Visit the [FFmpeg download page](https://www.ffmpeg.org/download.html).
5+
- Navigate to the Windows builds from [gyan.dev](https://www.gyan.dev/ffmpeg/builds/).
6+
- Download the `ffmpeg-git-full.7z` file.
7+
8+
2. Extract the Files:
9+
- Extract the downloaded `ffmpeg-git-full.7z` file.
10+
- Name the folder ffmpeg
11+
- Move the extracted folder to `C:\`. (I use WINRAR not sure if required)
12+
13+
3. Restart Your Computer:
14+
- Restart your computer to ensure all changes take effect.
15+
16+
4. Add FFmpeg to System Path:
17+
- Open Advanced System Settings.
18+
- Click on the Environment Variables button.
19+
- In the System variables section, find the `Path` variable and click Edit.
20+
- Click New and add the path to the `bin` folder of your FFmpeg installation (e.g., `C:\ffmpeg\bin`).
21+
- Click OK to close all windows.
22+
23+
24+
# pip installs
25+
- pip install yt_dlp
26+
- pip install pygame
27+
- pip install asyncio

youtube_downloader.py

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import os # Importing the os module for file and directory operations
2+
import yt_dlp as youtube_dl # Importing yt_dlp for downloading and extracting audio from YouTube
3+
import asyncio # Importing asyncio for asynchronous I/O operations
4+
from concurrent.futures import ThreadPoolExecutor # Importing ThreadPoolExecutor for running tasks in separate threads
5+
6+
# Common options for youtube_dl
7+
YDL_OPTS = {
8+
'format': 'bestaudio/best', # Download the best available audio quality
9+
'outtmpl': 'assets/downloads/%(title)s.%(ext)s', # Template for output file names
10+
'postprocessors': [{
11+
'key': 'FFmpegExtractAudio', # Use FFmpeg to extract audio
12+
'preferredcodec': 'mp3', # Convert audio to MP3 format
13+
'preferredquality': '192', # Set audio quality to 192 kbps
14+
}],
15+
'n_threads': 8, # Number of threads to use for downloading
16+
}
17+
18+
# Asynchronous function to download audio from a given URL
19+
async def url_download(url):
20+
if url.lower() in ["n", "no"]: # Check if the user input is "n" or "no" to cancel download
21+
print("Not downloaded")
22+
return
23+
24+
try:
25+
with youtube_dl.YoutubeDL(YDL_OPTS) as ydl:
26+
info_dict = ydl.extract_info(url, download=True) # Download the audio and get info
27+
title = info_dict.get('title', 'Unknown Title') # Get the title of the downloaded file
28+
file_path = os.path.join('assets', 'downloads', f'{title}.mp3')
29+
30+
# Check if the file was downloaded successfully and is not empty
31+
if os.path.exists(file_path) and os.path.getsize(file_path) > 0:
32+
print(f"Title: {title}")
33+
print("Download completed and verified successfully!")
34+
else:
35+
print("The downloaded file is corrupted or empty.")
36+
except Exception as e:
37+
print(f"An error occurred: {e}") # Print any errors encountered during download
38+
39+
# Asynchronous function to search for videos and download the selected one
40+
async def search_and_download():
41+
while True:
42+
search_query = input("Enter the search query (or -1 to exit): ") # Get search query from user
43+
if search_query == "-1": # Exit the loop if user inputs "-1"
44+
break
45+
46+
opts = YDL_OPTS.copy() # Copy the common options
47+
opts.update({'default_search': 'ytsearch5'}) # Set options to search for the top 5 results on YouTube
48+
49+
try:
50+
with youtube_dl.YoutubeDL(opts) as ydl:
51+
info_dict = ydl.extract_info(search_query, download=False) # Search without downloading
52+
results = info_dict.get('entries', []) # Get the list of search results
53+
54+
if not results: # If no results are found
55+
print("No results found.")
56+
continue
57+
58+
# Display search results
59+
for index, video in enumerate(results):
60+
print(f'{index + 1}. Title: {video["title"]}')
61+
print(f' URL: {video["webpage_url"]}')
62+
print(f' Duration: {video["duration"]} seconds')
63+
print('---')
64+
65+
choice = input("Enter the number of the video to download (-1 to exit): ") # Get user choice
66+
if choice == "-1": # Exit if user inputs "-1"
67+
print("Not downloaded")
68+
return
69+
70+
try:
71+
choice = int(choice) - 1 # Convert user choice to zero-based index
72+
if 0 <= choice < len(results): # Check if choice is valid
73+
await url_download(results[choice]['webpage_url']) # Download the selected video
74+
return
75+
else:
76+
print("Invalid choice. Not downloaded.")
77+
except ValueError:
78+
print("Invalid input. Please enter a number.")
79+
except Exception as e:
80+
print(f"An error occurred: {e}") # Print any errors encountered during search
81+
82+
# Function to run the asynchronous search_and_download function in a new event loop
83+
def run_search_and_download():
84+
loop = asyncio.new_event_loop() # Create a new event loop
85+
asyncio.set_event_loop(loop)
86+
loop.run_until_complete(search_and_download()) # Run the asynchronous function until complete
87+
88+
# Main entry point of the script
89+
if __name__ == "__main__":
90+
with ThreadPoolExecutor(max_workers=1) as executor: # Use a ThreadPoolExecutor to run the function in a separate thread
91+
future = executor.submit(run_search_and_download) # Submit the function to the executor
92+
future.result() # Wait for the thread to complete

0 commit comments

Comments
 (0)