SpotDown 0.0.1__py3-none-any.whl → 0.0.7__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,188 @@
1
+ # 05.04.2024
2
+
3
+ import os
4
+ import platform
5
+ from typing import Dict, List
6
+
7
+
8
+ # External imports
9
+ from rich.console import Console
10
+ from rich.prompt import Prompt
11
+
12
+
13
+ # Internal utils
14
+ from SpotDown.utils.config_json import config_manager
15
+
16
+
17
+
18
+ # Variable
19
+ CLEAN_CONSOLE = config_manager.get('DEFAULT', 'clean_console')
20
+ SHOW_MESSAGE = config_manager.get('DEFAULT', 'show_message')
21
+ AUTO_FIRST = config_manager.get('DOWNLOAD', 'auto_first')
22
+
23
+
24
+ class ConsoleUtils:
25
+ def __init__(self):
26
+ self.console = Console()
27
+
28
+ def display_spotify_info(self, spotify_info: Dict):
29
+ """
30
+ Display Spotify information
31
+
32
+ Args:
33
+ spotify_info (Dict): Spotify track data
34
+ """
35
+ self.console.print("[bold green]Spotify Track Information: [/bold green]")
36
+ self.console.print(f"[cyan]Title: [red]{spotify_info['title']}[/red]")
37
+ self.console.print(f"[cyan]Artist: [red]{spotify_info['artist']}[/red]")
38
+
39
+ if spotify_info.get('album'):
40
+ self.console.print(f"[cyan]Album: [red]{spotify_info['album']}[/red]")
41
+ if spotify_info.get('year'):
42
+ self.console.print(f"[cyan]Year: [red]{spotify_info['year']}[/red]")
43
+ if spotify_info.get('duration_formatted'):
44
+ self.console.print(f"[cyan]Duration: [red]{spotify_info['duration_formatted']}[/red]")
45
+ if spotify_info.get('label'):
46
+ self.console.print(f"[cyan]Label: [red]{spotify_info['label']}[/red]")
47
+
48
+ def display_youtube_results(self, youtube_results: List[Dict]):
49
+ """
50
+ Display YouTube results
51
+
52
+ Args:
53
+ youtube_results (List[Dict]): List of YouTube videos
54
+ """
55
+ if not youtube_results:
56
+ self.console.print("[red]No results found")
57
+ return
58
+
59
+ for i, video in enumerate(youtube_results, 1):
60
+ self.console.print(f"[green]{i}. {video['title']}[/green], Channel: [cyan]{video['channel']}[/cyan], Duration: [red]{video['duration_formatted']}[/red], Difference: [yellow]{video.get('duration_difference', 'N/A')}s[/yellow]")
61
+
62
+ def show_download_menu(self, results_count: int):
63
+ """
64
+ Show download selection menu
65
+
66
+ Args:
67
+ results_count (int): Number of available results
68
+ """
69
+ self.console.print("\n[bold cyan]Download selection: [/bold cyan]")
70
+ self.console.print("[dim]Options:[/dim]")
71
+ self.console.print(f"• [green]1-{results_count}[/green]: Download the corresponding video")
72
+ self.console.print("• [yellow]ENTER[/yellow]: Automatically download the first (most similar)")
73
+ self.console.print("• [red]0[/red]: Exit without downloading")
74
+
75
+ def get_download_choice(self, max_results: int) -> int:
76
+ """
77
+ Get user choice for download
78
+
79
+ Args:
80
+ max_results (int): Maximum number of results
81
+
82
+ Returns:
83
+ int: User choice (0 = exit, 1-n = video)
84
+ """
85
+ if AUTO_FIRST:
86
+ return 1
87
+
88
+ while True:
89
+ try:
90
+ choice = Prompt.ask(
91
+ "\n[bold purple]Which video do you want to download?[/bold purple]",
92
+ default="1"
93
+ ).strip()
94
+
95
+ if choice == "0":
96
+ return 0
97
+
98
+ # Default: first video
99
+ if choice == "" or choice == "1":
100
+ return 1
101
+
102
+ # Check for valid number
103
+ choice_num = int(choice)
104
+ if 1 <= choice_num <= max_results:
105
+ return choice_num
106
+ else:
107
+ self.console.print(f"[red]Invalid choice. Enter a number between 1 and {max_results}[/red]")
108
+ continue
109
+
110
+ except ValueError:
111
+ self.console.print("[red]Enter a valid number[/red]")
112
+ continue
113
+
114
+ def show_download_info(self, music_folder, filename: str):
115
+ """
116
+ Show download information
117
+
118
+ Args:
119
+ music_folder: Destination folder
120
+ filename (str): File name
121
+ """
122
+ self.console.print(f"[blue]File name: {filename}[/blue]")
123
+ self.console.print(f"[blue]Destination folder: {music_folder}[/blue]")
124
+
125
+ def show_download_start(self, video_title: str, video_url: str):
126
+ """
127
+ Show download start
128
+
129
+ Args:
130
+ video_title (str): Video title
131
+ video_url (str): Video URL
132
+ """
133
+ self.console.print(f"[yellow]\nDownloading: {video_title}[/yellow]")
134
+ self.console.print(f"[dim]URL: {video_url}[/dim]")
135
+
136
+ def show_success(self, message: str):
137
+ """Show success message"""
138
+ self.console.print(f"[green]{message}[/green]")
139
+
140
+ def show_error(self, message: str):
141
+ """Show error message"""
142
+ self.console.print(f"[red]{message}[/red]")
143
+
144
+ def show_warning(self, message: str):
145
+ """Show warning message"""
146
+ self.console.print(f"[yellow]{message}[/yellow]")
147
+
148
+ def show_info(self, message: str):
149
+ """Show informational message"""
150
+ self.console.print(f"[blue]{message}[/blue]")
151
+
152
+ def get_spotify_url(self) -> str:
153
+ """
154
+ Get Spotify URL from the user
155
+
156
+ Returns:
157
+ str: Entered Spotify URL
158
+ """
159
+ while True:
160
+ url = Prompt.ask("[purple]Enter Spotify URL[/purple][green]").strip()
161
+
162
+ if not url:
163
+ self.console.print("[red]URL cannot be empty. Please enter a Spotify track URL.[/red]")
164
+ continue
165
+
166
+ if "/track/" in url or "/playlist/" in url:
167
+ return url
168
+
169
+ self.console.print("[red]Invalid format. Please enter a valid Spotify track URL.[/red]")
170
+
171
+ def start_message(self):
172
+ """Display a stylized start message in the console."""
173
+
174
+ msg = r'''
175
+ _____ _ _ ___
176
+ | _ |___ ___ ___ _ _ _ ___ ___ _ _ ___ ___ ___| |_|_| _|_ _
177
+ | | _| _| . | | | | .'| _| |_'_| |_ -| . | . | _| | _| | |
178
+ |__|__|_| |_| |___|_____|__,|_| |_,_| |___| _|___|_| |_|_| |_ |
179
+ |_| |___|
180
+ '''
181
+
182
+ if CLEAN_CONSOLE:
183
+ os.system("cls" if platform.system() == 'Windows' else "clear")
184
+
185
+ if SHOW_MESSAGE:
186
+ self.console.print(f"[purple]{msg}")
187
+ separator = "_" * (self.console.width - 2)
188
+ self.console.print(f"[cyan]{separator}[/cyan]\n")
@@ -0,0 +1,129 @@
1
+ # 05.04.2024
2
+
3
+ import re
4
+ from pathlib import Path
5
+ from typing import Optional
6
+
7
+
8
+ # External imports
9
+ from unidecode import unidecode
10
+
11
+
12
+ class FileUtils:
13
+
14
+ @staticmethod
15
+ def get_music_folder() -> Path:
16
+ """
17
+ Gets the path to the Music folder.
18
+
19
+ Returns:
20
+ Path: Path to the Music folder
21
+ """
22
+ music_folder = Path.home() / "Music"
23
+ if not music_folder.exists():
24
+
25
+ # If "Music" does not exist, check for Italian "Musica" and rename it to "Music"
26
+ musica_folder = Path.home() / "Musica"
27
+ if musica_folder.exists():
28
+ musica_folder.rename(music_folder)
29
+ else:
30
+ music_folder.mkdir(exist_ok=True)
31
+
32
+ return music_folder
33
+
34
+ @staticmethod
35
+ def sanitize_filename(filename: str) -> str:
36
+ """
37
+ Cleans the filename of invalid characters and applies transliteration.
38
+
39
+ Args:
40
+ filename (str): Filename to clean
41
+
42
+ Returns:
43
+ str: Cleaned filename
44
+ """
45
+ # Transliterate to ASCII
46
+ filename = unidecode(filename)
47
+
48
+ # Remove/replace invalid characters for filenames
49
+ invalid_chars = '<>:"/\\|?*'
50
+ for char in invalid_chars:
51
+ filename = filename.replace(char, '')
52
+
53
+ # Remove multiple spaces and trim
54
+ filename = re.sub(r'\s+', ' ', filename).strip()
55
+
56
+ if len(filename) > 200:
57
+ filename = filename[:200]
58
+
59
+ return filename
60
+
61
+ @staticmethod
62
+ def create_filename(artist: str, title: str, extension: str = "mp3") -> str:
63
+ """
64
+ Creates a filename in the format: Artist - Title.extension
65
+
66
+ Args:
67
+ artist (str): Artist name
68
+ title (str): Song title
69
+ extension (str): File extension
70
+
71
+ Returns:
72
+ str: Formatted filename
73
+ """
74
+ clean_artist = FileUtils.sanitize_filename(artist)
75
+ clean_title = FileUtils.sanitize_filename(title)
76
+
77
+ # Create format: Artist - Title
78
+ filename = f"{clean_artist} - {clean_title}"
79
+
80
+ return filename
81
+
82
+ @staticmethod
83
+ def get_download_path(artist: str, title: str) -> Path:
84
+ """
85
+ Gets the full path for the download.
86
+
87
+ Args:
88
+ artist (str): Artist name
89
+ title (str): Song title
90
+
91
+ Returns:
92
+ Path: Full file path
93
+ """
94
+ music_folder = FileUtils.get_music_folder()
95
+ filename = FileUtils.create_filename(artist, title)
96
+
97
+ return music_folder / filename
98
+
99
+ @staticmethod
100
+ def file_exists(filepath: Path) -> bool:
101
+ """
102
+ Checks if a file exists.
103
+
104
+ Args:
105
+ filepath (Path): File path
106
+
107
+ Returns:
108
+ bool: True if the file exists
109
+ """
110
+ return filepath.exists()
111
+
112
+ @staticmethod
113
+ def find_downloaded_file(base_path: Path, pattern: str) -> Optional[Path]:
114
+ """
115
+ Finds a downloaded file using a pattern.
116
+
117
+ Args:
118
+ base_path (Path): Base folder for the search
119
+ pattern (str): Search pattern
120
+
121
+ Returns:
122
+ Path: First file found or None
123
+ """
124
+ try:
125
+ files = list(base_path.glob(pattern))
126
+ return files[0] if files else None
127
+
128
+ except Exception:
129
+ return None
@@ -0,0 +1,18 @@
1
+ # 05.04.2024
2
+
3
+
4
+ # External library
5
+ import ua_generator
6
+
7
+
8
+ # Variable
9
+ ua = ua_generator.generate(device='desktop', browser=('chrome', 'edge'))
10
+
11
+
12
+ def get_userAgent() -> str:
13
+ return ua_generator.generate(device='desktop', browser=('chrome', 'edge')).text
14
+
15
+
16
+
17
+ def get_headers() -> dict:
18
+ return ua.headers.get()