|
9 | 9 | import configparser |
10 | 10 | import json |
11 | 11 | import platform |
| 12 | +import threading |
| 13 | +import urllib.request |
| 14 | +import webbrowser |
| 15 | +from pathlib import Path |
12 | 16 |
|
13 | 17 | try: |
14 | 18 | import ffmpeg as ffmpeg_lib # optional: ffmpeg-python |
@@ -138,11 +142,48 @@ def select_ffmpeg_executable(): |
138 | 142 | config.write(configfile) |
139 | 143 | update_command_display() |
140 | 144 |
|
141 | | -def load_config_from_file(): |
142 | | - cfg = filedialog.askopenfilename(title="Carregar Configuração", filetypes=[("Configurações", "*.ini")]) |
143 | | - if cfg: |
144 | | - load_config(cfg) |
145 | | - messagebox.showinfo("Carregar Configuração", "Configuração carregada com sucesso!") |
| 145 | + |
| 146 | +def download_ffmpeg_and_maybe_install(): |
| 147 | + """Download a FFmpeg build to the user's Downloads folder. |
| 148 | + On Windows offer to try winget install if available and the user agrees. |
| 149 | + Runs in a background thread to avoid blocking the UI.""" |
| 150 | + def _worker(): |
| 151 | + try: |
| 152 | + downloads = Path.home() / 'Downloads' |
| 153 | + downloads.mkdir(parents=True, exist_ok=True) |
| 154 | + if os.name == 'nt': |
| 155 | + # Example Windows build (user-provided example) |
| 156 | + url = 'https://github.com/BtbN/FFmpeg-Builds/releases/download/latest/ffmpeg-n7.1-latest-win64-gpl-7.1.zip' |
| 157 | + out_path = downloads / url.split('/')[-1] |
| 158 | + messagebox.showinfo('Download', f'Baixando FFmpeg para {out_path} ...') |
| 159 | + urllib.request.urlretrieve(url, out_path) |
| 160 | + messagebox.showinfo('Download', f'Arquivo salvo em {out_path}') |
| 161 | + |
| 162 | + # Ask user if they want to attempt installation via winget |
| 163 | + if messagebox.askyesno('Instalar', 'Deseja tentar instalar via winget (Windows)?'): |
| 164 | + try: |
| 165 | + # Check winget availability |
| 166 | + res = subprocess.run(['winget', '--version'], capture_output=True, text=True) |
| 167 | + if res.returncode == 0: |
| 168 | + # Try a generic winget install for ffmpeg |
| 169 | + install_cmd = ['winget', 'install', 'ffmpeg', '-e'] |
| 170 | + proc = subprocess.run(install_cmd, capture_output=True, text=True) |
| 171 | + if proc.returncode == 0: |
| 172 | + messagebox.showinfo('Instalação', 'FFmpeg instalado via winget com sucesso.') |
| 173 | + else: |
| 174 | + messagebox.showwarning('Instalação', f'Falha na instalação via winget. Saída:\n{proc.stdout}\n{proc.stderr}') |
| 175 | + else: |
| 176 | + messagebox.showwarning('winget', 'winget não está disponível neste sistema.') |
| 177 | + except FileNotFoundError: |
| 178 | + messagebox.showwarning('winget', 'winget não encontrado.') |
| 179 | + else: |
| 180 | + # Non-Windows: open releases page in browser so user can choose |
| 181 | + webbrowser.open('https://github.com/BtbN/FFmpeg-Builds/releases') |
| 182 | + messagebox.showinfo('Download', 'Página de releases aberta no navegador. Faça o download manualmente.') |
| 183 | + except Exception as e: |
| 184 | + messagebox.showerror('Erro', f'Falha ao baixar/instalar FFmpeg: {e}') |
| 185 | + |
| 186 | + threading.Thread(target=_worker, daemon=True).start() |
146 | 187 |
|
147 | 188 | # Conversão e comando ffmpeg |
148 | 189 |
|
@@ -174,23 +215,29 @@ def convert_video(): |
174 | 215 | messagebox.showerror("Erro", f"O arquivo '{output_file}' já existe e não pode ser sobrescrito.") |
175 | 216 | return |
176 | 217 |
|
| 218 | + # Build command. If no audio is selected, add -an and skip audio options |
177 | 219 | command = f'"{ffmpeg_path}" -y -i "{input_file}"' |
178 | 220 | if video_bitrate: |
179 | 221 | command += f" -b:v {video_bitrate}" |
180 | | - if audio_bitrate: |
181 | | - command += f" -b:a {audio_bitrate}" |
| 222 | + if not no_audio_var.get(): |
| 223 | + if audio_bitrate: |
| 224 | + command += f" -b:a {audio_bitrate}" |
182 | 225 | if resolution != "original": |
183 | 226 | command += f" -s {resolution}" |
184 | 227 | if frame_rate: |
185 | 228 | command += f" -r {frame_rate}" |
186 | | - if audio_sample_rate: |
187 | | - command += f" -ar {audio_sample_rate}" |
188 | | - if audio_channels: |
189 | | - command += f" -ac {audio_channels}" |
| 229 | + if not no_audio_var.get(): |
| 230 | + if audio_sample_rate: |
| 231 | + command += f" -ar {audio_sample_rate}" |
| 232 | + if audio_channels: |
| 233 | + command += f" -ac {audio_channels}" |
190 | 234 | if video_codec != "auto": |
191 | 235 | command += f" -vcodec {video_codec}" |
192 | | - if audio_codec != "auto": |
193 | | - command += f" -acodec {audio_codec}" |
| 236 | + if not no_audio_var.get(): |
| 237 | + if audio_codec != "auto": |
| 238 | + command += f" -acodec {audio_codec}" |
| 239 | + if no_audio_var.get(): |
| 240 | + command += ' -an' |
194 | 241 |
|
195 | 242 | command += f" \"{output_file}\"" |
196 | 243 |
|
@@ -327,20 +374,26 @@ def update_command_display(event=None): |
327 | 374 | command = f'"{ffmpeg_path}" -y -i "{input_file}"' if input_file else '' |
328 | 375 | if video_bitrate: |
329 | 376 | command += f" -b:v {video_bitrate}" |
330 | | - if audio_bitrate: |
331 | | - command += f" -b:a {audio_bitrate}" |
| 377 | + if not no_audio_var.get(): |
| 378 | + if audio_bitrate: |
| 379 | + command += f" -b:a {audio_bitrate}" |
332 | 380 | if resolution != "original": |
333 | 381 | command += f" -s {resolution}" |
334 | 382 | if frame_rate: |
335 | 383 | command += f" -r {frame_rate}" |
336 | | - if audio_sample_rate: |
337 | | - command += f" -ar {audio_sample_rate}" |
338 | | - if audio_channels: |
339 | | - command += f" -ac {audio_channels}" |
| 384 | + if not no_audio_var.get(): |
| 385 | + if audio_sample_rate: |
| 386 | + command += f" -ar {audio_sample_rate}" |
| 387 | + if audio_channels: |
| 388 | + command += f" -ac {audio_channels}" |
340 | 389 | if video_codec != "auto": |
341 | 390 | command += f" -vcodec {video_codec}" |
342 | | - if audio_codec != "auto": |
343 | | - command += f" -acodec {audio_codec}" |
| 391 | + if not no_audio_var.get(): |
| 392 | + if audio_codec != "auto": |
| 393 | + command += f" -acodec {audio_codec}" |
| 394 | + if no_audio_var.get(): |
| 395 | + command += ' -an' |
| 396 | + |
344 | 397 | if output_file: |
345 | 398 | command += f" \"{output_file}\"" |
346 | 399 |
|
@@ -456,7 +509,25 @@ def show_about(): |
456 | 509 | ffmpeg_path_entry = tk.Entry(root, width=70) |
457 | 510 | ffmpeg_path_entry.grid(row=13, column=1, padx=10, pady=5) |
458 | 511 | ffmpeg_path_entry.bind("<KeyRelease>", lambda event: update_command_display()) |
459 | | - tk.Button(root, text="Procurar", command=select_ffmpeg_executable).grid(row=13, column=2, padx=10, pady=5) |
| 512 | +procurar_ffmpeg_btn = tk.Button(root, text="Procurar", command=select_ffmpeg_executable) |
| 513 | +procurar_ffmpeg_btn.grid(row=13, column=2, padx=10, pady=5) |
| 514 | + |
| 515 | +# Button to download FFmpeg |
| 516 | +download_ffmpeg_btn = tk.Button(root, text="Baixar FFmpeg (Downloads)", command=download_ffmpeg_and_maybe_install) |
| 517 | +download_ffmpeg_btn.grid(row=13, column=0, padx=10, pady=5, sticky='w') |
| 518 | + |
| 519 | +# Checkbox: arquivo sem áudio (desabilita campos de áudio e adiciona -an) |
| 520 | +no_audio_var = tk.BooleanVar() |
| 521 | +def _on_no_audio_toggle(): |
| 522 | + state = tk.DISABLED if no_audio_var.get() else tk.NORMAL |
| 523 | + audio_bitrate_entry.config(state=state) |
| 524 | + audio_sample_rate_entry.config(state=state) |
| 525 | + audio_channels_menu.config(state=state) |
| 526 | + audio_codec_menu.config(state=state) |
| 527 | + update_command_display() |
| 528 | + |
| 529 | +no_audio_check = tk.Checkbutton(root, text="Arquivo sem áudio (remover áudio)", variable=no_audio_var, command=_on_no_audio_toggle) |
| 530 | +no_audio_check.grid(row=14, column=2, padx=10, pady=5) |
460 | 531 |
|
461 | 532 | # Command display |
462 | 533 | tk.Label(root, text="Comando FFmpeg:").grid(row=14, column=0, padx=10, pady=5, sticky="w") |
|
0 commit comments