Tutorial: Crear un YouTube Local con FastAPI, React y un Bot de Telegram
En este tutorial, crearemos una aplicación web que permitirá descargar y reproducir videos localmente. Utilizaremos FastAPI para el backend, React para el frontend y un bot de Telegram para interactuar con la aplicación.
Tabla de contenidos
1. Configurar el Backend con FastAPI
Paso 1: Crear el Proyecto y Configurar el Entorno
Crear un directorio para tu proyecto
mkdir localYoutube
cd localYoutube
mkdir backend
cd backend
Crear un entorno virtual
python -m venv .venv
source .venv/bin/activate # En Windows: .venv\Scripts\activate
Instalar dependencias
pip install fastapi uvicorn yt-dlp python-telegram-bot python-decouple
Crea un archivo .env
en el directorio backend
para almacenar el token de Telegram:
TELEGRAM_TOKEN=your_telegram_bot_token
Paso 2: Crear el Archivo main.py
Crea el archivo main.py
y agrega el siguiente código:
from fastapi import FastAPI, HTTPException, BackgroundTasks
from fastapi.responses import FileResponse
from pydantic import BaseModel
from fastapi.middleware.cors import CORSMiddleware
from typing import List
import os
import yt_dlp
app = FastAPI()
# Configurar CORS
origins = [
"http://localhost:5173", # Dirección del frontend
"http://localhost:3000", # Otra dirección posible del frontend
# Añade aquí otras direcciones que necesites permitir
]
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Ruta donde se almacenan los videos
VIDEO_DIR = "videos"
if not os.path.exists(VIDEO_DIR):
os.makedirs(VIDEO_DIR)
class VideoURL(BaseModel):
url: str
def download_video(url: str):
ydl_opts = {
'outtmpl': os.path.join(VIDEO_DIR, '%(title)s.%(ext)s'),
'format': 'bestvideo+bestaudio/best',
}
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
ydl.download([url])
@app.get("/videos", response_model=List[str])
async def list_videos():
try:
videos = os.listdir(VIDEO_DIR)
return videos
except FileNotFoundError:
raise HTTPException(status_code=404, detail="Video directory not found")
@app.get("/videos/{video_name}")
async def get_video(video_name: str):
video_path = os.path.join(VIDEO_DIR, video_name)
if os.path.exists(video_path):
return FileResponse(video_path)
else:
raise HTTPException(status_code=404, detail="Video not found")
@app.post("/download")
async def download_video_endpoint(video_url: VideoURL, background_tasks: BackgroundTasks):
background_tasks.add_task(download_video, video_url.url)
return {"message": "El video se está descargando"}
@app.delete("/videos/{video_name}")
async def delete_video(video_name: str):
video_path = os.path.join(VIDEO_DIR, video_name)
if os.path.exists(video_path):
os.remove(video_path)
return {"message": "Video eliminado correctamente"}
else:
raise HTTPException(status_code=404, detail="Video not found")
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
Ejecutar el Servidor FastAPI
Ejecuta el servidor:
uvicorn main:app --reload
2. Configurar el Frontend con React
Paso 1: Crear el Proyecto React
Crear el proyecto usando vite
yarn create vite frontend --template react
cd frontend
yarn install
Instalar axios para las solicitudes http
yarn add axios
Paso 2: Crear la Interfaz de Usuario
src/App.jsx
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import './App.css'; // Importar el archivo CSS
const App = () => {
const [videos, setVideos] = useState([]);
const [selectedVideo, setSelectedVideo] = useState('');
const [videoURL, setVideoURL] = useState('');
const [message, setMessage] = useState('');
useEffect(() => {
fetchVideos();
}, []);
useEffect(() => {
if (message) {
const timer = setTimeout(() => {
setMessage('');
}, 10000); // 10 segundos
return () => clearTimeout(timer);
}
}, [message]);
const fetchVideos = () => {
axios.get('http://localhost:8000/videos')
.then(response => {
setVideos(response.data);
})
.catch(error => {
console.error('Error fetching videos:', error);
});
};
const handleVideoClick = (video) => {
setSelectedVideo(video);
};
const handleDownload = () => {
axios.post('http://localhost:8000/download', { url: videoURL })
.then(response => {
setMessage('El video se está descargando.');
setVideoURL('');
setTimeout(fetchVideos, 10000); // Esperar un tiempo y luego actualizar la lista de videos
})
.catch(error => {
setMessage('Error al descargar el video.');
console.error('Error downloading video:', error);
});
};
const handleDelete = (video) => {
axios.delete(`http://localhost:8000/videos/${video}`)
.then(response => {
setMessage('Video eliminado correctamente.');
fetchVideos();
if (selectedVideo === video) {
setSelectedVideo('');
}
})
.catch(error => {
setMessage('Error al eliminar el video.');
console.error('Error deleting video:', error);
});
};
return (
<div className="container">
<h1 className="header">My YouTube Local</h1>
<div className="input-container">
<input
type="text"
className="input"
value={videoURL}
onChange={(e) => setVideoURL(e.target.value)}
placeholder="Enter video URL"
/>
<button className="button" onClick={handleDownload}>Download Video</button>
</div>
{message && <div className="message-banner">{message}</div>}
<div className="content">
<div className="video-list">
<h2>Video List</h2>
<ul>
{videos.map(video => (
<li key={video} className="video-list-item">
<span onClick={() => handleVideoClick(video)}>{video}</span>
<button className="delete-button" onClick={() => handleDelete(video)}>X</button>
</li>
))}
</ul>
</div>
<div className="video-player-container">
<h2>Video Player</h2>
{selectedVideo ? (
<video key={selectedVideo} className="video-player" controls>
<source src={`http://localhost:8000/videos/${selectedVideo}`} type="video/mp4" />
Your browser does not support the video tag.
</video>
) : (
<p>Select a video from the list to play</p>
)}
</div>
</div>
</div>
);
};
export default App;
src/App.css
/* App.css */
body {
font-family: Arial, sans-serif;
background-color: #f0f0f0;
margin: 0;
padding: 0;
}
.container {
display: flex;
flex-direction: column;
align-items: center;
padding: 20px;
}
.header {
margin: 20px 0;
color: #333;
}
.input-container {
margin-bottom: 20px;
display: flex;
align-items: center;
}
.input {
padding: 10px;
margin-right: 10px;
border: 1px solid #ccc;
border-radius: 4px;
width: 300px;
}
.button {
padding: 10px 20px;
background-color: #007BFF;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.button:hover {
background-color: #0056b3;
}
.message-banner {
background-color: #007BFF;
color: white;
padding: 10px;
border-radius: 4px;
margin-bottom: 20px;
text-align: center;
width: 100%;
opacity: 1;
transition: opacity 1s ease-out;
}
.message-banner.fade-out {
opacity: 0;
}
.content {
display: flex;
width: 80%;
}
.video-list {
width: 30%;
border-right: 1px solid #ccc;
padding-right: 20px;
}
.video-list h2 {
margin-bottom: 10px;
}
.video-list ul {
padding: 0;
}
.video-list-item {
list
-style: none;
padding: 10px;
cursor: pointer;
border-bottom: 1px solid #eee;
display: flex;
justify-content: space-between;
align-items: center;
}
.video-list-item:hover {
background-color: #f9f9f9;
cursor: pointer;
}
.delete-button {
background-color: red;
color: white;
border: none;
border-radius: 50%;
cursor: pointer;
padding: 5px 10px;
}
.delete-button:hover {
background-color: darkred;
}
.video-player-container {
width: 70%;
padding-left: 20px;
}
.video-player {
width: 100%;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}
Ejecutar el Frontend
Ejecuta el servidor de desarrollo de Vite:
yarn dev
3. Configurar el Bot de Telegram
Paso 1: Crear un Bot en Telegram
- Abre Telegram y busca el BotFather.
- Crea un nuevo bot y obtén el token de la API.
Paso 2: Crear el Archivo bot.py
Crea un archivo llamado bot.py
en el directorio bot
y agrega el siguiente código:
import os
import requests
from telegram import Update
from telegram.ext import ApplicationBuilder, CommandHandler, MessageHandler, filters, ContextTypes
from decouple import config
# Obtén el token del bot desde el archivo .env
TELEGRAM_TOKEN = config('TELEGRAM_TOKEN')
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
await update.message.reply_text('Envía una URL de video para descargar.')
async def download(update: Update, context: ContextTypes.DEFAULT_TYPE):
video_url = update.message.text
response = requests.post('http://localhost:8000/download', json={"url": video_url})
if response.status_code == 200:
await update.message.reply_text('El video se está descargando.')
else:
await update.message.reply_text('Error al descargar el video.')
def main():
application = ApplicationBuilder().token(TELEGRAM_TOKEN).build()
application.add_handler(CommandHandler("start", start))
application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, download))
application.run_polling()
if __name__ == '__main__':
main()
Ejecutar el Bot de Telegram
Ejecuta el bot:
python bot.py
4. Conclusión
En este tutorial, configuramos un sistema para descargar y reproducir videos localmente usando FastAPI para el backend, React para el frontend y un bot de Telegram para interactuar con la aplicación. Ahora puedes disfrutar de tu propio YouTube local y gestionar los videos de manera eficiente.
¡Espero que este tutorial te haya sido útil! Si tienes alguna pregunta, no dudes en dejar un comentario.