Files
web_huakui_server/app/service/videoser/vtt.go
T
rootandClaude Opus 4.7 85c0071fd8 feat: 从黄果 NET hgdjnet-server 9bfbafe 导入 HKDJ 官网后端
来源:hgdjnet-server 9bfbafe10d417deea959bd53117bc88a10e60a83
项目身份 HKDJ / JHA-216,独立官网仓库,Jenkins 守卫键 hkdjnet
测试目录 /server/hkdj_net,App/Web API 30331/30332

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-16 11:20:04 +08:00

159 lines
4.2 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 videoser
import (
"bufio"
"bytes"
"fmt"
"hkdj-web-server/app/appg"
"hkdj-web-server/app/service/configser"
"hkdj-web-server/pub/common"
"hkdj-web-server/pub/common/enum/redisconst"
"hkdj-web-server/pub/common/errors"
"hkdj-web-server/pub/common/http"
"hkdj-web-server/pub/common/log"
"hkdj-web-server/pub/common/m3u8"
"hkdj-web-server/pub/common/srvdef"
"hkdj-web-server/pub/common/utils"
"hkdj-web-server/pub/redis"
"hkdj-web-server/pub/storage/proxy"
"io"
http2 "net/http"
"strings"
"github.com/gin-gonic/gin"
)
func GetVTTEx(ctx *gin.Context) {
fn := func(c *gin.Context) (contentLength int64, contentType string, reader io.Reader, extraHeaders map[string]string, err error) {
vttName := ctx.Param("key")
var req m3u8.Req
if err = ctx.BindQuery(&req); err != nil {
return
}
req.Name = vttName
cfg := configser.GetConfigInfo()
cdnDomain := cfg.ImgCdn
originDomain := appg.Conf.OrigDomain
buff, err := TransformVttEx(req.Name, cdnDomain, originDomain)
if err != nil {
return
}
contentType = "application/vnd.apple.mpegurl"
reader = bytes.NewReader(buff)
contentLength = int64(len(buff))
extraHeaders = map[string]string{}
return
}
contentLength, contentType, reader, extraHeaders, err := fn(ctx)
if err != nil {
common.ServerJSON(ctx, srvdef.DownloadM3u8File, err)
return
}
ctx.DataFromReader(http2.StatusOK, contentLength, contentType, reader, extraHeaders)
}
func TransformVttEx(name, cdnDomain, originDomain string) (buff []byte, err error) {
if name == "" {
err = errors.MakeWithMsg(errors.M3u8FileName, "m3u8 file name is empty")
return
}
if cdnDomain == "" || originDomain == "" {
err = errors.MakeWithMsg(errors.DomainErr, "cdnDomain or origDomain is empty")
return
}
// 处理媒资库 桶1(v1) 桶2(v2) 桶3(v3)
mediaVerson := proxy.AppendsMediaS3Version(name)
if len(mediaVerson) > 0 {
name = mediaVerson + name
}
// 原始vtt
srcReader, err := readFromRedis(name, originDomain)
if err != nil {
return
}
raw, err := io.ReadAll(srcReader)
if err != nil {
return nil, errors.MakeWithMsg(errors.M3u8Read, "read vtt reader fail: "+err.Error())
}
// 替换为cdn路径
newData := rewriteVttWithCDN(raw, cdnDomain)
reader := bytes.NewReader(newData)
buff, err = io.ReadAll(reader)
if err != nil {
return nil, errors.MakeWithMsg(errors.M3u8Read, "read vtt fail: "+err.Error())
}
return
}
// 从redis缓存中读取
func readFromRedis(name, domain string) (reader io.Reader, err error) {
vttKey := redisconst.MediaM3U8Key(name)
data, err := redis.GetBytes(vttKey)
if err == nil && len(data) > 0 {
reader = bytes.NewReader(data)
return
}
requestUrl := utils.JoinUrlPath(domain, name)
resp, err := http.GetLargeFile(requestUrl, nil)
if err != nil {
fmt.Println("requestUrl111", requestUrl)
return
}
fmt.Println("requestUrl", requestUrl, resp.StatusCode)
if resp.StatusCode != 200 {
msg := fmt.Sprintf("read vtt fail, url: %s, status code %d", requestUrl, resp.StatusCode)
err = errors.MakeWithMsg(errors.M3u8Read, msg)
return
}
if err := redis.Set(vttKey, resp.Data,
redisconst.MediaM3U8CacheExpire); err != nil {
log.Info("vtt:readFromRedis redis set err", log.E(err))
}
reader = bytes.NewReader(resp.Data)
return
}
// rewriteVttWithCDN 将 vtt 内 "path#xywh=..." 的 path 改为 cdnDomain/path
func rewriteVttWithCDN(vttData []byte, cdnDomain string) []byte {
cdnDomain = strings.TrimSpace(cdnDomain)
cdnDomain = strings.TrimRight(cdnDomain, "/")
var out bytes.Buffer
scanner := bufio.NewScanner(bytes.NewReader(vttData))
for scanner.Scan() {
line := scanner.Text()
trim := strings.TrimSpace(line)
// 只处理缩略图行:xxx#xywh=...
if strings.Contains(trim, "#xywh=") {
parts := strings.SplitN(trim, "#xywh=", 2)
if len(parts) == 2 {
p := strings.TrimSpace(parts[0]) // 原路径
xywh := parts[1]
// 已经是绝对URL则不改
if strings.HasPrefix(p, "http://") || strings.HasPrefix(p, "https://") {
out.WriteString(p + "#xywh=" + xywh + "\n")
continue
}
// 处理相对路径,确保只有一个 /
p = strings.TrimLeft(p, "/")
newPath := cdnDomain + "/" + p
out.WriteString(newPath + "#xywh=" + xywh + "\n")
continue
}
}
out.WriteString(line + "\n")
}
// scanner.Err()
return out.Bytes()
}