spark-server/internal/module/squarebot/logic/media.go
DESKTOP-L8OUL9F\gfl0034 715ace7ac6 feat(square): 广场 Bot/RSS 发布与媒体校验
支持广场机器人发帖与媒体类型检查,并补充相关迁移与 i18n。
2026-09-24 23:07:06 +08:00

105 lines
3.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package logic
import (
"context"
"fmt"
"io"
"net/http"
"strings"
"time"
infmedia "spark-server/internal/infrastructure/media"
pkgmedia "spark-server/internal/pkg/media"
)
const maxDownloadBytes = 80 << 20 // refuse absurd remote blobs before compress
// MediaCompressor downloads remote media and compresses to App-aligned specs before objectstore write.
// Square bot RSS worker should call FetchImage / FetchVideo instead of raw HTTP+Put.
type MediaCompressor struct {
Opt infmedia.Options
Client *http.Client
}
// NewMediaCompressor builds a compressor; ffmpegPath may be empty (defaults to "ffmpeg").
func NewMediaCompressor(lim pkgmedia.Limits, jpegQuality, videoCRF int, ffmpegPath string) *MediaCompressor {
return &MediaCompressor{
Opt: infmedia.OptionsFromLimits(lim, jpegQuality, videoCRF, ffmpegPath),
Client: &http.Client{
Timeout: 60 * time.Second,
},
}
}
// FetchImage GETs url and compresses to ≤1080p JPEG/PNG.
func (c *MediaCompressor) FetchImage(ctx context.Context, url string) (*infmedia.BotAsset, error) {
raw, err := c.download(ctx, url)
if err != nil {
return nil, err
}
return infmedia.PrepareBotImage(raw, c.Opt)
}
// FetchAvatar GETs url and compresses to ≤200×200.
func (c *MediaCompressor) FetchAvatar(ctx context.Context, url string) (*infmedia.BotAsset, error) {
raw, err := c.download(ctx, url)
if err != nil {
return nil, err
}
return infmedia.PrepareBotAvatar(raw, c.Opt)
}
// FetchVideo GETs url and compresses to ≤720p (requires ffmpeg).
func (c *MediaCompressor) FetchVideo(ctx context.Context, url string) (*infmedia.BotAsset, error) {
raw, err := c.download(ctx, url)
if err != nil {
return nil, err
}
return infmedia.PrepareBotVideo(ctx, raw, c.Opt)
}
// CompressBytesImage compresses already-downloaded image bytes (e.g. RSS enclosure already fetched).
func (c *MediaCompressor) CompressBytesImage(src []byte) (*infmedia.BotAsset, error) {
return infmedia.PrepareBotImage(src, c.Opt)
}
// CompressBytesVideo compresses already-downloaded video bytes.
func (c *MediaCompressor) CompressBytesVideo(ctx context.Context, src []byte) (*infmedia.BotAsset, error) {
return infmedia.PrepareBotVideo(ctx, src, c.Opt)
}
func (c *MediaCompressor) download(ctx context.Context, url string) ([]byte, error) {
url = strings.TrimSpace(url)
if url == "" {
return nil, fmt.Errorf("squarebot media: empty url")
}
client := c.Client
if client == nil {
client = http.DefaultClient
}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return nil, err
}
req.Header.Set("User-Agent", "SparkIM-SquareBot/1.0")
res, err := client.Do(req)
if err != nil {
return nil, err
}
defer func() { _ = res.Body.Close() }()
if res.StatusCode < 200 || res.StatusCode >= 300 {
return nil, fmt.Errorf("squarebot media: http %d", res.StatusCode)
}
data, err := io.ReadAll(io.LimitReader(res.Body, maxDownloadBytes+1))
if err != nil {
return nil, err
}
if len(data) > maxDownloadBytes {
return nil, fmt.Errorf("squarebot media: download too large")
}
if len(data) == 0 {
return nil, fmt.Errorf("squarebot media: empty body")
}
return data, nil
}