Files
image2api/backend/internal/util/mediadur.go
T
chiyiandDevin b4efe6b192 feat!: 账号池改渠道网关,对外协议换成 vivid /v1
- 新增 channels/channel_models:上游 URL + AES-GCM 加密 key +
  并发/权重/本地统计,模型经渠道 /v1/models 导入绑定
- internal/upstream:vivid 协议客户端(NDJSON 流 + jobId 断流重连 +
  uploads 预签名 + models + balance + 错误分类)
- service/v1 重写:权重分组调度、Redis 并发槽 conc:c:、故障转移,
  站内/测试/对外统一走渠道执行器
- 对外端点换成 vivid 四件套:POST /v1/generate(NDJSON+jobId 重连)、
  POST /v1/uploads(SigV4 预签名 PUT)、GET /v1/models、GET /v1/balance,
  错误包 {"error":{"code","message"}}
- API key 新发改 vk_+48hex;删除全部内置 provider 与 token 池
- 管理端:渠道 CRUD/拉模型/导入/余额端点,前端账号页换渠道页,
  概览/文档/测试弹窗适配

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-10-16 21:14:18 +08:00

139 lines
3.8 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 util
import (
"bytes"
"encoding/binary"
"math"
)
// maxMediaDurationSec 时长 sanity 上限:解析出超过 24h 视为垃圾数据丢弃。
const maxMediaDurationSec = 24 * 3600
// MediaDurationSeconds 从已读片段尽力解析媒体时长(秒)。
// mp4/mov/m4a 扫描 mvhd 盒子;webm 解 EBML Duration×TimecodeScale;wav 按
// byteRate 换算。解析不出返回 false——调用方可用文件尾片段再试一次
// (未 faststart 的 mp4 moov 盒子常在文件尾)。mp3/ogg 无廉价可靠头长度,跳过。
func MediaDurationSeconds(mime string, buf []byte) (float64, bool) {
if len(buf) == 0 {
return 0, false
}
var sec float64
var ok bool
switch mime {
case "video/mp4", "video/quicktime", "audio/mp4":
sec, ok = mvhdDuration(buf)
case "video/webm":
sec, ok = webmDuration(buf)
case "audio/wav":
sec, ok = wavDuration(buf)
}
if !ok || sec <= 0 || sec > maxMediaDurationSec {
return 0, false
}
return sec, true
}
// mvhdDuration 在缓冲里扫 "mvhd" 盒子并解析 duration/timescale。
// 盒子布局:[size4][type4=mvhd][version1][flags3]
// v0: creation4 mod4 timescale4 duration4 → timescale@idx+16, duration@idx+20
// v1: creation8 mod8 timescale4 duration8 → timescale@idx+24, duration@idx+28
func mvhdDuration(buf []byte) (float64, bool) {
for off := 0; ; {
i := bytes.Index(buf[off:], []byte("mvhd"))
if i < 0 {
return 0, false
}
i += off
if ver := buf[i+4]; ver == 0 && i+24 <= len(buf) {
ts := binary.BigEndian.Uint32(buf[i+16 : i+20])
dur := binary.BigEndian.Uint32(buf[i+20 : i+24])
if ts > 0 && dur > 0 {
return float64(dur) / float64(ts), true
}
} else if ver == 1 && i+36 <= len(buf) {
ts := binary.BigEndian.Uint32(buf[i+24 : i+28])
dur := binary.BigEndian.Uint64(buf[i+28 : i+36])
if ts > 0 && dur > 0 {
return float64(dur) / float64(ts), true
}
}
off = i + 4
}
}
// webmDuration 扫 EBML Duration 元素(0x4489,float)和 TimecodeScale
// (0x2AD7B1,uint,缺省 1,000,000ns)。秒 = duration × scale / 1e9。
func webmDuration(buf []byte) (float64, bool) {
scale := uint64(1_000_000)
if i := bytes.Index(buf, []byte{0x2a, 0xd7, 0xb1}); i >= 0 {
if v, n := ebmlVint(buf[i+3:]); n > 0 && i+3+n+v <= len(buf) {
scale = 0
for _, b := range buf[i+3+n : i+3+n+v] {
scale = scale<<8 | uint64(b)
}
if scale == 0 {
scale = 1_000_000
}
}
}
i := bytes.Index(buf, []byte{0x44, 0x89})
if i < 0 {
return 0, false
}
v, n := ebmlVint(buf[i+2:])
if n == 0 || i+2+n+v > len(buf) {
return 0, false
}
f := buf[i+2+n : i+2+n+v]
var dur float64
switch v {
case 4:
dur = float64(math.Float32frombits(binary.BigEndian.Uint32(f)))
case 8:
dur = math.Float64frombits(binary.BigEndian.Uint64(f))
default:
return 0, false
}
return dur * float64(scale) / 1e9, true
}
// ebmlVint 读 EBML 变长整数的长度值:返回 (负载长度, vint 本身字节数)。
func ebmlVint(buf []byte) (int, int) {
if len(buf) == 0 {
return 0, 0
}
for n := 1; n <= 8; n++ {
mask := byte(0x80 >> (n - 1))
if buf[0]&mask != 0 {
if len(buf) < n {
return 0, 0
}
v := int(buf[0] &^ mask)
for i := 1; i < n; i++ {
v = v<<8 | int(buf[i])
}
return v, n
}
}
return 0, 0
}
// wavDuration 解析 RIFF/WAVE:byteRate@28(LE u32) + "data" 块大小换算秒数。
func wavDuration(buf []byte) (float64, bool) {
if len(buf) < 44 || string(buf[:4]) != "RIFF" || string(buf[8:12]) != "WAVE" {
return 0, false
}
rate := binary.LittleEndian.Uint32(buf[28:32])
if rate == 0 {
return 0, false
}
for off := 12; off+8 <= len(buf); {
size := int(binary.LittleEndian.Uint32(buf[off+4 : off+8]))
if string(buf[off:off+4]) == "data" {
return float64(size) / float64(rate), true
}
off += 8 + size + size&1 // 块按 2 字节对齐
}
return 0, false
}