package api import ( "fmt" "net/http" "github.com/dustin/go-humanize" "github.com/gin-gonic/gin" "kenaito-media-picker/internal/classify" "kenaito-media-picker/internal/store" ) // MediaController 媒体库接口 type MediaController struct { store *store.Store } // NewMediaController 创建媒体控制器,注入数据访问层 func NewMediaController(st *store.Store) *MediaController { return &MediaController{store: st} } // mediaListReq 列表查询请求 type mediaListReq struct { Page int `json:"page"` PageSize int `json:"pageSize"` MediaType string `json:"mediaType"` Orientation string `json:"orientation"` Keyword string `json:"keyword"` Codec string `json:"codec"` Format string `json:"format"` Resolution string `json:"resolution"` SortField string `json:"sortField"` SortOrder string `json:"sortOrder"` } // mediaView 列表展示视图:补充方向/分辨率/可读大小等派生字段 type mediaView struct { store.MediaInfo Orientation string `json:"orientation"` // 横屏/竖屏/等比 Resolution string `json:"resolution"` // 分辨率档位 SizeText string `json:"sizeText"` // 人类可读体积 DurationText string `json:"durationText"` // 人类可读时长 } // List POST /api/media/list 分页查询媒体库 func (c *MediaController) List(ctx *gin.Context) { var req mediaListReq if err := ctx.ShouldBindJSON(&req); err != nil { Error(ctx, http.StatusBadRequest, "请求参数错误:"+err.Error()) return } if req.Page < 1 { req.Page = 1 } q := store.MediaQuery{ MediaType: req.MediaType, Orientation: req.Orientation, Keyword: req.Keyword, Codec: req.Codec, Format: req.Format, Resolution: req.Resolution, Page: req.Page, PageSize: req.PageSize, SortField: req.SortField, SortOrder: req.SortOrder, } list, err := c.store.ListMedia(q) if err != nil { Error(ctx, http.StatusInternalServerError, "查询媒体列表失败:"+err.Error()) return } total, err := c.store.CountMedia(q) if err != nil { Error(ctx, http.StatusInternalServerError, "统计媒体数量失败:"+err.Error()) return } views := make([]interface{}, 0, len(list)) for _, m := range list { views = append(views, toMediaView(m)) } Success(ctx, map[string]interface{}{"list": views, "total": total}) } // Stats POST /api/media/stats 媒体库统计看板 func (c *MediaController) Stats(ctx *gin.Context) { st, err := c.store.Stats() if err != nil { Error(ctx, http.StatusInternalServerError, "统计失败:"+err.Error()) return } Success(ctx, st) } // toMediaView 补充派生字段 func toMediaView(m store.MediaInfo) mediaView { v := mediaView{MediaInfo: m} if m.Width > 0 && m.Height > 0 { v.Orientation = classify.OrientationOf(m.Width, m.Height).String() v.Resolution = classify.ResLabel(m.Width, m.Height) } if m.FileSize > 0 { v.SizeText = humanize.Bytes(uint64(m.FileSize)) } if m.Duration > 0 { v.DurationText = formatDuration(m.Duration) } return v } // formatDuration 秒 → 时:分:秒 func formatDuration(sec float64) string { s := int(sec) h := s / 3600 m := (s % 3600) / 60 secs := s % 60 if h > 0 { return fmt.Sprintf("%d:%02d:%02d", h, m, secs) } return fmt.Sprintf("%02d:%02d", m, secs) }