Skip to content

Commit

Permalink
ts信息
Browse files Browse the repository at this point in the history
  • Loading branch information
orestonce committed Oct 12, 2024
1 parent 83f9e95 commit 1d77b00
Show file tree
Hide file tree
Showing 2 changed files with 248 additions and 0 deletions.
149 changes: 149 additions & 0 deletions mformat/ts.go
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
}
99 changes: 99 additions & 0 deletions mformat/ts_test.go
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)
}
}

0 comments on commit 1d77b00

Please sign in to comment.