1010import webbrowser
1111import tempfile
1212import zipfile
13+ import shlex
1314from io import BytesIO
1415from 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.\n Erro: { 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