Skip to content

Commit 3ebcc4e

Browse files
committed
tk/qt: PowerShell-safe args, winget button, dynamic FFmpeg download/extract, no-audio (-an), ASF, default resolution 'original'; fix syntax/indentation
1 parent 11894b7 commit 3ebcc4e

2 files changed

Lines changed: 516 additions & 35 deletions

File tree

GUI_pyqt6_WINFF.py

Lines changed: 73 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import webbrowser
1111
import tempfile
1212
import zipfile
13+
import shlex
1314
from io import BytesIO
1415
from functools import partial
1516

@@ -34,10 +35,13 @@ def _build_ui(self):
3435
info_btn.clicked.connect(self.show_video_info)
3536
dl_btn = QPushButton('Baixar FFmpeg')
3637
dl_btn.clicked.connect(self.download_ffmpeg_and_maybe_install)
38+
winget_btn = QPushButton('Instalar FFmpeg (winget)')
39+
winget_btn.clicked.connect(self.install_ffmpeg_via_winget)
3740
top_layout.addWidget(about_btn)
3841
top_layout.addStretch()
3942
top_layout.addWidget(info_btn)
4043
top_layout.addWidget(dl_btn)
44+
top_layout.addWidget(winget_btn)
4145
layout.addLayout(top_layout)
4246

4347
# input file
@@ -244,7 +248,7 @@ def save_config(self):
244248
with open(file, 'w') as f:
245249
cp.write(f)
246250

247-
def build_command(self):
251+
def build_command_list(self):
248252
inp = self.input_edit.text()
249253
fmt = self.format_combo.currentText()
250254
video_bitrate = self.video_bitrate.text()
@@ -263,40 +267,45 @@ def build_command(self):
263267
out_dir = self.output_edit.text()
264268
output_file = os.path.join(out_dir, os.path.splitext(os.path.basename(inp))[0] + '.' + fmt) if inp else ''
265269

266-
cmd = f'"{ffmpeg}" -y -i "{inp}"' if inp else ''
270+
if not inp:
271+
return []
272+
args = [ffmpeg, '-y', '-i', inp]
267273
if video_bitrate:
268-
cmd += f' -b:v {video_bitrate}'
274+
args += ['-b:v', video_bitrate]
269275
if self.no_audio_chk.isChecked():
270-
cmd += ' -an'
276+
args += ['-an']
271277
elif audio_bitrate:
272-
cmd += f' -b:a {audio_bitrate}'
278+
args += ['-b:a', audio_bitrate]
273279
if resolution != 'original':
274-
cmd += f' -s {resolution}'
280+
args += ['-s', resolution]
275281
if frame_rate:
276-
cmd += f' -r {frame_rate}'
282+
args += ['-r', frame_rate]
277283
if not self.no_audio_chk.isChecked() and audio_sample_rate:
278-
cmd += f' -ar {audio_sample_rate}'
284+
args += ['-ar', audio_sample_rate]
279285
if not self.no_audio_chk.isChecked() and audio_channels:
280-
cmd += f' -ac {audio_channels}'
286+
args += ['-ac', audio_channels]
281287
if video_codec != 'auto':
282-
cmd += f' -vcodec {video_codec}'
288+
args += ['-vcodec', video_codec]
283289
if not self.no_audio_chk.isChecked() and audio_codec != 'auto':
284-
cmd += f' -acodec {audio_codec}'
290+
args += ['-acodec', audio_codec]
285291
if output_file:
286-
cmd += f' "{output_file}"'
287-
return cmd
292+
args += [output_file]
293+
return args
288294

289295
def update_command_display(self):
290-
cmd = self.build_command()
291-
self.command_display.setPlainText(cmd)
296+
args = self.build_command_list()
297+
if not args:
298+
self.command_display.setPlainText('')
299+
return
300+
self.command_display.setPlainText(' '.join(shlex.quote(a) for a in args))
292301

293302
def convert_video(self):
294-
cmd = self.build_command()
295-
if not cmd:
303+
args = self.build_command_list()
304+
if not args:
296305
QtWidgets.QMessageBox.warning(self, 'Erro', 'Preencha os campos necessários')
297306
return
298307
try:
299-
subprocess.run(cmd, shell=True, check=True)
308+
subprocess.run(args, check=True)
300309
QtWidgets.QMessageBox.information(self, 'Sucesso', 'Vídeo convertido com sucesso!')
301310
except subprocess.CalledProcessError as e:
302311
QtWidgets.QMessageBox.critical(self, 'Erro', f'Falha ao converter vídeo.\nErro: {e}')
@@ -309,7 +318,19 @@ def show_video_info(self):
309318
if not inp:
310319
QtWidgets.QMessageBox.warning(self, 'Atenção', 'Nenhum arquivo selecionado')
311320
return
312-
ffprobe = os.path.join(os.path.dirname(self.ffmpeg_path.text()), 'ffprobe.exe' if os.name == 'nt' else 'ffprobe')
321+
# Resolve ffprobe path: if ffmpeg_path is a directory or a full path, try alongside; otherwise fall back to PATH
322+
configured = self.ffmpeg_path.text().strip()
323+
ffprobe_name = 'ffprobe.exe' if os.name == 'nt' else 'ffprobe'
324+
candidate = None
325+
if configured and os.path.isabs(configured):
326+
base = configured
327+
if os.path.isdir(base):
328+
candidate = os.path.join(base, ffprobe_name)
329+
else:
330+
candidate = os.path.join(os.path.dirname(base), ffprobe_name)
331+
if not candidate or not os.path.exists(candidate):
332+
candidate = ffprobe_name # rely on PATH
333+
ffprobe = candidate
313334
if not os.path.exists(ffprobe):
314335
QtWidgets.QMessageBox.critical(self, 'Erro', 'ffprobe não encontrado no caminho do ffmpeg')
315336
return
@@ -392,23 +413,41 @@ def worker():
392413
ff = self._extract_ffmpeg_zip(r.content)
393414
if ff:
394415
# Update UI on main thread
395-
QtCore.QMetaObject.invokeMethod(
396-
self.ffmpeg_path, 'setText', QtCore.Qt.ConnectionType.QueuedConnection, QtCore.Q_ARG(str, ff)
397-
)
398-
QtCore.QMetaObject.invokeMethod(
399-
self, 'show_info_msg', QtCore.Qt.ConnectionType.QueuedConnection,
400-
QtCore.Q_ARG(str, 'FFmpeg baixado e extraído com sucesso.')
401-
)
416+
QtCore.QTimer.singleShot(0, lambda: self.ffmpeg_path.setText(ff))
417+
QtCore.QTimer.singleShot(0, lambda: self.show_info_msg('FFmpeg baixado e extraído com sucesso.'))
418+
else:
419+
QtCore.QTimer.singleShot(0, lambda: self.show_error_msg('Não foi possível localizar o executável ffmpeg após extração.'))
420+
except Exception as e:
421+
QtCore.QTimer.singleShot(0, lambda: self.show_error_msg(f'Falha no download: {e}'))
422+
423+
threading.Thread(target=worker, daemon=True).start()
424+
425+
def install_ffmpeg_via_winget(self):
426+
if platform.system() != 'Windows':
427+
self.show_error_msg('Instalação via winget só está disponível no Windows.')
428+
return
429+
430+
def worker():
431+
try:
432+
# Try common ids: Gyan.FFmpeg, then FFmpeg.FFmpeg
433+
cmds = [
434+
['winget', 'install', '-e', '--id', 'Gyan.FFmpeg'],
435+
['winget', 'install', '-e', '--id', 'FFmpeg.FFmpeg']
436+
]
437+
ok = False
438+
for c in cmds:
439+
p = subprocess.run(c, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
440+
if p.returncode == 0:
441+
ok = True
442+
break
443+
if ok:
444+
# After install, assume ffmpeg is on PATH
445+
QtCore.QTimer.singleShot(0, lambda: self.ffmpeg_path.setText('ffmpeg'))
446+
QtCore.QTimer.singleShot(0, lambda: self.show_info_msg('FFmpeg instalado via winget.'))
402447
else:
403-
QtCore.QMetaObject.invokeMethod(
404-
self, 'show_error_msg', QtCore.Qt.ConnectionType.QueuedConnection,
405-
QtCore.Q_ARG(str, 'Não foi possível localizar o executável ffmpeg após extração.')
406-
)
448+
QtCore.QTimer.singleShot(0, lambda: self.show_error_msg('Falha ao instalar via winget.'))
407449
except Exception as e:
408-
QtCore.QMetaObject.invokeMethod(
409-
self, 'show_error_msg', QtCore.Qt.ConnectionType.QueuedConnection,
410-
QtCore.Q_ARG(str, f'Falha no download: {e}')
411-
)
450+
QtCore.QTimer.singleShot(0, lambda: self.show_error_msg(f'Erro winget: {e}'))
412451

413452
threading.Thread(target=worker, daemon=True).start()
414453

0 commit comments

Comments
 (0)