Compare commits
No commits in common. "5ee60f97c298f7541278762ef4f21df92c8e019f" and "1bbe8260a5edd63306fb588c24c83d7e1f303c2f" have entirely different histories.
5ee60f97c2
...
1bbe8260a5
@ -1,29 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,58 +0,0 @@
|
|||||||
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
Normal file
406
main.go
Normal file
@ -0,0 +1,406 @@
|
|||||||
|
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
293
mpls.go
@ -1,293 +0,0 @@
|
|||||||
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
614
parse.go
@ -1,614 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
BIN
testFiles/timmy.narnian.us/00000.mpls
Normal file
BIN
testFiles/timmy.narnian.us/00000.mpls
Normal file
Binary file not shown.
BIN
testFiles/timmy.narnian.us/00002.mpls
Normal file
BIN
testFiles/timmy.narnian.us/00002.mpls
Normal file
Binary file not shown.
BIN
testFiles/timmy.narnian.us/00003.mpls
Normal file
BIN
testFiles/timmy.narnian.us/00003.mpls
Normal file
Binary file not shown.
BIN
testFiles/timmy.narnian.us/00005.mpls
Normal file
BIN
testFiles/timmy.narnian.us/00005.mpls
Normal file
Binary file not shown.
BIN
testFiles/timmy.narnian.us/00008.mpls
Normal file
BIN
testFiles/timmy.narnian.us/00008.mpls
Normal file
Binary file not shown.
BIN
testFiles/timmy.narnian.us/00020.mpls
Normal file
BIN
testFiles/timmy.narnian.us/00020.mpls
Normal file
Binary file not shown.
BIN
testFiles/timmy.narnian.us/00021.mpls
Normal file
BIN
testFiles/timmy.narnian.us/00021.mpls
Normal file
Binary file not shown.
BIN
testFiles/timmy.narnian.us/00050.mpls
Normal file
BIN
testFiles/timmy.narnian.us/00050.mpls
Normal file
Binary file not shown.
BIN
testFiles/timmy.narnian.us/00051.mpls
Normal file
BIN
testFiles/timmy.narnian.us/00051.mpls
Normal file
Binary file not shown.
BIN
testFiles/timmy.narnian.us/00100.mpls
Normal file
BIN
testFiles/timmy.narnian.us/00100.mpls
Normal file
Binary file not shown.
BIN
testFiles/timmy.narnian.us/00101.mpls
Normal file
BIN
testFiles/timmy.narnian.us/00101.mpls
Normal file
Binary file not shown.
BIN
testFiles/timmy.narnian.us/00110.mpls
Normal file
BIN
testFiles/timmy.narnian.us/00110.mpls
Normal file
Binary file not shown.
BIN
testFiles/timmy.narnian.us/00120.mpls
Normal file
BIN
testFiles/timmy.narnian.us/00120.mpls
Normal file
Binary file not shown.
BIN
testFiles/timmy.narnian.us/00201.mpls
Normal file
BIN
testFiles/timmy.narnian.us/00201.mpls
Normal file
Binary file not shown.
BIN
testFiles/timmy.narnian.us/00202.mpls
Normal file
BIN
testFiles/timmy.narnian.us/00202.mpls
Normal file
Binary file not shown.
BIN
testFiles/timmy.narnian.us/00203.mpls
Normal file
BIN
testFiles/timmy.narnian.us/00203.mpls
Normal file
Binary file not shown.
BIN
testFiles/timmy.narnian.us/00204.mpls
Normal file
BIN
testFiles/timmy.narnian.us/00204.mpls
Normal file
Binary file not shown.
BIN
testFiles/timmy.narnian.us/00205.mpls
Normal file
BIN
testFiles/timmy.narnian.us/00205.mpls
Normal file
Binary file not shown.
BIN
testFiles/timmy.narnian.us/00206.mpls
Normal file
BIN
testFiles/timmy.narnian.us/00206.mpls
Normal file
Binary file not shown.
BIN
testFiles/timmy.narnian.us/00207.mpls
Normal file
BIN
testFiles/timmy.narnian.us/00207.mpls
Normal file
Binary file not shown.
BIN
testFiles/timmy.narnian.us/00208.mpls
Normal file
BIN
testFiles/timmy.narnian.us/00208.mpls
Normal file
Binary file not shown.
BIN
testFiles/timmy.narnian.us/00209.mpls
Normal file
BIN
testFiles/timmy.narnian.us/00209.mpls
Normal file
Binary file not shown.
BIN
testFiles/timmy.narnian.us/00210.mpls
Normal file
BIN
testFiles/timmy.narnian.us/00210.mpls
Normal file
Binary file not shown.
BIN
testFiles/timmy.narnian.us/00211.mpls
Normal file
BIN
testFiles/timmy.narnian.us/00211.mpls
Normal file
Binary file not shown.
BIN
testFiles/timmy.narnian.us/00212.mpls
Normal file
BIN
testFiles/timmy.narnian.us/00212.mpls
Normal file
Binary file not shown.
BIN
testFiles/timmy.narnian.us/00213.mpls
Normal file
BIN
testFiles/timmy.narnian.us/00213.mpls
Normal file
Binary file not shown.
BIN
testFiles/timmy.narnian.us/00214.mpls
Normal file
BIN
testFiles/timmy.narnian.us/00214.mpls
Normal file
Binary file not shown.
BIN
testFiles/timmy.narnian.us/00215.mpls
Normal file
BIN
testFiles/timmy.narnian.us/00215.mpls
Normal file
Binary file not shown.
BIN
testFiles/timmy.narnian.us/00900.mpls
Normal file
BIN
testFiles/timmy.narnian.us/00900.mpls
Normal file
Binary file not shown.
BIN
testFiles/timmy.narnian.us/00901.mpls
Normal file
BIN
testFiles/timmy.narnian.us/00901.mpls
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user