1
0
mirror of https://github.com/Mrs4s/MiraiGo.git synced 2025-05-04 19:17:38 +08:00

feature short video upload & sending.

This commit is contained in:
Mrs4s 2021-01-07 23:57:45 +08:00
parent 72521dec9b
commit 7605dc4635
11 changed files with 1035 additions and 198 deletions

View File

@ -16,7 +16,6 @@ import (
"github.com/Mrs4s/MiraiGo/client/pb/cmd0x352" "github.com/Mrs4s/MiraiGo/client/pb/cmd0x352"
"github.com/Mrs4s/MiraiGo/client/pb/msg" "github.com/Mrs4s/MiraiGo/client/pb/msg"
"github.com/Mrs4s/MiraiGo/client/pb/oidb" "github.com/Mrs4s/MiraiGo/client/pb/oidb"
"github.com/Mrs4s/MiraiGo/client/pb/pttcenter"
"github.com/Mrs4s/MiraiGo/client/pb/structmsg" "github.com/Mrs4s/MiraiGo/client/pb/structmsg"
"github.com/Mrs4s/MiraiGo/message" "github.com/Mrs4s/MiraiGo/message"
"github.com/Mrs4s/MiraiGo/protocol/crypto" "github.com/Mrs4s/MiraiGo/protocol/crypto"
@ -1010,31 +1009,6 @@ func (c *QQClient) buildImageOcrRequestPacket(url, md5 string, size, weight, hei
return seq, packet return seq, packet
} }
// PttCenterSvr.ShortVideoDownReq
func (c *QQClient) buildPttShortVideoDownReqPacket(uuid, md5 []byte) (uint16, []byte) {
seq := c.nextSeq()
body := &pttcenter.ShortVideoReqBody{
Cmd: 400,
Seq: int32(seq),
PttShortVideoDownloadReq: &pttcenter.ShortVideoDownloadReq{
FromUin: c.Uin,
ToUin: c.Uin,
ChatType: 1,
ClientType: 7,
FileId: string(uuid),
GroupCode: 1,
FileMd5: md5,
BusinessType: 1,
FileType: 2,
DownType: 2,
SceneType: 2,
},
}
payload, _ := proto.Marshal(body)
packet := packets.BuildUniPacket(c.Uin, seq, "PttCenterSvr.ShortVideoDownReq", 1, c.OutGoingPacketSessionId, EmptyBytes, c.sigInfo.d2Key, payload)
return seq, packet
}
// LightAppSvc.mini_app_info.GetAppInfoById // LightAppSvc.mini_app_info.GetAppInfoById
func (c *QQClient) buildAppInfoRequestPacket(id string) (uint16, []byte) { func (c *QQClient) buildAppInfoRequestPacket(id string) (uint16, []byte) {
seq := c.nextSeq() seq := c.nextSeq()

View File

@ -139,7 +139,6 @@ var decoders = map[string]func(*QQClient, uint16, []byte) (interface{}, error){
"OidbSvc.0xd79": decodeWordSegmentation, "OidbSvc.0xd79": decodeWordSegmentation,
"OidbSvc.0x990": decodeTranslateResponse, "OidbSvc.0x990": decodeTranslateResponse,
"SummaryCard.ReqSummaryCard": decodeSummaryCardResponse, "SummaryCard.ReqSummaryCard": decodeSummaryCardResponse,
"PttCenterSvr.ShortVideoDownReq": decodePttShortVideoDownResponse,
"LightAppSvc.mini_app_info.GetAppInfoById": decodeAppInfoResponse, "LightAppSvc.mini_app_info.GetAppInfoById": decodeAppInfoResponse,
"PttCenterSvr.pb_pttCenter_CMD_REQ_APPLY_UPLOAD-500": decodePrivatePttStoreResponse, "PttCenterSvr.pb_pttCenter_CMD_REQ_APPLY_UPLOAD-500": decodePrivatePttStoreResponse,
} }
@ -421,14 +420,6 @@ func (c *QQClient) GetFriendList() (*FriendListResponse, error) {
return r, nil return r, nil
} }
func (c *QQClient) GetShortVideoUrl(uuid, md5 []byte) string {
i, err := c.sendAndWait(c.buildPttShortVideoDownReqPacket(uuid, md5))
if err != nil {
return ""
}
return i.(string)
}
func (c *QQClient) SendPrivateMessage(target int64, m *message.SendingMessage) *message.PrivateMessage { func (c *QQClient) SendPrivateMessage(target int64, m *message.SendingMessage) *message.PrivateMessage {
mr := int32(rand.Uint32()) mr := int32(rand.Uint32())
seq := c.nextFriendSeq() seq := c.nextFriendSeq()

View File

@ -12,7 +12,6 @@ import (
"time" "time"
"github.com/Mrs4s/MiraiGo/client/pb/notify" "github.com/Mrs4s/MiraiGo/client/pb/notify"
"github.com/Mrs4s/MiraiGo/client/pb/pttcenter"
"github.com/Mrs4s/MiraiGo/client/pb/qweb" "github.com/Mrs4s/MiraiGo/client/pb/qweb"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -992,18 +991,6 @@ func decodeImageOcrResponse(_ *QQClient, _ uint16, payload []byte) (interface{},
}, nil }, nil
} }
// PttCenterSvr.ShortVideoDownReq
func decodePttShortVideoDownResponse(_ *QQClient, _ uint16, payload []byte) (interface{}, error) {
rsp := pttcenter.ShortVideoRspBody{}
if err := proto.Unmarshal(payload, &rsp); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal protobuf message")
}
if rsp.PttShortVideoDownloadRsp == nil || rsp.PttShortVideoDownloadRsp.DownloadAddr == nil {
return nil, errors.New("resp error")
}
return rsp.PttShortVideoDownloadRsp.DownloadAddr.Host[0] + rsp.PttShortVideoDownloadRsp.DownloadAddr.UrlArgs, nil
}
// LightAppSvc.mini_app_info.GetAppInfoById // LightAppSvc.mini_app_info.GetAppInfoById
func decodeAppInfoResponse(_ *QQClient, _ uint16, payload []byte) (interface{}, error) { func decodeAppInfoResponse(_ *QQClient, _ uint16, payload []byte) (interface{}, error) {
pkg := qweb.QWebRsp{} pkg := qweb.QWebRsp{}

View File

@ -97,11 +97,16 @@ func (c *QQClient) highwayUploadStream(ip uint32, port int, updKey []byte, strea
return nil return nil
} }
func (c *QQClient) highwayUploadByBDH(stream io.ReadSeeker, cmdId int32, ticket, ext []byte) ([]byte, error) { func (c *QQClient) highwayUploadByBDH(stream io.ReadSeeker, cmdId int32, ticket, ext []byte, encrypt bool) ([]byte, error) {
// TODO: encrypted upload support.
if len(c.srvSsoAddrs) == 0 { if len(c.srvSsoAddrs) == 0 {
return nil, errors.New("srv addrs not found. maybe miss some packet?") return nil, errors.New("srv addrs not found. maybe miss some packet?")
} }
if encrypt {
if c.highwaySession == nil || len(c.highwaySession.SessionKey) == 0 {
return nil, errors.New("session key not found. maybe miss some packet?")
}
ext = binary.NewTeaCipher(c.highwaySession.SessionKey).Encrypt(ext)
}
h := md5.New() h := md5.New()
length, _ := io.Copy(h, stream) length, _ := io.Copy(h, stream)
fh := h.Sum(nil) fh := h.Sum(nil)
@ -194,7 +199,7 @@ func (c *QQClient) highwayUploadFileMultiThreadingByBDH(path string, cmdId int32
return nil, errors.Wrap(err, "open file error") return nil, errors.Wrap(err, "open file error")
} }
if stat.Size() < 1024*1024*3 { if stat.Size() < 1024*1024*3 {
return c.highwayUploadByBDH(file, cmdId, ticket, ext) return c.highwayUploadByBDH(file, cmdId, ticket, ext, false)
} }
type BlockMetaData struct { type BlockMetaData struct {
Id int Id int

View File

@ -44,7 +44,7 @@ func (c *QQClient) UploadGroupImage(groupCode int64, img io.ReadSeeker) (*messag
c.srvSsoAddrs = append(c.srvSsoAddrs, fmt.Sprintf("%v:%v", binary.UInt32ToIPV4Address(uint32(addr)), rsp.UploadPort[i])) c.srvSsoAddrs = append(c.srvSsoAddrs, fmt.Sprintf("%v:%v", binary.UInt32ToIPV4Address(uint32(addr)), rsp.UploadPort[i]))
} }
} }
if _, err = c.highwayUploadByBDH(img, 2, rsp.UploadKey, EmptyBytes); err == nil { if _, err = c.highwayUploadByBDH(img, 2, rsp.UploadKey, EmptyBytes, true); err == nil {
goto ok goto ok
} }
return nil, errors.New("upload failed") return nil, errors.New("upload failed")

File diff suppressed because it is too large Load Diff

View File

@ -5,15 +5,29 @@ option go_package = ".;pttcenter";
message ShortVideoReqBody { message ShortVideoReqBody {
int32 cmd = 1; int32 cmd = 1;
int32 seq = 2; int32 seq = 2;
ShortVideoUploadReq pttShortVideoUploadReq = 3;
ShortVideoDownloadReq pttShortVideoDownloadReq = 4; ShortVideoDownloadReq pttShortVideoDownloadReq = 4;
repeated ShortVideoExtensionReq extensionReq = 100;
} }
message ShortVideoRspBody { message ShortVideoRspBody {
int32 cmd = 1; int32 cmd = 1;
int32 seq = 2; int32 seq = 2;
ShortVideoUploadRsp pttShortVideoUploadRsp = 3;
ShortVideoDownloadRsp pttShortVideoDownloadRsp = 4; ShortVideoDownloadRsp pttShortVideoDownloadRsp = 4;
} }
message ShortVideoUploadReq {
int64 fromUin = 1;
int64 toUin = 2;
int32 chatType = 3;
int32 clientType = 4;
ShortVideoFileInfo info = 5;
int64 groupCode = 6;
int32 agentType = 7;
int32 businessType = 8;
int32 supportLargeSize = 20;
}
message ShortVideoDownloadReq { message ShortVideoDownloadReq {
int64 fromUin = 1; int64 fromUin = 1;
int64 toUin = 2; int64 toUin = 2;
@ -42,6 +56,36 @@ message ShortVideoDownloadRsp {
bytes encryptKey = 10; bytes encryptKey = 10;
} }
message ShortVideoUploadRsp {
int32 retCode = 1;
string retMsg = 2;
repeated ShortVideoIpList sameAreaOutAddr = 3;
repeated ShortVideoIpList diffAreaOutAddr = 4;
string fileId = 5;
bytes uKey = 6;
int32 fileExists = 7;
repeated ShortVideoIpList sameAreaInnerAddr = 8;
repeated ShortVideoIpList diffAreaInnerAddr = 9;
repeated DataHole dataHole = 10;
}
message ShortVideoFileInfo {
string fileName = 1;
bytes fileMd5 = 2;
bytes thumbFileMd5 = 3;
int64 fileSize = 4;
int32 fileResLength = 5;
int32 fileResWidth = 6;
int32 fileFormat = 7;
int32 fileTime = 8;
int64 thumbFileSize = 9;
}
message DataHole {
int64 begin = 1;
int64 end = 2;
}
message ShortVideoIpList { message ShortVideoIpList {
int32 ip = 1; int32 ip = 1;
int32 port = 2; int32 port = 2;
@ -52,3 +96,8 @@ message ShortVideoAddr {
string urlArgs = 11; string urlArgs = 11;
//repeated string domain = 13; //repeated string domain = 13;
} }
message ShortVideoExtensionReq {
int32 subBusiType = 1;
int32 userCnt = 2;
}

View File

@ -3,19 +3,23 @@ package client
import ( import (
"crypto/md5" "crypto/md5"
"encoding/hex" "encoding/hex"
"io"
"github.com/Mrs4s/MiraiGo/binary" "github.com/Mrs4s/MiraiGo/binary"
"github.com/Mrs4s/MiraiGo/client/pb" "github.com/Mrs4s/MiraiGo/client/pb"
"github.com/Mrs4s/MiraiGo/client/pb/cmd0x346" "github.com/Mrs4s/MiraiGo/client/pb/cmd0x346"
"github.com/Mrs4s/MiraiGo/client/pb/msg" "github.com/Mrs4s/MiraiGo/client/pb/msg"
"github.com/Mrs4s/MiraiGo/client/pb/pttcenter"
"github.com/Mrs4s/MiraiGo/message" "github.com/Mrs4s/MiraiGo/message"
"github.com/Mrs4s/MiraiGo/protocol/packets" "github.com/Mrs4s/MiraiGo/protocol/packets"
"github.com/Mrs4s/MiraiGo/utils"
"github.com/pkg/errors" "github.com/pkg/errors"
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
"io"
) )
// 语音相关处理逻辑 func init() {
decoders["PttCenterSvr.ShortVideoDownReq"] = decodePttShortVideoDownResponse
decoders["PttCenterSvr.GroupShortVideoUpReq"] = decodeGroupShortVideoUploadResponse
}
// UploadGroupPtt 将语音数据使用群语音通道上传到服务器, 返回 message.GroupVoiceElement 可直接发送 // UploadGroupPtt 将语音数据使用群语音通道上传到服务器, 返回 message.GroupVoiceElement 可直接发送
func (c *QQClient) UploadGroupPtt(groupCode int64, voice io.ReadSeeker) (*message.GroupVoiceElement, error) { func (c *QQClient) UploadGroupPtt(groupCode int64, voice io.ReadSeeker) (*message.GroupVoiceElement, error) {
@ -24,7 +28,7 @@ func (c *QQClient) UploadGroupPtt(groupCode int64, voice io.ReadSeeker) (*messag
fh := h.Sum(nil) fh := h.Sum(nil)
_, _ = voice.Seek(0, io.SeekStart) _, _ = voice.Seek(0, io.SeekStart)
ext := c.buildGroupPttStoreBDHExt(groupCode, fh[:], int32(length), 0, int32(length)) ext := c.buildGroupPttStoreBDHExt(groupCode, fh[:], int32(length), 0, int32(length))
rsp, err := c.highwayUploadByBDH(voice, 29, c.highwaySession.SigSession, ext) rsp, err := c.highwayUploadByBDH(voice, 29, c.highwaySession.SigSession, ext, false)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -38,29 +42,6 @@ func (c *QQClient) UploadGroupPtt(groupCode int64, voice io.ReadSeeker) (*messag
if len(pkt.MsgTryUpPttRsp) == 0 { if len(pkt.MsgTryUpPttRsp) == 0 {
return nil, errors.New("miss try up rsp") return nil, errors.New("miss try up rsp")
} }
/*
seq, pkt := c.buildGroupPttStorePacket(groupCode, h[:], int32(len(voice)), 0, int32(len(voice)))
r, err := c.sendAndWait(seq, pkt)
if err != nil {
return nil, err
}
rsp := r.(pttUploadResponse)
if rsp.ResultCode != 0 {
return nil, errors.New(rsp.Message)
}
if rsp.IsExists {
goto ok
}
for i, ip := range rsp.UploadIp {
err := c.uploadPtt(ip, rsp.UploadPort[i], rsp.UploadKey, rsp.FileKey, voice, h[:])
if err != nil {
continue
}
goto ok
}
return nil, errors.New("upload failed")
ok:
*/
return &message.GroupVoiceElement{ return &message.GroupVoiceElement{
Ptt: &msg.Ptt{ Ptt: &msg.Ptt{
FileType: proto.Int32(4), FileType: proto.Int32(4),
@ -107,6 +88,52 @@ ok:
}}, nil }}, nil
} }
func (c *QQClient) UploadGroupShortVideo(groupCode int64, video, thumb io.ReadSeeker) (*message.ShortVideoElement, error) {
videoHash, videoLen := utils.GetMd5AndLength(video)
thumbHash, thumbLen := utils.GetMd5AndLength(thumb)
i, err := c.sendAndWait(c.buildPttGroupShortVideoUploadReqPacket(videoHash, thumbHash, groupCode, videoLen, thumbLen))
if err != nil {
return nil, errors.Wrap(err, "upload req error")
}
rsp := i.(*pttcenter.ShortVideoUploadRsp)
if rsp.FileExists == 1 {
return &message.ShortVideoElement{
Uuid: []byte(rsp.FileId),
Size: int32(videoLen),
ThumbSize: int32(thumbLen),
Md5: videoHash,
ThumbMd5: thumbHash,
}, nil
}
ext, _ := proto.Marshal(c.buildPttGroupShortVideoProto(videoHash, thumbHash, groupCode, videoLen, thumbLen).PttShortVideoUploadReq)
hwRsp, err := c.highwayUploadByBDH(utils.MultiReadSeeker(thumb, video), 25, c.highwaySession.SigSession, ext, true)
if err != nil {
return nil, errors.Wrap(err, "upload video file error")
}
if len(hwRsp) == 0 {
return nil, errors.New("resp is empty")
}
rsp = &pttcenter.ShortVideoUploadRsp{}
if err = proto.Unmarshal(hwRsp, rsp); err != nil {
return nil, errors.Wrap(err, "decode error")
}
return &message.ShortVideoElement{
Uuid: []byte(rsp.FileId),
Size: int32(videoLen),
ThumbSize: int32(thumbLen),
Md5: videoHash,
ThumbMd5: thumbHash,
}, nil
}
func (c *QQClient) GetShortVideoUrl(uuid, md5 []byte) string {
i, err := c.sendAndWait(c.buildPttShortVideoDownReqPacket(uuid, md5))
if err != nil {
return ""
}
return i.(string)
}
// PttStore.GroupPttUp // PttStore.GroupPttUp
func (c *QQClient) buildGroupPttStorePacket(groupCode int64, md5 []byte, size, codec, voiceLength int32) (uint16, []byte) { func (c *QQClient) buildGroupPttStorePacket(groupCode int64, md5 []byte, size, codec, voiceLength int32) (uint16, []byte) {
seq := c.nextSeq() seq := c.nextSeq()
@ -142,6 +169,72 @@ func (c *QQClient) buildGroupPttStoreBDHExt(groupCode int64, md5 []byte, size, c
return payload return payload
} }
// PttCenterSvr.ShortVideoDownReq
func (c *QQClient) buildPttShortVideoDownReqPacket(uuid, md5 []byte) (uint16, []byte) {
seq := c.nextSeq()
body := &pttcenter.ShortVideoReqBody{
Cmd: 400,
Seq: int32(seq),
PttShortVideoDownloadReq: &pttcenter.ShortVideoDownloadReq{
FromUin: c.Uin,
ToUin: c.Uin,
ChatType: 1,
ClientType: 7,
FileId: string(uuid),
GroupCode: 1,
FileMd5: md5,
BusinessType: 1,
FileType: 2,
DownType: 2,
SceneType: 2,
},
}
payload, _ := proto.Marshal(body)
packet := packets.BuildUniPacket(c.Uin, seq, "PttCenterSvr.ShortVideoDownReq", 1, c.OutGoingPacketSessionId, EmptyBytes, c.sigInfo.d2Key, payload)
return seq, packet
}
func (c *QQClient) buildPttGroupShortVideoProto(videoHash, thumbHash []byte, toUin, videoSize, thumbSize int64) *pttcenter.ShortVideoReqBody {
seq := c.nextSeq()
return &pttcenter.ShortVideoReqBody{
Cmd: 300,
Seq: int32(seq),
PttShortVideoUploadReq: &pttcenter.ShortVideoUploadReq{
FromUin: c.Uin,
ToUin: toUin,
ChatType: 1,
ClientType: 2,
Info: &pttcenter.ShortVideoFileInfo{
FileName: hex.EncodeToString(videoHash) + ".mp4",
FileMd5: videoHash,
ThumbFileMd5: thumbHash,
FileSize: videoSize,
FileResLength: 1280,
FileResWidth: 720,
FileFormat: 3,
FileTime: 120,
ThumbFileSize: thumbSize,
},
GroupCode: toUin,
SupportLargeSize: 1,
},
ExtensionReq: []*pttcenter.ShortVideoExtensionReq{
{
SubBusiType: 0,
UserCnt: 1,
},
},
}
}
// PttCenterSvr.GroupShortVideoUpReq
func (c *QQClient) buildPttGroupShortVideoUploadReqPacket(videoHash, thumbHash []byte, toUin, videoSize, thumbSize int64) (uint16, []byte) {
seq := c.nextSeq()
payload, _ := proto.Marshal(c.buildPttGroupShortVideoProto(videoHash, thumbHash, toUin, videoSize, thumbSize))
packet := packets.BuildUniPacket(c.Uin, seq, "PttCenterSvr.GroupShortVideoUpReq", 1, c.OutGoingPacketSessionId, EmptyBytes, c.sigInfo.d2Key, payload)
return seq, packet
}
// PttStore.GroupPttUp // PttStore.GroupPttUp
func decodeGroupPttStoreResponse(_ *QQClient, _ uint16, payload []byte) (interface{}, error) { func decodeGroupPttStoreResponse(_ *QQClient, _ uint16, payload []byte) (interface{}, error) {
pkt := pb.D388RespBody{} pkt := pb.D388RespBody{}
@ -230,3 +323,30 @@ func decodePrivatePttStoreResponse(c *QQClient, _ uint16, payload []byte) (inter
FileKey: rsp.ApplyUploadRsp.Uuid, FileKey: rsp.ApplyUploadRsp.Uuid,
}, nil }, nil
} }
// PttCenterSvr.ShortVideoDownReq
func decodePttShortVideoDownResponse(_ *QQClient, _ uint16, payload []byte) (interface{}, error) {
rsp := pttcenter.ShortVideoRspBody{}
if err := proto.Unmarshal(payload, &rsp); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal protobuf message")
}
if rsp.PttShortVideoDownloadRsp == nil || rsp.PttShortVideoDownloadRsp.DownloadAddr == nil {
return nil, errors.New("resp error")
}
return rsp.PttShortVideoDownloadRsp.DownloadAddr.Host[0] + rsp.PttShortVideoDownloadRsp.DownloadAddr.UrlArgs, nil
}
// PttCenterSvr.GroupShortVideoUpReq
func decodeGroupShortVideoUploadResponse(_ *QQClient, _ uint16, payload []byte) (interface{}, error) {
rsp := pttcenter.ShortVideoRspBody{}
if err := proto.Unmarshal(payload, &rsp); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal protobuf message")
}
if rsp.PttShortVideoUploadRsp == nil {
return nil, errors.New("resp error")
}
if rsp.PttShortVideoUploadRsp.RetCode != 0 {
return nil, errors.Errorf("ret code error: %v", rsp.PttShortVideoUploadRsp.RetCode)
}
return rsp.PttShortVideoUploadRsp, nil
}

View File

@ -88,11 +88,13 @@ type ReplyElement struct {
} }
type ShortVideoElement struct { type ShortVideoElement struct {
Name string Name string
Uuid []byte Uuid []byte
Size int32 Size int32
Md5 []byte ThumbSize int32
Url string Md5 []byte
ThumbMd5 []byte
Url string
} }
type ServiceElement struct { type ServiceElement struct {

View File

@ -1,6 +1,7 @@
package message package message
import ( import (
"encoding/hex"
"github.com/Mrs4s/MiraiGo/binary" "github.com/Mrs4s/MiraiGo/binary"
"github.com/Mrs4s/MiraiGo/client/pb/msg" "github.com/Mrs4s/MiraiGo/client/pb/msg"
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
@ -256,3 +257,32 @@ func (e *GroupShowPicElement) Pack() (r []*msg.Elem) {
}) })
return return
} }
func (e *ShortVideoElement) Pack() (r []*msg.Elem) {
r = append(r, &msg.Elem{
Text: &msg.Text{
Str: proto.String("你的QQ暂不支持查看视频短片请期待后续版本。"),
},
})
r = append(r, &msg.Elem{
VideoFile: &msg.VideoFile{
FileUuid: e.Uuid,
FileMd5: e.Md5,
FileName: []byte(hex.EncodeToString(e.Md5) + ".mp4"),
FileFormat: proto.Int32(3),
FileTime: proto.Int32(10),
FileSize: proto.Int32(e.Size),
ThumbWidth: proto.Int32(1280),
ThumbHeight: proto.Int32(720),
ThumbFileMd5: e.ThumbMd5,
ThumbFileSize: proto.Int32(e.ThumbSize),
BusiType: proto.Int32(0),
FromChatType: proto.Int32(-1),
ToChatType: proto.Int32(-1),
BoolSupportProgressive: proto.Bool(true),
FileWidth: proto.Int32(1280),
FileHeight: proto.Int32(720),
},
})
return
}

View File

@ -1,10 +1,18 @@
package utils package utils
import ( import (
"crypto/md5"
"errors"
"io"
"reflect" "reflect"
"unsafe" "unsafe"
) )
type multiReadSeeker struct {
readers []io.ReadSeeker
multiReader io.Reader
}
func IsChanClosed(ch interface{}) bool { func IsChanClosed(ch interface{}) bool {
if reflect.TypeOf(ch).Kind() != reflect.Chan { if reflect.TypeOf(ch).Kind() != reflect.Chan {
panic("object is not a channel.") panic("object is not a channel.")
@ -15,3 +23,36 @@ func IsChanClosed(ch interface{}) bool {
ptr += unsafe.Sizeof(uint16(0)) ptr += unsafe.Sizeof(uint16(0))
return *(*uint32)(unsafe.Pointer(ptr)) > 0 return *(*uint32)(unsafe.Pointer(ptr)) > 0
} }
func GetMd5AndLength(r io.Reader) ([]byte, int64) {
h := md5.New()
length, _ := io.Copy(h, r)
fh := h.Sum(nil)
return fh[:], length
}
func (r *multiReadSeeker) Read(p []byte) (int, error) {
if r.multiReader == nil {
var readers []io.Reader
for i := range r.readers {
_, _ = r.readers[i].Seek(0, io.SeekStart)
readers = append(readers, r.readers[i])
}
r.multiReader = io.MultiReader(readers...)
}
return r.multiReader.Read(p)
}
func (r *multiReadSeeker) Seek(offset int64, whence int) (int64, error) {
if whence != 0 || offset != 0 {
return -1, errors.New("unsupported offset")
}
r.multiReader = nil
return 0, nil
}
func MultiReadSeeker(r ...io.ReadSeeker) io.ReadSeeker {
return &multiReadSeeker{
readers: r,
}
}