Inicial, descargar y conversion basicas

This commit is contained in:
2026-08-27 14:38:59 +02:00
commit b05ea516ee
11 changed files with 611 additions and 0 deletions

0
utils/__init__.py Normal file
View File

88
utils/converter.py Normal file
View File

@@ -0,0 +1,88 @@
import os
import subprocess
import re
from PyQt6.QtCore import QThread, pyqtSignal
class MediaConverter(QThread):
progress_signal = pyqtSignal(str) # Mensaje de estado
finished_signal = pyqtSignal(bool, str) # (Éxito?, Mensaje final)
def __init__(self, input_file, output_file, preset_key, bin_dir="bin"):
super().__init__()
self.input_file = input_file
self.output_file = output_file
self.preset_key = preset_key
self.bin_dir = os.path.abspath(bin_dir)
def run(self):
ffmpeg_exe = os.path.join(self.bin_dir, "ffmpeg.exe")
if not os.path.exists(ffmpeg_exe):
self.finished_signal.emit(False, "No se encontró ffmpeg.exe en la carpeta bin.")
return
if not os.path.exists(self.input_file):
self.finished_signal.emit(False, "El archivo de entrada no existe.")
return
# Configuración de argumentos según el preset
if self.preset_key == "mp4_std":
# MP4 Estándar sin reescalado
codec_args = ["-c:v", "libx264", "-c:a", "aac"]
elif self.preset_key == "mp4_1080p":
# MP4 Max 1080p Verticales (mantiene aspecto, no escala si es <= 1080)
codec_args = ["-vf", "scale=-2:'min(1080,ih)'", "-c:v", "libx264", "-c:a", "aac"]
elif self.preset_key == "mp3_192k":
# Estéreo completo a MP3 192 kbps
codec_args = ["-vn", "-c:a", "libmp3lame", "-b:a", "192k"]
elif self.preset_key == "mp3_ch1":
# Solo Canal 1 (Izquierdo / Left) -> Mono MP3 192 kbps
codec_args = ["-vn", "-af", "pan=mono|c0=c0", "-c:a", "libmp3lame", "-b:a", "192k"]
elif self.preset_key == "mp3_ch2":
# Solo Canal 2 (Derecho / Right) -> Mono MP3 192 kbps
codec_args = ["-vn", "-af", "pan=mono|c0=c1", "-c:a", "libmp3lame", "-b:a", "192k"]
else:
self.finished_signal.emit(False, "Preset no reconocido.")
return
# Sobrescribir archivo de salida si ya existe (-y)
command = [ffmpeg_exe, "-y", "-i", self.input_file] + codec_args + [self.output_file]
try:
startupinfo = None
if os.name == 'nt':
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
process = subprocess.Popen(
command,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
startupinfo=startupinfo,
encoding='utf-8',
errors='replace'
)
for line in process.stdout:
line_str = line.strip()
if line_str:
if "time=" in line_str:
match = re.search(r'time=(\d{2}:\d{2}:\d{2}\.\d{2})', line_str)
if match:
self.progress_signal.emit(f"Procesando... Tiempo: {match.group(1)}")
process.wait()
if process.returncode == 0:
self.finished_signal.emit(True, "¡Conversión completada con éxito!")
else:
self.finished_signal.emit(False, "Ocurrió un error durante la conversión con FFmpeg.")
except Exception as e:
self.finished_signal.emit(False, f"Error de ejecución: {str(e)}")

81
utils/downloader.py Normal file
View File

@@ -0,0 +1,81 @@
import os
import subprocess
import re
from PyQt6.QtCore import QThread, pyqtSignal
class VideoDownloader(QThread):
progress_signal = pyqtSignal(str)
finished_signal = pyqtSignal(bool, str)
def __init__(self, url, download_path, bin_dir="bin"):
super().__init__()
self.url = url
self.download_path = download_path
self.bin_dir = os.path.abspath(bin_dir)
def run(self):
ytdlp_exe = os.path.join(self.bin_dir, "yt-dlp.exe")
ffmpeg_exe = os.path.join(self.bin_dir, "ffmpeg.exe")
if not os.path.exists(ytdlp_exe):
self.finished_signal.emit(False, f"No se encontró yt-dlp.exe en: {ytdlp_exe}")
return
if not os.path.exists(ffmpeg_exe):
self.finished_signal.emit(False, f"No se encontró ffmpeg.exe en: {ffmpeg_exe}")
return
# Creamos una copia del entorno de sistema y añadimos la carpeta 'bin' al PATH
# Esto asegura que tanto yt-dlp como subprocess encuentren ffmpeg y ffprobe sin fallar
env = os.environ.copy()
env["PATH"] = self.bin_dir + os.pathsep + env.get("PATH", "")
# Comando idéntico a tu terminal
command = [
ytdlp_exe,
"-f", "bestvideo[ext=mp4]+bestaudio[ext=m4a]/bestvideo+bestaudio",
"--merge-output-format", "mp4",
"--ffmpeg-location", self.bin_dir,
"-P", self.download_path,
self.url
]
try:
startupinfo = None
if os.name == 'nt':
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
process = subprocess.Popen(
command,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
startupinfo=startupinfo,
env=env, # Le pasamos el entorno enriquecido con el PATH a bin
encoding='utf-8',
errors='replace'
)
for line in process.stdout:
line_str = line.strip()
if line_str:
# Imprime en la consola de Python para poder ver cualquier advertencia de yt-dlp
print(line_str)
if "[download]" in line_str and "%" in line_str:
match = re.search(r'(\d+\.\d+%)', line_str)
if match:
self.progress_signal.emit(f"Descargando: {match.group(1)}")
elif "[Merger]" in line_str or "Merging" in line_str:
self.progress_signal.emit("Uniendo vídeo y audio en MP4...")
process.wait()
if process.returncode == 0:
self.finished_signal.emit(True, "¡Vídeo descargado y unido correctamente!")
else:
self.finished_signal.emit(False, "Error durante la descarga o la fusión.")
except Exception as e:
self.finished_signal.emit(False, f"Error de ejecución: {str(e)}")

93
utils/updater.py Normal file
View File

@@ -0,0 +1,93 @@
import os
import requests
import zipfile
import shutil
from PyQt6.QtCore import QThread, pyqtSignal
# URL de yt-dlp ejecutable directo
URL_YTDLP = "https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp.exe"
class DependencyChecker(QThread):
progress_signal = pyqtSignal(str, int) # (Mensaje, Porcentaje)
finished_signal = pyqtSignal()
def __init__(self, bin_dir="bin"):
super().__init__()
self.bin_dir = os.path.abspath(bin_dir)
def run(self):
os.makedirs(self.bin_dir, exist_ok=True)
ytdlp_path = os.path.join(self.bin_dir, "yt-dlp.exe")
ffmpeg_path = os.path.join(self.bin_dir, "ffmpeg.exe")
ffprobe_path = os.path.join(self.bin_dir, "ffprobe.exe")
# Limpiar archivos corruptos o de tamaño inferior a 1 MB
self.cleanup_invalid_files([ytdlp_path, ffmpeg_path, ffprobe_path])
# 1. Verificar / Descargar yt-dlp.exe
if not os.path.exists(ytdlp_path):
self.download_file("yt-dlp.exe", URL_YTDLP, ytdlp_path)
# 2. Verificar / Descargar FFmpeg y FFprobe reales desde la API de GitHub
if not os.path.exists(ffmpeg_path) or not os.path.exists(ffprobe_path):
self.download_ffmpeg_suite()
self.progress_signal.emit("Todos los componentes están listos.", 100)
self.finished_signal.emit()
def cleanup_invalid_files(self, paths):
for p in paths:
if os.path.exists(p) and os.path.getsize(p) < 1000000: # Menor a ~1 MB
try:
os.remove(p)
except Exception:
pass
def download_file(self, name, url, save_path):
headers = {"User-Agent": "Mozilla/5.0"}
response = requests.get(url, headers=headers, stream=True, allow_redirects=True)
total_size = int(response.headers.get('content-length', 0))
downloaded = 0
with open(save_path, 'wb') as file:
for chunk in response.iter_content(chunk_size=16384):
if chunk:
file.write(chunk)
downloaded += len(chunk)
if total_size > 0:
percent = int((downloaded / total_size) * 100)
self.progress_signal.emit(f"Descargando {name}...", percent)
def download_ffmpeg_suite(self):
self.progress_signal.emit("Buscando versión de FFmpeg...", 5)
headers = {"User-Agent": "Mozilla/5.0"}
# Consultar la API de GitHub de GyanD/codexffmpeg para obtener la URL directa del ZIP
api_url = "https://api.github.com/repos/GyanD/codexffmpeg/releases/latest"
r = requests.get(api_url, headers=headers).json()
download_url = None
for asset in r.get("assets", []):
if asset["name"].endswith("essentials_build.zip") or asset["name"].endswith("full_build.zip"):
download_url = asset["browser_download_url"]
break
if not download_url:
# URL de respaldo estática si falla la API
download_url = "https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-essentials.zip"
zip_path = os.path.join(self.bin_dir, "ffmpeg.zip")
self.download_file("FFmpeg Suite (ZIP)", download_url, zip_path)
self.progress_signal.emit("Extrayendo FFmpeg y FFprobe...", 90)
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
for member in zip_ref.namelist():
filename = os.path.basename(member)
if filename in ["ffmpeg.exe", "ffprobe.exe"]:
target_path = os.path.join(self.bin_dir, filename)
with zip_ref.open(member) as source, open(target_path, "wb") as target:
shutil.copyfileobj(source, target)
if os.path.exists(zip_path):
os.remove(zip_path)