初始提交:短剧库源码(含转码优化 ultrafast)

This commit is contained in:
yvan
2026-09-17 19:09:08 +08:00
commit 07dc0f1059
247 changed files with 43242 additions and 0 deletions
+124
View File
@@ -0,0 +1,124 @@
package app
import (
"bytes"
"context"
"errors"
"fmt"
"image/jpeg"
"io"
"net/http"
"os/exec"
"strings"
"time"
)
func isHongguoImageHost(host string) bool {
host = strings.ToLower(host)
return host == "hongguoduanju.com" || host == "www.hongguoduanju.com" ||
strings.HasSuffix(host, ".byteimg.com") || strings.HasSuffix(host, ".fqnovelpic.com")
}
func (app *UIApp) loadCoverImage(ctx context.Context, remoteURL string, decode func([]byte, string) []byte) ([]byte, error) {
source, _ := ctx.Value(coverSourceKey{}).(string)
referer := sourceCoverReferer(source, remoteURL)
return app.coverImages.load(ctx, remoteURL+"\x00"+referer, func(ctx context.Context) ([]byte, error) {
ctx = context.WithValue(ctx, coverRequestKey{}, true)
request, err := http.NewRequestWithContext(ctx, http.MethodGet, remoteURL, nil)
if err != nil {
return nil, err
}
if !validImageURL(request.URL) {
return nil, errors.New("封面地址不受支持")
}
request.Header.Set("Referer", referer)
request.Header.Set("User-Agent", userAgent)
request.Header.Set("Accept", "image/webp,image/jpeg,image/png,image/gif,*/*;q=0.5")
client := *app.downloader.client
client.CheckRedirect = func(request *http.Request, via []*http.Request) error {
if len(via) >= 5 || !validImageURL(request.URL) {
return errors.New("封面重定向地址不受支持")
}
request.Header.Set("Referer", referer)
return nil
}
response, err := client.Do(request)
if err != nil {
return nil, err
}
defer response.Body.Close()
if response.StatusCode != http.StatusOK {
return nil, fmt.Errorf("封面上游 HTTP %d", response.StatusCode)
}
if response.ContentLength > maxCoverBytes {
return nil, errors.New("封面文件过大")
}
data, err := io.ReadAll(io.LimitReader(response.Body, maxCoverBytes+1))
if err != nil {
return nil, err
}
if len(data) > maxCoverBytes {
return nil, errors.New("封面文件过大")
}
if decode != nil {
data = decode(data, remoteURL)
}
if isKnownImage(data) {
return data, nil
}
if isHEICImage(data) {
return app.downloader.convertHEICCover(ctx, data)
}
return nil, errors.New("封面内容无效,请检查代理或站点是否需要验证")
})
}
type coverOutput struct {
buffer bytes.Buffer
}
func (output *coverOutput) Write(data []byte) (int, error) {
if len(data) > (4<<20)-output.buffer.Len() {
return 0, errors.New("封面转换结果过大")
}
return output.buffer.Write(data)
}
func (output *coverOutput) Bytes() []byte { return output.buffer.Bytes() }
func (downloader *Downloader) convertHEICCover(ctx context.Context, data []byte) ([]byte, error) {
image, err := extractHEICImage(data)
if err != nil {
return nil, err
}
ffmpeg, err := downloader.ensureFFmpeg(ctx)
if err != nil {
return nil, fmt.Errorf("封面转换需要 FFmpeg:%w", err)
}
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
filters := append(image.filters, "scale=w='min(800,iw)':h='min(800,ih)':force_original_aspect_ratio=decrease")
command := exec.CommandContext(ctx, ffmpeg, "-hide_banner", "-loglevel", "error", "-nostdin",
"-max_alloc", "67108864", "-protocol_whitelist", "pipe", "-threads", "1", "-f", "hevc", "-i", "pipe:0",
"-frames:v", "1", "-an", "-sn", "-vf", strings.Join(filters, ","), "-filter_threads", "1",
"-threads", "1", "-c:v", "mjpeg", "-pix_fmt", "yuvj420p", "-q:v", "3", "-f", "image2pipe", "pipe:1")
command.Stdin = bytes.NewReader(image.data)
var output coverOutput
stderr := &cappedStringWriter{limit: 4096}
command.Stdout, command.Stderr = &output, stderr
if err := command.Run(); err != nil {
if ctx.Err() != nil {
return nil, ctx.Err()
}
detail := strings.TrimSpace(stderr.String())
if detail == "" {
detail = err.Error()
}
return nil, fmt.Errorf("HEIC 封面转换失败:%s", detail)
}
config, err := jpeg.DecodeConfig(bytes.NewReader(output.Bytes()))
if err != nil || config.Width <= 0 || config.Height <= 0 || config.Width > 800 || config.Height > 800 {
return nil, errors.New("HEIC 封面转换结果无效")
}
return output.Bytes(), nil
}