-
Notifications
You must be signed in to change notification settings - Fork 98
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
248 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
package mformat | ||
|
||
import ( | ||
"crypto/aes" | ||
"crypto/cipher" | ||
"encoding/binary" | ||
"encoding/hex" | ||
"errors" | ||
"fmt" | ||
"strconv" | ||
"strings" | ||
) | ||
|
||
// TsInfo 用于保存 ts 文件的下载地址和文件名 | ||
type TsInfo struct { | ||
Name string | ||
Url string // 后续填充 | ||
URI string | ||
Seq uint64 // 如果是aes加密并且没有iv, 这个seq需要充当iv | ||
TimeSec float64 // 此ts片段占用多少秒 | ||
Idx_EXT_X_DISCONTINUITY int // 分段编号 | ||
Key TsKeyInfo | ||
} | ||
|
||
type TsKeyInfo struct { | ||
Method string | ||
Key []byte | ||
KeyURI string // 后续填充 | ||
Iv []byte | ||
} | ||
|
||
// IsNestedPlaylists 返回是否为 嵌套播放列表 | ||
func (this *M3U8File) IsNestedPlaylists() bool { | ||
for _, one := range this.PartList { | ||
if one.Playlist != nil { | ||
return true | ||
} | ||
} | ||
return false | ||
} | ||
|
||
func (this *M3U8File) ContainsMediaSegment() bool { | ||
for _, one := range this.PartList { | ||
if one.Segment != nil { | ||
return true | ||
} | ||
} | ||
return false | ||
} | ||
|
||
// LookupHDPlaylist 找个最高清的播放列表 | ||
func (this *M3U8File) LookupHDPlaylist() (playlist *M3U8Playlist) { | ||
for _, one := range this.PartList { | ||
if one.Playlist != nil { | ||
if playlist == nil { | ||
playlist = one.Playlist | ||
} else if one.Playlist.Bandwidth != playlist.Bandwidth { | ||
if one.Playlist.Bandwidth > playlist.Bandwidth { | ||
playlist = one.Playlist | ||
} | ||
} else if one.Playlist.Resolution.Width != playlist.Resolution.Width { | ||
if one.Playlist.Resolution.Width > playlist.Resolution.Width { | ||
playlist = one.Playlist | ||
} | ||
} | ||
} | ||
} | ||
return playlist | ||
} | ||
|
||
// GetTsList 获取ts文件列表, 此处不组装url, 不下载key内容 | ||
func (this *M3U8File) GetTsList() (list []TsInfo) { | ||
var beginSeq = uint64(this.MediaSequence) | ||
var index = 0 | ||
var discontinutyIdx = 0 | ||
var curKey *M3U8Key | ||
|
||
for _, part := range this.PartList { | ||
if part.Is_EXT_X_DISCONTINUITY && len(list) > 0 { | ||
discontinutyIdx++ | ||
} | ||
if part.Key != nil { | ||
if part.Key.Method == EncryptMethod_NONE { | ||
curKey = nil | ||
} else { | ||
curKey = part.Key | ||
} | ||
} | ||
if part.Segment != nil { | ||
var seg = part.Segment | ||
index++ | ||
var info = TsInfo{ | ||
Name: fmt.Sprintf("%05d.ts", index), // ts视频片段命名规则 | ||
URI: seg.URI, | ||
Url: "", // 之后填充 | ||
Seq: beginSeq + uint64(index-1), | ||
TimeSec: seg.Duration, | ||
Idx_EXT_X_DISCONTINUITY: discontinutyIdx, | ||
} | ||
if curKey != nil { | ||
iv := []byte(strings.TrimPrefix(strings.ToLower(curKey.IV), "0x")) | ||
if len(iv) == 0 { | ||
if curKey.Method == EncryptMethod_AES128 { | ||
iv = make([]byte, 16) | ||
binary.BigEndian.PutUint64(iv[8:], info.Seq) | ||
} | ||
} else { | ||
var err error | ||
iv, err = hex.DecodeString(string(iv)) | ||
if err != nil { | ||
iv = nil | ||
} | ||
} | ||
info.Key = TsKeyInfo{ | ||
Method: curKey.Method, | ||
Key: nil, // 之后填充 | ||
KeyURI: curKey.URI, | ||
Iv: iv, | ||
} | ||
} | ||
list = append(list, info) | ||
} | ||
} | ||
return list | ||
} | ||
|
||
// AesDecrypt 解密加密后的ts文件 | ||
func AesDecrypt(encrypted []byte, key TsKeyInfo) ([]byte, error) { | ||
block, err := aes.NewCipher(key.Key) | ||
if err != nil { | ||
return nil, err | ||
} | ||
iv := key.Iv | ||
if len(iv) == 0 { | ||
return nil, errors.New("key URI " + key.KeyURI + ", invalid iv len(iv) == 0") | ||
} | ||
blockMode := cipher.NewCBCDecrypter(block, iv) | ||
if len(iv) == 0 || len(encrypted)%len(iv) != 0 { | ||
return nil, errors.New("invalid encrypted data len " + strconv.Itoa(len(encrypted))) | ||
} | ||
origData := make([]byte, len(encrypted)) | ||
blockMode.CryptBlocks(origData, encrypted) | ||
length := len(origData) | ||
unpadding := int(origData[length-1]) | ||
if length-unpadding < 0 { | ||
return nil, fmt.Errorf(`invalid length of unpadding %v - %v`, length, unpadding) | ||
} | ||
return origData[:(length - unpadding)], nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
package mformat | ||
|
||
import ( | ||
"bytes" | ||
"encoding/binary" | ||
"testing" | ||
) | ||
|
||
func TestAesDecrypt(t *testing.T) { | ||
encInfo := TsKeyInfo{ | ||
Method: EncryptMethod_AES128, | ||
Key: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, | ||
Iv: make([]byte, 16), | ||
} | ||
binary.BigEndian.PutUint64(encInfo.Iv[8:], 1) | ||
before := []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 4} | ||
after, err := AesDecrypt(before, encInfo) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
if bytes.Equal(after, []byte{69, 46, 52, 180, 68, 205, 99, 220, 193, 44, 116, 174, 96, 196, 199, 87, 214, 77, 67, 5, 37, 8, 139, 146, 229, 120, 164, 76, 107, 0, 204, 0}) == false { | ||
t.Fatal(after) | ||
} | ||
} | ||
|
||
func TestM3U8File_GetTsList(t *testing.T) { | ||
info, ok := M3U8Parse([]byte(`#EXTM3U | ||
#EXT-X-VERSION:3 | ||
#EXT-X-PLAYLIST-TYPE:VOD | ||
#EXT-X-MEDIA-SEQUENCE:0 | ||
#EXT-X-TARGETDURATION:8 | ||
#EXT-X-KEY:METHOD=AES-128,URI="/20230502/xthms/2000kb/hls/key.key",IV=0x10c27a9e3fa363dfe4c44b59b67304b3 | ||
#EXT-X-DISCONTINUITY | ||
#EXTINF:4, | ||
0000000.ts | ||
#EXTINF:4.24, | ||
0000001.ts | ||
#EXTINF:4.08, | ||
0000002.ts | ||
#EXT-X-ENDLIST | ||
`)) | ||
if !ok || len(info.PartList) != 6 { | ||
t.Fatal() | ||
} | ||
|
||
list := info.GetTsList() | ||
if len(list) != 3 { | ||
t.Fatal() | ||
} | ||
if info.ContainsMediaSegment() == false { | ||
t.Fatal() | ||
} | ||
for _, one := range list { | ||
if one.Idx_EXT_X_DISCONTINUITY != 0 { | ||
t.Fatal(one.Idx_EXT_X_DISCONTINUITY, one.Seq) | ||
} | ||
switch one.Seq { | ||
case 0: | ||
if one.URI != `0000000.ts` || one.Name != "00001.ts" { | ||
t.Fatal(one.URI, one.Name) | ||
} | ||
case 1: | ||
if one.URI != `0000001.ts` || one.Name != "00002.ts" { | ||
t.Fatal(one.URI, one.Name) | ||
} | ||
case 2: | ||
if one.URI != `0000002.ts` || one.Name != "00003.ts" { | ||
t.Fatal(one.URI, one.Name) | ||
} | ||
} | ||
} | ||
} | ||
|
||
func TestM3U8File_LookupHDPlaylist(t *testing.T) { | ||
info, ok := M3U8Parse([]byte(`#EXTM3U | ||
#EXT-X-VERSION:3 | ||
#EXT-X-STREAM-INF:BANDWIDTH=1280000,RESOLUTION=640x480 | ||
master_640x480.m3u8 | ||
#EXT-X-STREAM-INF:BANDWIDTH=2560000,RESOLUTION=1280x720 | ||
master_1280x720.m3u8 | ||
#EXT-X-STREAM-INF:BANDWIDTH=5120000,RESOLUTION=1920x1080 | ||
master_1920x1080.m3u8 | ||
#EXT-X-STREAM-INF:BANDWIDTH=5120000,RESOLUTION=2560x1440 | ||
master_2560X1440.m3u8 | ||
`)) | ||
if ok == false || len(info.PartList) != 4 { | ||
t.Fatal() | ||
} | ||
|
||
is := info.IsNestedPlaylists() | ||
if is == false { | ||
t.Fatal() | ||
} | ||
|
||
playlist := info.LookupHDPlaylist() | ||
if playlist == nil || playlist.URI != "master_2560X1440.m3u8" { | ||
t.Fatal(playlist.URI) | ||
} | ||
} |