diff --git a/.gitea/workflows/build-push.yaml b/.gitea/workflows/build-push.yaml deleted file mode 100644 index 696d092..0000000 --- a/.gitea/workflows/build-push.yaml +++ /dev/null @@ -1,44 +0,0 @@ -name: Build and Push Docker Image - -on: - push: - branches: - - main - -env: - REGISTRY: git.savethenurse.com - # CI/CD 파이프라인이 동작하는 Gitea 서버 자체의 레지스트리로 변경합니다. - IMAGE_NAME: ${{ github.repository }} - -jobs: - build-and-push: - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Log in to the Container registry - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: 18b8c375c76a07b9a6954bfdda794f5064dc6aa1 - - - name: Extract metadata (tags, labels) for Docker - id: meta - uses: docker/metadata-action@v5 - with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - - - name: Build and push Docker image - uses: docker/build-push-action@v5 - with: - context: . - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 3f43517..0000000 --- a/Dockerfile +++ /dev/null @@ -1,50 +0,0 @@ -# 1. Build Go Backend -FROM golang:1.22-bookworm AS builder - -# Debian 기반 이미지는 빌드 도구가 이미 포함되어 있으므로 추가 설치 없이 진행합니다. - -WORKDIR /build - -# Cache go modules -COPY backend/go.mod backend/go.sum ./ -RUN go mod download - -COPY backend/ ./ -# CGO_ENABLED=1 is required for sqlite3 -RUN CGO_ENABLED=1 go build -ldflags="-s -w" -o main main.go - -# 2. Final Minimal Image (Python + Go binary + Frontend) -FROM python:3.10-slim - -# 파이썬 출력 버퍼링을 비활성화하여 도커 로그에 즉각 표시되도록 설정합니다. -ENV PYTHONUNBUFFERED=1 - -# Install system dependencies (ffmpeg is required for yt-dlp) -RUN apt-get update && apt-get install -y \ - ffmpeg \ - ca-certificates \ - && rm -rf /var/lib/apt/lists/* - -WORKDIR /app - -# Install Python dependencies for worker -COPY worker/requirements.txt ./worker/ -RUN pip install --no-cache-dir -r worker/requirements.txt - -# Copy Go binary from builder -COPY --from=builder /build/main ./main - -# Copy worker script -COPY worker/ ./worker/ - -# Copy frontend -COPY frontend/ ./frontend/ - -# Ensure directories exist -RUN mkdir -p db downloads - -# Expose Go server port -EXPOSE 3000 - -# Run the Go server command (which will also call the yt-dlp python script when needed) -CMD ["./main"] diff --git a/TODO.md b/TODO.md deleted file mode 100644 index 4190ac4..0000000 --- a/TODO.md +++ /dev/null @@ -1,15 +0,0 @@ -# 전체 작업 진행 점검표 - -- [x] 1. 프로젝트 폴더 구조 생성 -- [x] 2. Dockerfile 및 Unraid XML 템플릿 작성 -- [x] 3. Gitea Actions CI/CD 파일 작성 (.gitea/workflows/build-push.yaml) -- [x] 4. SQLite DB 모델 및 초기화 로직 (backend/models) -- [x] 5. Python yt-dlp 워커 스크립트 작성 (worker/downloader.py) -- [x] 6. Go 백엔드 라우팅 및 파일 업로드(Zone B) 구현 -- [x] 7. Go - Python 연동 로직 (Zone C 다운로드 실행) -- [x] 8. Google CSE 및 Gemini 2.5 Flash 연동 로직 (Zone A) -- [x] 9. WebSocket 서버 및 진행률 방송 로직 -- [x] 10. 프론트엔드 메인 UI 구성 (Tailwind 3-Zone Layout) -- [x] 11. 프론트엔드 JS 통신 로직 및 상태 바 렌더링 연동 -- [x] 12. 전체 기능 통합 테스트 -- [x] 13. Git Init 및 자동 Push (Gitea) diff --git a/backend/go.mod b/backend/go.mod deleted file mode 100644 index b212384..0000000 --- a/backend/go.mod +++ /dev/null @@ -1,29 +0,0 @@ -module github.com/savethenurse/ai-media-hub/backend - -go 1.22.2 - -require ( - github.com/gofiber/fiber/v2 v2.52.12 - github.com/gofiber/websocket/v2 v2.2.1 - gorm.io/driver/sqlite v1.5.5 - gorm.io/gorm v1.25.10 -) - -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/jinzhu/inflection v1.0.0 // indirect - github.com/jinzhu/now v1.1.5 // 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/mattn/go-sqlite3 v1.14.17 // 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 -) diff --git a/backend/go.sum b/backend/go.sum deleted file mode 100644 index a78dc1b..0000000 --- a/backend/go.sum +++ /dev/null @@ -1,43 +0,0 @@ -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/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= -github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= -github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= -github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= -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/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM= -github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= -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= -gorm.io/driver/sqlite v1.5.5 h1:7MDMtUZhV065SilG62E0MquljeArQZNfJnjd9i9gx3E= -gorm.io/driver/sqlite v1.5.5/go.mod h1:6NgQ7sQWAIFsPrJJl1lSNSu2TABh0ZZ/zm5fosATavE= -gorm.io/gorm v1.25.10 h1:dQpO+33KalOA+aFYGlK+EfxcI5MbO7EP2yYygwh9h+s= -gorm.io/gorm v1.25.10/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= diff --git a/backend/handlers/api.go b/backend/handlers/api.go deleted file mode 100644 index 7f6b06f..0000000 --- a/backend/handlers/api.go +++ /dev/null @@ -1,140 +0,0 @@ -package handlers - -import ( - "encoding/json" - "fmt" - "os" - "os/exec" - "path/filepath" - - "github.com/gofiber/fiber/v2" - "github.com/savethenurse/ai-media-hub/backend/models" - "github.com/savethenurse/ai-media-hub/backend/services" -) - -// SearchAndFilter handles Zone A logic -func SearchAndFilter(c *fiber.Ctx) error { - query := c.Query("q") - if query == "" { - return c.Status(400).JSON(fiber.Map{"error": "Query required"}) - } - - // 1. Send search query to CSE - urls, err := services.PerformSearch(query) - if err != nil { - return c.Status(500).JSON(fiber.Map{"error": err.Error()}) - } - - if len(urls) == 0 { - return c.JSON(fiber.Map{"recommended": []string{}}) - } - - // 2. Filter with Gemini - result, err := services.FilterImagesWithGemini(query, urls) - if err != nil { - return c.Status(500).JSON(fiber.Map{"error": err.Error()}) - } - - return c.JSON(result) -} - -// UploadMedia handles Zone B logic -func UploadMedia(c *fiber.Ctx) error { - file, err := c.FormFile("file") - if err != nil { - return c.Status(400).JSON(fiber.Map{"error": "File upload required"}) - } - - downloadsDir := os.Getenv("DOWNLOADS_DIR") - if downloadsDir == "" { - downloadsDir = "/app/downloads" - } - - dest := filepath.Join(downloadsDir, file.Filename) - if err := c.SaveFile(file, dest); err != nil { - return c.Status(500).JSON(fiber.Map{"error": err.Error()}) - } - - // Logging to DB - models.DB.Create(&models.MediaHistory{ - SourceURL: file.Filename, // Just to log it, though not a URL - FilePath: dest, - Status: "success", - Type: "upload", - }) - - return c.JSON(fiber.Map{"status": "success", "filename": file.Filename}) -} - -// DownloadMedia handles Zone C logic -func DownloadMedia(c *fiber.Ctx) error { - type Request struct { - URL string `json:"url"` - Start string `json:"start"` - End string `json:"end"` - } - - var req Request - if err := c.BodyParser(&req); err != nil { - return c.Status(400).JSON(fiber.Map{"error": "Invalid JSON"}) - } - - // Check duplicates - var hist models.MediaHistory - res := models.DB.Where("source_url = ?", req.URL).First(&hist) - if res.RowsAffected > 0 && c.Query("confirm") != "true" { - return c.Status(statusConflict).JSON(fiber.Map{"error": "Duplicate", "need_confirm": true}) - } - - downloadsDir := os.Getenv("DOWNLOADS_DIR") - if downloadsDir == "" { - downloadsDir = "/app/downloads" - } - - // Execute Python worker asynchronously or synchronously - // For simplicity, we'll do it synchronously and broadcast progress via WS if we capture it. - // But it's easier to just run the command and wait. - BroadcastProgress("Downloading: " + req.URL) - - go func() { - args := []string{"./worker/downloader.py", "--url", req.URL, "--outdir", downloadsDir} - if req.Start != "" { - args = append(args, "--start", req.Start) - } - if req.End != "" { - args = append(args, "--end", req.End) - } - - cmd := exec.Command("python", args...) - fmt.Printf("[DEBUG Go Exec] Command: %v\n", cmd.String()) - - output, err := cmd.CombinedOutput() - fmt.Printf("[DEBUG Go Exec] Output:\n%s\n", string(output)) - - if err != nil { - fmt.Printf("[DEBUG Go Exec] Download error: %v\n", string(output)) - BroadcastProgress("Error: " + err.Error()) - models.DB.Create(&models.MediaHistory{ - SourceURL: req.URL, - Status: "error", - Type: "download", - }) - return - } - - var result map[string]interface{} - json.Unmarshal(output, &result) - - BroadcastProgress(fmt.Sprintf("Download Success: %v", req.URL)) - models.DB.Create(&models.MediaHistory{ - SourceURL: req.URL, - Status: "success", - Type: "download", - FilePath: fmt.Sprintf("%v", result["filepath"]), - }) - }() - - return c.JSON(fiber.Map{"status": "started", "message": "Download process started"}) -} - -const statusConflict = 409 diff --git a/backend/handlers/ws.go b/backend/handlers/ws.go deleted file mode 100644 index 77fc08f..0000000 --- a/backend/handlers/ws.go +++ /dev/null @@ -1,47 +0,0 @@ -package handlers - -import ( - "log" - "sync" - - "github.com/gofiber/websocket/v2" -) - -var clients = make(map[*websocket.Conn]bool) -var clientsMutex sync.Mutex - -func WsHandler(c *websocket.Conn) { - clientsMutex.Lock() - clients[c] = true - clientsMutex.Unlock() - - defer func() { - clientsMutex.Lock() - delete(clients, c) - clientsMutex.Unlock() - c.Close() - }() - - for { - // Just keep alive, ignore incoming messages - _, _, err := c.ReadMessage() - if err != nil { - log.Println("ws error:", err) - break - } - } -} - -func BroadcastProgress(message string) { - clientsMutex.Lock() - defer clientsMutex.Unlock() - - for client := range clients { - err := client.WriteMessage(websocket.TextMessage, []byte(message)) - if err != nil { - log.Println("ws broadcast error:", err) - client.Close() - delete(clients, client) - } - } -} diff --git a/backend/main.go b/backend/main.go deleted file mode 100644 index 5243c13..0000000 --- a/backend/main.go +++ /dev/null @@ -1,59 +0,0 @@ -package main - -import ( - "log" - "os" - - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/cors" - "github.com/gofiber/fiber/v2/middleware/logger" - "github.com/gofiber/websocket/v2" - "github.com/savethenurse/ai-media-hub/backend/handlers" - "github.com/savethenurse/ai-media-hub/backend/models" -) - -func main() { - dbDir := os.Getenv("DB_DIR") - if dbDir == "" { - dbDir = "/app/db" - } - dbPath := dbDir + "/media_hub.db" - - // Ensure DB directory exists - os.MkdirAll(dbDir, os.ModePerm) - - // Initialize Database - models.InitDB(dbPath) - - app := fiber.New() - - app.Use(logger.New()) - app.Use(cors.New()) - - // Static files (Frontend) - app.Static("/", "./frontend") - - // API Routes - api := app.Group("/api") - api.Get("/search", handlers.SearchAndFilter) - api.Post("/upload", handlers.UploadMedia) - api.Post("/download", handlers.DownloadMedia) - - // WebSocket Route - 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(handlers.WsHandler)) - - port := os.Getenv("PORT") - if port == "" { - port = "3000" - } - - log.Printf("Starting Server on port %s", port) - app.Listen(":" + port) -} diff --git a/backend/models/db.go b/backend/models/db.go deleted file mode 100644 index 5953080..0000000 --- a/backend/models/db.go +++ /dev/null @@ -1,50 +0,0 @@ -package models - -import ( - "log" - "os" - - "gorm.io/driver/sqlite" - "gorm.io/gorm" - "gorm.io/gorm/logger" -) - -var DB *gorm.DB - -type MediaHistory struct { - gorm.Model - SourceURL string `gorm:"uniqueIndex"` - FilePath string - Status string - Type string // ENUM: "download", "upload" -} - -func InitDB(dbPath string) { - var err error - - // 도커 로그 디버깅을 위해 SQL 쿼리를 디테일하게 출력하는 로거 설정 - newLogger := logger.New( - log.New(os.Stdout, "\r\n", log.LstdFlags), - logger.Config{ - LogLevel: logger.Info, - Colorful: true, - }, - ) - - log.Println("Connecting to SQLite at:", dbPath) - DB, err = gorm.Open(sqlite.Open(dbPath), &gorm.Config{ - Logger: newLogger, - }) - if err != nil { - log.Fatal("Failed to connect to database:", err) - } - - // SQLite 동시성 최적화를 위해 WAL(Write-Ahead Logging) 모드를 활성화 - DB.Exec("PRAGMA journal_mode=WAL;") - - err = DB.AutoMigrate(&MediaHistory{}) - if err != nil { - log.Println("Database migration error:", err) - } - log.Println("Database initialized and migrated.") -} diff --git a/backend/services/ai.go b/backend/services/ai.go deleted file mode 100644 index cd81789..0000000 --- a/backend/services/ai.go +++ /dev/null @@ -1,94 +0,0 @@ -package services - -import ( - "bytes" - "encoding/json" - "fmt" - "io/ioutil" - "net/http" - "os" -) - -type RecommendResult struct { - Recommended []struct { - URL string `json:"url"` - Reason string `json:"reason"` - } `json:"recommended"` -} - -// FilterImagesWithGemini asks Gemini 2.5 Flash to pick the best thumbnails -func FilterImagesWithGemini(query string, urls []string) (*RecommendResult, error) { - apiKey := os.Getenv("GEMINI_API_KEY") - if apiKey == "" { - return nil, fmt.Errorf("GEMINI_API_KEY not configured") - } - - // Because Gemini multi-modal expects base64 or public URLs, since these are public URLs from Google search, - // we can try providing instructions with URLs to be analyzed, or if direct URL access fails, we might need to download them. - // For simplicity, we assume Gemini can process URLs if we provide them as text, or we just ask it to pick based on text/URL proxy. - // Actually, the standard way is to download and attach as inline data. Let's do a simplified version where we just pass URLs as text to the model and ask it to assume they are image links. - // Real implementation usually requires base64 encoding the actual images. To keep it fast, we'll instruct the model to do its best with the metadata or text provided, or use a pseudo-approach. - - prompt := fmt.Sprintf(`제공된 이미지 URL 목록에서 사용자의 검색어 '%s'에 가장 부합하는 고품질 이미지를 1~5개 선별하고, 추천 이유와 함께 JSON 형태로 반환하라. -형식: {"recommended": [{"url": "url", "reason": "이유"}]} -URL 목록: %v`, query, urls) - - payload := map[string]interface{}{ - "contents": []map[string]interface{}{ - { - "parts": []map[string]interface{}{ - {"text": prompt}, - }, - }, - }, - "generationConfig": map[string]interface{}{ - "response_mime_type": "application/json", - }, - } - - bodyBytes, _ := json.Marshal(payload) - url := fmt.Sprintf("https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key=%s", apiKey) - - req, _ := http.NewRequest("POST", url, bytes.NewBuffer(bodyBytes)) - req.Header.Set("Content-Type", "application/json") - - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - if resp.StatusCode != 200 { - return nil, fmt.Errorf("Gemini API returned status: %d", resp.StatusCode) - } - - respBody, _ := ioutil.ReadAll(resp.Body) - - // Parse Gemini Response - var geminiResp struct { - Candidates []struct { - Content struct { - Parts []struct { - Text string `json:"text"` - } `json:"parts"` - } `json:"content"` - } `json:"candidates"` - } - - if err := json.Unmarshal(respBody, &geminiResp); err != nil { - return nil, err - } - - if len(geminiResp.Candidates) > 0 && len(geminiResp.Candidates[0].Content.Parts) > 0 { - jsonStr := geminiResp.Candidates[0].Content.Parts[0].Text - var res RecommendResult - err := json.Unmarshal([]byte(jsonStr), &res) - if err != nil { - return nil, err - } - return &res, nil - } - - return nil, fmt.Errorf("No valid response from Gemini") -} diff --git a/backend/services/search.go b/backend/services/search.go deleted file mode 100644 index b74a419..0000000 --- a/backend/services/search.go +++ /dev/null @@ -1,64 +0,0 @@ -package services - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "net/http" - "os" -) - -type SearchResultItem struct { - Title string `json:"title"` - Link string `json:"link"` - Pagemap struct { - CseImage []struct { - Src string `json:"src"` - } `json:"cse_image"` - CseThumbnail []struct { - Src string `json:"src"` - } `json:"cse_thumbnail"` - } `json:"pagemap"` -} - -type SearchResponse struct { - Items []SearchResultItem `json:"items"` -} - -// PerformSearch calls Google Custom Search API targeting YouTube, TikTok, Envato, Artgrid -func PerformSearch(query string) ([]string, error) { - apiKey := os.Getenv("GOOGLE_CSE_API_KEY") - cx := os.Getenv("GOOGLE_CSE_ID") - - if apiKey == "" || cx == "" { - return nil, fmt.Errorf("Google CSE credentials not configured") - } - - // We append "site:youtube.com OR site:tiktok.com OR site:envato.com OR site:artgrid.io" to restrict - // depending on CSE settings, but the easiest is doing it in query string - fullQuery := fmt.Sprintf("%s site:youtube.com OR site:tiktok.com OR site:envato.com OR site:artgrid.io", query) - url := fmt.Sprintf("https://www.googleapis.com/customsearch/v1?key=%s&cx=%s&q=%s&searchType=image&num=10", apiKey, cx, fullQuery) - - resp, err := http.Get(url) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - if resp.StatusCode != 200 { - return nil, fmt.Errorf("Google CSE API returned status: %d", resp.StatusCode) - } - - body, _ := ioutil.ReadAll(resp.Body) - var res SearchResponse - if err := json.Unmarshal(body, &res); err != nil { - return nil, err - } - - var thumbnails []string - for _, item := range res.Items { - thumbnails = append(thumbnails, item.Link) - } - - return thumbnails, nil -} diff --git a/frontend/app.js b/frontend/app.js deleted file mode 100644 index bc82676..0000000 --- a/frontend/app.js +++ /dev/null @@ -1,198 +0,0 @@ -const statusText = document.getElementById('status-text'); -const wsIndicator = document.getElementById('ws-indicator'); -const searchBtn = document.getElementById('search-btn'); -const searchInput = document.getElementById('search-input'); -const resultsContainer = document.getElementById('discovery-results'); - -const dropzone = document.getElementById('dropzone'); -const fileInput = document.getElementById('file-input'); - -const dlBtn = document.getElementById('dl-btn'); -const dlUrl = document.getElementById('dl-url'); -const dlStart = document.getElementById('dl-start'); -const dlEnd = document.getElementById('dl-end'); -const confirmModal = document.getElementById('confirm-modal'); -const dlConfirmBtn = document.getElementById('dl-confirm-btn'); -const dlCancelBtn = document.getElementById('dl-cancel-btn'); - - -// --- WebSocket --- -let ws; -function connectWS() { - const proto = window.location.protocol === 'https:' ? 'wss' : 'ws'; - const wsUrl = `${proto}://${window.location.host}/ws`; - // For local dev without docker, port might be 3000 - const finalWsUrl = window.location.port === '' ? `${proto}://${window.location.hostname}:3000/ws` : wsUrl; - - ws = new WebSocket(finalWsUrl); - - ws.onopen = () => { - wsIndicator.className = 'h-2 w-2 rounded-full bg-green-500'; - }; - - ws.onmessage = (event) => { - statusText.textContent = event.data; - // flash text - statusText.classList.add('text-blue-400'); - setTimeout(() => statusText.classList.remove('text-blue-400'), 500); - }; - - ws.onclose = () => { - wsIndicator.className = 'h-2 w-2 rounded-full bg-red-500'; - setTimeout(connectWS, 2000); // Reconnect - }; -} -connectWS(); - -// --- Zone A: Search --- -searchBtn.addEventListener('click', async () => { - const query = searchInput.value.trim(); - if (!query) return; - - searchBtn.disabled = true; - searchBtn.textContent = '검색 중...'; - resultsContainer.innerHTML = '
인공지능이 이미지를 탐색하고 선별 중입니다...
'; - - try { - const port = window.location.port ? `:${window.location.port}` : ':3000'; - const res = await fetch(`http://${window.location.hostname}${port}/api/search?q=${encodeURIComponent(query)}`); - const data = await res.json(); - - if (data.error) { - resultsContainer.innerHTML = `
Error: ${data.error}
`; - } else if (data.recommended && data.recommended.length > 0) { - resultsContainer.innerHTML = ''; - data.recommended.forEach((item, index) => { - const delay = index * 100; - resultsContainer.innerHTML += ` -
- ✨ AI Recommended -
- -
-
-

${item.reason}

-
- -
- `; - }); - } else { - resultsContainer.innerHTML = `
결과를 찾을 수 없습니다.
`; - } - } catch (err) { - resultsContainer.innerHTML = `
Exception: ${err.message}
`; - } finally { - searchBtn.disabled = false; - searchBtn.textContent = '검색'; - } -}); - - -// --- Zone B: Upload --- -dropzone.addEventListener('dragover', (e) => { - e.preventDefault(); - dropzone.classList.add('border-purple-500', 'bg-[#303030]'); -}); - -dropzone.addEventListener('dragleave', () => { - dropzone.classList.remove('border-purple-500', 'bg-[#303030]'); -}); - -dropzone.addEventListener('drop', (e) => { - e.preventDefault(); - dropzone.classList.remove('border-purple-500', 'bg-[#303030]'); - - if (e.dataTransfer.files.length > 0) { - uploadFile(e.dataTransfer.files[0]); - } -}); - -dropzone.addEventListener('click', () => fileInput.click()); -fileInput.addEventListener('change', (e) => { - if (e.target.files.length > 0) { - uploadFile(e.target.files[0]); - } -}); - -async function uploadFile(file) { - const formData = new FormData(); - formData.append('file', file); - - statusText.textContent = `Uploading ${file.name}...`; - - try { - const port = window.location.port ? `:${window.location.port}` : ':3000'; - const res = await fetch(`http://${window.location.hostname}${port}/api/upload`, { - method: 'POST', - body: formData - }); - const data = await res.json(); - - if (data.error) { - statusText.textContent = `Upload Error: ${data.error}`; - } else { - statusText.textContent = `Upload Success: ${data.filename}`; - } - } catch (err) { - statusText.textContent = `Upload Failed: ${err.message}`; - } -} - - -// --- Zone C: Download --- -async function requestDownload(confirm = false) { - const url = dlUrl.value.trim(); - if (!url) return; - - dlBtn.disabled = true; - dlBtn.textContent = '요청 중...'; - - const payload = { - url: url, - start: dlStart.value.trim(), - end: dlEnd.value.trim() - }; - - try { - const port = window.location.port ? `:${window.location.port}` : ':3000'; - const res = await fetch(`http://${window.location.hostname}${port}/api/download${confirm ? '?confirm=true' : ''}`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(payload) - }); - - if (res.status === 409) { - // Duplicate! - confirmModal.classList.remove('hidden'); - confirmModal.classList.add('flex'); - dlBtn.disabled = false; - dlBtn.textContent = '다운로드 시작'; - return; - } - - const data = await res.json(); - if (data.error) { - statusText.textContent = `Download Error: ${data.error}`; - } else { - // The WS will handle the progress - dlUrl.value = ''; - dlStart.value = ''; - dlEnd.value = ''; - confirmModal.classList.add('hidden'); - confirmModal.classList.remove('flex'); - } - } catch (err) { - statusText.textContent = `Download Request Failed: ${err.message}`; - } finally { - dlBtn.disabled = false; - dlBtn.textContent = '다운로드 시작'; - } -} - -dlBtn.addEventListener('click', () => requestDownload(false)); -dlConfirmBtn.addEventListener('click', () => requestDownload(true)); -dlCancelBtn.addEventListener('click', () => { - confirmModal.classList.add('hidden'); - confirmModal.classList.remove('flex'); -}); diff --git a/frontend/index.html b/frontend/index.html deleted file mode 100644 index 152de87..0000000 --- a/frontend/index.html +++ /dev/null @@ -1,103 +0,0 @@ - - - - - - AI Media Hub - - - - - - - - - -
-

- AI Media Hub Multimodal Ingest -

-
- Ready -
-
-
- -
- - -
-

- 🎯 Zone A: AI Smart Discovery -

-
- - -
- -
- -
- - 검색어를 입력하여 이미지를 탐색하세요 -
-
-
- - -
- - -
-

- 📥 Zone B: Smart Ingest -

-
- -

이곳에 미디어 파일을 드래그 & 드랍

- -
-
- - -
-

- ✂️ Zone C: Direct Downloader -

-
-
- - -
-
-
- - -
-
- - -
-
- - - -
-
-
-
- - - - diff --git a/frontend/style.css b/frontend/style.css deleted file mode 100644 index 982a41b..0000000 --- a/frontend/style.css +++ /dev/null @@ -1,32 +0,0 @@ -/* Custom scrollbar to keep it dark and minimal */ -::-webkit-scrollbar { - width: 8px; - height: 8px; -} -::-webkit-scrollbar-track { - background: #1a1a1a; -} -::-webkit-scrollbar-thumb { - background: #333; - border-radius: 4px; -} -::-webkit-scrollbar-thumb:hover { - background: #555; -} - -/* Glassmorphism for floating status elements if needed later */ -.glass { - background: rgba(30, 30, 30, 0.7); - backdrop-filter: blur(10px); - -webkit-backdrop-filter: blur(10px); -} - -/* Animate recommend badge */ -@keyframes pop { - 0% { transform: scale(0.9); opacity: 0; } - 100% { transform: scale(1); opacity: 1; } -} - -.recommend-card { - animation: pop 0.4s cubic-bezier(0.16, 1, 0.3, 1) forwards; -} diff --git a/logs/build-push-build-and-push-11.log b/logs/build-push-build-and-push-11.log deleted file mode 100644 index e8f2a22..0000000 --- a/logs/build-push-build-and-push-11.log +++ /dev/null @@ -1,475 +0,0 @@ -2026-03-12T05:22:20.8080714Z 7906ada0cbde(version:v0.3.0) received task 11 of job build-and-push, be triggered by event: push -2026-03-12T05:22:20.8088265Z workflow prepared -2026-03-12T05:22:20.8090138Z evaluating expression 'success()' -2026-03-12T05:22:20.8091290Z expression 'success()' evaluated to 'true' -2026-03-12T05:22:20.8091622Z 🚀 Start image=docker.gitea.com/runner-images:ubuntu-latest -2026-03-12T05:22:20.8178173Z 🐳 docker pull image=docker.gitea.com/runner-images:ubuntu-latest platform= username= forcePull=false -2026-03-12T05:22:20.8178654Z 🐳 docker pull docker.gitea.com/runner-images:ubuntu-latest -2026-03-12T05:22:20.8202404Z Image exists? true -2026-03-12T05:22:20.8304999Z Cleaning up network for job build-and-push, and network name is: GITEA-ACTIONS-TASK-11_WORKFLOW-Build-and-Push-Docker-Image_JOB-build-and-push-build-and-push-network -2026-03-12T05:22:21.1507980Z 🐳 docker create image=docker.gitea.com/runner-images:ubuntu-latest platform= entrypoint=["/bin/sleep" "10800"] cmd=[] network="GITEA-ACTIONS-TASK-11_WORKFLOW-Build-and-Push-Docker-Image_JOB-build-and-push-build-and-push-network" -2026-03-12T05:22:22.1475899Z Created container name=GITEA-ACTIONS-TASK-11_WORKFLOW-Build-and-Push-Docker-Image_JOB-build-and-push id=6b8ceba3c42acad1c2bf22d83e423ed5e4574ef48b336a5605fc2d4c0c836b05 from image docker.gitea.com/runner-images:ubuntu-latest (platform: ) -2026-03-12T05:22:22.1476864Z ENV ==> [RUNNER_TOOL_CACHE=/opt/hostedtoolcache RUNNER_OS=Linux RUNNER_ARCH=X64 RUNNER_TEMP=/tmp LANG=C.UTF-8] -2026-03-12T05:22:22.1477397Z 🐳 docker run image=docker.gitea.com/runner-images:ubuntu-latest platform= entrypoint=["/bin/sleep" "10800"] cmd=[] network="GITEA-ACTIONS-TASK-11_WORKFLOW-Build-and-Push-Docker-Image_JOB-build-and-push-build-and-push-network" -2026-03-12T05:22:22.1477732Z Starting container: 6b8ceba3c42acad1c2bf22d83e423ed5e4574ef48b336a5605fc2d4c0c836b05 -2026-03-12T05:22:23.4554533Z Started container: 6b8ceba3c42acad1c2bf22d83e423ed5e4574ef48b336a5605fc2d4c0c836b05 -2026-03-12T05:22:23.5869986Z Writing entry to tarball workflow/event.json len:5042 -2026-03-12T05:22:23.5870487Z Writing entry to tarball workflow/envs.txt len:0 -2026-03-12T05:22:23.5870710Z Extracting content to '/var/run/act/' -2026-03-12T05:22:23.5942871Z expression '${{ github.repository }}' rewritten to 'format('{0}', github.repository)' -2026-03-12T05:22:23.5943336Z evaluating expression 'format('{0}', github.repository)' -2026-03-12T05:22:23.5943729Z expression 'format('{0}', github.repository)' evaluated to '%!t(string=savethenurse/ai-media-hub)' -2026-03-12T05:22:23.6011097Z ☁ git clone 'https://github.com/actions/checkout' # ref=v4 -2026-03-12T05:22:23.6011679Z cloning https://github.com/actions/checkout to /root/.cache/act/c3fe249fe73091a17d6638fe1341e7bd0bcc3466ce52323c0688e83e2463a4ab -2026-03-12T05:22:24.2614719Z Unable to pull refs/heads/v4: non-fast-forward update -2026-03-12T05:22:24.2615100Z Cloned https://github.com/actions/checkout to /root/.cache/act/c3fe249fe73091a17d6638fe1341e7bd0bcc3466ce52323c0688e83e2463a4ab -2026-03-12T05:22:24.2912631Z Checked out v4 -2026-03-12T05:22:24.3005883Z ☁ git clone 'https://github.com/docker/setup-buildx-action' # ref=v3 -2026-03-12T05:22:24.3006273Z cloning https://github.com/docker/setup-buildx-action to /root/.cache/act/6a647958c11e138a6cfcaf32d2b372bc8e0c97871d617bfb441d003d505b77cf -2026-03-12T05:22:25.1810487Z Cloned https://github.com/docker/setup-buildx-action to /root/.cache/act/6a647958c11e138a6cfcaf32d2b372bc8e0c97871d617bfb441d003d505b77cf -2026-03-12T05:22:25.7295354Z Checked out v3 -2026-03-12T05:22:25.7382561Z ☁ git clone 'https://github.com/docker/login-action' # ref=v3 -2026-03-12T05:22:25.7383333Z cloning https://github.com/docker/login-action to /root/.cache/act/f4980c6ac598e909987ac91567f6966749e4ffb3917249bbe2a2399d45f65943 -2026-03-12T05:22:27.0549391Z Cloned https://github.com/docker/login-action to /root/.cache/act/f4980c6ac598e909987ac91567f6966749e4ffb3917249bbe2a2399d45f65943 -2026-03-12T05:22:27.6267209Z Checked out v3 -2026-03-12T05:22:27.6337218Z ☁ git clone 'https://github.com/docker/metadata-action' # ref=v5 -2026-03-12T05:22:27.6337639Z cloning https://github.com/docker/metadata-action to /root/.cache/act/bcd26377c6fbfb33a804ad7d884c58b832133e558f518b09111bfd2c3b68d901 -2026-03-12T05:22:28.6314210Z Cloned https://github.com/docker/metadata-action to /root/.cache/act/bcd26377c6fbfb33a804ad7d884c58b832133e558f518b09111bfd2c3b68d901 -2026-03-12T05:22:29.2679590Z Checked out v5 -2026-03-12T05:22:29.2775375Z ☁ git clone 'https://github.com/docker/build-push-action' # ref=v5 -2026-03-12T05:22:29.2775885Z cloning https://github.com/docker/build-push-action to /root/.cache/act/16f092375cb92ceefe9cff65655024119642d5d4113fd0b0d1e367f0a1becd07 -2026-03-12T05:22:30.5078462Z Cloned https://github.com/docker/build-push-action to /root/.cache/act/16f092375cb92ceefe9cff65655024119642d5d4113fd0b0d1e367f0a1becd07 -2026-03-12T05:22:31.0132424Z Checked out v5 -2026-03-12T05:22:31.0303571Z evaluating expression '' -2026-03-12T05:22:31.0304169Z expression '' evaluated to 'true' -2026-03-12T05:22:31.0304523Z ⭐ Run Main Checkout repository -2026-03-12T05:22:31.0304797Z Writing entry to tarball workflow/outputcmd.txt len:0 -2026-03-12T05:22:31.0305007Z Writing entry to tarball workflow/statecmd.txt len:0 -2026-03-12T05:22:31.0305153Z Writing entry to tarball workflow/pathcmd.txt len:0 -2026-03-12T05:22:31.0305295Z Writing entry to tarball workflow/envs.txt len:0 -2026-03-12T05:22:31.0305426Z Writing entry to tarball workflow/SUMMARY.md len:0 -2026-03-12T05:22:31.0305572Z Extracting content to '/var/run/act' -2026-03-12T05:22:31.0394960Z expression '${{ github.repository }}' rewritten to 'format('{0}', github.repository)' -2026-03-12T05:22:31.0395706Z evaluating expression 'format('{0}', github.repository)' -2026-03-12T05:22:31.0396350Z expression 'format('{0}', github.repository)' evaluated to '%!t(string=savethenurse/ai-media-hub)' -2026-03-12T05:22:31.0396867Z expression '${{ github.token }}' rewritten to 'format('{0}', github.token)' -2026-03-12T05:22:31.0397089Z evaluating expression 'format('{0}', github.token)' -2026-03-12T05:22:31.0397534Z expression 'format('{0}', github.token)' evaluated to '%!t(string=***)' -2026-03-12T05:22:31.0398448Z type=remote-action actionDir=/root/.cache/act/c3fe249fe73091a17d6638fe1341e7bd0bcc3466ce52323c0688e83e2463a4ab actionPath= workdir=/workspace/savethenurse/ai-media-hub actionCacheDir=/root/.cache/act actionName=c3fe249fe73091a17d6638fe1341e7bd0bcc3466ce52323c0688e83e2463a4ab containerActionDir=/var/run/act/actions/c3fe249fe73091a17d6638fe1341e7bd0bcc3466ce52323c0688e83e2463a4ab -2026-03-12T05:22:31.0398849Z /var/run/act/actions/c3fe249fe73091a17d6638fe1341e7bd0bcc3466ce52323c0688e83e2463a4ab -2026-03-12T05:22:31.0399228Z Removing /root/.cache/act/c3fe249fe73091a17d6638fe1341e7bd0bcc3466ce52323c0688e83e2463a4ab/.gitignore before docker cp -2026-03-12T05:22:31.0401205Z 🐳 docker cp src=/root/.cache/act/c3fe249fe73091a17d6638fe1341e7bd0bcc3466ce52323c0688e83e2463a4ab/ dst=/var/run/act/actions/c3fe249fe73091a17d6638fe1341e7bd0bcc3466ce52323c0688e83e2463a4ab/ -2026-03-12T05:22:31.0403236Z Writing tarball /tmp/act1831349509 from /root/.cache/act/c3fe249fe73091a17d6638fe1341e7bd0bcc3466ce52323c0688e83e2463a4ab/ -2026-03-12T05:22:31.0403705Z Stripping prefix:/root/.cache/act/c3fe249fe73091a17d6638fe1341e7bd0bcc3466ce52323c0688e83e2463a4ab/ src:/root/.cache/act/c3fe249fe73091a17d6638fe1341e7bd0bcc3466ce52323c0688e83e2463a4ab/ -2026-03-12T05:22:31.1378297Z Extracting content from '/tmp/act1831349509' to '/var/run/act/actions/c3fe249fe73091a17d6638fe1341e7bd0bcc3466ce52323c0688e83e2463a4ab/' -2026-03-12T05:22:31.2866259Z executing remote job container: [node /var/run/act/actions/c3fe249fe73091a17d6638fe1341e7bd0bcc3466ce52323c0688e83e2463a4ab/dist/index.js] -2026-03-12T05:22:31.2866783Z 🐳 docker exec cmd=[node /var/run/act/actions/c3fe249fe73091a17d6638fe1341e7bd0bcc3466ce52323c0688e83e2463a4ab/dist/index.js] user= workdir= -2026-03-12T05:22:31.2866962Z Exec command '[node /var/run/act/actions/c3fe249fe73091a17d6638fe1341e7bd0bcc3466ce52323c0688e83e2463a4ab/dist/index.js]' -2026-03-12T05:22:31.2867430Z Working directory '/workspace/savethenurse/ai-media-hub' -2026-03-12T05:22:31.4417167Z ::add-matcher::/run/act/actions/c3fe249fe73091a17d6638fe1341e7bd0bcc3466ce52323c0688e83e2463a4ab/dist/problem-matcher.json -2026-03-12T05:22:31.4417429Z ::add-matcher::/run/act/actions/c3fe249fe73091a17d6638fe1341e7bd0bcc3466ce52323c0688e83e2463a4ab/dist/problem-matcher.json -2026-03-12T05:22:31.4423079Z Syncing repository: savethenurse/ai-media-hub -2026-03-12T05:22:31.4428196Z ::group::Getting Git version info -2026-03-12T05:22:31.4428593Z Working directory is '/workspace/savethenurse/ai-media-hub' -2026-03-12T05:22:31.4479774Z [command]/usr/bin/git version -2026-03-12T05:22:31.4526943Z git version 2.53.0 -2026-03-12T05:22:31.4555977Z ::endgroup:: -2026-03-12T05:22:31.5102227Z Temporarily overriding HOME='/tmp/4f119427-9efa-4ae7-bc4b-840b654b29be' before making global git config changes -2026-03-12T05:22:31.5103929Z Adding repository directory to the temporary git global config as a safe directory -2026-03-12T05:22:31.5114896Z [command]/usr/bin/git config --global --add safe.directory /workspace/savethenurse/ai-media-hub -2026-03-12T05:22:31.5177561Z Deleting the contents of '/workspace/savethenurse/ai-media-hub' -2026-03-12T05:22:31.5188658Z ::group::Initializing the repository -2026-03-12T05:22:31.5197651Z [command]/usr/bin/git init /workspace/savethenurse/ai-media-hub -2026-03-12T05:22:31.5254387Z hint: Using 'master' as the name for the initial branch. This default branch name -2026-03-12T05:22:31.5255347Z hint: will change to "main" in Git 3.0. To configure the initial branch name -2026-03-12T05:22:31.5255802Z hint: to use in all of your new repositories, which will suppress this warning, -2026-03-12T05:22:31.5256359Z hint: call: -2026-03-12T05:22:31.5256847Z hint: -2026-03-12T05:22:31.5257211Z hint: git config --global init.defaultBranch -2026-03-12T05:22:31.5257805Z hint: -2026-03-12T05:22:31.5258130Z hint: Names commonly chosen instead of 'master' are 'main', 'trunk' and -2026-03-12T05:22:31.5258509Z hint: 'development'. The just-created branch can be renamed via this command: -2026-03-12T05:22:31.5258890Z hint: -2026-03-12T05:22:31.5259060Z hint: git branch -m -2026-03-12T05:22:31.5259176Z hint: -2026-03-12T05:22:31.5259292Z hint: Disable this message with "git config set advice.defaultBranchName false" -2026-03-12T05:22:31.5259559Z Initialized empty Git repository in /workspace/savethenurse/ai-media-hub/.git/ -2026-03-12T05:22:31.5280454Z [command]/usr/bin/git remote add origin https://git.savethenurse.com/savethenurse/ai-media-hub -2026-03-12T05:22:31.5362804Z ::endgroup:: -2026-03-12T05:22:31.5363812Z ::group::Disabling automatic garbage collection -2026-03-12T05:22:31.5371499Z [command]/usr/bin/git config --local gc.auto 0 -2026-03-12T05:22:31.5420150Z ::endgroup:: -2026-03-12T05:22:31.5420591Z ::group::Setting up auth -2026-03-12T05:22:31.5437354Z [command]/usr/bin/git config --local --name-only --get-regexp core\.sshCommand -2026-03-12T05:22:31.5489623Z [command]/usr/bin/git submodule foreach --recursive sh -c "git config --local --name-only --get-regexp 'core\.sshCommand' && git config --local --unset-all 'core.sshCommand' || :" -2026-03-12T05:22:31.5949888Z [command]/usr/bin/git config --local --name-only --get-regexp http\.https\:\/\/git\.savethenurse\.com\/\.extraheader -2026-03-12T05:22:31.5986151Z [command]/usr/bin/git submodule foreach --recursive sh -c "git config --local --name-only --get-regexp 'http\.https\:\/\/git\.savethenurse\.com\/\.extraheader' && git config --local --unset-all 'http.https://git.savethenurse.com/.extraheader' || :" -2026-03-12T05:22:31.6278032Z [command]/usr/bin/git config --local --name-only --get-regexp ^includeIf\.gitdir: -2026-03-12T05:22:31.6322368Z [command]/usr/bin/git submodule foreach --recursive git config --local --show-origin --name-only --get-regexp remote.origin.url -2026-03-12T05:22:31.6748847Z [command]/usr/bin/git config --local http.https://git.savethenurse.com/.extraheader AUTHORIZATION: basic *** -2026-03-12T05:22:31.6812178Z ::endgroup:: -2026-03-12T05:22:31.6812605Z ::group::Fetching the repository -2026-03-12T05:22:31.6822584Z [command]/usr/bin/git -c protocol.version=2 fetch --no-tags --prune --no-recurse-submodules --depth=1 origin +4cdf1066e687a295e42483d74ad6747ad69ab1d4:refs/remotes/origin/main -2026-03-12T05:22:32.5663315Z From https://git.savethenurse.com/savethenurse/ai-media-hub -2026-03-12T05:22:32.5664165Z * [new ref] 4cdf1066e687a295e42483d74ad6747ad69ab1d4 -> origin/main -2026-03-12T05:22:32.5697829Z ::endgroup:: -2026-03-12T05:22:32.5698336Z ::group::Determining the checkout info -2026-03-12T05:22:32.5701515Z ::endgroup:: -2026-03-12T05:22:32.5707771Z [command]/usr/bin/git sparse-checkout disable -2026-03-12T05:22:32.5773457Z [command]/usr/bin/git config --local --unset-all extensions.worktreeConfig -2026-03-12T05:22:32.5828521Z ::group::Checking out the ref -2026-03-12T05:22:32.5836139Z [command]/usr/bin/git checkout --progress --force -B main refs/remotes/origin/main -2026-03-12T05:22:32.5909254Z Switched to a new branch 'main' -2026-03-12T05:22:32.5912154Z branch 'main' set up to track 'origin/main'. -2026-03-12T05:22:32.5923212Z ::endgroup:: -2026-03-12T05:22:32.5989947Z [command]/usr/bin/git log -1 --format=%H -2026-03-12T05:22:32.6023992Z 4cdf1066e687a295e42483d74ad6747ad69ab1d4 -2026-03-12T05:22:32.6047140Z ::remove-matcher owner=checkout-git:: -2026-03-12T05:22:33.5869610Z (node:140) [DEP0040] DeprecationWarning: The `punycode` module is deprecated. Please use a userland alternative instead. -2026-03-12T05:22:33.5870179Z (Use `node --trace-deprecation ...` to show where the warning was created) -2026-03-12T05:22:33.5933557Z ::group::Docker info -2026-03-12T05:22:33.5965273Z [command]/usr/bin/docker version -2026-03-12T05:22:33.6855781Z Client: -2026-03-12T05:22:33.6859901Z Version: 29.2.1-1 -2026-03-12T05:22:33.6860200Z API version: 1.47 (downgraded from 1.53) -2026-03-12T05:22:33.6860444Z Go version: go1.25.7 -2026-03-12T05:22:33.6860589Z Git commit: a5c7197d720daef7d8b9e6174ee78c0743cea166 -2026-03-12T05:22:33.6860746Z Built: Tue Feb 10 03:37:51 2026 -2026-03-12T05:22:33.6860885Z OS/Arch: linux/amd64 -2026-03-12T05:22:33.6861164Z Context: default -2026-03-12T05:22:33.6861440Z -2026-03-12T05:22:33.6861610Z Server: Docker Engine - Community -2026-03-12T05:22:33.6861776Z Engine: -2026-03-12T05:22:33.6862086Z Version: 27.5.1 -2026-03-12T05:22:33.6862340Z API version: 1.47 (minimum version 1.24) -2026-03-12T05:22:33.6862849Z Go version: go1.22.11 -2026-03-12T05:22:33.6863292Z Git commit: 4c9b3b0 -2026-03-12T05:22:33.6863823Z Built: Wed Jan 22 13:41:24 2025 -2026-03-12T05:22:33.6864088Z OS/Arch: linux/amd64 -2026-03-12T05:22:33.6864544Z Experimental: false -2026-03-12T05:22:33.6864770Z containerd: -2026-03-12T05:22:33.6865196Z Version: v1.7.25 -2026-03-12T05:22:33.6865447Z GitCommit: bcc810d6b9066471b0b6fa75f557a15a1cbf31bb -2026-03-12T05:22:33.6865797Z runc: -2026-03-12T05:22:33.6866129Z Version: 1.2.4 -2026-03-12T05:22:33.6866373Z GitCommit: v1.2.4-0-g6c52b3f -2026-03-12T05:22:33.6866812Z docker-init: -2026-03-12T05:22:33.6867055Z Version: 0.19.0 -2026-03-12T05:22:33.6867517Z GitCommit: de40ad0 -2026-03-12T05:22:33.6951959Z [command]/usr/bin/docker info -2026-03-12T05:22:34.1058632Z Client: -2026-03-12T05:22:34.1059167Z Version: 29.2.1-1 -2026-03-12T05:22:34.1059374Z Context: default -2026-03-12T05:22:34.1059514Z Debug Mode: false -2026-03-12T05:22:34.1059746Z Plugins: -2026-03-12T05:22:34.1059957Z buildx: Docker Buildx (Docker Inc.) -2026-03-12T05:22:34.1060159Z Version: 0.31.1-1 -2026-03-12T05:22:34.1060292Z Path: /usr/libexec/docker/cli-plugins/docker-buildx -2026-03-12T05:22:34.1060416Z compose: Docker Compose (Docker Inc.) -2026-03-12T05:22:34.1060549Z Version: 5.0.2-1 -2026-03-12T05:22:34.1060750Z Path: /usr/libexec/docker/cli-plugins/docker-compose -2026-03-12T05:22:34.1060974Z -2026-03-12T05:22:34.1061109Z Server: -2026-03-12T05:22:34.1061224Z Containers: 15 -2026-03-12T05:22:34.1061333Z Running: 13 -2026-03-12T05:22:34.1061444Z Paused: 0 -2026-03-12T05:22:34.1061619Z Stopped: 2 -2026-03-12T05:22:34.1061773Z Images: 19 -2026-03-12T05:22:34.1061902Z Server Version: 27.5.1 -2026-03-12T05:22:34.1062143Z Storage Driver: overlay2 -2026-03-12T05:22:34.1062279Z Backing Filesystem: btrfs -2026-03-12T05:22:34.1062395Z Supports d_type: true -2026-03-12T05:22:34.1062505Z Using metacopy: false -2026-03-12T05:22:34.1062614Z Native Overlay Diff: true -2026-03-12T05:22:34.1062725Z userxattr: false -2026-03-12T05:22:34.1062863Z Logging Driver: json-file -2026-03-12T05:22:34.1063182Z Cgroup Driver: cgroupfs -2026-03-12T05:22:34.1063510Z Cgroup Version: 2 -2026-03-12T05:22:34.1063699Z Plugins: -2026-03-12T05:22:34.1063883Z Volume: local -2026-03-12T05:22:34.1064094Z Network: bridge host ipvlan macvlan null overlay -2026-03-12T05:22:34.1064315Z Log: awslogs fluentd gcplogs gelf journald json-file local splunk syslog -2026-03-12T05:22:34.1064528Z Swarm: inactive -2026-03-12T05:22:34.1064712Z Runtimes: io.containerd.runc.v2 runc -2026-03-12T05:22:34.1064910Z Default Runtime: runc -2026-03-12T05:22:34.1065102Z Init Binary: docker-init -2026-03-12T05:22:34.1065287Z containerd version: bcc810d6b9066471b0b6fa75f557a15a1cbf31bb -2026-03-12T05:22:34.1065490Z runc version: v1.2.4-0-g6c52b3f -2026-03-12T05:22:34.1065676Z init version: de40ad0 -2026-03-12T05:22:34.1065876Z Security Options: -2026-03-12T05:22:34.1066057Z seccomp -2026-03-12T05:22:34.1066237Z Profile: builtin -2026-03-12T05:22:34.1066422Z cgroupns -2026-03-12T05:22:34.1066599Z Kernel Version: 6.12.54-Unraid -2026-03-12T05:22:34.1066802Z Operating System: Unraid OS 7.2 x86_64 -2026-03-12T05:22:34.1066990Z OSType: linux -2026-03-12T05:22:34.1067175Z Architecture: x86_64 -2026-03-12T05:22:34.1067355Z CPUs: 8 -2026-03-12T05:22:34.1067551Z Total Memory: 7.73GiB -2026-03-12T05:22:34.1067741Z Name: SaveTheNurse -2026-03-12T05:22:34.1067943Z ID: e4146e4f-8d6a-4dab-81bc-ba642d17e483 -2026-03-12T05:22:34.1068138Z Docker Root Dir: /var/lib/docker -2026-03-12T05:22:34.1068321Z Debug Mode: false -2026-03-12T05:22:34.1068527Z Experimental: false -2026-03-12T05:22:34.1068710Z Insecure Registries: -2026-03-12T05:22:34.1068898Z 127.0.0.0/8 -2026-03-12T05:22:34.1069075Z Live Restore Enabled: false -2026-03-12T05:22:34.1069264Z Product License: Community Engine -2026-03-12T05:22:34.1069464Z -2026-03-12T05:22:34.1069676Z WARNING: No swap limit support -2026-03-12T05:22:34.1091223Z ::endgroup:: -2026-03-12T05:22:34.2692215Z ::group::Buildx version -2026-03-12T05:22:34.2713927Z [command]/usr/bin/docker buildx version -2026-03-12T05:22:34.3907481Z github.com/docker/buildx 0.31.1-1 a2675950d46b2cb171b23c2015ca44fb88607531 -2026-03-12T05:22:34.3984799Z ::endgroup:: -2026-03-12T05:22:34.5070775Z ::group::Inspecting default docker context -2026-03-12T05:22:34.5368440Z [ -2026-03-12T05:22:34.5369320Z { -2026-03-12T05:22:34.5369620Z "Name": "default", -2026-03-12T05:22:34.5370031Z "Metadata": {}, -2026-03-12T05:22:34.5370271Z "Endpoints": { -2026-03-12T05:22:34.5370494Z "docker": { -2026-03-12T05:22:34.5370901Z "Host": "unix:///var/run/docker.sock", -2026-03-12T05:22:34.5371160Z "SkipTLSVerify": false -2026-03-12T05:22:34.5371405Z } -2026-03-12T05:22:34.5371774Z }, -2026-03-12T05:22:34.5372006Z "TLSMaterial": {}, -2026-03-12T05:22:34.5372276Z "Storage": { -2026-03-12T05:22:34.5372651Z "MetadataPath": "", -2026-03-12T05:22:34.5372928Z "TLSPath": "" -2026-03-12T05:22:34.5373434Z } -2026-03-12T05:22:34.5373661Z } -2026-03-12T05:22:34.5373761Z ] -2026-03-12T05:22:34.5374062Z ::endgroup:: -2026-03-12T05:22:34.5374449Z ::group::Creating a new builder instance -2026-03-12T05:22:34.7657953Z [command]/usr/bin/docker buildx create --name builder-09de8a91-9180-4eae-8d38-e1d397e9450a --driver docker-container --buildkitd-flags --allow-insecure-entitlement security.insecure --allow-insecure-entitlement network.host --use -2026-03-12T05:22:35.0088005Z builder-09de8a91-9180-4eae-8d38-e1d397e9450a -2026-03-12T05:22:35.0154368Z ::endgroup:: -2026-03-12T05:22:35.0155000Z ::group::Booting builder -2026-03-12T05:22:35.0195387Z [command]/usr/bin/docker buildx inspect --bootstrap --builder builder-09de8a91-9180-4eae-8d38-e1d397e9450a -2026-03-12T05:22:35.1226126Z #1 [internal] booting buildkit -2026-03-12T05:22:35.2735968Z #1 pulling image moby/buildkit:buildx-stable-1 -2026-03-12T05:22:37.4933653Z #1 pulling image moby/buildkit:buildx-stable-1 2.4s done -2026-03-12T05:22:37.6442569Z #1 creating container buildx_buildkit_builder-09de8a91-9180-4eae-8d38-e1d397e9450a0 -2026-03-12T05:22:44.7439601Z #1 creating container buildx_buildkit_builder-09de8a91-9180-4eae-8d38-e1d397e9450a0 7.3s done -2026-03-12T05:22:44.7452743Z #1 DONE 9.6s -2026-03-12T05:22:44.7941698Z Name: builder-09de8a91-9180-4eae-8d38-e1d397e9450a -2026-03-12T05:22:44.7942226Z Driver: docker-container -2026-03-12T05:22:44.7942478Z Last Activity: 2026-03-12 05:22:34 +0000 UTC -2026-03-12T05:22:44.7942623Z -2026-03-12T05:22:44.7942846Z Nodes: -2026-03-12T05:22:44.7945112Z Name: builder-09de8a91-9180-4eae-8d38-e1d397e9450a0 -2026-03-12T05:22:44.7945467Z Endpoint: unix:///var/run/docker.sock -2026-03-12T05:22:44.7945714Z Status: running -2026-03-12T05:22:44.7953478Z BuildKit daemon flags: --allow-insecure-entitlement security.insecure --allow-insecure-entitlement network.host -2026-03-12T05:22:44.7953813Z BuildKit version: v0.27.1 -2026-03-12T05:22:44.7954075Z Platforms: linux/amd64, linux/amd64/v2, linux/386 -2026-03-12T05:22:44.7954291Z Labels: -2026-03-12T05:22:44.7954545Z org.mobyproject.buildkit.worker.executor: oci -2026-03-12T05:22:44.7954700Z org.mobyproject.buildkit.worker.hostname: efe86b7630bc -2026-03-12T05:22:44.7954838Z org.mobyproject.buildkit.worker.network: host -2026-03-12T05:22:44.7954975Z org.mobyproject.buildkit.worker.oci.process-mode: sandbox -2026-03-12T05:22:44.7955135Z org.mobyproject.buildkit.worker.selinux.enabled: false -2026-03-12T05:22:44.7955268Z org.mobyproject.buildkit.worker.snapshotter: overlayfs -2026-03-12T05:22:44.7955493Z GC Policy rule#0: -2026-03-12T05:22:44.7955630Z All: false -2026-03-12T05:22:44.7955866Z Filters: type==source.local,type==exec.cachemount,type==source.git.checkout -2026-03-12T05:22:44.7956014Z Keep Duration: 48h0m0s -2026-03-12T05:22:44.7956150Z Max Used Space: 488.3MiB -2026-03-12T05:22:44.7956453Z GC Policy rule#1: -2026-03-12T05:22:44.7956586Z All: false -2026-03-12T05:22:44.7956720Z Keep Duration: 1440h0m0s -2026-03-12T05:22:44.7956849Z Reserved Space: 6.519GiB -2026-03-12T05:22:44.7957081Z Max Used Space: 48.43GiB -2026-03-12T05:22:44.7957322Z Min Free Space: 12.11GiB -2026-03-12T05:22:44.7957485Z GC Policy rule#2: -2026-03-12T05:22:44.7957621Z All: false -2026-03-12T05:22:44.7957759Z Reserved Space: 6.519GiB -2026-03-12T05:22:44.7958009Z Max Used Space: 48.43GiB -2026-03-12T05:22:44.7958209Z Min Free Space: 12.11GiB -2026-03-12T05:22:44.7958480Z GC Policy rule#3: -2026-03-12T05:22:44.7958627Z All: true -2026-03-12T05:22:44.7958868Z Reserved Space: 6.519GiB -2026-03-12T05:22:44.7959111Z Max Used Space: 48.43GiB -2026-03-12T05:22:44.7959249Z Min Free Space: 12.11GiB -2026-03-12T05:22:44.8034813Z ::endgroup:: -2026-03-12T05:22:44.9713359Z ::group::Inspect builder -2026-03-12T05:22:45.0531548Z { -2026-03-12T05:22:45.0532018Z "nodes": [ -2026-03-12T05:22:45.0532145Z { -2026-03-12T05:22:45.0532328Z "name": "builder-09de8a91-9180-4eae-8d38-e1d397e9450a0", -2026-03-12T05:22:45.0532649Z "endpoint": "unix:///var/run/docker.sock", -2026-03-12T05:22:45.0532774Z "status": "running", -2026-03-12T05:22:45.0532889Z "buildkitd-flags": "--allow-insecure-entitlement security.insecure --allow-insecure-entitlement network.host", -2026-03-12T05:22:45.0533208Z "buildkit": "v0.27.1", -2026-03-12T05:22:45.0533449Z "platforms": "linux/amd64,linux/amd64/v2,linux/386", -2026-03-12T05:22:45.0533574Z "features": { -2026-03-12T05:22:45.0533698Z "Automatically load images to the Docker Engine image store": true, -2026-03-12T05:22:45.0533826Z "Cache export": true, -2026-03-12T05:22:45.0534023Z "Direct push": true, -2026-03-12T05:22:45.0534134Z "Docker exporter": true, -2026-03-12T05:22:45.0534254Z "Multi-platform build": true, -2026-03-12T05:22:45.0534361Z "OCI exporter": true -2026-03-12T05:22:45.0534467Z }, -2026-03-12T05:22:45.0534567Z "labels": { -2026-03-12T05:22:45.0534737Z "org.mobyproject.buildkit.worker.executor": "oci", -2026-03-12T05:22:45.0534887Z "org.mobyproject.buildkit.worker.hostname": "efe86b7630bc", -2026-03-12T05:22:45.0535001Z "org.mobyproject.buildkit.worker.network": "host", -2026-03-12T05:22:45.0535112Z "org.mobyproject.buildkit.worker.oci.process-mode": "sandbox", -2026-03-12T05:22:45.0535229Z "org.mobyproject.buildkit.worker.selinux.enabled": "false", -2026-03-12T05:22:45.0535363Z "org.mobyproject.buildkit.worker.snapshotter": "overlayfs" -2026-03-12T05:22:45.0535553Z }, -2026-03-12T05:22:45.0535672Z "gcPolicy": [ -2026-03-12T05:22:45.0535775Z { -2026-03-12T05:22:45.0535892Z "all": false, -2026-03-12T05:22:45.0536005Z "filter": [ -2026-03-12T05:22:45.0536149Z "type==source.local", -2026-03-12T05:22:45.0536332Z "type==exec.cachemount", -2026-03-12T05:22:45.0536477Z "type==source.git.checkout" -2026-03-12T05:22:45.0536607Z ], -2026-03-12T05:22:45.0536734Z "keepDuration": "48h0m0s", -2026-03-12T05:22:45.0537031Z "maxUsedSpace": "488.3MiB" -2026-03-12T05:22:45.0537199Z }, -2026-03-12T05:22:45.0537312Z { -2026-03-12T05:22:45.0537428Z "all": false, -2026-03-12T05:22:45.0537536Z "keepDuration": "1440h0m0s", -2026-03-12T05:22:45.0537739Z "reservedSpace": "6.519GiB", -2026-03-12T05:22:45.0537924Z "maxUsedSpace": "48.43GiB", -2026-03-12T05:22:45.0538043Z "minFreeSpace": "12.11GiB" -2026-03-12T05:22:45.0538161Z }, -2026-03-12T05:22:45.0538364Z { -2026-03-12T05:22:45.0538652Z "all": false, -2026-03-12T05:22:45.0538856Z "reservedSpace": "6.519GiB", -2026-03-12T05:22:45.0539000Z "maxUsedSpace": "48.43GiB", -2026-03-12T05:22:45.0539112Z "minFreeSpace": "12.11GiB" -2026-03-12T05:22:45.0539230Z }, -2026-03-12T05:22:45.0539333Z { -2026-03-12T05:22:45.0539435Z "all": true, -2026-03-12T05:22:45.0539548Z "reservedSpace": "6.519GiB", -2026-03-12T05:22:45.0539749Z "maxUsedSpace": "48.43GiB", -2026-03-12T05:22:45.0539850Z "minFreeSpace": "12.11GiB" -2026-03-12T05:22:45.0539952Z } -2026-03-12T05:22:45.0540048Z ] -2026-03-12T05:22:45.0540161Z } -2026-03-12T05:22:45.0540256Z ], -2026-03-12T05:22:45.0540371Z "name": "builder-09de8a91-9180-4eae-8d38-e1d397e9450a", -2026-03-12T05:22:45.0540485Z "driver": "docker-container", -2026-03-12T05:22:45.0540594Z "lastActivity": "2026-03-12T05:22:34.000Z" -2026-03-12T05:22:45.0540709Z } -2026-03-12T05:22:45.0541281Z ::endgroup:: -2026-03-12T05:22:45.0541724Z ::group::BuildKit version -2026-03-12T05:22:45.0541947Z builder-09de8a91-9180-4eae-8d38-e1d397e9450a0: v0.27.1 -2026-03-12T05:22:45.0542250Z ::endgroup:: -2026-03-12T05:22:47.4603651Z Logging into ghcr.io... -2026-03-12T05:22:47.4633235Z (node:461) [DEP0040] DeprecationWarning: The `punycode` module is deprecated. Please use a userland alternative instead. -2026-03-12T05:22:47.4633729Z (Use `node --trace-deprecation ...` to show where the warning was created) -2026-03-12T05:22:48.1391786Z ::error::Error response from daemon: Get "https://ghcr.io/v2/": denied: denied -2026-03-12T05:22:48.1524523Z ❌ Failure - Main Log in to the Container registry -2026-03-12T05:22:48.1593968Z exitcode '1': failure -2026-03-12T05:22:48.2086347Z skipping post step for 'Build and push Docker image'; main step was skipped -2026-03-12T05:22:48.2086622Z skipping post step for 'Extract metadata (tags, labels) for Docker'; main step was skipped -2026-03-12T05:22:48.2212541Z expression '${{ secrets.GITHUB_TOKEN }}' rewritten to 'format('{0}', secrets.GITHUB_TOKEN)' -2026-03-12T05:22:48.2212947Z evaluating expression 'format('{0}', secrets.GITHUB_TOKEN)' -2026-03-12T05:22:48.2213471Z expression 'format('{0}', secrets.GITHUB_TOKEN)' evaluated to '%!t(string=***)' -2026-03-12T05:22:48.2213743Z expression '${{ github.actor }}' rewritten to 'format('{0}', github.actor)' -2026-03-12T05:22:48.2213959Z evaluating expression 'format('{0}', github.actor)' -2026-03-12T05:22:48.2214318Z expression 'format('{0}', github.actor)' evaluated to '%!t(string=savethenurse)' -2026-03-12T05:22:48.2214560Z expression '${{ env.REGISTRY }}' rewritten to 'format('{0}', env.REGISTRY)' -2026-03-12T05:22:48.2214739Z evaluating expression 'format('{0}', env.REGISTRY)' -2026-03-12T05:22:48.2215103Z expression 'format('{0}', env.REGISTRY)' evaluated to '%!t(string=ghcr.io)' -2026-03-12T05:22:48.2267412Z evaluating expression 'always()' -2026-03-12T05:22:48.2267932Z expression 'always()' evaluated to 'true' -2026-03-12T05:22:48.2268099Z ⭐ Run Post Log in to the Container registry -2026-03-12T05:22:48.2268317Z Writing entry to tarball workflow/outputcmd.txt len:0 -2026-03-12T05:22:48.2268510Z Writing entry to tarball workflow/statecmd.txt len:0 -2026-03-12T05:22:48.2268641Z Writing entry to tarball workflow/pathcmd.txt len:0 -2026-03-12T05:22:48.2268777Z Writing entry to tarball workflow/envs.txt len:0 -2026-03-12T05:22:48.2268898Z Writing entry to tarball workflow/SUMMARY.md len:0 -2026-03-12T05:22:48.2269101Z Extracting content to '/var/run/act' -2026-03-12T05:22:48.2286836Z run post step for 'Log in to the Container registry' -2026-03-12T05:22:48.2287590Z executing remote job container: [node /var/run/act/actions/f4980c6ac598e909987ac91567f6966749e4ffb3917249bbe2a2399d45f65943/dist/index.js] -2026-03-12T05:22:48.2287796Z 🐳 docker exec cmd=[node /var/run/act/actions/f4980c6ac598e909987ac91567f6966749e4ffb3917249bbe2a2399d45f65943/dist/index.js] user= workdir= -2026-03-12T05:22:48.2287949Z Exec command '[node /var/run/act/actions/f4980c6ac598e909987ac91567f6966749e4ffb3917249bbe2a2399d45f65943/dist/index.js]' -2026-03-12T05:22:48.2288311Z Working directory '/workspace/savethenurse/ai-media-hub' -2026-03-12T05:22:48.6895580Z ::group::Logout from ghcr.io -2026-03-12T05:22:48.6896309Z ::group::Logout from ghcr.io -2026-03-12T05:22:48.6925660Z (node:489) [DEP0040] DeprecationWarning: The `punycode` module is deprecated. Please use a userland alternative instead. -2026-03-12T05:22:48.6926074Z (Use `node --trace-deprecation ...` to show where the warning was created) -2026-03-12T05:22:48.6948884Z [command]/usr/bin/docker logout ghcr.io -2026-03-12T05:22:48.7218576Z Removing login credentials for ghcr.io -2026-03-12T05:22:48.7266673Z ::endgroup:: -2026-03-12T05:22:48.7266998Z ::endgroup:: -2026-03-12T05:22:48.7267205Z ::group::Post cache -2026-03-12T05:22:48.7267317Z ::group::Post cache -2026-03-12T05:22:48.7269903Z State not set -2026-03-12T05:22:48.7270165Z ::endgroup:: -2026-03-12T05:22:48.7270356Z ::endgroup:: -2026-03-12T05:22:48.7406302Z ✅ Success - Post Log in to the Container registry -2026-03-12T05:22:48.7708941Z evaluating expression 'always()' -2026-03-12T05:22:48.7709962Z expression 'always()' evaluated to 'true' -2026-03-12T05:22:48.7710396Z ⭐ Run Post Set up Docker Buildx -2026-03-12T05:22:48.7710759Z Writing entry to tarball workflow/outputcmd.txt len:0 -2026-03-12T05:22:48.7711107Z Writing entry to tarball workflow/statecmd.txt len:0 -2026-03-12T05:22:48.7711362Z Writing entry to tarball workflow/pathcmd.txt len:0 -2026-03-12T05:22:48.7711610Z Writing entry to tarball workflow/envs.txt len:0 -2026-03-12T05:22:48.7711838Z Writing entry to tarball workflow/SUMMARY.md len:0 -2026-03-12T05:22:48.7712090Z Extracting content to '/var/run/act' -2026-03-12T05:22:48.7734186Z run post step for 'Set up Docker Buildx' -2026-03-12T05:22:48.7735187Z executing remote job container: [node /var/run/act/actions/6a647958c11e138a6cfcaf32d2b372bc8e0c97871d617bfb441d003d505b77cf/dist/index.js] -2026-03-12T05:22:48.7735447Z 🐳 docker exec cmd=[node /var/run/act/actions/6a647958c11e138a6cfcaf32d2b372bc8e0c97871d617bfb441d003d505b77cf/dist/index.js] user= workdir= -2026-03-12T05:22:48.7735651Z Exec command '[node /var/run/act/actions/6a647958c11e138a6cfcaf32d2b372bc8e0c97871d617bfb441d003d505b77cf/dist/index.js]' -2026-03-12T05:22:48.7736158Z Working directory '/workspace/savethenurse/ai-media-hub' -2026-03-12T05:22:49.1570347Z ::group::Removing builder -2026-03-12T05:22:49.1570992Z ::group::Removing builder -2026-03-12T05:22:49.1580308Z (node:516) [DEP0040] DeprecationWarning: The `punycode` module is deprecated. Please use a userland alternative instead. -2026-03-12T05:22:49.1580620Z (Use `node --trace-deprecation ...` to show where the warning was created) -2026-03-12T05:22:49.3097182Z [command]/usr/bin/docker buildx rm builder-09de8a91-9180-4eae-8d38-e1d397e9450a -2026-03-12T05:22:50.8221459Z builder-09de8a91-9180-4eae-8d38-e1d397e9450a removed -2026-03-12T05:22:50.8296516Z ::endgroup:: -2026-03-12T05:22:50.8297092Z ::endgroup:: -2026-03-12T05:22:50.8297513Z ::group::Cleaning up certificates -2026-03-12T05:22:50.8297757Z ::group::Cleaning up certificates -2026-03-12T05:22:50.8300113Z ::endgroup:: -2026-03-12T05:22:50.8300438Z ::endgroup:: -2026-03-12T05:22:50.8301160Z ::group::Post cache -2026-03-12T05:22:50.8301395Z ::group::Post cache -2026-03-12T05:22:50.8305159Z State not set -2026-03-12T05:22:50.8305785Z ::endgroup:: -2026-03-12T05:22:50.8305986Z ::endgroup:: -2026-03-12T05:22:50.8441237Z ✅ Success - Post Set up Docker Buildx -2026-03-12T05:22:50.8781270Z evaluating expression 'always()' -2026-03-12T05:22:50.8782024Z expression 'always()' evaluated to 'true' -2026-03-12T05:22:50.8782332Z ⭐ Run Post Checkout repository -2026-03-12T05:22:50.8782641Z Writing entry to tarball workflow/outputcmd.txt len:0 -2026-03-12T05:22:50.8782913Z Writing entry to tarball workflow/statecmd.txt len:0 -2026-03-12T05:22:50.8783539Z Writing entry to tarball workflow/pathcmd.txt len:0 -2026-03-12T05:22:50.8783806Z Writing entry to tarball workflow/envs.txt len:0 -2026-03-12T05:22:50.8783993Z Writing entry to tarball workflow/SUMMARY.md len:0 -2026-03-12T05:22:50.8784189Z Extracting content to '/var/run/act' -2026-03-12T05:22:50.8810494Z run post step for 'Checkout repository' -2026-03-12T05:22:50.8811505Z executing remote job container: [node /var/run/act/actions/c3fe249fe73091a17d6638fe1341e7bd0bcc3466ce52323c0688e83e2463a4ab/dist/index.js] -2026-03-12T05:22:50.8811917Z 🐳 docker exec cmd=[node /var/run/act/actions/c3fe249fe73091a17d6638fe1341e7bd0bcc3466ce52323c0688e83e2463a4ab/dist/index.js] user= workdir= -2026-03-12T05:22:50.8812263Z Exec command '[node /var/run/act/actions/c3fe249fe73091a17d6638fe1341e7bd0bcc3466ce52323c0688e83e2463a4ab/dist/index.js]' -2026-03-12T05:22:50.8813263Z Working directory '/workspace/savethenurse/ai-media-hub' -2026-03-12T05:22:51.0388997Z [command]/usr/bin/git version -2026-03-12T05:22:51.1133913Z git version 2.53.0 -2026-03-12T05:22:51.1171090Z *** -2026-03-12T05:22:51.1192837Z Temporarily overriding HOME='/tmp/d722ead4-46c5-4da1-b2a9-5e8b952b6530' before making global git config changes -2026-03-12T05:22:51.1193279Z Adding repository directory to the temporary git global config as a safe directory -2026-03-12T05:22:51.1199926Z [command]/usr/bin/git config --global --add safe.directory /workspace/savethenurse/ai-media-hub -2026-03-12T05:22:51.1255135Z [command]/usr/bin/git config --local --name-only --get-regexp core\.sshCommand -2026-03-12T05:22:51.1313987Z [command]/usr/bin/git submodule foreach --recursive sh -c "git config --local --name-only --get-regexp 'core\.sshCommand' && git config --local --unset-all 'core.sshCommand' || :" -2026-03-12T05:22:51.1710321Z [command]/usr/bin/git config --local --name-only --get-regexp http\.https\:\/\/git\.savethenurse\.com\/\.extraheader -2026-03-12T05:22:51.1734679Z http.https://git.savethenurse.com/.extraheader -2026-03-12T05:22:51.1747334Z [command]/usr/bin/git config --local --unset-all http.https://git.savethenurse.com/.extraheader -2026-03-12T05:22:51.1780576Z [command]/usr/bin/git submodule foreach --recursive sh -c "git config --local --name-only --get-regexp 'http\.https\:\/\/git\.savethenurse\.com\/\.extraheader' && git config --local --unset-all 'http.https://git.savethenurse.com/.extraheader' || :" -2026-03-12T05:22:51.2159927Z [command]/usr/bin/git config --local --name-only --get-regexp ^includeIf\.gitdir: -2026-03-12T05:22:51.2205022Z [command]/usr/bin/git submodule foreach --recursive git config --local --show-origin --name-only --get-regexp remote.origin.url -2026-03-12T05:22:51.2758073Z ✅ Success - Post Checkout repository -2026-03-12T05:22:51.2836738Z Cleaning up container for job build-and-push -2026-03-12T05:22:52.7044729Z Removed container: 6b8ceba3c42acad1c2bf22d83e423ed5e4574ef48b336a5605fc2d4c0c836b05 -2026-03-12T05:22:52.7053684Z 🐳 docker volume rm GITEA-ACTIONS-TASK-11_WORKFLOW-Build-and-Push-Docker-Image_JOB-build-and-push -2026-03-12T05:22:52.8156069Z 🐳 docker volume rm GITEA-ACTIONS-TASK-11_WORKFLOW-Build-and-Push-Docker-Image_JOB-build-and-push-env -2026-03-12T05:22:53.1113526Z Cleaning up network for job build-and-push, and network name is: GITEA-ACTIONS-TASK-11_WORKFLOW-Build-and-Push-Docker-Image_JOB-build-and-push-build-and-push-network -2026-03-12T05:22:53.4537623Z 🏁 Job failed -2026-03-12T05:22:53.4616802Z Job 'build-and-push' failed diff --git a/unraid-template.xml b/unraid-template.xml deleted file mode 100644 index e770fae..0000000 --- a/unraid-template.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - ai-media-hub - git.savethenurse.com/savethenurse/ai-media-hub - https://git.savethenurse.com/savethenurse/ai-media-hub/packages - bridge - - sh - false - https://github.com/savethenurse/ai-media-hub/issues - https://git.savethenurse.com/savethenurse/ai-media-hub - AI Media Hub - Multimodal Vision Edition. Ingest media from YouTube, TikTok, Envato, Artgrid via Google CSE and Gemini 2.5 Flash. Download and crop via yt-dlp and ffmpeg. - MediaApp:Video Web:Dashboard Tools:Utilities - http://[IP]:[PORT:3000] - - https://raw.githubusercontent.com/docker-library/docs/master/docker/logo.png - - - - - - - - 3000 - /mnt/user/appdata/ai-media-hub/downloads - /mnt/user/appdata/ai-media-hub/db - - - - diff --git a/worker/downloader.py b/worker/downloader.py deleted file mode 100644 index 2f1ec09..0000000 --- a/worker/downloader.py +++ /dev/null @@ -1,82 +0,0 @@ -import sys -import json -import subprocess -import os -import argparse -import traceback - -def download_and_crop(url, output_dir, start_time=None, end_time=None): - print(f"[DEBUG Worker] Starting download job for URL={url}, start={start_time}, end={end_time}", file=sys.stderr) - try: - os.makedirs(output_dir, exist_ok=True) - - # Get video info - info_cmd = ["yt-dlp", "-J", url] - print(f"[DEBUG Worker] Running info command: {' '.join(info_cmd)}", file=sys.stderr) - result = subprocess.run(info_cmd, capture_output=True, text=True, check=True) - info = json.loads(result.stdout) - - # We will download the best single file with audio and video, or just let yt-dlp decide - video_id = info.get("id", "unknown") - title = info.get("title", "video").replace("/", "_").replace("\\", "_") - # Ensure ascii or keep some safe unicode - safe_title = "".join([c for c in title if c.isalpha() or c.isdigit() or c in (' ', '-', '_')]).rstrip() - filename = f"{safe_title}_{video_id}.mp4" - filepath = os.path.join(output_dir, filename) - - # Construct download options - # We use ffmpeg to crop if start/end times are provided - if start_time or end_time: - # Using yt-dlp's native download range features to avoid full download if possible - # OR pass postprocessor args - download_args = [ - "yt-dlp", - "-f", "bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best", - "--merge-output-format", "mp4", - "-o", filepath, - ] - - post_args = [] - if start_time: - post_args.extend(["-ss", start_time]) - if end_time: - post_args.extend(["-to", end_time]) - - if post_args: - download_args.extend(["--downloader", "ffmpeg", "--downloader-args", f"ffmpeg:{' '.join(post_args)}"]) - - download_args.append(url) - else: - download_args = [ - "yt-dlp", - "-f", "bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best", - "--merge-output-format", "mp4", - "-o", filepath, - url - ] - - # Execute - print(f"[DEBUG Worker] Executing download command: {' '.join(download_args)}", file=sys.stderr) - subprocess.run(download_args, check=True) - - print(json.dumps({"status": "success", "filepath": filepath, "title": title})) - return True - except subprocess.CalledProcessError as e: - print(f"[DEBUG Worker] Command Error Output: {e.stderr or e.output}", file=sys.stderr) - print(json.dumps({"status": "error", "message": f"Command failed: {e.stderr or e.output}"})) - return False - except Exception as e: - print(f"[DEBUG Worker] Unexpected Exception:", file=sys.stderr) - traceback.print_exc() - print(json.dumps({"status": "error", "message": str(e)})) - return False - -if __name__ == "__main__": - parser = argparse.ArgumentParser(description="Download and crop media via yt-dlp") - parser.add_argument("--url", required=True, help="Media URL") - parser.add_argument("--outdir", required=True, help="Output directory") - parser.add_argument("--start", help="Start time (hh:mm:ss)") - parser.add_argument("--end", help="End time (hh:mm:ss)") - - args = parser.parse_args() - download_and_crop(args.url, args.outdir, args.start, args.end) diff --git a/worker/requirements.txt b/worker/requirements.txt deleted file mode 100644 index b1df116..0000000 --- a/worker/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -yt-dlp