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.

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

  1. Abre Telegram y busca el BotFather.
  2. 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.

Comentarios

No comments found.