Reconvertir app/ a carpeta normal, no submódulo
This commit is contained in:
16
app/Dockerfile
Normal file
16
app/Dockerfile
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
FROM python:3.11-slim
|
||||||
|
|
||||||
|
# Instala dependencias del sistema
|
||||||
|
RUN apt-get update && apt-get install -y ffmpeg curl
|
||||||
|
|
||||||
|
# Instala yt-dlp
|
||||||
|
RUN curl -L https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp_linux -o /usr/local/bin/yt-dlp && \
|
||||||
|
#RUN curl -L https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp -o /usr/local/bin/yt-dlp && \
|
||||||
|
chmod a+rx /usr/local/bin/yt-dlp
|
||||||
|
|
||||||
|
# Crea carpetas necesarias
|
||||||
|
WORKDIR /app
|
||||||
|
COPY . /app
|
||||||
|
RUN pip install -r requirements.txt
|
||||||
|
|
||||||
|
CMD ["python", "main.py"]
|
||||||
104
app/main.py
Normal file
104
app/main.py
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
from flask import Flask, render_template, request, send_file
|
||||||
|
import subprocess
|
||||||
|
import os
|
||||||
|
import uuid
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
BASE_DOWNLOADS_DIR = "downloads"
|
||||||
|
|
||||||
|
os.makedirs(BASE_DOWNLOADS_DIR, exist_ok=True)
|
||||||
|
|
||||||
|
@app.route("/", methods=["GET", "POST"])
|
||||||
|
def index():
|
||||||
|
if request.method == "POST":
|
||||||
|
url = request.form.get("url")
|
||||||
|
mode = request.form.get("mode") # 'audio' o 'video'
|
||||||
|
download_type = request.form.get("type") # 'single' o 'playlist'
|
||||||
|
|
||||||
|
if not url:
|
||||||
|
return render_template("index.html", error="URL obligatoria")
|
||||||
|
|
||||||
|
|
||||||
|
# Modo single o playlist
|
||||||
|
if download_type == "single":
|
||||||
|
folder_id = str(uuid.uuid4())
|
||||||
|
folder_path = os.path.join(BASE_DOWNLOADS_DIR, folder_id)
|
||||||
|
os.makedirs(folder_path, exist_ok=True)
|
||||||
|
|
||||||
|
filename_template = "%(title)s.%(ext)s"
|
||||||
|
output_path = os.path.join(folder_path, filename_template)
|
||||||
|
|
||||||
|
cmd = ["yt-dlp", "-o", output_path]
|
||||||
|
|
||||||
|
if mode == "audio":
|
||||||
|
cmd += ["-x", "--audio-format", "mp3"]
|
||||||
|
|
||||||
|
cmd.append(url)
|
||||||
|
|
||||||
|
try:
|
||||||
|
subprocess.run(cmd, check=True)
|
||||||
|
|
||||||
|
# Buscar el archivo descargado (único archivo en carpeta)
|
||||||
|
files = os.listdir(folder_path)
|
||||||
|
if not files:
|
||||||
|
return render_template("index.html", error="No se pudo descargar el archivo.")
|
||||||
|
|
||||||
|
downloaded_file_path = os.path.join(folder_path, files[0])
|
||||||
|
return send_file(downloaded_file_path, as_attachment=True, download_name=files[0])
|
||||||
|
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
return render_template("index.html", error="Error al descargar el vídeo.")
|
||||||
|
|
||||||
|
elif download_type == "playlist":
|
||||||
|
folder_id = str(uuid.uuid4())
|
||||||
|
folder_path = os.path.join(BASE_DOWNLOADS_DIR, folder_id)
|
||||||
|
os.makedirs(folder_path, exist_ok=True)
|
||||||
|
|
||||||
|
# Obtener título real de la playlist
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
["yt-dlp", "--flat-playlist", "--print", "%(playlist_title)s", url],
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
check=True,
|
||||||
|
text=True
|
||||||
|
)
|
||||||
|
#playlist_title = result.stdout.strip()
|
||||||
|
playlist_title = result.stdout.strip().splitlines()[0]
|
||||||
|
# Limpiar el nombre para que sea válido como nombre de archivo
|
||||||
|
playlist_title_clean = "".join(c for c in playlist_title if c.isalnum() or c in " _-").strip().replace(" ", "_")
|
||||||
|
print(f"Título original: {playlist_title}")
|
||||||
|
print(f"Título limpio: {playlist_title_clean}")
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
return render_template("index.html", error="Error al obtener el título de la playlist.")
|
||||||
|
|
||||||
|
output_path = os.path.join(folder_path, "%(title)s.%(ext)s")
|
||||||
|
cmd = ["yt-dlp", "-o", output_path]
|
||||||
|
|
||||||
|
if mode == "audio":
|
||||||
|
cmd += ["-x", "--audio-format", "mp3"]
|
||||||
|
|
||||||
|
cmd.append(url)
|
||||||
|
|
||||||
|
try:
|
||||||
|
subprocess.run(cmd, check=True)
|
||||||
|
|
||||||
|
# Comprimir en ZIP
|
||||||
|
zip_path = shutil.make_archive(folder_path, 'zip', folder_path)
|
||||||
|
zip_filename = f"{playlist_title_clean}.zip"
|
||||||
|
|
||||||
|
# Enviar al navegador
|
||||||
|
return send_file(zip_path, as_attachment=True, download_name=zip_filename, mimetype='application/zip')
|
||||||
|
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
return render_template("index.html", error="Error al descargar la lista.")
|
||||||
|
|
||||||
|
else:
|
||||||
|
return render_template("index.html", error="Tipo de descarga no válido.")
|
||||||
|
|
||||||
|
return render_template("index.html")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app.run(host="0.0.0.0", port=5000)
|
||||||
|
|
||||||
87
app/main.py.bak
Normal file
87
app/main.py.bak
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
from flask import Flask, render_template, request, send_file
|
||||||
|
import subprocess
|
||||||
|
import os
|
||||||
|
import uuid
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
BASE_DOWNLOADS_DIR = "downloads"
|
||||||
|
|
||||||
|
os.makedirs(BASE_DOWNLOADS_DIR, exist_ok=True)
|
||||||
|
|
||||||
|
@app.route("/", methods=["GET", "POST"])
|
||||||
|
def index():
|
||||||
|
if request.method == "POST":
|
||||||
|
url = request.form.get("url")
|
||||||
|
mode = request.form.get("mode") # 'audio' o 'video'
|
||||||
|
download_type = request.form.get("type") # 'single' o 'playlist'
|
||||||
|
|
||||||
|
if not url:
|
||||||
|
return render_template("index.html", error="URL obligatoria")
|
||||||
|
|
||||||
|
# Modo single o playlist
|
||||||
|
if download_type == "single":
|
||||||
|
unique_id = str(uuid.uuid4())
|
||||||
|
filename_template = f"{unique_id}.%(ext)s"
|
||||||
|
output_path = os.path.join(BASE_DOWNLOADS_DIR, filename_template)
|
||||||
|
|
||||||
|
cmd = ["yt-dlp", "-o", output_path]
|
||||||
|
|
||||||
|
if mode == "audio":
|
||||||
|
cmd += ["-x", "--audio-format", "mp3"]
|
||||||
|
|
||||||
|
cmd.append(url)
|
||||||
|
|
||||||
|
try:
|
||||||
|
subprocess.run(cmd, check=True)
|
||||||
|
|
||||||
|
# Encuentra el archivo resultante
|
||||||
|
downloaded_file = None
|
||||||
|
for file in os.listdir(BASE_DOWNLOADS_DIR):
|
||||||
|
if file.startswith(unique_id):
|
||||||
|
downloaded_file = os.path.join(BASE_DOWNLOADS_DIR, file)
|
||||||
|
break
|
||||||
|
|
||||||
|
if not downloaded_file:
|
||||||
|
return render_template("index.html", error="No se encontró el archivo descargado.")
|
||||||
|
|
||||||
|
# Enviar al navegador y luego borrar
|
||||||
|
return send_file(downloaded_file, as_attachment=True, download_name=os.path.basename(downloaded_file), mimetype='application/octet-stream')
|
||||||
|
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
return render_template("index.html", error="Error al descargar el archivo.")
|
||||||
|
|
||||||
|
elif download_type == "playlist":
|
||||||
|
folder_id = str(uuid.uuid4())
|
||||||
|
folder_path = os.path.join(BASE_DOWNLOADS_DIR, folder_id)
|
||||||
|
os.makedirs(folder_path, exist_ok=True)
|
||||||
|
|
||||||
|
output_path = os.path.join(folder_path, "%(title)s.%(ext)s")
|
||||||
|
cmd = ["yt-dlp", "-o", output_path]
|
||||||
|
|
||||||
|
if mode == "audio":
|
||||||
|
cmd += ["-x", "--audio-format", "mp3"]
|
||||||
|
|
||||||
|
cmd.append(url)
|
||||||
|
|
||||||
|
try:
|
||||||
|
subprocess.run(cmd, check=True)
|
||||||
|
|
||||||
|
# Comprimir en ZIP
|
||||||
|
zip_path = shutil.make_archive(folder_path, 'zip', folder_path)
|
||||||
|
|
||||||
|
# Enviar al navegador
|
||||||
|
# return send_file(zip_path, as_attachment=True, download_name="descarga_playlist.zip", mimetype='application/zip')
|
||||||
|
return send_file(zip_path, as_attachment=True, download_name="%(playlist_title).zip", mimetype='application/zip')
|
||||||
|
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
return render_template("index.html", error="Error al descargar la lista.")
|
||||||
|
|
||||||
|
else:
|
||||||
|
return render_template("index.html", error="Tipo de descarga no válido.")
|
||||||
|
|
||||||
|
return render_template("index.html")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app.run(host="0.0.0.0", port=5000)
|
||||||
|
|
||||||
1
app/requirements.txt
Normal file
1
app/requirements.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Flask
|
||||||
108
app/templates/index.html
Normal file
108
app/templates/index.html
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="es">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Descargador de YouTube</title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<style>
|
||||||
|
.fade-out {
|
||||||
|
opacity: 1;
|
||||||
|
transition: opacity 1s ease-out;
|
||||||
|
}
|
||||||
|
.fade-out.hide {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body class="bg-light">
|
||||||
|
<div class="container mt-5">
|
||||||
|
<h1 class="mb-4 text-center">Descargador de YouTube</h1>
|
||||||
|
|
||||||
|
<!-- Tabs -->
|
||||||
|
<ul class="nav nav-tabs" id="downloadTab" role="tablist">
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link active" id="single-tab" data-bs-toggle="tab" data-bs-target="#single" type="button" role="tab">Vídeo / MP3 individual</button>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link" id="playlist-tab" data-bs-toggle="tab" data-bs-target="#playlist" type="button" role="tab">Lista de reproducción</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="tab-content pt-4" id="downloadTabContent">
|
||||||
|
<!-- Descarga individual -->
|
||||||
|
<div class="tab-pane fade show active" id="single" role="tabpanel">
|
||||||
|
<form method="POST">
|
||||||
|
<input type="hidden" name="type" value="single">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="url1" class="form-label">URL del vídeo</label>
|
||||||
|
<input type="url" class="form-control" name="url" id="url1" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Formato</label><br>
|
||||||
|
<div class="form-check form-check-inline">
|
||||||
|
<input class="form-check-input" type="radio" name="mode" id="video1" value="video" checked>
|
||||||
|
<label class="form-check-label" for="video1">Vídeo</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check form-check-inline">
|
||||||
|
<input class="form-check-input" type="radio" name="mode" id="audio1" value="audio">
|
||||||
|
<label class="form-check-label" for="audio1">MP3</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary">Descargar</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Descarga de lista -->
|
||||||
|
<div class="tab-pane fade" id="playlist" role="tabpanel">
|
||||||
|
<form method="POST">
|
||||||
|
<input type="hidden" name="type" value="playlist">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="url2" class="form-label">URL de la lista</label>
|
||||||
|
<input type="url" class="form-control" name="url" id="url2" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Formato</label><br>
|
||||||
|
<div class="form-check form-check-inline">
|
||||||
|
<input class="form-check-input" type="radio" name="mode" id="video2" value="video" checked>
|
||||||
|
<label class="form-check-label" for="video2">Vídeo</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check form-check-inline">
|
||||||
|
<input class="form-check-input" type="radio" name="mode" id="audio2" value="audio">
|
||||||
|
<label class="form-check-label" for="audio2">MP3</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary">Descargar Lista</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Modal -->
|
||||||
|
<div class="modal fade" id="feedbackModal" tabindex="-1" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content bg-light">
|
||||||
|
<div class="modal-body">
|
||||||
|
{% if success %}
|
||||||
|
<div class="alert alert-success fade-out" role="alert">{{ success }}</div>
|
||||||
|
{% elif error %}
|
||||||
|
<div class="alert alert-danger fade-out" role="alert">{{ error }}</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const modal = new bootstrap.Modal(document.getElementById('feedbackModal'), {});
|
||||||
|
{% if success or error %}
|
||||||
|
window.addEventListener("load", () => {
|
||||||
|
modal.show();
|
||||||
|
setTimeout(() => {
|
||||||
|
modal.hide();
|
||||||
|
}, 3000);
|
||||||
|
});
|
||||||
|
{% endif %}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
42
app/templates/index.html.bak
Normal file
42
app/templates/index.html.bak
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="es">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Descargador de YouTube</title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
<body class="bg-light">
|
||||||
|
<div class="container mt-5">
|
||||||
|
<h1 class="mb-4 text-center">Descargador de YouTube</h1>
|
||||||
|
|
||||||
|
{% if error %}
|
||||||
|
<div class="alert alert-danger">{{ error }}</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if success %}
|
||||||
|
<div class="alert alert-success">{{ success }}</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<form method="POST">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="url" class="form-label">URL de YouTube</label>
|
||||||
|
<input type="url" class="form-control" name="url" id="url" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Tipo de descarga</label><br>
|
||||||
|
<div class="form-check form-check-inline">
|
||||||
|
<input class="form-check-input" type="radio" name="mode" id="video" value="video" checked>
|
||||||
|
<label class="form-check-label" for="video">Vídeo</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check form-check-inline">
|
||||||
|
<input class="form-check-input" type="radio" name="mode" id="audio" value="audio">
|
||||||
|
<label class="form-check-label" for="audio">Audio (MP3)</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary">Descargar</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<hr class="my-4">
|
||||||
|
<p class="text-muted">Los archivos descargados se guardan en la carpeta <code>downloads/</code>.</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user