Compare commits

..

10 Commits

Author SHA1 Message Date
lordwelch
5ee60f97c2 Add program to list SegmentMaps and Playlist durations 2019-05-23 09:28:35 -07:00
lordwelch
3d80d92e2a Add SegmentMap and Duration processing 2019-05-23 09:27:44 -07:00
lordwelch
3808678a59 cleanup
cleanup test files and separate into a main package and a library package in prep for writing mpls files
2019-05-17 10:59:10 -07:00
lordwelch
a80335b840 Separating code in prep for writing MPLS files 2019-05-17 10:57:44 -07:00
lordwelch
33bbc52f13 Finished initial parsing 2019-05-06 02:38:52 -07:00
lordwelch
888b173721 Fix linter errors 2019-04-21 18:56:10 -07:00
lordwelch
0a447d6791 Finished the PlayItem now onto the SubPath. Design of these structs my become simplified later. 2019-04-08 20:22:48 -07:00
lordwelch
8f3efef8d6 Re-factored error checking https://dave.cheney.net/2019/01/27/eliminate-error-handling-by-eliminating-errors
Split MPLS header into FileType and Version
fixed types on AppInfoPlaylist
Added STNTable to PlayItem and added STNTable parser
Unexported Parse functions on most structs
Combined extraneous reads
Added StreamEntry parser
Added StreamAttributes parser
2019-03-26 23:56:09 -07:00
lordwelch
f7141aae07 Create structs for the STN Table and primary streams. 2019-03-20 21:46:27 -07:00
lordwelch
cff8e957d4 Add and update structs 2019-03-19 23:44:48 -07:00
35 changed files with 994 additions and 406 deletions

29
cmd/mpls/main.go Normal file
View 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
View 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
View File

@ -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
View 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
View 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
}