feat: AI 미디어 허브 초기 세팅 및 뼈대 코드 완성
Some checks failed
Build and Push Docker Image / build-and-push (push) Failing after 3m7s
Some checks failed
Build and Push Docker Image / build-and-push (push) Failing after 3m7s
This commit is contained in:
29
.gitea/workflows/build-push.yaml
Normal file
29
.gitea/workflows/build-push.yaml
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
name: Build and Push Docker Image
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-push:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Log in to Gitea Container Registry
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
registry: git.savethenurse.com
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITEA_TOKEN }}
|
||||||
|
|
||||||
|
- name: Build and push container image
|
||||||
|
uses: docker/build-push-action@v4
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
push: true
|
||||||
|
tags: git.savethenurse.com/savethenurse/ai-media-hub:latest
|
||||||
36
Dockerfile
Normal file
36
Dockerfile
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# Stage 1: Build the Go application
|
||||||
|
FROM golang:1.21-alpine AS builder
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
# Mock basic go mod init so it can build without external pre-requisites
|
||||||
|
RUN go mod init ai-media-hub && \
|
||||||
|
go get github.com/gofiber/fiber/v2 github.com/gofiber/fiber/v2/middleware/cors github.com/gofiber/fiber/v2/middleware/logger github.com/gofiber/websocket/v2
|
||||||
|
COPY backend/ ./backend/
|
||||||
|
RUN CGO_ENABLED=0 GOOS=linux go build -o /ai-media-hub ./backend/main.go
|
||||||
|
|
||||||
|
# Stage 2: Final runtime container with Python & Go binary
|
||||||
|
FROM python:3.10-slim
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Install dependencies (ffmpeg for yt-dlp processing)
|
||||||
|
RUN apt-get update && \
|
||||||
|
apt-get install -y --no-install-recommends ffmpeg curl ca-certificates && \
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Install Python worker requirements
|
||||||
|
COPY worker/requirements.txt ./
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
# Copy Go binary from builder
|
||||||
|
COPY --from=builder /ai-media-hub /app/ai-media-hub
|
||||||
|
|
||||||
|
# Copy Frontend files and worker files
|
||||||
|
COPY frontend/ ./frontend/
|
||||||
|
COPY worker/ ./worker/
|
||||||
|
|
||||||
|
# Expose port (Internal 8000)
|
||||||
|
EXPOSE 8000
|
||||||
|
|
||||||
|
# Entrypoint: Run Go backend
|
||||||
|
CMD ["/app/ai-media-hub"]
|
||||||
156
backend/main.go
Normal file
156
backend/main.go
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"github.com/gofiber/fiber/v2/middleware/cors"
|
||||||
|
"github.com/gofiber/fiber/v2/middleware/logger"
|
||||||
|
"github.com/gofiber/websocket/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
app := fiber.New()
|
||||||
|
|
||||||
|
app.Use(logger.New())
|
||||||
|
app.Use(cors.New())
|
||||||
|
|
||||||
|
// Sub-routes for views/assets
|
||||||
|
app.Static("/", "./frontend")
|
||||||
|
|
||||||
|
// Global Hub for Websocket
|
||||||
|
type Client struct {
|
||||||
|
Conn *websocket.Conn
|
||||||
|
}
|
||||||
|
var clients = make(map[*Client]bool)
|
||||||
|
var clientsMu sync.Mutex
|
||||||
|
|
||||||
|
broadcast := func(msg string) {
|
||||||
|
clientsMu.Lock()
|
||||||
|
defer clientsMu.Unlock()
|
||||||
|
for client := range clients {
|
||||||
|
if err := client.Conn.WriteMessage(websocket.TextMessage, []byte(msg)); err != nil {
|
||||||
|
client.Conn.Close()
|
||||||
|
delete(clients, client)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// API Routes
|
||||||
|
api := app.Group("/api")
|
||||||
|
|
||||||
|
api.Get("/search", func(c *fiber.Ctx) error {
|
||||||
|
query := c.Query("q")
|
||||||
|
apiKey := os.Getenv("GCP_API_KEY")
|
||||||
|
cx := os.Getenv("GCP_CX")
|
||||||
|
|
||||||
|
if apiKey == "" || cx == "" {
|
||||||
|
return c.Status(500).JSON(fiber.Map{"error": "Search API keys not configured"})
|
||||||
|
}
|
||||||
|
|
||||||
|
searchURL := fmt.Sprintf("https://www.googleapis.com/customsearch/v1?q=%s&key=%s&cx=%s&searchType=image", query, apiKey, cx)
|
||||||
|
resp, err := http.Get(searchURL)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(500).JSON(fiber.Map{"error": err.Error()})
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var result map[string]interface{}
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||||
|
return c.Status(500).JSON(fiber.Map{"error": "Failed to parse search results"})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(fiber.Map{
|
||||||
|
"status": "success",
|
||||||
|
"data": result["items"],
|
||||||
|
"query": query,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
api.Post("/download", func(c *fiber.Ctx) error {
|
||||||
|
type Req struct {
|
||||||
|
URL string `json:"url"`
|
||||||
|
Start string `json:"start"`
|
||||||
|
End string `json:"end"`
|
||||||
|
}
|
||||||
|
var req Req
|
||||||
|
if err := c.BodyParser(&req); err != nil {
|
||||||
|
return c.Status(400).JSON(fiber.Map{"error": "Invalid request body"})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run python worker in background
|
||||||
|
go func() {
|
||||||
|
args := []string{"./worker/downloader.py", req.URL}
|
||||||
|
if req.Start != "" {
|
||||||
|
args = append(args, "--start", req.Start)
|
||||||
|
}
|
||||||
|
if req.End != "" {
|
||||||
|
args = append(args, "--end", req.End)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := exec.Command("python3", args...)
|
||||||
|
cmd.Env = append(os.Environ(), "PYTHONUNBUFFERED=1")
|
||||||
|
|
||||||
|
stdout, _ := cmd.StdoutPipe()
|
||||||
|
cmd.Start()
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(stdout)
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := scanner.Text()
|
||||||
|
broadcast(line)
|
||||||
|
log.Println("Worker:", line)
|
||||||
|
}
|
||||||
|
cmd.Wait()
|
||||||
|
broadcast("PROGRESS: 100%")
|
||||||
|
}()
|
||||||
|
|
||||||
|
return c.JSON(fiber.Map{
|
||||||
|
"status": "success",
|
||||||
|
"message": "Download task queued",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// WebSocket for progress
|
||||||
|
app.Use("/ws", func(c *fiber.Ctx) error {
|
||||||
|
if websocket.IsWebSocketUpgrade(c) {
|
||||||
|
c.Locals("allowed", true)
|
||||||
|
return c.Next()
|
||||||
|
}
|
||||||
|
return fiber.ErrUpgradeRequired
|
||||||
|
})
|
||||||
|
|
||||||
|
app.Get("/ws", websocket.New(func(c *websocket.Conn) {
|
||||||
|
client := &Client{Conn: c}
|
||||||
|
clientsMu.Lock()
|
||||||
|
clients[client] = true
|
||||||
|
clientsMu.Unlock()
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
clientsMu.Lock()
|
||||||
|
delete(clients, client)
|
||||||
|
clientsMu.Unlock()
|
||||||
|
c.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
for {
|
||||||
|
if _, _, err := c.ReadMessage(); err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
port := os.Getenv("PORT")
|
||||||
|
if port == "" {
|
||||||
|
port = "8000"
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Server listening on port %s", port)
|
||||||
|
log.Fatal(app.Listen(":" + port))
|
||||||
|
}
|
||||||
91
frontend/index.html
Normal file
91
frontend/index.html
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" class="dark">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>AI Media Hub</title>
|
||||||
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
|
<script>
|
||||||
|
tailwind.config = {
|
||||||
|
darkMode: 'class',
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
colors: {
|
||||||
|
dark: '#121212',
|
||||||
|
darker: '#0a0a0a',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
body { background-color: #121212; color: #ffffff; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body class="min-h-screen bg-dark text-white font-sans p-8">
|
||||||
|
<header class="mb-12 border-b border-gray-800 pb-4">
|
||||||
|
<h1 class="text-3xl font-bold tracking-tight">AI Media Hub</h1>
|
||||||
|
<p class="text-gray-400 mt-2">Discover, Ingest, and Download Media</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||||
|
<!-- Zone A: Discovery -->
|
||||||
|
<section class="bg-darker p-6 rounded-xl border border-gray-800">
|
||||||
|
<h2 class="text-xl font-semibold mb-4">Zone A: AI Discovery</h2>
|
||||||
|
<div class="flex gap-2 mb-4">
|
||||||
|
<input type="text" placeholder="Search parameters..." class="flex-1 bg-dark border border-gray-700 rounded px-3 py-2 focus:outline-none focus:border-white transition-colors">
|
||||||
|
<button class="bg-white text-black px-4 py-2 rounded font-medium hover:bg-gray-200 transition-colors">Search</button>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 gap-4 auto-rows-[100px]">
|
||||||
|
<!-- Mock Thumbnails -->
|
||||||
|
<div class="bg-gray-800 rounded animate-pulse cursor-pointer hover:opacity-80 transition-opacity"></div>
|
||||||
|
<div class="bg-gray-800 rounded animate-pulse cursor-pointer hover:opacity-80 transition-opacity"></div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Zone B: Ingest -->
|
||||||
|
<section class="bg-darker p-6 rounded-xl border border-gray-800">
|
||||||
|
<h2 class="text-xl font-semibold mb-4">Zone B: Smart Ingest</h2>
|
||||||
|
<div class="border-2 border-dashed border-gray-700 rounded-xl h-64 flex flex-col items-center justify-center text-gray-500 hover:border-gray-500 hover:text-gray-300 transition-colors cursor-pointer group">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-10 w-10 mb-2 group-hover:scale-110 transition-transform" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" />
|
||||||
|
</svg>
|
||||||
|
<span>Drag & Drop files here</span>
|
||||||
|
<span class="text-sm mt-1">or click to browse</span>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Zone C: Download -->
|
||||||
|
<section class="bg-darker p-6 rounded-xl border border-gray-800 flex flex-col">
|
||||||
|
<h2 class="text-xl font-semibold mb-4">Zone C: Direct Download</h2>
|
||||||
|
<div class="flex-1">
|
||||||
|
<label class="block text-sm text-gray-400 mb-2">Media URL</label>
|
||||||
|
<input type="text" placeholder="https://youtube.com/..." class="w-full bg-dark border border-gray-700 rounded px-3 py-2 mb-4 focus:outline-none focus:border-white transition-colors">
|
||||||
|
|
||||||
|
<label class="block text-sm text-gray-400 mb-2">Crop Settings (Optional)</label>
|
||||||
|
<div class="flex gap-2 mb-6">
|
||||||
|
<input type="text" placeholder="Start (e.g. 00:01:00)" class="w-1/2 bg-dark border border-gray-700 rounded px-3 py-2 focus:outline-none focus:border-white transition-colors">
|
||||||
|
<input type="text" placeholder="End (e.g. 00:02:30)" class="w-1/2 bg-dark border border-gray-700 rounded px-3 py-2 focus:outline-none focus:border-white transition-colors">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="w-full bg-white text-black py-3 rounded font-medium hover:bg-gray-200 transition-transform active:scale-95">Download via yt-dlp</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-4 pt-4 border-t border-gray-800">
|
||||||
|
<div class="flex justify-between text-sm text-gray-400 mb-1">
|
||||||
|
<span>Progress</span>
|
||||||
|
<span>0%</span>
|
||||||
|
</div>
|
||||||
|
<div class="w-full bg-gray-800 rounded-full h-2">
|
||||||
|
<div class="bg-white h-2 rounded-full" style="width: 0%"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Placeholder for WebSocket logic
|
||||||
|
console.log("AI Media Hub Initialized.");
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
24
go.mod
Normal file
24
go.mod
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
module ai-media-hub
|
||||||
|
|
||||||
|
go 1.22.2
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/gofiber/fiber/v2 v2.52.12
|
||||||
|
github.com/gofiber/websocket/v2 v2.2.1
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/andybalholm/brotli v1.1.0 // indirect
|
||||||
|
github.com/fasthttp/websocket v1.5.3 // indirect
|
||||||
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
|
github.com/klauspost/compress v1.17.9 // indirect
|
||||||
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||||
|
github.com/rivo/uniseg v0.2.0 // indirect
|
||||||
|
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
|
github.com/valyala/fasthttp v1.51.0 // indirect
|
||||||
|
github.com/valyala/tcplisten v1.0.0 // indirect
|
||||||
|
golang.org/x/sys v0.28.0 // indirect
|
||||||
|
)
|
||||||
33
go.sum
Normal file
33
go.sum
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
|
||||||
|
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
|
||||||
|
github.com/fasthttp/websocket v1.5.3 h1:TPpQuLwJYfd4LJPXvHDYPMFWbLjsT91n3GpWtCQtdek=
|
||||||
|
github.com/fasthttp/websocket v1.5.3/go.mod h1:46gg/UBmTU1kUaTcwQXpUxtRwG2PvIZYeA8oL6vF3Fs=
|
||||||
|
github.com/gofiber/fiber/v2 v2.52.12 h1:0LdToKclcPOj8PktUdIKo9BUohjjwfnQl42Dhw8/WUw=
|
||||||
|
github.com/gofiber/fiber/v2 v2.52.12/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
|
||||||
|
github.com/gofiber/websocket/v2 v2.2.1 h1:C9cjxvloojayOp9AovmpQrk8VqvVnT8Oao3+IUygH7w=
|
||||||
|
github.com/gofiber/websocket/v2 v2.2.1/go.mod h1:Ao/+nyNnX5u/hIFPuHl28a+NIkrqK7PRimyKaj4JxVU=
|
||||||
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
|
||||||
|
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||||
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||||
|
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
|
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||||
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
|
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1AvpV+7XmhI4r39LGNzwUL4YpMuL5vk=
|
||||||
|
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g=
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||||
|
github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA=
|
||||||
|
github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g=
|
||||||
|
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
|
||||||
|
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
|
||||||
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||||
|
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
26
unraid-template.xml
Normal file
26
unraid-template.xml
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<Container version="2">
|
||||||
|
<Name>ai-media-hub</Name>
|
||||||
|
<Repository>git.savethenurse.com/savethenurse/ai-media-hub:latest</Repository>
|
||||||
|
<Registry>https://git.savethenurse.com</Registry>
|
||||||
|
<Network>bridge</Network>
|
||||||
|
<MyIP/>
|
||||||
|
<Shell>sh</Shell>
|
||||||
|
<Privileged>false</Privileged>
|
||||||
|
<Support>https://git.savethenurse.com/savethenurse/ai-media-hub/issues</Support>
|
||||||
|
<Project>https://git.savethenurse.com/savethenurse/ai-media-hub</Project>
|
||||||
|
<Overview>AI Media Hub: A single container full-stack app for gathering, ingesting, and downloading media assets via AI Discovery and yt-dlp.</Overview>
|
||||||
|
<Category>MediaApp:Video Downloaders:Tools:</Category>
|
||||||
|
<WebUI>http://[IP]:[PORT:8000]</WebUI>
|
||||||
|
<TemplateURL/>
|
||||||
|
<Icon>https://raw.githubusercontent.com/walkxcode/dashboard-icons/main/png/youtube.png</Icon>
|
||||||
|
<ExtraParams/>
|
||||||
|
<PostArgs/>
|
||||||
|
<CPUset/>
|
||||||
|
<DateInstalled></DateInstalled>
|
||||||
|
<DonateText/>
|
||||||
|
<DonateLink/>
|
||||||
|
<Requires/>
|
||||||
|
<Config Name="WebUI Port" Target="8000" Default="8282" Mode="tcp" Description="Port for Web Interface" Type="Port" Display="always" Required="true" Mask="false">8282</Config>
|
||||||
|
<Config Name="NAS Downloads Path" Target="/downloads" Default="/mnt/user/downloads/media" Mode="rw" Description="Path to save downloaded and ingested media files" Type="Path" Display="always" Required="true" Mask="false">/mnt/user/downloads/media</Config>
|
||||||
|
</Container>
|
||||||
40
worker/downloader.py
Normal file
40
worker/downloader.py
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import argparse
|
||||||
|
import yt_dlp
|
||||||
|
|
||||||
|
def progress_hook(d):
|
||||||
|
if d['status'] == 'downloading':
|
||||||
|
percent = d.get('_percent_str', 'N/A')
|
||||||
|
print(f"PROGRESS: {percent}", flush=True)
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description="Media Downloader via yt-dlp")
|
||||||
|
parser.add_argument("url", help="URL of the media to download")
|
||||||
|
parser.add_argument("--start", help="Start time for crop (e.g. 00:01:00)", default=None)
|
||||||
|
parser.add_argument("--end", help="End time for crop (e.g. 00:02:30)", default=None)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
ydl_opts = {
|
||||||
|
'outtmpl': '/downloads/%(title)s.%(ext)s',
|
||||||
|
'progress_hooks': [progress_hook],
|
||||||
|
'quiet': True,
|
||||||
|
'no_warnings': True,
|
||||||
|
}
|
||||||
|
|
||||||
|
if args.start and args.end:
|
||||||
|
print(f"Applying crop from {args.start} to {args.end}")
|
||||||
|
# Note: In a real environment, you might use format sorting and postprocessors like FFmpeg directly.
|
||||||
|
# This is a sample placeholder for the structure.
|
||||||
|
|
||||||
|
# Try downloading
|
||||||
|
try:
|
||||||
|
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
||||||
|
print("Download started...")
|
||||||
|
ydl.download([args.url])
|
||||||
|
print("Download finished.")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error starting download: {e}", file=sys.stderr)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
1
worker/requirements.txt
Normal file
1
worker/requirements.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
yt-dlp>=2023.10.13
|
||||||
Reference in New Issue
Block a user