Skip to content

Commit

Permalink
V2.0
Browse files Browse the repository at this point in the history
  • Loading branch information
darshitlimbad committed Sep 2, 2024
0 parents commit b4eacb6
Show file tree
Hide file tree
Showing 8 changed files with 407 additions and 0 deletions.
18 changes: 18 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#environment
.venv/

# Byte-compiled / optimized / DLL files
__pycache__/

# Distribution / packaging
build/
dist/
downloads/

# PyInstaller
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt
67 changes: 67 additions & 0 deletions Readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# YouTube Video Downloader 2.0

Effortlessly download YouTube videos in the highest quality with our user-friendly YouTube Video Downloader. Whether you need the video, audio, or both, our tool handles it all, directly saving files to your chosen folder for easy access and convenience.

## Important Note

**Users should have downloaded FFmpeg on their PC because `yt_dlp` needs it to function properly.**

### How to Install FFmpeg

1. Download FFmpeg from the official website: [FFmpeg.org](https://ffmpeg.org/download.html).
2. Follow the installation instructions for your operating system.
3. Make sure FFmpeg is added to your system's PATH.

## Features

- Download videos in the highest quality.
- Option to download only audio or video.
- Save files directly to your specified folder.

## Requirements

- Python 3.6 or higher
- `yt_dlp` module
- FFmpeg (ensure it is installed and available in your system's PATH)

## Installation

1. Clone the repository:
```bash
git clone https://github.com/darshitlimbad/YT_Video_Downloader.git
```
2. Navigate to the project directory:
```bash
cd YT_Video_Downloader
```
3. Install the required Python packages:
```bash
pip install -r requirements.txt
```

## Usage

1. Run the script:
```bash
python main.py
```
2. Follow the on-screen instructions to download your desired video.

## Building the Executable

To build the executable file using cxfreeze, follow these steps:

1. Install cxfreeze:
```bash
pip install cxfreeze
```
2. Generate the executable:
```bash
cxfreeze main.py --build_exe dist/ --icon app.ico
```

The executable will be available in the `dist` folder.

## License

IDK
Binary file added app.ico
Binary file not shown.
31 changes: 31 additions & 0 deletions main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import yt_dlp

from module.download import *
from module.formatting import print_colored_text

def main():
print("-" * 100)

# Display options to the user
print("Please choose an option for download:")
print("1. Playlist")
print("2. Video")

option = input("Enter your choice (1 or 2): ").strip()

# Execute based on user's choice
if option == "1":
print("-" * 100)
print_colored_text(text="Enter Playlist URL:", fg_color="green")
playlist_url = input()
download_playlist(playlist_url)
elif option == "2":
print("-" * 100)
print_colored_text(text="Enter Video URL:", fg_color="green")
video_url = input()
download_video(video_url)
else:
print_colored_text(text="Invalid choice! Please enter 1 or 2.", fg_color="red")

if __name__ == '__main__':
main()
124 changes: 124 additions & 0 deletions module/download.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import os
import yt_dlp

from .utils import *
from .formatting import *

def download_video_media(video_url, download_folder, video_format, audio_format):
ydl_options = {
'format': f'{video_format}+{audio_format}',
'outtmpl': os.path.join(download_folder, '%(title)s.%(ext)s'),
'progress_hooks': [hook_function],
}

with yt_dlp.YoutubeDL(ydl_options) as ydl:
ydl.download([video_url])

def download_video(video_url,download_folder:str=".\\Downloads"):
try:
if '/playlist?' in video_url:
download_playlist(video_url)
return

if not os.path.exists(download_folder):
os.mkdir(download_folder)

video_only_formats= {}
audio_only_formats= {}

# Get video info
ydl_opts = {
'quiet': True,
'listformats': False,
'noplaylist': True,
}
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
video_info = ydl.extract_info(video_url, download=False)

print(f"\nProcessing: {video_info['title']}")

formats = video_info.get('formats', [])

video_only_formats = [f for f in formats if f.get('vcodec','none') != 'none' and f.get('acodec','none') == 'none']
audio_only_formats = [f for f in formats if f.get('vcodec','none') == 'none' and f.get('acodec','none') != 'none']

if not len(video_only_formats) or not len(audio_only_formats):
print_colored_text("No Format found\n",fg_color="red")
return

# Validate and get user input for the desired video and audio
print_formats(format_type="video",video_only_formats=video_only_formats)
print_colored_text(text="Enter Video Index:",fg_color="green")
video_index = get_valid_index("", len(video_only_formats))

print_formats(format_type="audio",audio_only_formats=audio_only_formats)
print_colored_text(text="Enter Audio Index: ",fg_color="green")
audio_index = get_valid_index("", len(audio_only_formats))

# Retrieve the selected format IDs
video_format = video_only_formats[video_index]['format_id']
audio_format = audio_only_formats[audio_index]['format_id']

if not video_format or not audio_format:
print_colored_text("No Format found\n",fg_color="red")
return

# Download the video with yt-dlp, which will automatically merge video and audio
download_video_media(video_url, download_folder, video_format, audio_format)

except Exception as e:
print_colored_text("\nSomething went wrong:",fg_color="red")
print(e)

def download_playlist(playlist_url):
try:
if '/watch?' in playlist_url:
download_video(playlist_url)
return

download_root = '.\\Downloads'
if not os.path.exists(download_root):
os.mkdir(download_root)

# Get playlist info
ydl_opts = {
'quiet': True,
'listformats': False,
}

with yt_dlp.YoutubeDL(ydl_opts) as ydl:
playlist_info = ydl.extract_info(playlist_url, download=False)

playlist_title = playlist_info.get('title', 'Playlist')
playlist_folder = os.path.join(download_root, playlist_title)

if not os.path.exists(playlist_folder):
os.mkdir(playlist_folder)

print_colored_text(text="\nDownloading Playlist: ",fg_color="green")
print(playlist_title)
print_colored_text(text="Saving files to:",fg_color="green")
print(playlist_folder,end="\n\n")

for entry in playlist_info['entries']:
video_url = entry['webpage_url']
download_video(video_url,download_folder=playlist_folder)

print_colored_text(text="\nPlaylist download successfully completed.",fg_color="green")
print_colored_text(text="\nPlaylist folder:",fg_color="green")
print(playlist_folder,end="\n\n")
print("-"*100)


except KeyError as ke:
print_colored_text("\nProcess Terminated. \nIt looks like you entered a single video URL instead of a playlist URL.",fg_color="red")

if os.path.exists(playlist_folder) and len(os.listdir(playlist_folder)) == 0:
os.rmdir(playlist_folder)
except Exception as e:
print_colored_text("\nSomething went wrong:",fg_color="red")
print(e)

if os.path.exists(playlist_folder) and len(os.listdir(playlist_folder)) == 0:
os.rmdir(playlist_folder)

100 changes: 100 additions & 0 deletions module/formatting.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
from .utils import get_file_size_from_url , convert_bytes

# ascii escape codes
esc_codes = {
# color codes
'fore': {
'red': '\033[31m', # Red text
'green': '\033[32m', # Green text
'bright_red': '\033[91m', # Bright Red text
'bright_green': '\033[92m', # Bright Green text
'dark_grey': '\033[90m', # Dark Grey text
'reset': '\033[39m', # Reset to default foreground
},
'back': {
'red': '\033[41m', # Red background
'green': '\033[42m', # Green background
'light_red': '\033[101m', # Light Red background
'light_green': '\033[102m', # Light Green background
'light_yellow': '\033[103m', # Light Yellow background
'light_blue': '\033[104m', # Light Blue background
'light_magenta': '\033[105m', # Light Magenta background
'light_cyan': '\033[106m', # Light Cyan background
'light_white': '\033[107m', # Light White background
'reset': '\033[49m', # Reset to default background

},

'bold': '\033[1m', # Bold text attribute
'underline': '\033[4m', # Underlined text attribute

'reset': '\033[0m', # Reset all attributes
}


def print_formats(format_type:str="video", video_only_formats:dict =None, audio_only_formats:dict =None):
"""
Prints available formats in a structured table format based on the format type.
Args:
format_type (str): Type of formats to print, either 'video' or 'audio'.
video_only_formats (list, optional): A list of dictionaries containing video format information.
audio_only_formats (list, optional): A list of dictionaries containing audio format information.
Description:
This function displays the available formats (video or audio) in a tabular format.
It includes headers for each column and prints a list of formats with their details.
Each format is displayed with an index, which allows users to choose a format based
on its number.
"""

if format_type == 'video':
header = f"{f'{esc_codes['fore']['bright_green']}Index{esc_codes['reset']}':<5} {'Format ID':<15} {'Codec':<15} {'Resolution':<20} {'FPS':<10} {'Filesize':<20}"
formats = video_only_formats
fields = ['format_id', 'vcodec', 'format_note', 'fps', 'filesize']
elif format_type == 'audio':
header = f"{f'{esc_codes['fore']['bright_green']}Index{esc_codes['reset']}{esc_codes['bold']}':<5} {'Format ID':<15} {'Codec':<15} {'Quality':<20} {'Filesize':<20}"
formats = audio_only_formats
fields = ['format_id', 'acodec', 'format_note', 'filesize']
else:
raise ValueError("Invalid format type. Choose 'video' or 'audio'.")

heading = f"\n{esc_codes['bold']}{esc_codes['fore']['green']}Available {format_type} formats:{esc_codes['reset']}"
print(heading)
print("=" * len(header)) # Separator line
print(f'{esc_codes['bold']}{header}{esc_codes['reset']}') # Print the formats header
print("=" * len(header)) # Separator line

for i, f in enumerate(formats, start=1):

filesize = convert_bytes(f.get('filesize')) or f"{esc_codes['fore']['dark_grey']} ~ {convert_bytes(get_file_size_from_url(f.get('url')))} ( dash ) {esc_codes['reset']}"


# Define the common elements for both video and audio
base_format = (f"{esc_codes['fore']['green']}{esc_codes['bold']}{i:<5}{esc_codes['reset']} "
f"{f.get(fields[0], 'N/A'):<15} {f.get(fields[1], 'N/A'):<15} {f.get(fields[2], 'N/A'):<20}")

# Add specific details based on the format type
if format_type == 'video':
print(f"{base_format} {f.get(fields[3], 'N/A'):<10} {filesize:<20}")
elif format_type == 'audio':
print(f"{base_format} {filesize:<20}")

print("=" * len(header))

def print_colored_text(text, fg_color=None, bg_color=None):
"""
Prints text with specified foreground and background colors, with an option to reset color attributes.
Args:
text (str): The text to print.
fg_color (str, optional): The foreground color. Choices: 'red', 'green', 'bright_red', 'bright_green', etc.
bg_color (str, optional): The background color. Choices: 'light_red', 'light_green', etc.
reset (str, optional): The type of reset. Choices: 'all', 'foreground', 'background'.
"""
fg_code = esc_codes['fore'].get(fg_color, '')
bg_code = esc_codes['back'].get(bg_color, '')
reset_code = esc_codes['reset']

# Print text with color codes
print(f"{fg_code}{bg_code}{text}{reset_code}",end="")
Loading

0 comments on commit b4eacb6

Please sign in to comment.