first commit
This commit is contained in:
@@ -0,0 +1,36 @@
|
|||||||
|
name: Build and Push Docker Image
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-push:
|
||||||
|
runs-on: self-hosted
|
||||||
|
|
||||||
|
steps:
|
||||||
|
# --- Installa Node.js nel contesto del workflow ---
|
||||||
|
- name: Set up Node.js
|
||||||
|
uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: '20'
|
||||||
|
|
||||||
|
# --- Checkout del repository (richiede Node) ---
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
# --- Login al Gitea Container Registry ---
|
||||||
|
- name: Login to Gitea Container Registry
|
||||||
|
run: |
|
||||||
|
echo "${{ secrets.REGISTRY_TOKEN }}" | docker login gitea.enrilinux.ovh -u "${{ secrets.REGISTRY_USER }}" --password-stdin
|
||||||
|
|
||||||
|
# --- Build della Docker image della tua app ---
|
||||||
|
- name: Build Docker image
|
||||||
|
run: |
|
||||||
|
docker build -t gitea.enrilinux.ovh/${{ secrets.REGISTRY_USER }}/streamvault:latest .
|
||||||
|
|
||||||
|
# --- Push della Docker image al registry ---
|
||||||
|
- name: Push Docker image
|
||||||
|
run: |
|
||||||
|
docker push gitea.enrilinux.ovh/${{ secrets.REGISTRY_USER }}/streamvault:latest
|
||||||
+17
@@ -0,0 +1,17 @@
|
|||||||
|
FROM python:3.11
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y ffmpeg
|
||||||
|
|
||||||
|
COPY requirements.txt .
|
||||||
|
|
||||||
|
RUN pip install -r requirements.txt
|
||||||
|
|
||||||
|
RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
|
||||||
|
&& apt-get install -y nodejs
|
||||||
|
|
||||||
|
COPY app ./app
|
||||||
|
RUN mkdir downloads
|
||||||
|
|
||||||
|
CMD ["uvicorn","app.main:app","--host","0.0.0.0","--port","8000"]
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import yt_dlp
|
||||||
|
import glob
|
||||||
|
import re
|
||||||
|
import os
|
||||||
|
|
||||||
|
DOWNLOAD_DIR = "downloads"
|
||||||
|
|
||||||
|
def sanitize_filename(name):
|
||||||
|
"""Rimuove caratteri non validi dai nomi dei file"""
|
||||||
|
return re.sub(r'[\\/*?:"<>|]', '', name)
|
||||||
|
|
||||||
|
def download_video(url, mode):
|
||||||
|
# Template che usa il titolo e l'ID
|
||||||
|
template = f"{DOWNLOAD_DIR}/%(title)s - %(id)s.%(ext)s"
|
||||||
|
|
||||||
|
if mode == "audio":
|
||||||
|
opts = {
|
||||||
|
"format": "bestaudio/best",
|
||||||
|
"outtmpl": template,
|
||||||
|
"postprocessors": [{
|
||||||
|
"key": "FFmpegExtractAudio",
|
||||||
|
"preferredcodec": "mp3",
|
||||||
|
"preferredquality": "192",
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
opts = {
|
||||||
|
"format": "bestvideo+bestaudio/best",
|
||||||
|
"merge_output_format": "mp4",
|
||||||
|
"outtmpl": template
|
||||||
|
}
|
||||||
|
|
||||||
|
with yt_dlp.YoutubeDL(opts) as ydl:
|
||||||
|
info = ydl.extract_info(url, download=True)
|
||||||
|
|
||||||
|
# Prende il file scaricato e lo rinomina in modo sicuro
|
||||||
|
downloaded_files = glob.glob(f"{DOWNLOAD_DIR}/*{info['id']}*")
|
||||||
|
final_path = downloaded_files[0]
|
||||||
|
safe_path = os.path.join(
|
||||||
|
DOWNLOAD_DIR,
|
||||||
|
sanitize_filename(os.path.basename(final_path))
|
||||||
|
)
|
||||||
|
if safe_path != final_path:
|
||||||
|
os.rename(final_path, safe_path)
|
||||||
|
|
||||||
|
return safe_path
|
||||||
+30
@@ -0,0 +1,30 @@
|
|||||||
|
from fastapi import FastAPI, Request, Form
|
||||||
|
from fastapi.responses import HTMLResponse, FileResponse
|
||||||
|
from fastapi.templating import Jinja2Templates
|
||||||
|
import os
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
from app.downloader import download_video
|
||||||
|
|
||||||
|
app = FastAPI()
|
||||||
|
|
||||||
|
templates = Jinja2Templates(directory="app/templates")
|
||||||
|
|
||||||
|
DOWNLOAD_DIR = "downloads"
|
||||||
|
os.makedirs(DOWNLOAD_DIR, exist_ok=True)
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/", response_class=HTMLResponse)
|
||||||
|
async def home(request: Request):
|
||||||
|
return templates.TemplateResponse("index.html", {"request": request})
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/download")
|
||||||
|
async def download(url: str = Form(...), mode: str = Form(...)):
|
||||||
|
path = download_video(url, mode)
|
||||||
|
return {"path": path}
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/file")
|
||||||
|
async def file(path: str):
|
||||||
|
return FileResponse(path)
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>StreamVault</title>
|
||||||
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body class="bg-gray-900 text-white flex items-center justify-center h-screen">
|
||||||
|
|
||||||
|
<div class="bg-gray-800 p-10 rounded-2xl shadow-2xl w-96">
|
||||||
|
|
||||||
|
<h1 class="text-3xl font-bold mb-6 text-center">
|
||||||
|
StreamVault
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<input
|
||||||
|
id="url"
|
||||||
|
placeholder="Paste video URL"
|
||||||
|
class="w-full p-3 rounded bg-gray-700 mb-4"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<select id="mode" class="w-full p-3 rounded bg-gray-700 mb-4">
|
||||||
|
<option value="video">Video (MP4)</option>
|
||||||
|
<option value="audio">Audio (MP3)</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<button
|
||||||
|
id="downloadBtn"
|
||||||
|
class="w-full bg-blue-600 hover:bg-blue-700 p-3 rounded font-semibold"
|
||||||
|
>
|
||||||
|
Download
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div id="status" class="mt-6 text-center text-sm"></div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
document.getElementById("downloadBtn").addEventListener("click", async () => {
|
||||||
|
|
||||||
|
const url = document.getElementById("url").value
|
||||||
|
const mode = document.getElementById("mode").value
|
||||||
|
const status = document.getElementById("status")
|
||||||
|
|
||||||
|
if(!url){
|
||||||
|
status.innerHTML = "Insert an URL"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
status.innerHTML = "Downloading"
|
||||||
|
|
||||||
|
const form = new FormData()
|
||||||
|
form.append("url", url)
|
||||||
|
form.append("mode", mode)
|
||||||
|
|
||||||
|
try{
|
||||||
|
|
||||||
|
const res = await fetch("/download",{
|
||||||
|
method:"POST",
|
||||||
|
body:form
|
||||||
|
})
|
||||||
|
|
||||||
|
const data = await res.json()
|
||||||
|
|
||||||
|
status.innerHTML =
|
||||||
|
`Done<br>
|
||||||
|
<a href="/file?path=${data.path}" class="text-green-400 underline">
|
||||||
|
Download file
|
||||||
|
</a>`
|
||||||
|
|
||||||
|
}catch(e){
|
||||||
|
|
||||||
|
status.innerHTML = "Done"
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
services:
|
||||||
|
|
||||||
|
streamvault:
|
||||||
|
|
||||||
|
build: .
|
||||||
|
|
||||||
|
ports:
|
||||||
|
- "8087:8000"
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
- ./downloads:/app/downloads
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
fastapi
|
||||||
|
uvicorn
|
||||||
|
jinja2
|
||||||
|
python-multipart
|
||||||
|
yt-dlp
|
||||||
Reference in New Issue
Block a user