Compare commits
10 Commits
1bbe8260a5
...
5ee60f97c2
Author | SHA1 | Date | |
---|---|---|---|
|
5ee60f97c2 | ||
|
3d80d92e2a | ||
|
3808678a59 | ||
|
a80335b840 | ||
|
33bbc52f13 | ||
|
888b173721 | ||
|
0a447d6791 | ||
|
8f3efef8d6 | ||
|
f7141aae07 | ||
|
cff8e957d4 |
29
cmd/mpls/main.go
Normal file
29
cmd/mpls/main.go
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"timmy.narnian.us/mpls"
|
||||||
|
|
||||||
|
"github.com/kr/pretty"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var (
|
||||||
|
file io.Reader
|
||||||
|
Mpls mpls.MPLS
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
file, err = os.Open(filepath.Clean(os.Args[1]))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
Mpls, err = mpls.Parse(file)
|
||||||
|
pretty.Println(Mpls)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
58
cmd/mpls_map/main.go
Normal file
58
cmd/mpls_map/main.go
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"timmy.narnian.us/mpls"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
dir *os.File
|
||||||
|
files []string
|
||||||
|
Seconds int64
|
||||||
|
)
|
||||||
|
flag.Int64Var(&Seconds, "s", 120, "Minimum duration of playlist")
|
||||||
|
flag.Int64Var(&Seconds, "seconds", 120, "Minimum duration of playlist")
|
||||||
|
flag.Parse()
|
||||||
|
name := filepath.Join(flag.Arg(0), "BDMV", "PLAYLIST")
|
||||||
|
dir, err = os.Open(name)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
files, err = dir.Readdirnames(0)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
for _, v := range files {
|
||||||
|
var (
|
||||||
|
file *os.File
|
||||||
|
playlist mpls.MPLS
|
||||||
|
duration time.Duration
|
||||||
|
)
|
||||||
|
|
||||||
|
file, err = os.Open(filepath.Join(name, v))
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
playlist, err = mpls.Parse(file)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if playlist.Duration > Seconds {
|
||||||
|
duration = time.Duration(playlist.Duration) * time.Second
|
||||||
|
fmt.Printf("%s %3d:%02d\n", v, int(duration.Minutes()), int(duration.Seconds())%60)
|
||||||
|
|
||||||
|
fmt.Println(strings.Join(playlist.SegmentMap, ","))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
406
main.go
406
main.go
@ -1,406 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/binary"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
)
|
|
||||||
|
|
||||||
// User Operation mask table
|
|
||||||
const (
|
|
||||||
ChapterSearchMask = 1 << iota
|
|
||||||
TimeSearchMask
|
|
||||||
SkipToNextPointMask
|
|
||||||
SkipBackToPreviousPointMask
|
|
||||||
ForwardPlayMask
|
|
||||||
BackwardPlayMask
|
|
||||||
PlayMask
|
|
||||||
StopMask
|
|
||||||
PauseOnMask
|
|
||||||
PauseOffMask
|
|
||||||
StillOffMask
|
|
||||||
ResumeMask
|
|
||||||
MoveUpSelectedButtonMask
|
|
||||||
MoveDownSelectedButtonMask
|
|
||||||
MoveLeftSelectedButtonMask
|
|
||||||
MoveRightSelectedButtonMask
|
|
||||||
SelectButtonMask
|
|
||||||
ActivateAndActivateMask
|
|
||||||
SelectAndActivateMask
|
|
||||||
AudioChangeMask
|
|
||||||
PgTextstChangeMask
|
|
||||||
AngleChangeMask
|
|
||||||
PopupOnMask
|
|
||||||
PopupOffMask
|
|
||||||
SelectMenuLanguageMask
|
|
||||||
)
|
|
||||||
|
|
||||||
// Playlist Flags
|
|
||||||
const (
|
|
||||||
PlaylistRandomAccess = 1 << iota
|
|
||||||
AudioMixApp
|
|
||||||
LosslessMayBypassMixer
|
|
||||||
reserved
|
|
||||||
)
|
|
||||||
|
|
||||||
// Angle Flags
|
|
||||||
const (
|
|
||||||
IsDifferentAudios = 1 << (iota + 7)
|
|
||||||
IsSeamlessAngleChange
|
|
||||||
)
|
|
||||||
|
|
||||||
// MPLS is a struct representing an MPLS file
|
|
||||||
type MPLS struct {
|
|
||||||
Header string
|
|
||||||
playlistStart int
|
|
||||||
playlistMarkStart int
|
|
||||||
extensionDataStart int
|
|
||||||
AppInfoPlaylist AppInfoPlaylist
|
|
||||||
Playlist Playlist
|
|
||||||
}
|
|
||||||
|
|
||||||
// AppInfoPlaylist sucks
|
|
||||||
type AppInfoPlaylist struct {
|
|
||||||
Len int
|
|
||||||
PlaybackType int
|
|
||||||
PlaybackCount int
|
|
||||||
UOMask uint64
|
|
||||||
PlaylistFlags uint16
|
|
||||||
}
|
|
||||||
|
|
||||||
// Playlist sucks
|
|
||||||
type Playlist struct {
|
|
||||||
len int
|
|
||||||
playItemCount uint16
|
|
||||||
subPathCount uint16
|
|
||||||
playItems []PlayItem
|
|
||||||
}
|
|
||||||
|
|
||||||
// PlayItem contains information about a an item in the playlist
|
|
||||||
type PlayItem struct {
|
|
||||||
len uint16
|
|
||||||
clpi CLPI
|
|
||||||
flags uint16 // multiangle/connection condition
|
|
||||||
STCID byte
|
|
||||||
inTime int
|
|
||||||
outTime int
|
|
||||||
UOMask uint64
|
|
||||||
RandomAccessFlag byte
|
|
||||||
stillMode byte
|
|
||||||
stillTime uint16
|
|
||||||
angleCount byte
|
|
||||||
angleFlags byte
|
|
||||||
angles []CLPI
|
|
||||||
}
|
|
||||||
|
|
||||||
// CLPI contains the filename and the codec ID
|
|
||||||
type CLPI struct {
|
|
||||||
ClipFile string
|
|
||||||
ClipID string // M2TS
|
|
||||||
STCID byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
Mpls, err := Parse(os.Args[1])
|
|
||||||
fmt.Println(Mpls)
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse parses an MPLS file into an MPLS struct
|
|
||||||
func Parse(filename string) (Mpls MPLS, err error) {
|
|
||||||
var (
|
|
||||||
file *bytes.Reader
|
|
||||||
f []byte
|
|
||||||
)
|
|
||||||
|
|
||||||
f, err = ioutil.ReadFile(filepath.Clean(filename))
|
|
||||||
if err != nil {
|
|
||||||
return MPLS{}, err
|
|
||||||
}
|
|
||||||
file = bytes.NewReader(f)
|
|
||||||
err = Mpls.Parse(file)
|
|
||||||
return Mpls, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse reads MPLS data from an io.ReadSeeker
|
|
||||||
func (Mpls *MPLS) Parse(file io.ReadSeeker) error {
|
|
||||||
var (
|
|
||||||
buf [10]byte
|
|
||||||
n int
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
|
|
||||||
n, err = file.Read(buf[:8])
|
|
||||||
if err != nil || n != 8 {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
str := string(buf[:8])
|
|
||||||
if str[:4] != "MPLS" {
|
|
||||||
return fmt.Errorf("not an mpls file it must start with 'MPLS' it started with '%s'", str[:4])
|
|
||||||
}
|
|
||||||
if str[4:8] != "0200" {
|
|
||||||
fmt.Fprintf(os.Stderr, "warning: mpls may not work it is version %s\n", str[4:8])
|
|
||||||
}
|
|
||||||
|
|
||||||
Mpls.Header = str
|
|
||||||
|
|
||||||
Mpls.playlistStart, err = readInt32(file, buf[:4])
|
|
||||||
fmt.Println("int:", Mpls.playlistStart, "binary:", buf[:4])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
Mpls.playlistMarkStart, err = readInt32(file, buf[:4])
|
|
||||||
fmt.Println("int:", Mpls.playlistMarkStart, "binary:", buf[:4])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
Mpls.extensionDataStart, err = readInt32(file, buf[:4])
|
|
||||||
fmt.Println("int:", Mpls.extensionDataStart, "binary:", buf[:4])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = file.Seek(20, io.SeekCurrent)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = Mpls.AppInfoPlaylist.parse(file)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = file.Seek(int64(Mpls.playlistStart), io.SeekStart)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = Mpls.Playlist.Parse(file)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse reads AppInfoPlaylist data from an io.ReadSeeker
|
|
||||||
func (aip *AppInfoPlaylist) parse(file io.ReadSeeker) error {
|
|
||||||
var (
|
|
||||||
buf [10]byte
|
|
||||||
err error
|
|
||||||
n int
|
|
||||||
)
|
|
||||||
aip.Len, err = readInt32(file, buf[:4])
|
|
||||||
fmt.Println("int:", aip.Len, "binary:", buf[:4])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
n, err = file.Read(buf[:4])
|
|
||||||
if err != nil || n != 4 {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
aip.PlaybackType = int(buf[1])
|
|
||||||
fmt.Println("int:", aip.PlaybackType, "binary:", buf[1])
|
|
||||||
|
|
||||||
aip.PlaybackCount = int(binary.BigEndian.Uint16(buf[2:4]))
|
|
||||||
fmt.Println("int:", aip.PlaybackCount, "binary:", buf[2:4])
|
|
||||||
|
|
||||||
aip.UOMask, err = readUInt64(file, buf[:8])
|
|
||||||
fmt.Println("int:", aip.UOMask, "binary:", buf[:8])
|
|
||||||
if err != nil || n != 1 {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
aip.PlaylistFlags, err = readUInt16(file, buf[:2])
|
|
||||||
fmt.Println("int:", aip.PlaylistFlags, "binary:", buf[:2])
|
|
||||||
if err != nil || n != 1 {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse reads Playlist data from an io.ReadSeeker
|
|
||||||
func (p *Playlist) Parse(file io.ReadSeeker) error {
|
|
||||||
var (
|
|
||||||
buf [10]byte
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
|
|
||||||
p.len, err = readInt32(file, buf[:])
|
|
||||||
fmt.Println("int:", p.len, "binary:", buf[:4])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = file.Seek(2, io.SeekCurrent)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
p.playItemCount, err = readUInt16(file, buf[:])
|
|
||||||
fmt.Println("int:", p.playItemCount, "binary:", buf[:2])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
p.subPathCount, err = readUInt16(file, buf[:])
|
|
||||||
fmt.Println("int:", p.subPathCount, "binary:", buf[:2])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for i := 0; i < int(p.playItemCount); i++ {
|
|
||||||
var item PlayItem
|
|
||||||
err = item.Parse(file)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
p.playItems = append(p.playItems, item)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse reads PlayItem data from an io.ReadSeeker
|
|
||||||
func (pi *PlayItem) Parse(file io.Reader) error {
|
|
||||||
var (
|
|
||||||
buf [10]byte
|
|
||||||
n int
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
|
|
||||||
pi.len, err = readUInt16(file, buf[:])
|
|
||||||
fmt.Println("int:", pi.len, "binary:", buf[:2])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
n, err = file.Read(buf[:9])
|
|
||||||
if err != nil || n != 9 {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
str := string(buf[:9])
|
|
||||||
if str[5:9] != "M2TS" {
|
|
||||||
fmt.Fprintf(os.Stderr, "warning: this playlist may be faulty it has a play item that is '%s' not 'M2TS'", str[4:8])
|
|
||||||
}
|
|
||||||
pi.clpi.ClipFile = str[:5]
|
|
||||||
pi.clpi.ClipID = str[5:9]
|
|
||||||
|
|
||||||
pi.flags, err = readUInt16(file, buf[:])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
n, err = file.Read(buf[:1])
|
|
||||||
if err != nil || n != 1 {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
pi.STCID = buf[0]
|
|
||||||
|
|
||||||
pi.inTime, err = readInt32(file, buf[:])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
pi.outTime, err = readInt32(file, buf[:])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
pi.UOMask, err = readUInt64(file, buf[:])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
n, err = file.Read(buf[:1])
|
|
||||||
if err != nil || n != 1 {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
pi.RandomAccessFlag = buf[0]
|
|
||||||
|
|
||||||
n, err = file.Read(buf[:1])
|
|
||||||
if err != nil || n != 1 {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
pi.stillMode = buf[0]
|
|
||||||
|
|
||||||
pi.stillTime, err = readUInt16(file, buf[:])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if pi.flags&1 == 1 {
|
|
||||||
n, err = file.Read(buf[:1])
|
|
||||||
if err != nil || n != 1 {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
pi.angleCount = buf[0]
|
|
||||||
|
|
||||||
n, err = file.Read(buf[:1])
|
|
||||||
if err != nil || n != 1 {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
pi.angleFlags = buf[0]
|
|
||||||
|
|
||||||
for i := 0; i < int(pi.angleCount); i++ {
|
|
||||||
var angle CLPI
|
|
||||||
err = angle.Parse(file)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
pi.angles = append(pi.angles, angle)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse reads angle data from an io.ReadSeeker
|
|
||||||
func (clpi *CLPI) Parse(file io.Reader) error {
|
|
||||||
var (
|
|
||||||
buf [10]byte
|
|
||||||
n int
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
n, err = file.Read(buf[:9])
|
|
||||||
if err != nil || n != 9 {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
str := string(buf[:9])
|
|
||||||
clpi.ClipFile = str[:5]
|
|
||||||
clpi.ClipID = str[5:9]
|
|
||||||
|
|
||||||
n, err = file.Read(buf[:1])
|
|
||||||
if err != nil || n != 1 {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
clpi.STCID = buf[0]
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func readUInt16(file io.Reader, buf []byte) (uint16, error) {
|
|
||||||
n, err := file.Read(buf[:2])
|
|
||||||
if err != nil || n != 2 {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return binary.BigEndian.Uint16(buf[:2]), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func readInt32(file io.Reader, buf []byte) (int, error) {
|
|
||||||
n, err := readUInt32(file, buf)
|
|
||||||
return int(n), err
|
|
||||||
}
|
|
||||||
|
|
||||||
func readUInt32(file io.Reader, buf []byte) (uint32, error) {
|
|
||||||
n, err := file.Read(buf[:4])
|
|
||||||
if err != nil || n != 4 {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return binary.BigEndian.Uint32(buf[:4]), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func readUInt64(file io.Reader, buf []byte) (uint64, error) {
|
|
||||||
n, err := file.Read(buf[:8])
|
|
||||||
if err != nil || n != 8 {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return binary.BigEndian.Uint64(buf[:8]), nil
|
|
||||||
}
|
|
293
mpls.go
Normal file
293
mpls.go
Normal file
@ -0,0 +1,293 @@
|
|||||||
|
package mpls
|
||||||
|
|
||||||
|
// User Operation mask table
|
||||||
|
const (
|
||||||
|
UOChapterSearchMask = 1 << iota
|
||||||
|
UOTimeSearchMask
|
||||||
|
UOSkipToNextPointMask
|
||||||
|
UOSkipBackToPreviousPointMask
|
||||||
|
UOForwardPlayMask
|
||||||
|
UOBackwardPlayMask
|
||||||
|
UOPlayMask
|
||||||
|
UOStopMask
|
||||||
|
UOPauseOnMask
|
||||||
|
UOPauseOffMask
|
||||||
|
UOStillOffMask
|
||||||
|
UOResumeMask
|
||||||
|
UOMoveUpSelectedButtonMask
|
||||||
|
UOMoveDownSelectedButtonMask
|
||||||
|
UOMoveLeftSelectedButtonMask
|
||||||
|
UOMoveRightSelectedButtonMask
|
||||||
|
UOSelectButtonMask
|
||||||
|
UOActivateAndActivateMask
|
||||||
|
UOSelectAndActivateMask
|
||||||
|
UOAudioChangeMask
|
||||||
|
UOPgTextstChangeMask
|
||||||
|
UOAngleChangeMask
|
||||||
|
UOPopupOnMask
|
||||||
|
UOPopupOffMask
|
||||||
|
UOSelectMenuLanguageMask
|
||||||
|
)
|
||||||
|
|
||||||
|
// Playlist Flags
|
||||||
|
const (
|
||||||
|
PFPlaylistRandomAccess = 1 << iota
|
||||||
|
PFAudioMixApp
|
||||||
|
PFLosslessMayBypassMixer
|
||||||
|
PFreserved
|
||||||
|
)
|
||||||
|
|
||||||
|
// Angle Flags
|
||||||
|
const (
|
||||||
|
AFIsDifferentAudios = 1 << (iota + 7)
|
||||||
|
AFIsSeamlessAngleChange
|
||||||
|
)
|
||||||
|
|
||||||
|
// VideoType
|
||||||
|
const (
|
||||||
|
VTMPEG1Video = 0x01
|
||||||
|
VTMPEG2Video = 0x02
|
||||||
|
VTVC1 = 0xea
|
||||||
|
VTH264 = 0x1b
|
||||||
|
)
|
||||||
|
|
||||||
|
// AudioType
|
||||||
|
const (
|
||||||
|
ATMPEG1Audio = 0x03
|
||||||
|
ATMPEG2Audio = 0x04
|
||||||
|
ATLPCM = 0x80
|
||||||
|
ATAC3 = 0x81
|
||||||
|
ATDTS = 0x82
|
||||||
|
ATTRUEHD = 0x83
|
||||||
|
ATAC3Plus = 0x84
|
||||||
|
ATDTSHD = 0x85
|
||||||
|
ATDTSHDMaster = 0x86
|
||||||
|
)
|
||||||
|
|
||||||
|
// OtherType
|
||||||
|
const (
|
||||||
|
PresentationGraphics = 0x90
|
||||||
|
InteractiveGraphics = 0x91
|
||||||
|
TextSubtitle = 0x92
|
||||||
|
)
|
||||||
|
|
||||||
|
// VideoFormat
|
||||||
|
const (
|
||||||
|
VFReserved = iota
|
||||||
|
VF480I
|
||||||
|
VF576I
|
||||||
|
VF480P
|
||||||
|
VF1080I
|
||||||
|
VF720P
|
||||||
|
VF1080P
|
||||||
|
VF576P
|
||||||
|
)
|
||||||
|
|
||||||
|
// FrameRate
|
||||||
|
const (
|
||||||
|
FRReserved = iota
|
||||||
|
FR23976 // 23.976
|
||||||
|
FR24 // 24
|
||||||
|
FR25 // 25
|
||||||
|
FR2997 // 29.97
|
||||||
|
FR50 // 50
|
||||||
|
FR5994 // 59.94
|
||||||
|
)
|
||||||
|
|
||||||
|
// AspectRatio
|
||||||
|
const (
|
||||||
|
ARReserved = 0
|
||||||
|
AR43 = 2 //4:3
|
||||||
|
AR169 = 3 //16:9
|
||||||
|
)
|
||||||
|
|
||||||
|
// AudioPresentation
|
||||||
|
const (
|
||||||
|
APReserved = 0
|
||||||
|
APMono = 1
|
||||||
|
APDualMono = 2
|
||||||
|
APStereo = 3
|
||||||
|
APMulti = 6
|
||||||
|
APCombo = 12
|
||||||
|
)
|
||||||
|
|
||||||
|
// SampleRate
|
||||||
|
const (
|
||||||
|
SRReserved = 0
|
||||||
|
SR48 = 1
|
||||||
|
SR96 = 4
|
||||||
|
SR192 = 5
|
||||||
|
SR48192 = 12 // 48/192
|
||||||
|
SR4896 = 14 // 48/96
|
||||||
|
)
|
||||||
|
|
||||||
|
// CharacterCode
|
||||||
|
const (
|
||||||
|
ReservedCharacterCode = iota
|
||||||
|
UTF8
|
||||||
|
UTF16
|
||||||
|
ShiftJIS // Japanese
|
||||||
|
KSC5601 // Korean
|
||||||
|
GB18030 // Chinese
|
||||||
|
GB2312 // Chinese
|
||||||
|
BIG5 // Chinese
|
||||||
|
) // Chinese
|
||||||
|
|
||||||
|
// MPLS is a struct representing an MPLS file
|
||||||
|
type MPLS struct {
|
||||||
|
FileType string
|
||||||
|
Version string
|
||||||
|
PlaylistStart int
|
||||||
|
PlaylistMarkStart int
|
||||||
|
ExtensionDataStart int
|
||||||
|
AppInfoPlaylist AppInfoPlaylist
|
||||||
|
Playlist Playlist
|
||||||
|
MarkPlaylist PlaylistMark
|
||||||
|
SegmentMap []string
|
||||||
|
Duration int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppInfoPlaylist sucks
|
||||||
|
type AppInfoPlaylist struct {
|
||||||
|
Len int
|
||||||
|
PlaybackType byte
|
||||||
|
PlaybackCount uint16
|
||||||
|
PlaylistFlags uint16
|
||||||
|
UOMask uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
type Playlist struct {
|
||||||
|
Len int
|
||||||
|
PlayItemCount uint16
|
||||||
|
SubPathCount uint16
|
||||||
|
PlayItems []PlayItem
|
||||||
|
SubPaths []SubPath
|
||||||
|
}
|
||||||
|
|
||||||
|
// PlayItem contains information about a an item in the playlist
|
||||||
|
type PlayItem struct {
|
||||||
|
Len uint16
|
||||||
|
Flags uint16 // multiangle/connection condition
|
||||||
|
InTime int
|
||||||
|
OutTime int
|
||||||
|
UOMask uint64
|
||||||
|
RandomAccessFlag byte
|
||||||
|
AngleCount byte
|
||||||
|
AngleFlags byte
|
||||||
|
StillMode byte
|
||||||
|
StillTime uint16
|
||||||
|
Clpi CLPI
|
||||||
|
Angles []CLPI
|
||||||
|
StreamTable STNTable
|
||||||
|
}
|
||||||
|
|
||||||
|
// STNTable STream Number Table
|
||||||
|
type STNTable struct {
|
||||||
|
Len uint16 // Reserved uint16
|
||||||
|
PrimaryVideoStreamCount byte
|
||||||
|
PrimaryAudioStreamCount byte
|
||||||
|
PrimaryPGStreamCount byte
|
||||||
|
PrimaryIGStreamCount byte
|
||||||
|
SecondaryVideoStreamCount byte
|
||||||
|
SecondaryAudioStreamCount byte
|
||||||
|
PIPPGStreamCount byte
|
||||||
|
PrimaryVideoStreams []PrimaryStream
|
||||||
|
PrimaryAudioStreams []PrimaryStream
|
||||||
|
PrimaryPGStreams []PrimaryStream
|
||||||
|
PrimaryIGStreams []PrimaryStream
|
||||||
|
SecondaryAudioStreams []SecondaryAudioStream
|
||||||
|
SecondaryVideoStreams []SecondaryVideoStream
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrimaryStream holds a stream entry and attributes
|
||||||
|
type PrimaryStream struct {
|
||||||
|
StreamEntry
|
||||||
|
StreamAttributes
|
||||||
|
}
|
||||||
|
|
||||||
|
// SecondaryStream holds stream references
|
||||||
|
type SecondaryStream struct {
|
||||||
|
RefrenceEntryCount byte
|
||||||
|
StreamIDs []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// SecondaryAudioStream holds a primary stream and a secondary stream
|
||||||
|
type SecondaryAudioStream struct {
|
||||||
|
PrimaryStream
|
||||||
|
ExtraAttributes SecondaryStream
|
||||||
|
}
|
||||||
|
|
||||||
|
// SecondaryVideoStream holds a primary stream and a secondary stream for the video
|
||||||
|
// and a secondary stream for the Presentation Graphics/pip
|
||||||
|
type SecondaryVideoStream struct {
|
||||||
|
PrimaryStream
|
||||||
|
ExtraAttributes SecondaryStream
|
||||||
|
PGStream SecondaryStream
|
||||||
|
}
|
||||||
|
|
||||||
|
// StreamEntry holds the information for the data stream
|
||||||
|
type StreamEntry struct {
|
||||||
|
Len byte
|
||||||
|
Type byte
|
||||||
|
PID uint16
|
||||||
|
SubPathID byte
|
||||||
|
SubClipID byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// StreamAttributes holds metadata about the data stream
|
||||||
|
type StreamAttributes struct {
|
||||||
|
Len byte
|
||||||
|
Encoding byte
|
||||||
|
Format byte
|
||||||
|
Rate byte
|
||||||
|
CharacterCode byte
|
||||||
|
Language string
|
||||||
|
}
|
||||||
|
|
||||||
|
// CLPI contains the fiLename and the codec ID
|
||||||
|
type CLPI struct {
|
||||||
|
ClipFile string
|
||||||
|
ClipID string // M2TS
|
||||||
|
STCID byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type SubPath struct {
|
||||||
|
Len int
|
||||||
|
Type byte
|
||||||
|
PlayItemCount byte
|
||||||
|
Flags uint16
|
||||||
|
SubPlayItems []SubPlayItem
|
||||||
|
}
|
||||||
|
|
||||||
|
// SubPlayItem contains information about a PlayItem in the subpath
|
||||||
|
type SubPlayItem struct {
|
||||||
|
Len uint16
|
||||||
|
Flags byte // multiangle/connection condition
|
||||||
|
StartOfPlayitem uint32
|
||||||
|
InTime int
|
||||||
|
OutTime int
|
||||||
|
UOMask uint64
|
||||||
|
RandomAccessFlag byte
|
||||||
|
AngleCount byte
|
||||||
|
AngleFlags byte
|
||||||
|
StillMode byte
|
||||||
|
StillTime uint16
|
||||||
|
PlayItemID uint16
|
||||||
|
Clpi CLPI
|
||||||
|
Angles []CLPI
|
||||||
|
StreamTable STNTable
|
||||||
|
}
|
||||||
|
|
||||||
|
type PlaylistMark struct {
|
||||||
|
Len uint64
|
||||||
|
MarkCount uint16
|
||||||
|
Marks []Mark
|
||||||
|
}
|
||||||
|
|
||||||
|
type Mark struct {
|
||||||
|
Type byte
|
||||||
|
PlayItemRef uint16
|
||||||
|
Time uint32
|
||||||
|
PID uint16
|
||||||
|
Duration uint32
|
||||||
|
}
|
614
parse.go
Normal file
614
parse.go
Normal file
@ -0,0 +1,614 @@
|
|||||||
|
package mpls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
type errReader struct {
|
||||||
|
RS *bytes.Reader
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (er *errReader) Read(p []byte) (n int, err error) {
|
||||||
|
if er.err != nil {
|
||||||
|
return 0, er.err
|
||||||
|
}
|
||||||
|
|
||||||
|
n, er.err = er.RS.Read(p)
|
||||||
|
if n != len(p) {
|
||||||
|
er.err = fmt.Errorf("%s", "Invalid read")
|
||||||
|
}
|
||||||
|
|
||||||
|
return n, er.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (er *errReader) Seek(offset int64, whence int) (int64, error) {
|
||||||
|
if er.err != nil {
|
||||||
|
return 0, er.err
|
||||||
|
}
|
||||||
|
|
||||||
|
var n64 int64
|
||||||
|
n64, er.err = er.RS.Seek(offset, whence)
|
||||||
|
|
||||||
|
return n64, er.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse parses an MPLS file into an MPLS struct
|
||||||
|
func Parse(reader io.Reader) (mpls MPLS, err error) {
|
||||||
|
var (
|
||||||
|
file []byte
|
||||||
|
)
|
||||||
|
|
||||||
|
file, err = ioutil.ReadAll(reader)
|
||||||
|
if err != nil {
|
||||||
|
return MPLS{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = mpls.Parse(file)
|
||||||
|
return mpls, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse reads MPLS data from an io.ReadSeeker
|
||||||
|
func (mpls *MPLS) Parse(file []byte) error {
|
||||||
|
var (
|
||||||
|
buf [10]byte
|
||||||
|
n int
|
||||||
|
err error
|
||||||
|
start int64
|
||||||
|
)
|
||||||
|
|
||||||
|
reader := &errReader{
|
||||||
|
RS: bytes.NewReader(file),
|
||||||
|
err: nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err = reader.Read(buf[:8])
|
||||||
|
if err != nil || n != 8 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
str := string(buf[:8])
|
||||||
|
if str[:4] != "MPLS" {
|
||||||
|
return fmt.Errorf("not an mpls file it must start with 'MPLS' it started with '%s'", str[:4])
|
||||||
|
}
|
||||||
|
if str[4:8] != "0200" {
|
||||||
|
fmt.Fprintf(os.Stderr, "warning: mpls may not work it is version %s\n", str[4:8])
|
||||||
|
}
|
||||||
|
|
||||||
|
mpls.FileType = str[:4]
|
||||||
|
mpls.Version = str[4:8]
|
||||||
|
|
||||||
|
mpls.PlaylistStart, _ = readInt32(reader, buf[:])
|
||||||
|
|
||||||
|
mpls.PlaylistMarkStart, _ = readInt32(reader, buf[:])
|
||||||
|
|
||||||
|
mpls.ExtensionDataStart, _ = readInt32(reader, buf[:])
|
||||||
|
|
||||||
|
_, _ = reader.Seek(20, io.SeekCurrent)
|
||||||
|
|
||||||
|
_ = mpls.AppInfoPlaylist.parse(reader)
|
||||||
|
|
||||||
|
start, _ = reader.Seek(0, io.SeekCurrent)
|
||||||
|
if start != int64(mpls.PlaylistStart) {
|
||||||
|
fmt.Fprintf(os.Stderr, "Playlist doesn't start at the right place. Current position is %d position should be %d\n", start, int64(mpls.PlaylistStart))
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _ = reader.Seek(int64(mpls.PlaylistStart), io.SeekStart)
|
||||||
|
_ = mpls.Playlist.parse(reader)
|
||||||
|
|
||||||
|
start, _ = reader.Seek(0, io.SeekCurrent)
|
||||||
|
if start != int64(mpls.PlaylistMarkStart) {
|
||||||
|
fmt.Fprintf(os.Stderr, "Mark Playlist doesn't start at the right place. Current position is %d position should be %d\n", start, int64(mpls.PlaylistStart))
|
||||||
|
}
|
||||||
|
|
||||||
|
// _ = mpls.MarkPlaylist.parse(reader)
|
||||||
|
mpls.SegmentMap = make([]string, 0, len(mpls.Playlist.PlayItems))
|
||||||
|
for _, playitem := range mpls.Playlist.PlayItems {
|
||||||
|
mpls.SegmentMap = append(mpls.SegmentMap, playitem.Clpi.ClipFile)
|
||||||
|
mpls.Duration += int64(playitem.OutTime - playitem.InTime)
|
||||||
|
}
|
||||||
|
mpls.Duration = mpls.Duration / 4500
|
||||||
|
return reader.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse reads AppInfoPlaylist data from an *errReader
|
||||||
|
func (aip *AppInfoPlaylist) parse(reader *errReader) error {
|
||||||
|
var (
|
||||||
|
buf [10]byte
|
||||||
|
start int64
|
||||||
|
end int64
|
||||||
|
)
|
||||||
|
|
||||||
|
aip.Len, _ = readInt32(reader, buf[:])
|
||||||
|
|
||||||
|
start, _ = reader.Seek(0, io.SeekCurrent)
|
||||||
|
|
||||||
|
_, _ = reader.Read(buf[:2])
|
||||||
|
|
||||||
|
aip.PlaybackType = buf[1]
|
||||||
|
|
||||||
|
aip.PlaybackCount, _ = readUInt16(reader, buf[:])
|
||||||
|
|
||||||
|
aip.UOMask, _ = readUInt64(reader, buf[:])
|
||||||
|
|
||||||
|
aip.PlaylistFlags, _ = readUInt16(reader, buf[:])
|
||||||
|
|
||||||
|
end, _ = reader.Seek(0, io.SeekCurrent)
|
||||||
|
if end != (start + int64(aip.Len)) {
|
||||||
|
fmt.Fprintf(os.Stderr, "App Info Playlist is not aligned. App Info Playlist started at %d current position is %d position should be %d\n", start, end, start+int64(aip.Len))
|
||||||
|
}
|
||||||
|
|
||||||
|
return reader.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse reads Playlist data from an *errReader
|
||||||
|
func (p *Playlist) parse(reader *errReader) error {
|
||||||
|
var (
|
||||||
|
buf [10]byte
|
||||||
|
err error
|
||||||
|
start int64
|
||||||
|
end int64
|
||||||
|
)
|
||||||
|
|
||||||
|
p.Len, _ = readInt32(reader, buf[:])
|
||||||
|
|
||||||
|
start, _ = reader.Seek(0, io.SeekCurrent)
|
||||||
|
|
||||||
|
_, _ = reader.Seek(2, io.SeekCurrent)
|
||||||
|
|
||||||
|
p.PlayItemCount, _ = readUInt16(reader, buf[:])
|
||||||
|
|
||||||
|
p.SubPathCount, _ = readUInt16(reader, buf[:])
|
||||||
|
|
||||||
|
for i := 0; i < int(p.PlayItemCount); i++ {
|
||||||
|
var item PlayItem
|
||||||
|
err = item.parse(reader)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.PlayItems = append(p.PlayItems, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < int(p.SubPathCount); i++ {
|
||||||
|
var item SubPath
|
||||||
|
err = item.parse(reader)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.SubPaths = append(p.SubPaths, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
end, _ = reader.Seek(0, io.SeekCurrent)
|
||||||
|
if end != (start + int64(p.Len)) {
|
||||||
|
fmt.Fprintf(os.Stderr, "Playlist is not aligned. Playlist started at %d current position is %d position should be %d\n", start, end, start+int64(p.Len))
|
||||||
|
}
|
||||||
|
|
||||||
|
return reader.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse reads PlayItem data from an *errReader
|
||||||
|
func (pi *PlayItem) parse(reader *errReader) error {
|
||||||
|
var (
|
||||||
|
buf [10]byte
|
||||||
|
err error
|
||||||
|
start int64
|
||||||
|
end int64
|
||||||
|
)
|
||||||
|
|
||||||
|
pi.Len, _ = readUInt16(reader, buf[:])
|
||||||
|
|
||||||
|
start, _ = reader.Seek(0, io.SeekCurrent)
|
||||||
|
|
||||||
|
_, _ = reader.Read(buf[:9])
|
||||||
|
|
||||||
|
str := string(buf[:9])
|
||||||
|
if str[5:9] != "M2TS" {
|
||||||
|
fmt.Fprintf(os.Stderr, "warning: this playlist may be faulty it has a play item that is '%s' not 'M2TS'", str[4:8])
|
||||||
|
}
|
||||||
|
pi.Clpi.ClipFile = str[:5]
|
||||||
|
pi.Clpi.ClipID = str[5:9]
|
||||||
|
|
||||||
|
pi.Flags, _ = readUInt16(reader, buf[:])
|
||||||
|
|
||||||
|
_, _ = reader.Read(buf[:1])
|
||||||
|
|
||||||
|
pi.Clpi.STCID = buf[0]
|
||||||
|
|
||||||
|
pi.InTime, _ = readInt32(reader, buf[:])
|
||||||
|
|
||||||
|
pi.OutTime, _ = readInt32(reader, buf[:])
|
||||||
|
|
||||||
|
pi.UOMask, _ = readUInt64(reader, buf[:])
|
||||||
|
|
||||||
|
_, _ = reader.Read(buf[:2])
|
||||||
|
|
||||||
|
pi.RandomAccessFlag = buf[0]
|
||||||
|
|
||||||
|
pi.StillMode = buf[1]
|
||||||
|
|
||||||
|
pi.StillTime, _ = readUInt16(reader, buf[:])
|
||||||
|
|
||||||
|
if pi.Flags&1<<3 == 1 {
|
||||||
|
_, _ = reader.Read(buf[:2])
|
||||||
|
|
||||||
|
pi.AngleCount = buf[0]
|
||||||
|
|
||||||
|
pi.AngleFlags = buf[1]
|
||||||
|
|
||||||
|
for i := 0; i < int(pi.AngleCount); i++ {
|
||||||
|
var angle CLPI
|
||||||
|
_ = angle.parse(reader)
|
||||||
|
_, err = reader.Read(buf[:1])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
angle.STCID = buf[0]
|
||||||
|
pi.Angles = append(pi.Angles, angle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = pi.StreamTable.parse(reader)
|
||||||
|
|
||||||
|
end, _ = reader.Seek(0, io.SeekCurrent)
|
||||||
|
if end != (start + int64(pi.Len)) {
|
||||||
|
fmt.Fprintf(os.Stderr, "playitem is not aligned. Playitem started at %d current position is %d position should be %d\n", start, end, start+int64(pi.Len))
|
||||||
|
}
|
||||||
|
|
||||||
|
return reader.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse reads angle data from an *errReader
|
||||||
|
func (clpi *CLPI) parse(reader *errReader) error {
|
||||||
|
var (
|
||||||
|
buf [10]byte
|
||||||
|
)
|
||||||
|
_, _ = reader.Read(buf[:])
|
||||||
|
|
||||||
|
str := string(buf[:9])
|
||||||
|
clpi.ClipFile = str[:5]
|
||||||
|
clpi.ClipID = str[5:9]
|
||||||
|
|
||||||
|
// clpi.STCID = buf[9]
|
||||||
|
return reader.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse reads PrimaryStream data from an *errReader
|
||||||
|
func (stnt *STNTable) parse(reader *errReader) error {
|
||||||
|
var (
|
||||||
|
buf [10]byte
|
||||||
|
err error
|
||||||
|
start int64
|
||||||
|
end int64
|
||||||
|
)
|
||||||
|
stnt.Len, _ = readUInt16(reader, buf[:])
|
||||||
|
|
||||||
|
start, _ = reader.Seek(0, io.SeekCurrent)
|
||||||
|
|
||||||
|
_, _ = reader.Read(buf[:9])
|
||||||
|
|
||||||
|
stnt.PrimaryVideoStreamCount = buf[2]
|
||||||
|
stnt.PrimaryAudioStreamCount = buf[3]
|
||||||
|
stnt.PrimaryPGStreamCount = buf[4]
|
||||||
|
stnt.PrimaryIGStreamCount = buf[5]
|
||||||
|
stnt.SecondaryAudioStreamCount = buf[6]
|
||||||
|
stnt.SecondaryVideoStreamCount = buf[7]
|
||||||
|
stnt.PIPPGStreamCount = buf[8]
|
||||||
|
|
||||||
|
_, _ = reader.Seek(5, io.SeekCurrent)
|
||||||
|
|
||||||
|
for i := 0; i < int(stnt.PrimaryVideoStreamCount); i++ {
|
||||||
|
var stream PrimaryStream
|
||||||
|
err = stream.parse(reader)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
stnt.PrimaryVideoStreams = append(stnt.PrimaryVideoStreams, stream)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < int(stnt.PrimaryAudioStreamCount); i++ {
|
||||||
|
var stream PrimaryStream
|
||||||
|
err = stream.parse(reader)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
stnt.PrimaryAudioStreams = append(stnt.PrimaryAudioStreams, stream)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < int(stnt.PrimaryPGStreamCount); i++ {
|
||||||
|
var stream PrimaryStream
|
||||||
|
err = stream.parse(reader)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
stnt.PrimaryPGStreams = append(stnt.PrimaryPGStreams, stream)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < int(stnt.PrimaryIGStreamCount); i++ {
|
||||||
|
var stream PrimaryStream
|
||||||
|
err = stream.parse(reader)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
stnt.PrimaryIGStreams = append(stnt.PrimaryIGStreams, stream)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < int(stnt.SecondaryAudioStreamCount); i++ {
|
||||||
|
var stream SecondaryAudioStream
|
||||||
|
err = stream.parse(reader)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
stnt.SecondaryAudioStreams = append(stnt.SecondaryAudioStreams, stream)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < int(stnt.SecondaryVideoStreamCount); i++ {
|
||||||
|
var stream SecondaryVideoStream
|
||||||
|
err = stream.parse(reader)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
stnt.SecondaryVideoStreams = append(stnt.SecondaryVideoStreams, stream)
|
||||||
|
}
|
||||||
|
|
||||||
|
end, _ = reader.Seek(0, io.SeekCurrent)
|
||||||
|
if end != (start + int64(stnt.Len)) {
|
||||||
|
fmt.Fprintf(os.Stderr, "STN Table is not aligned. STN Table started at %d current position is %d position should be %d\n", start, end, start+int64(stnt.Len))
|
||||||
|
}
|
||||||
|
|
||||||
|
return reader.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse reads SecondaryStream data from an *errReader
|
||||||
|
func (ss *SecondaryStream) parse(reader *errReader) error {
|
||||||
|
var (
|
||||||
|
buf [10]byte
|
||||||
|
)
|
||||||
|
|
||||||
|
_, _ = reader.Read(buf[:2])
|
||||||
|
ss.RefrenceEntryCount = buf[0]
|
||||||
|
ss.StreamIDs = make([]byte, ss.RefrenceEntryCount)
|
||||||
|
_, _ = reader.Read(ss.StreamIDs)
|
||||||
|
if ss.RefrenceEntryCount%2 != 0 {
|
||||||
|
_, _ = reader.Seek(1, io.SeekCurrent)
|
||||||
|
}
|
||||||
|
return reader.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse reads SecondaryAudioStream data from an *errReader
|
||||||
|
func (sas *SecondaryAudioStream) parse(reader *errReader) error {
|
||||||
|
_ = sas.PrimaryStream.parse(reader)
|
||||||
|
_ = sas.ExtraAttributes.parse(reader)
|
||||||
|
|
||||||
|
return reader.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse reads SecondaryVideoStream data from an *errReader
|
||||||
|
func (svs *SecondaryVideoStream) parse(reader *errReader) error {
|
||||||
|
_ = svs.PrimaryStream.parse(reader)
|
||||||
|
_ = svs.ExtraAttributes.parse(reader)
|
||||||
|
_ = svs.PGStream.parse(reader)
|
||||||
|
|
||||||
|
return reader.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse reads Stream data from an *errReader
|
||||||
|
func (ps *PrimaryStream) parse(reader *errReader) error {
|
||||||
|
|
||||||
|
_ = ps.StreamEntry.parse(reader)
|
||||||
|
|
||||||
|
_ = ps.StreamAttributes.parse(reader)
|
||||||
|
|
||||||
|
return reader.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse reads Stream data from an *errReader
|
||||||
|
func (se *StreamEntry) parse(reader *errReader) error {
|
||||||
|
var (
|
||||||
|
buf [10]byte
|
||||||
|
start int64
|
||||||
|
end int64
|
||||||
|
)
|
||||||
|
|
||||||
|
_, _ = reader.Read(buf[:1])
|
||||||
|
|
||||||
|
se.Len = buf[0]
|
||||||
|
|
||||||
|
start, _ = reader.Seek(0, io.SeekCurrent)
|
||||||
|
|
||||||
|
_, _ = reader.Read(buf[:9])
|
||||||
|
se.Type = buf[0]
|
||||||
|
switch se.Type {
|
||||||
|
case 1:
|
||||||
|
se.PID = binary.BigEndian.Uint16(buf[1:3])
|
||||||
|
case 2, 4:
|
||||||
|
se.SubPathID = buf[1]
|
||||||
|
se.SubClipID = buf[2]
|
||||||
|
se.PID = binary.BigEndian.Uint16(buf[3:5])
|
||||||
|
case 3:
|
||||||
|
se.SubPathID = buf[1]
|
||||||
|
se.PID = binary.BigEndian.Uint16(buf[2:4])
|
||||||
|
}
|
||||||
|
|
||||||
|
end, _ = reader.Seek(0, io.SeekCurrent)
|
||||||
|
if end != (start + int64(se.Len)) {
|
||||||
|
fmt.Fprintf(os.Stderr, "Stream Entry is not aligned. Stream Entry started at %d current position is %d position should be %d\n", start, end, start+int64(se.Len))
|
||||||
|
}
|
||||||
|
|
||||||
|
return reader.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse reads Stream data from an *errReader
|
||||||
|
func (sa *StreamAttributes) parse(reader *errReader) error {
|
||||||
|
var (
|
||||||
|
buf [10]byte
|
||||||
|
start int64
|
||||||
|
end int64
|
||||||
|
)
|
||||||
|
|
||||||
|
_, _ = reader.Read(buf[:1])
|
||||||
|
|
||||||
|
sa.Len = buf[0]
|
||||||
|
|
||||||
|
start, _ = reader.Seek(0, io.SeekCurrent)
|
||||||
|
|
||||||
|
_, _ = reader.Read(buf[:1])
|
||||||
|
|
||||||
|
sa.Encoding = buf[0]
|
||||||
|
|
||||||
|
switch sa.Encoding {
|
||||||
|
case VTMPEG1Video, VTMPEG2Video, VTVC1, VTH264:
|
||||||
|
_, _ = reader.Read(buf[:1])
|
||||||
|
|
||||||
|
sa.Format = buf[0] & 0xf0 >> 4
|
||||||
|
sa.Rate = buf[0] & 0x0F
|
||||||
|
_, _ = reader.Seek(3, io.SeekCurrent)
|
||||||
|
|
||||||
|
case ATMPEG1Audio, ATMPEG2Audio, ATLPCM, ATAC3, ATDTS, ATTRUEHD, ATAC3Plus, ATDTSHD, ATDTSHDMaster:
|
||||||
|
_, _ = reader.Read(buf[:4])
|
||||||
|
|
||||||
|
sa.Format = buf[0] & 0xf0 >> 4
|
||||||
|
sa.Rate = buf[0] & 0x0F
|
||||||
|
sa.Language = string(buf[1:4])
|
||||||
|
|
||||||
|
case PresentationGraphics, InteractiveGraphics:
|
||||||
|
_, _ = reader.Read(buf[:3])
|
||||||
|
|
||||||
|
sa.Language = string(buf[:3])
|
||||||
|
_, _ = reader.Seek(1, io.SeekCurrent)
|
||||||
|
|
||||||
|
case TextSubtitle:
|
||||||
|
_, _ = reader.Read(buf[:4])
|
||||||
|
|
||||||
|
sa.CharacterCode = buf[0]
|
||||||
|
sa.Language = string(buf[1:4])
|
||||||
|
default:
|
||||||
|
fmt.Fprintf(os.Stderr, "warning: unrecognized encoding: '%02X'\n", sa.Encoding)
|
||||||
|
}
|
||||||
|
|
||||||
|
end, _ = reader.Seek(0, io.SeekCurrent)
|
||||||
|
if end != (start + int64(sa.Len)) {
|
||||||
|
fmt.Fprintf(os.Stderr, "Stream Attributes is not aligned. Stream Attributes started at %d current position is %d position should be %d\n", start, end, start+int64(sa.Len))
|
||||||
|
}
|
||||||
|
|
||||||
|
return reader.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sp *SubPath) parse(reader *errReader) error {
|
||||||
|
var (
|
||||||
|
buf [10]byte
|
||||||
|
err error
|
||||||
|
start int64
|
||||||
|
end int64
|
||||||
|
)
|
||||||
|
|
||||||
|
sp.Len, _ = readInt32(reader, buf[:])
|
||||||
|
|
||||||
|
start, _ = reader.Seek(0, io.SeekCurrent)
|
||||||
|
|
||||||
|
_, _ = reader.Read(buf[:2])
|
||||||
|
sp.Type = buf[1]
|
||||||
|
sp.Flags, _ = readUInt16(reader, buf[:])
|
||||||
|
|
||||||
|
_, _ = reader.Read(buf[:2])
|
||||||
|
sp.PlayItemCount = buf[1]
|
||||||
|
|
||||||
|
for i := 0; i < int(sp.PlayItemCount); i++ {
|
||||||
|
var item SubPlayItem
|
||||||
|
err = item.parse(reader)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
sp.SubPlayItems = append(sp.SubPlayItems, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
end, _ = reader.Seek(0, io.SeekCurrent)
|
||||||
|
if end != (start + int64(sp.Len)) {
|
||||||
|
fmt.Fprintf(os.Stderr, "Subpath is not aligned. Subpath started at %d current position is %d position should be %d\n", start, end, start+int64(sp.Len))
|
||||||
|
}
|
||||||
|
|
||||||
|
return reader.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (spi *SubPlayItem) parse(reader *errReader) error {
|
||||||
|
var (
|
||||||
|
buf [10]byte
|
||||||
|
err error
|
||||||
|
start int64
|
||||||
|
end int64
|
||||||
|
)
|
||||||
|
|
||||||
|
spi.Len, _ = readUInt16(reader, buf[:])
|
||||||
|
|
||||||
|
start, _ = reader.Seek(0, io.SeekCurrent)
|
||||||
|
|
||||||
|
_ = spi.Clpi.parse(reader)
|
||||||
|
|
||||||
|
_, _ = reader.Read(buf[:4])
|
||||||
|
|
||||||
|
spi.Flags = buf[2]
|
||||||
|
spi.Clpi.STCID = buf[3]
|
||||||
|
|
||||||
|
spi.InTime, _ = readInt32(reader, buf[:])
|
||||||
|
spi.OutTime, _ = readInt32(reader, buf[:])
|
||||||
|
|
||||||
|
spi.PlayItemID, _ = readUInt16(reader, buf[:])
|
||||||
|
spi.StartOfPlayitem, _ = readUInt32(reader, buf[:])
|
||||||
|
|
||||||
|
if spi.Flags&1<<3 == 1 {
|
||||||
|
_, _ = reader.Read(buf[:2])
|
||||||
|
|
||||||
|
spi.AngleCount = buf[0]
|
||||||
|
spi.AngleFlags = buf[1]
|
||||||
|
|
||||||
|
for i := 0; i < int(spi.AngleCount); i++ {
|
||||||
|
var angle CLPI
|
||||||
|
_ = angle.parse(reader)
|
||||||
|
_, err = reader.Read(buf[:1])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
angle.STCID = buf[0]
|
||||||
|
spi.Angles = append(spi.Angles, angle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
end, _ = reader.Seek(0, io.SeekCurrent)
|
||||||
|
if end != (start + int64(spi.Len)) {
|
||||||
|
fmt.Fprintf(os.Stderr, "Subplayitem is not aligned. Subplayitem started at %d current position is %d position should be %d\n", start, end, start+int64(spi.Len))
|
||||||
|
}
|
||||||
|
|
||||||
|
return reader.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func readUInt16(reader io.Reader, buf []byte) (uint16, error) {
|
||||||
|
n, err := reader.Read(buf[:2])
|
||||||
|
if err != nil || n != 2 {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return binary.BigEndian.Uint16(buf[:2]), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readInt32(reader io.Reader, buf []byte) (int, error) {
|
||||||
|
n, err := readUInt32(reader, buf)
|
||||||
|
return int(n), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func readUInt32(reader io.Reader, buf []byte) (uint32, error) {
|
||||||
|
n, err := reader.Read(buf[:4])
|
||||||
|
if err != nil || n != 4 {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return binary.BigEndian.Uint32(buf[:4]), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readUInt64(reader io.Reader, buf []byte) (uint64, error) {
|
||||||
|
n, err := reader.Read(buf[:8])
|
||||||
|
if err != nil || n != 8 {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return binary.BigEndian.Uint64(buf[:8]), nil
|
||||||
|
}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user