mediago-dev--mediago
218603ff9f
- Change video stream route from /videos/*filepath to /videos/:id (lookup file path from database by task ID instead of filename) - Add mimeType field to Video API response so video.js knows the format - Fix getVideoURL double-slash bug (/videos/... → //videos/...) - Handle directory downloads: scan inside for first video file when CheckFileExists returns a directory (e.g. multi-part bilibili downloads) - Remove unused videoRoot field and ServeVideo function - Update player-ui PlaylistItem to pass full VideoItem object Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
157 行
3.7 KiB
Go
157 行
3.7 KiB
Go
package video
|
|
|
|
import (
|
|
"fmt"
|
|
"mime"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"caorushizi.cn/mediago/internal/db"
|
|
"caorushizi.cn/mediago/internal/db/repo"
|
|
"caorushizi.cn/mediago/internal/service"
|
|
)
|
|
|
|
// Service provides video-related operations backed by the download database
|
|
type Service interface {
|
|
GetVideoFiles() ([]Video, error)
|
|
GetVideoByID(id int64) (*Video, error)
|
|
GetVideoFilePath(id int64) (string, error)
|
|
}
|
|
|
|
type dbService struct {
|
|
repo *repo.VideoRepository
|
|
localPath string
|
|
}
|
|
|
|
// NewService creates a video service backed by the download database
|
|
func NewService(repo *repo.VideoRepository, localPath string) Service {
|
|
return &dbService{
|
|
repo: repo,
|
|
localPath: localPath,
|
|
}
|
|
}
|
|
|
|
// resolveFilePath finds the actual file path for a download record.
|
|
// If CheckFileExists returns a directory (multi-part downloads), it scans
|
|
// inside for the first video file.
|
|
func (s *dbService) resolveFilePath(rec *db.Video) (string, error) {
|
|
searchDir := s.localPath
|
|
if rec.Folder != nil && *rec.Folder != "" {
|
|
searchDir = filepath.Join(s.localPath, *rec.Folder)
|
|
}
|
|
|
|
exists, filePath := service.CheckFileExists(rec.Name, searchDir)
|
|
if !exists {
|
|
return "", fmt.Errorf("video file not found")
|
|
}
|
|
|
|
// If the path is a directory, find the first video file inside
|
|
info, err := os.Stat(filePath)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if info.IsDir() {
|
|
found, err := findFirstVideoInDir(filePath)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return found, nil
|
|
}
|
|
|
|
return filePath, nil
|
|
}
|
|
|
|
// findFirstVideoInDir scans a directory for the first video file
|
|
func findFirstVideoInDir(dir string) (string, error) {
|
|
entries, err := os.ReadDir(dir)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
for _, entry := range entries {
|
|
if entry.IsDir() {
|
|
continue
|
|
}
|
|
ext := filepath.Ext(entry.Name())
|
|
mimeType := mime.TypeByExtension(ext)
|
|
if mimeType != "" && strings.HasPrefix(mimeType, "video") {
|
|
return filepath.Join(dir, entry.Name()), nil
|
|
}
|
|
}
|
|
|
|
return "", fmt.Errorf("no video file found in directory %s", dir)
|
|
}
|
|
|
|
// GetVideoFiles returns all successfully downloaded videos that exist on disk
|
|
func (s *dbService) GetVideoFiles() ([]Video, error) {
|
|
records, err := s.repo.FindByStatus([]string{"success"})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to query download records: %w", err)
|
|
}
|
|
|
|
var videos []Video
|
|
for _, rec := range records {
|
|
filePath, err := s.resolveFilePath(rec)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
videos = append(videos, Video{
|
|
ID: rec.ID,
|
|
Title: rec.Name,
|
|
URL: fmt.Sprintf("/videos/%d", rec.ID),
|
|
MimeType: mimeFromPath(filePath),
|
|
})
|
|
}
|
|
|
|
return videos, nil
|
|
}
|
|
|
|
// GetVideoByID returns a single video by its download task ID
|
|
func (s *dbService) GetVideoByID(id int64) (*Video, error) {
|
|
rec, err := s.repo.FindByIDOrFail(id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if rec.Status != "success" {
|
|
return nil, fmt.Errorf("download task %d is not completed", id)
|
|
}
|
|
|
|
filePath, err := s.resolveFilePath(rec)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("video file not found for task %d", id)
|
|
}
|
|
|
|
return &Video{
|
|
ID: rec.ID,
|
|
Title: rec.Name,
|
|
URL: fmt.Sprintf("/videos/%d", rec.ID),
|
|
MimeType: mimeFromPath(filePath),
|
|
}, nil
|
|
}
|
|
|
|
// GetVideoFilePath returns the absolute file path for a video by its task ID
|
|
func (s *dbService) GetVideoFilePath(id int64) (string, error) {
|
|
rec, err := s.repo.FindByIDOrFail(id)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
if rec.Status != "success" {
|
|
return "", fmt.Errorf("download task %d is not completed", id)
|
|
}
|
|
|
|
return s.resolveFilePath(rec)
|
|
}
|
|
|
|
// mimeFromPath returns the MIME type for a file path based on its extension
|
|
func mimeFromPath(filePath string) string {
|
|
ext := filepath.Ext(filePath)
|
|
if mimeType := mime.TypeByExtension(ext); mimeType != "" {
|
|
return mimeType
|
|
}
|
|
return "video/mp4"
|
|
}
|