323 lines
9.6 KiB
Go
323 lines
9.6 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"gitea.seeseepuff.be/seeseemelk/mysqlite"
|
|
"github.com/donseba/go-htmx"
|
|
"github.com/gin-gonic/gin"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
)
|
|
|
|
type App struct {
|
|
db *mysqlite.Db
|
|
htmx *htmx.HTMX
|
|
}
|
|
|
|
func serveWebview(db *mysqlite.Db) {
|
|
addr := "0.0.0.0:8081"
|
|
|
|
app := &App{
|
|
db: db,
|
|
}
|
|
|
|
log.Printf("Listening on %s", addr)
|
|
|
|
router := gin.Default()
|
|
router.StaticFile("/", "static/index.html")
|
|
router.GET("/stream/:id", app.serveStream)
|
|
|
|
api := router.Group("/api")
|
|
api.GET("/homepage", app.homePage)
|
|
|
|
err := router.Run(addr)
|
|
if err != nil {
|
|
log.Fatalf("Failed to start server: %v", err)
|
|
}
|
|
//http.Handle("/static/", http.StripPrefix("/static/", staticFileServer()))
|
|
//http.HandleFunc("/video/", app.serveVideo)
|
|
//http.HandleFunc("/stream/", app.serveStream)
|
|
//http.HandleFunc("/mark-watched/", app.handleMarkWatched)
|
|
//http.HandleFunc("/mark-unwatched/", app.handleMarkUnwatched)
|
|
//http.HandleFunc("/", app.serveIndex)
|
|
//
|
|
//err := http.ListenAndServe(addr, nil)
|
|
//if err != nil {
|
|
// panic(err)
|
|
//}
|
|
}
|
|
|
|
type VideoModel struct {
|
|
ID int `json:"id"`
|
|
Thumbnail string `json:"thumbnail"`
|
|
URL string `json:"url"`
|
|
}
|
|
|
|
type HomePageModel struct {
|
|
CurrentVideo VideoModel `json:"currentVideo"`
|
|
}
|
|
|
|
func (a *App) homePage(c *gin.Context) {
|
|
model := HomePageModel{}
|
|
err := a.db.Query("SELECT id, thumbnail FROM videos WHERE (watch_state IS NULL OR watch_state != 'watched') ORDER BY upload_date, episode LIMIT 1").
|
|
ScanSingle(&model.CurrentVideo.ID, &model.CurrentVideo.Thumbnail)
|
|
if err != nil {
|
|
log.Printf("Failed to fetch current video: %v", err)
|
|
c.JSON(500, gin.H{"error": "Failed to fetch current video"})
|
|
return
|
|
}
|
|
model.CurrentVideo.Thumbnail = "https://imgproxy.fourthwall.com/GHX1MSukwJHMQeiqcJLZL906v2--re7qQbQIlHgy5jY/rs:fill:607:342:0/q:90/sm:1/enc/NDJiMWM5YzU3ODJh/MzgzZXFIti10srar/EYcgeYXIe6MXrQge/VBPZVk3b6DCE53dq/VGponLkc45F9743G/4la9S4IjbcNxs4lF/WnLXCMzFhaSUaZnL/Og4de_FSYxQOtzCC/0pfkgH1g4FJfajde/KrZfes95EWXzg9sP/kvEwcqro2EM.webp"
|
|
model.CurrentVideo.URL = fmt.Sprintf("/stream/%d", model.CurrentVideo.ID)
|
|
|
|
c.JSON(200, model)
|
|
}
|
|
|
|
//func (a *App) renderTemplate(w http.ResponseWriter, name string, viewmodel interface{}) {
|
|
// err := renderTemplate(w, "index", viewmodel)
|
|
// if err != nil {
|
|
// http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
// }
|
|
//}
|
|
//
|
|
//func (a *App) getViewModelForVideo(id int) (*VideoInfoVM, error) {
|
|
// // Query the database for the requested video
|
|
// var title, description, thumbnail string
|
|
// var watchState *string
|
|
// err := a.db.Query(`SELECT title, description, thumbnail, watch_state FROM videos WHERE id = ? LIMIT 1`).Bind(id).ScanSingle(&title, &description, &thumbnail, &watchState)
|
|
// if err != nil {
|
|
// return nil, err
|
|
// }
|
|
//
|
|
// // Find the previous video ID based on upload date and episode number
|
|
// var previousID int
|
|
// err = a.db.Query(`
|
|
// SELECT id FROM videos
|
|
// WHERE (upload_date < (SELECT upload_date FROM videos WHERE id = ?))
|
|
// OR (upload_date = (SELECT upload_date FROM videos WHERE id = ?) AND episode < (SELECT episode FROM videos WHERE id = ?))
|
|
// ORDER BY upload_date DESC, episode DESC
|
|
// LIMIT 1
|
|
// `).Bind(id, id, id).ScanSingle(&previousID)
|
|
// if err != nil {
|
|
// // If there's no previous video, set previousID to 0 or -1 to indicate no previous video
|
|
// previousID = 0
|
|
// }
|
|
//
|
|
// // Determine if the video has been watched
|
|
// isWatched := watchState != nil && *watchState == "watched"
|
|
//
|
|
// return &VideoInfoVM{
|
|
// ID: id,
|
|
// PreviousID: previousID,
|
|
// Title: title,
|
|
// Description: description,
|
|
// Thumbnail: thumbnail,
|
|
// IsWatched: isWatched,
|
|
// }, nil
|
|
//}
|
|
//
|
|
//func (a *App) getViewModelForUnwachtedVideo() (*VideoInfoVM, error) {
|
|
// // Query the database for the oldest unwatched episode
|
|
// var id int
|
|
// err := a.db.Query(`SELECT id FROM videos WHERE (watch_state IS NULL OR watch_state != 'watched') ORDER BY upload_date, episode LIMIT 1`).ScanSingle(&id)
|
|
// if err != nil {
|
|
// return nil, err
|
|
// }
|
|
// return a.getViewModelForVideo(id)
|
|
//}
|
|
//
|
|
//func (a *App) serveIndex(w http.ResponseWriter, r *http.Request) {
|
|
// var vm *VideoInfoVM
|
|
// var err error
|
|
//
|
|
// // Check if a specific video ID was requested
|
|
// videoIDParam := r.URL.Query().Get("video")
|
|
// if videoIDParam != "" {
|
|
// videoID, parseErr := strconv.Atoi(videoIDParam)
|
|
// if parseErr == nil {
|
|
// vm, err = a.getViewModelForVideo(videoID)
|
|
// } else {
|
|
// http.Error(w, "Invalid video ID", http.StatusBadRequest)
|
|
// return
|
|
// }
|
|
// } else {
|
|
// // Fall back to the default behavior - get the oldest unwatched video
|
|
// vm, err = a.getViewModelForUnwachtedVideo()
|
|
// }
|
|
//
|
|
// if err != nil {
|
|
// log.Printf("Failed to find video: %v", err)
|
|
// http.Error(w, "Failed to find video", http.StatusInternalServerError)
|
|
// return
|
|
// }
|
|
// a.renderTemplate(w, "index", vm)
|
|
//}
|
|
//
|
|
//func (a *App) serveVideo(w http.ResponseWriter, r *http.Request) {
|
|
// // Parse the video ID from the URL
|
|
// pathParts := strings.Split(r.URL.Path, "/")
|
|
// if len(pathParts) < 3 {
|
|
// http.Error(w, "Invalid video ID", http.StatusBadRequest)
|
|
// return
|
|
// }
|
|
//
|
|
// videoIDStr := pathParts[2]
|
|
// videoID, err := strconv.Atoi(videoIDStr)
|
|
// if err != nil {
|
|
// http.Error(w, "Invalid video ID", http.StatusBadRequest)
|
|
// return
|
|
// }
|
|
//
|
|
// // Query the database for the video with the given ID
|
|
// var title, description string
|
|
// err = a.db.Query(`SELECT title, description FROM videos WHERE id = ?`).Bind(videoID).ScanSingle(&title, &description)
|
|
// if err != nil {
|
|
// log.Printf("Failed to find video: %v", err)
|
|
// http.Error(w, "Video not found", http.StatusNotFound)
|
|
// return
|
|
// }
|
|
//
|
|
// vm := VideoPlayerVM{
|
|
// ID: videoID,
|
|
// Title: title,
|
|
// Description: description,
|
|
// }
|
|
// a.renderTemplate(w, "video", vm)
|
|
//}
|
|
|
|
func (a *App) serveStream(c *gin.Context) {
|
|
// Parse the video ID from the URL
|
|
idStr := c.Param("id")
|
|
videoID, err := strconv.Atoi(idStr)
|
|
if err != nil {
|
|
log.Printf("Failed to parse video ID: %v", err)
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid video ID"})
|
|
return
|
|
}
|
|
|
|
// Get video metadata from database
|
|
var year, episode int
|
|
err = a.db.Query(`SELECT year, episode FROM videos WHERE id = ?`).Bind(videoID).ScanSingle(&year, &episode)
|
|
if err != nil {
|
|
log.Printf("Failed to find video: %v", err)
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Video not found"})
|
|
return
|
|
}
|
|
|
|
// Determine the video file path
|
|
downloadDir := os.Getenv("VIVAPLUS_DESTINATION")
|
|
if downloadDir == "" {
|
|
downloadDir = "./downloads"
|
|
}
|
|
|
|
seasonDir := filepath.Join(downloadDir, fmt.Sprintf("Season %d", year))
|
|
videoFilename := fmt.Sprintf("S%dE%03d.mp4", year, episode)
|
|
videoPath := filepath.Join(seasonDir, videoFilename)
|
|
|
|
// Check if the file exists
|
|
log.Printf("Streaming video: %s", videoPath)
|
|
_, err = os.Stat(videoPath)
|
|
if os.IsNotExist(err) {
|
|
log.Printf("Video file not found: %s", videoPath)
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Video file not found"})
|
|
return
|
|
}
|
|
|
|
// Serve the file
|
|
w := c.Writer
|
|
r := c.Request
|
|
w.Header().Set("Content-Type", "video/mp4")
|
|
w.Header().Set("Content-Disposition", fmt.Sprintf("inline; filename=\"%s\"", videoFilename))
|
|
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
|
|
w.Header().Set("Pragma", "no-cache")
|
|
w.Header().Set("Expires", "0")
|
|
log.Printf("Serving video file: %s", videoPath)
|
|
http.ServeFile(w, r, videoPath)
|
|
}
|
|
|
|
//func (a *App) handleMarkWatched(w http.ResponseWriter, r *http.Request) {
|
|
// // Only handle POST requests
|
|
// if r.Method != http.MethodPost {
|
|
// http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
|
// return
|
|
// }
|
|
//
|
|
// // Parse the video ID from the URL
|
|
// pathParts := strings.Split(r.URL.Path, "/")
|
|
// if len(pathParts) < 3 {
|
|
// http.Error(w, "Invalid video ID", http.StatusBadRequest)
|
|
// return
|
|
// }
|
|
//
|
|
// videoIDStr := pathParts[2]
|
|
// videoID, err := strconv.Atoi(videoIDStr)
|
|
// if err != nil {
|
|
// http.Error(w, "Invalid video ID", http.StatusBadRequest)
|
|
// return
|
|
// }
|
|
//
|
|
// // Update the watch state in the database
|
|
// err = a.db.Query(`UPDATE videos SET watch_state = NULL WHERE id = ?`).Bind(videoID).Exec()
|
|
// if err != nil {
|
|
// http.Error(w, "Failed to update watch state", http.StatusInternalServerError)
|
|
// return
|
|
// }
|
|
//
|
|
// h := a.htmx.NewHandler(w, r)
|
|
// if h.RenderPartial() {
|
|
// vm, err := a.getViewModelForVideo(videoID)
|
|
// if err != nil {
|
|
// http.Error(w, "Failed to find video", http.StatusInternalServerError)
|
|
// return
|
|
// }
|
|
// a.renderTemplate(w, "frag-index", vm)
|
|
// } else {
|
|
// // Redirect back to the homepage
|
|
// http.Redirect(w, r, "/", http.StatusSeeOther)
|
|
// }
|
|
//}
|
|
//
|
|
//func (a *App) handleMarkUnwatched(w http.ResponseWriter, r *http.Request) {
|
|
// // Only handle POST requests
|
|
// if r.Method != http.MethodPost {
|
|
// http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
|
// return
|
|
// }
|
|
//
|
|
// // Parse the video ID from the URL
|
|
// pathParts := strings.Split(r.URL.Path, "/")
|
|
// if len(pathParts) < 3 {
|
|
// http.Error(w, "Invalid video ID", http.StatusBadRequest)
|
|
// return
|
|
// }
|
|
//
|
|
// videoIDStr := pathParts[2]
|
|
// videoID, err := strconv.Atoi(videoIDStr)
|
|
// if err != nil {
|
|
// http.Error(w, "Invalid video ID", http.StatusBadRequest)
|
|
// return
|
|
// }
|
|
//
|
|
// // Update the watch state in the database
|
|
// err = a.db.Query(`UPDATE videos SET watch_state = 'unwatched' WHERE id = ?`).Bind(videoID).Exec()
|
|
// if err != nil {
|
|
// http.Error(w, "Failed to update watch state", http.StatusInternalServerError)
|
|
// return
|
|
// }
|
|
//
|
|
// h := a.htmx.NewHandler(w, r)
|
|
// if h.RenderPartial() {
|
|
// vm, err := a.getViewModelForVideo(videoID)
|
|
// if err != nil {
|
|
// http.Error(w, "Failed to find video", http.StatusInternalServerError)
|
|
// return
|
|
// }
|
|
// a.renderTemplate(w, "frag-index", vm)
|
|
// } else {
|
|
// // Redirect back to the homepage
|
|
// http.Redirect(w, r, "/", http.StatusSeeOther)
|
|
// }
|
|
//}
|