├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── ebml ├── decoder.go ├── encoder.go └── type.go └── matroska ├── matroska.go ├── matroska_test.go └── stream.go /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | .idea/ 4 | bin/ 5 | pkg/ 6 | 7 | *.coverprofile 8 | *.coverage 9 | 10 | matroska/testdata/ 11 | matroska/testdata.zip 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | sudo: false 3 | go: 4 | - 1.8 5 | before_install: 6 | - go get github.com/mattn/goveralls 7 | - go get golang.org/x/tools/cmd/cover 8 | - go get github.com/golang/lint/golint 9 | script: 10 | - go vet ./... 11 | - go test -v -coverprofile=matroska.coverprofile ./matroska 12 | - 'echo "mode: set" > .coverage && grep -h -v "mode: set" *.coverprofile >> .coverage' 13 | - $HOME/gopath/bin/goveralls -coverprofile=.coverage -service=travis-ci 14 | - $HOME/gopath/bin/golint ./... 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Vasily Vasilyev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Golang implementation of Matroska and WebM media container formats 2 | 3 | [![Build Status](https://travis-ci.org/pixelbender/go-matroska.svg)](https://travis-ci.org/pixelbender/go-matroska) 4 | [![Coverage Status](https://coveralls.io/repos/github/pixelbender/go-matroska/badge.svg?branch=master)](https://coveralls.io/github/pixelbender/go-matroska?branch=master) 5 | [![Go Report Card](https://goreportcard.com/badge/github.com/pixelbender/go-matroska)](https://goreportcard.com/report/github.com/pixelbender/go-matroska) 6 | [![GoDoc](https://godoc.org/github.com/pixelbender/go-matroska?status.svg)](https://godoc.org/github.com/pixelbender/go-matroska) 7 | 8 | ## Installation 9 | 10 | ```sh 11 | go get github.com/pixelbender/go-matroska/... 12 | ``` 13 | 14 | ## Usage 15 | 16 | ```go 17 | import ( 18 | "os" 19 | "fmt" 20 | "github.com/pixelbender/go-matroska/matroska" 21 | ) 22 | 23 | func main() { 24 | doc, err := matroska.Decode("example.mkv") 25 | if err != nil { 26 | fmt.Println(err) 27 | return 28 | } 29 | fmt.Println(doc.Segment.Info[0].Duration) 30 | } 31 | ``` 32 | 33 | ## Specifications 34 | 35 | - [Matroska Media Container - Specifications](https://matroska.org/technical/specs/index.html) 36 | - [WebM Container - Guidelines](https://www.webmproject.org/docs/container/) 37 | -------------------------------------------------------------------------------- /ebml/decoder.go: -------------------------------------------------------------------------------- 1 | package ebml 2 | 3 | import ( 4 | "encoding/binary" 5 | "errors" 6 | "io" 7 | "math" 8 | "reflect" 9 | "time" 10 | ) 11 | 12 | const ( 13 | bufferSize = 64000 14 | maxBufferSize = 64000 15 | ) 16 | 17 | type DecodeOptions struct { 18 | SkipDamaged bool 19 | DecodeUnknown func(id uint32, elem *Reader) error 20 | } 21 | 22 | func NewReader(r io.Reader, opt *DecodeOptions) *Reader { 23 | seek, _ := r.(io.ReadSeeker) 24 | return &Reader{ 25 | dec: &decoderState{ 26 | opt: opt, 27 | buf: make([]byte, bufferSize), 28 | seek: seek, 29 | src: r, 30 | }, 31 | len: -1, 32 | } 33 | } 34 | 35 | func NewReaderBytes(b []byte, opt *DecodeOptions) *Reader { 36 | return &Reader{ 37 | dec: &decoderState{ 38 | opt: opt, 39 | buf: b, 40 | }, 41 | len: int64(len(b)), 42 | } 43 | } 44 | 45 | type Reader struct { 46 | dec *decoderState 47 | sub *Reader 48 | len int64 49 | } 50 | 51 | // Read reads the EBML-encoded element bytes into b. 52 | func (r *Reader) Read(b []byte) (int, error) { 53 | if err := r.skip(); err != nil { 54 | return 0, err 55 | } 56 | if r.len < 0 { 57 | return r.dec.Read(b) 58 | } 59 | if r.len < int64(len(b)) { 60 | b = b[:int(r.len)] 61 | } 62 | n, err := r.dec.Read(b) 63 | r.len -= int64(n) 64 | return n, err 65 | } 66 | 67 | // Decode reads the next EBML-encoded value from its input and stores it in the value pointed to by v. 68 | func (r *Reader) Decode(v interface{}) error { 69 | if u, ok := v.(Unmarshaler); ok { 70 | return u.UnmarshalEBML(r) 71 | } 72 | if v == nil { 73 | return errors.New("ebml: decode nil") 74 | } 75 | p := reflect.ValueOf(v) 76 | if p.Kind() != reflect.Ptr { 77 | return errors.New("ebml: decode not a pointer") 78 | } 79 | return unmarshal(r, p, r.dec.opt) 80 | } 81 | 82 | // ReadElement reads the next EMBL-encoded element ID and size 83 | func (r *Reader) ReadElement() (id uint32, elem *Reader, err error) { 84 | id, err = r.readID() 85 | if err != nil { 86 | return 87 | } 88 | elem, err = r.readElement() 89 | r.sub = elem 90 | return 91 | } 92 | 93 | // ReadString reads and returns a UTF-8 encoded EBML string value. 94 | func (r *Reader) ReadString() (string, error) { 95 | if r.len < 0 { 96 | return "", errFormat("string") 97 | } 98 | if r.len > maxBufferSize { 99 | return "", errors.New("ebml: string length too large") 100 | } 101 | b, err := r.next(int(r.len)) 102 | if err != nil { 103 | return "", err 104 | } 105 | i := len(b) 106 | for i > 0 && b[i-1] == 0 { 107 | i-- 108 | } 109 | return string(b[:i]), nil 110 | } 111 | 112 | // Len returns remaining bytes length of the Element. 113 | // Returns -1 if length is not known. 114 | func (r *Reader) Len() int64 { 115 | return r.len 116 | } 117 | 118 | // ReadFloat reads and returns a EBML int value. 119 | func (r *Reader) ReadInt() (int64, error) { 120 | if r.len < 0 || r.len > 8 { 121 | return 0, errFormat("int") 122 | } 123 | b, err := r.next(int(r.len)) 124 | if err != nil { 125 | return 0, err 126 | } 127 | v := int64(0) 128 | for _, it := range b { 129 | v = (v << 8) | int64(it) 130 | } 131 | return v, nil 132 | } 133 | 134 | // ReadFloat reads and returns a EBML boolean value. 135 | func (r *Reader) ReadBool() (bool, error) { 136 | v, err := r.ReadInt() 137 | return v != 0, err 138 | } 139 | 140 | // ReadFloat reads and returns a EBML float value. 141 | func (r *Reader) ReadFloat() (float64, error) { 142 | switch r.len { 143 | case 4: 144 | b, err := r.next(4) 145 | if err != nil { 146 | return 0, err 147 | } 148 | return float64(math.Float32frombits(binary.BigEndian.Uint32(b))), nil 149 | case 8: 150 | b, err := r.next(8) 151 | if err != nil { 152 | return 0, err 153 | } 154 | return math.Float64frombits(binary.BigEndian.Uint64(b)), nil 155 | default: 156 | return 0, errFormat("float") 157 | } 158 | } 159 | 160 | // ReadTime reads and returns a EBML time value. 161 | func (r *Reader) ReadTime() (time.Time, error) { 162 | v, err := r.ReadInt() 163 | return timeAbs.Add(time.Duration(v) * time.Nanosecond), err 164 | } 165 | 166 | func (r *Reader) Next(n int) ([]byte, error) { 167 | return r.next(n) 168 | } 169 | 170 | func (r *Reader) next(n int) ([]byte, error) { 171 | if err := r.skip(); err != nil { 172 | return nil, err 173 | } 174 | if r.len < 0 { 175 | return r.dec.Next(n) 176 | } 177 | if r.len == 0 { 178 | return nil, io.EOF 179 | } 180 | if n < 0 || r.len < int64(n) { 181 | return nil, errFormat("length") 182 | } 183 | b, err := r.dec.Next(n) 184 | r.len -= int64(n) 185 | return b, err 186 | } 187 | 188 | func (r *Reader) skip() error { 189 | if r.sub == nil { 190 | return nil 191 | } 192 | s, v := r.sub, int64(0) 193 | for s != nil { 194 | if s.len > 0 { 195 | v += s.len 196 | } 197 | s = s.sub 198 | } 199 | r.sub = nil 200 | return r.dec.Skip(v) 201 | } 202 | 203 | func (r *Reader) readID() (uint32, error) { 204 | b, err := r.next(1) 205 | for err == nil && b[0] < 0x10 { 206 | // Skip incomplete elements 207 | b, err = r.next(1) 208 | } 209 | if err != nil { 210 | return 0, err 211 | } 212 | n, v, bit := 0, b[0], uint8(0x80) 213 | for bit > 0xf && v&bit == 0 { 214 | n++ 215 | bit >>= 1 216 | } 217 | if n > 3 { 218 | return 0, errFormat("id") 219 | } 220 | id := uint32(v) 221 | if n > 0 { 222 | if b, err = r.next(n); err != nil { 223 | return 0, err 224 | } 225 | for _, v := range b { 226 | id = (id << 8) | uint32(v) 227 | } 228 | } 229 | return id, nil 230 | } 231 | 232 | func (r *Reader) ReadVInt() (int64, error) { 233 | b, err := r.next(1) 234 | if err != nil { 235 | return 0, err 236 | } 237 | n, v, bit := 0, b[0], uint8(0x80) 238 | for bit > 0 && v&bit == 0 { 239 | n++ 240 | bit >>= 1 241 | } 242 | bit-- 243 | i := int64(v & bit) 244 | if n > 0 { 245 | if b, err = r.next(n); err != nil { 246 | return 0, err 247 | } 248 | for _, v := range b { 249 | i = (i << 8) | int64(v) 250 | } 251 | } 252 | return i, nil 253 | } 254 | 255 | func (r *Reader) readElement() (*Reader, error) { 256 | b, err := r.next(1) 257 | if err != nil { 258 | return nil, err 259 | } 260 | n, v, bit := 0, b[0], uint8(0x80) 261 | for bit > 0 && v&bit == 0 { 262 | n++ 263 | bit >>= 1 264 | } 265 | if n > 7 { 266 | return nil, errFormat("size") 267 | } 268 | bit-- 269 | size := int64(v & bit) 270 | mask := v | ^bit 271 | if n > 0 { 272 | if b, err = r.next(n); err != nil { 273 | return nil, err 274 | } 275 | for _, v := range b { 276 | mask &= v 277 | size = (size << 8) | int64(v) 278 | } 279 | } 280 | if mask == 0xff { 281 | // Unknown element size 282 | return &Reader{r.dec, nil, -1}, nil 283 | } 284 | if r.len >= 0 { 285 | if r.len < size { 286 | return nil, io.ErrUnexpectedEOF 287 | } 288 | r.len -= size 289 | } 290 | return &Reader{r.dec, nil, size}, nil 291 | } 292 | 293 | type decoderState struct { 294 | opt *DecodeOptions 295 | seek io.ReadSeeker 296 | src io.Reader 297 | buf []byte 298 | r, w int 299 | off int64 300 | } 301 | 302 | func (s *decoderState) Offset() int64 { 303 | return s.off 304 | } 305 | 306 | func (s *decoderState) Next(n int) ([]byte, error) { 307 | if len(s.buf) < n { 308 | return nil, errors.New("ebml: buffer too small") 309 | } 310 | if p := n - s.w + s.r; p > 0 { 311 | if err := s.fill(p); err != nil { 312 | return nil, err 313 | } 314 | } 315 | b := s.buf[s.r : s.r+n] 316 | s.r += n 317 | s.off += int64(n) 318 | return b, nil 319 | } 320 | 321 | func (s *decoderState) Read(b []byte) (int, error) { 322 | if s.w-s.r >= len(b) { 323 | s.r += copy(b, s.buf[s.r:]) 324 | s.off += int64(len(b)) 325 | return len(b), nil 326 | } 327 | p := 0 328 | if s.w > s.r { 329 | p = copy(b, s.buf[s.r:s.w]) 330 | s.r, s.w = 0, 0 331 | b = b[p:] 332 | } 333 | if len(b) > len(s.buf) { 334 | n, err := io.ReadFull(s.src, b) 335 | s.off += int64(p + n) 336 | return p + n, err 337 | } 338 | if err := s.fill(len(b)); err != nil { 339 | // TODO: partial read.... 340 | s.off += int64(p) 341 | return p, err 342 | } 343 | s.r += copy(b, s.buf[s.r:]) 344 | s.off += int64(p + len(b)) 345 | return p + len(b), nil 346 | } 347 | 348 | func (s *decoderState) Skip(n int64) error { 349 | d := int64(s.w - s.r) 350 | if d >= n { 351 | s.r += int(n) 352 | s.off += n 353 | return nil 354 | } 355 | if d > 0 { 356 | n -= d 357 | s.off += d 358 | s.r, s.w = 0, 0 359 | } 360 | if s.seek != nil { 361 | _, err := s.seek.Seek(n, io.SeekCurrent) 362 | if err != nil { 363 | return err 364 | } 365 | s.off += n 366 | return nil 367 | } 368 | for n > 0 { 369 | p := len(s.buf) 370 | if n < int64(p) { 371 | p = int(n) 372 | } 373 | m, err := io.ReadAtLeast(s.src, s.buf, p) 374 | if err != nil { 375 | return err 376 | } 377 | n -= int64(p) 378 | s.off += int64(p) 379 | s.r, s.w = p, m 380 | } 381 | return nil 382 | } 383 | 384 | func (s *decoderState) fill(n int) error { 385 | if s.r > 0 { 386 | s.w = copy(s.buf, s.buf[s.r:s.w]) 387 | s.r = 0 388 | } 389 | p, err := io.ReadAtLeast(s.src, s.buf[s.w:], n) 390 | if err != nil { 391 | return err 392 | } 393 | s.w += p 394 | return nil 395 | } 396 | 397 | type errFormat string 398 | 399 | func (e errFormat) Error() string { 400 | return "ebml: " + string(e) + " format error" 401 | } 402 | -------------------------------------------------------------------------------- /ebml/encoder.go: -------------------------------------------------------------------------------- 1 | package ebml 2 | 3 | import ( 4 | // "errors" 5 | // "io" 6 | // "reflect" 7 | ) 8 | 9 | /* 10 | type EncodeOptions struct { 11 | } 12 | 13 | type Encoder struct { 14 | w *Writer 15 | } 16 | 17 | func NewEncoder(w io.Writer, opt *EncodeOptions) *Encoder { 18 | seek,_ := w.(io.WriteSeeker) 19 | dec.w = &Writer{ 20 | dec: &encoderState{ 21 | buf: make([]byte, bufferSize), 22 | seek: seek, 23 | src: r, 24 | opt: opt, 25 | }, 26 | len: -1, 27 | } 28 | return dec 29 | } 30 | 31 | // Encode writes the EBML encoding of v to the stream. 32 | func (enc *Encoder) Flush() (err error) { 33 | enc.pack() 34 | enc.render() 35 | 36 | enc.flush(w) 37 | // TODO: flush 38 | enc.elem = nil 39 | return nil 40 | } 41 | 42 | type Writer struct { 43 | *encoderState 44 | } 45 | 46 | type encoderState struct { 47 | } 48 | 49 | // An Encoder writes EBML elements to an output stream. 50 | type Encoder struct { 51 | w io.Writer 52 | 53 | id int64 54 | size int 55 | buf []byte 56 | elem []*Encoder 57 | } 58 | 59 | // NewEncoder returns a new encoder that writes to w. 60 | func NewEncoder(w io.Writer) *Encoder { 61 | enc := new(Encoder) 62 | enc.w = w 63 | return enc 64 | } 65 | 66 | // Encode writes the EBML encoding of v to the stream. 67 | func (enc *Encoder) Encode(v interface{}) (err error) { 68 | if u, ok := v.(Marshaler); ok { 69 | return u.MarshalEBML(enc) 70 | } 71 | if v == nil { 72 | return errors.New("ebml: Encode nil") 73 | } 74 | ref := reflect.ValueOf(v) 75 | if ref = ref.Elem(); ref.Kind() != reflect.Struct { 76 | return errors.New("ebml: Decode not a struct") 77 | } 78 | codec := &typeCodec{ref} 79 | return codec.MarshalEBML(enc) 80 | } 81 | 82 | 83 | 84 | func (enc *Encoder) pack() int64 { 85 | for _, it := range enc.elem { 86 | enc.size += it.pack() 87 | } 88 | return enc.size 89 | } 90 | 91 | func (enc *Encoder) render() { 92 | if enc.id != 0 { 93 | 94 | } 95 | for _, it := range enc.elem { 96 | it.render() 97 | } 98 | } 99 | 100 | // NewElement writes the EBML element of v to the stream, adding outermost element header. 101 | func (enc *Encoder) NewElement(id int64) *Encoder { 102 | e := &Encoder{id: id} 103 | enc.elem = append(enc.elem, e) 104 | return e 105 | } 106 | 107 | func (enc *Encoder) WriteInt(id int64, v int64) { 108 | // TODO: optimized version of byte write 109 | var b []byte 110 | pos := 0 111 | for i, off := range intOffset { 112 | it := byte(v >> off) 113 | if it != 0 { 114 | if b == nil { 115 | b = enc.next(8 - i) 116 | } 117 | b[pos] = it 118 | pos++ 119 | } 120 | } 121 | } 122 | 123 | func (enc *Encoder) WriteData(id int64, v []byte) { 124 | 125 | } 126 | 127 | func (enc *Encoder) WriteString(id int64, v string) { 128 | 129 | } 130 | 131 | func (enc *Encoder) writeVint(id int64, v int64) { 132 | 133 | } 134 | 135 | func (enc *Encoder) next(n int) (b []byte) { 136 | off := enc.size + n 137 | if m := len(enc.buf); m < off { 138 | if m = off; m < 64 { 139 | m = 64 140 | } else { 141 | m <<= 1 142 | } 143 | b = make([]byte, m) 144 | copy(b, enc.buf[:enc.size]) 145 | enc.buf = b 146 | } 147 | b, enc.size = enc.buf[enc.size:off], off 148 | return 149 | } 150 | 151 | func (dec *Decoder) readVsint(off int) (v int64, n int, err error) { 152 | m, err := dec.buf.ReadByte() 153 | if err != nil { 154 | return 155 | } 156 | dec.len-- 157 | var bit byte 158 | for n, bit = range mask { 159 | if m&bit != 0 { 160 | v = int64(m & rest[n+off]) 161 | break 162 | } 163 | } 164 | if n > 0 { 165 | if dec.len < int64(n) && 0 < dec.size { 166 | err = io.EOF 167 | return 168 | } 169 | var b []byte 170 | if b, err = dec.buf.Peek(n); err != nil { 171 | return 172 | } 173 | for _, it := range b { 174 | v = (v << 8) | int64(it) 175 | } 176 | if _, err = dec.buf.Discard(n); err != nil { 177 | return 178 | } 179 | dec.len -= int64(n) 180 | } 181 | return 182 | } 183 | 184 | */ 185 | -------------------------------------------------------------------------------- /ebml/type.go: -------------------------------------------------------------------------------- 1 | package ebml 2 | 3 | import ( 4 | "errors" 5 | "io" 6 | "reflect" 7 | "strconv" 8 | "strings" 9 | "sync" 10 | "time" 11 | ) 12 | 13 | // Unmarshaler is the interface implemented by objects that can unmarshal 14 | // a EBML description of themselves. UnmarshalEBML must copy the EBML data 15 | // if it wishes to retain the data after returning. 16 | type Unmarshaler interface { 17 | UnmarshalEBML(r *Reader) error 18 | } 19 | 20 | // Marshaler is the interface implemented by objects that can marshal themselves into valid EBML. 21 | // type Marshaler interface { 22 | // MarshalEBML(enc *Encoder) error 23 | // } 24 | 25 | // func marshal(w *Writer, v reflect.Value, opt *EncodeOptions) error { 26 | // } 27 | 28 | func unmarshal(r *Reader, v reflect.Value, opt *DecodeOptions) error { 29 | switch v.Kind() { 30 | case reflect.Struct: 31 | t := v.Type() 32 | switch t { 33 | case timeType: 34 | t, err := r.ReadTime() 35 | if err != nil { 36 | return err 37 | } 38 | v.Set(reflect.ValueOf(t)) 39 | default: 40 | s, err := getStructMapping(t) 41 | if err != nil { 42 | return err 43 | } 44 | if err = s.setDefaults(v); err != nil { 45 | return err 46 | } 47 | if u, ok := v.Interface().(Unmarshaler); ok { 48 | return u.UnmarshalEBML(r) 49 | } 50 | return s.unmarshal(r, v, opt) 51 | } 52 | case reflect.Ptr: 53 | e := v.Type().Elem() 54 | if v.IsNil() { 55 | v.Set(reflect.New(e)) 56 | } 57 | if u, ok := v.Interface().(Unmarshaler); ok { 58 | return u.UnmarshalEBML(r) 59 | } 60 | return unmarshal(r, v.Elem(), opt) 61 | case reflect.Slice: 62 | e := v.Type().Elem() 63 | switch e.Kind() { 64 | case reflect.Uint8: 65 | b, err := r.next(int(r.len)) // TODO: limit 66 | if err != nil { 67 | return err 68 | } 69 | v.SetBytes(b) 70 | default: 71 | n := v.Len() 72 | v.Set(reflect.Append(v, reflect.Zero(e))) 73 | if err := unmarshal(r, v.Index(n), opt); err != nil { 74 | v.SetLen(n) 75 | return err 76 | } 77 | return nil 78 | } 79 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 80 | i, err := r.ReadInt() 81 | if err != nil { 82 | return err 83 | } 84 | v.SetInt(i) 85 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 86 | i, err := r.ReadInt() 87 | if err != nil { 88 | return err 89 | } 90 | v.SetUint(uint64(i)) 91 | case reflect.Bool: 92 | b, err := r.ReadBool() 93 | if err != nil { 94 | return err 95 | } 96 | v.SetBool(b) 97 | case reflect.Float32, reflect.Float64: 98 | f, err := r.ReadFloat() 99 | if err != nil { 100 | return err 101 | } 102 | v.SetFloat(f) 103 | case reflect.String: 104 | s, err := r.ReadString() 105 | if err != nil { 106 | return err 107 | } 108 | v.SetString(s) 109 | case reflect.Interface: 110 | if !v.IsNil() { 111 | return unmarshal(r, v.Elem(), opt) 112 | } 113 | default: 114 | return &errUnmarshal{v.Type()} 115 | } 116 | return nil 117 | } 118 | 119 | type errUnmarshal struct { 120 | t reflect.Type 121 | } 122 | 123 | func (e *errUnmarshal) Error() string { 124 | return "ebml: can not unmarshal " + e.t.String() 125 | } 126 | 127 | type structMapping struct { 128 | fields []*field 129 | ids map[uint32]*field 130 | } 131 | 132 | func newStructMapping(t reflect.Type) (*structMapping, error) { 133 | if t.Kind() != reflect.Struct { 134 | return nil, errors.New("ebml: Decode not a struct") 135 | } 136 | n := t.NumField() 137 | fields := make([]*field, 0, n) 138 | ids := make(map[uint32]*field) 139 | 140 | for i := 0; i < n; i++ { 141 | p := t.Field(i) 142 | if p.PkgPath != "" && !p.Anonymous { // TODO: implement 143 | continue 144 | } 145 | tag := p.Tag.Get("ebml") 146 | if tag == "" || tag == "-" { 147 | continue 148 | } 149 | f, err := newField(p, i, tag) 150 | if err != nil { 151 | return nil, &errStructMapping{t, p.Name, err} 152 | } 153 | fields = append(fields, f) 154 | ids[f.id] = f 155 | } 156 | return &structMapping{fields, ids}, nil 157 | } 158 | 159 | type errStructMapping struct { 160 | t reflect.Type 161 | name string 162 | err error 163 | } 164 | 165 | func (e *errStructMapping) Error() string { 166 | return "ebml: " + e.name + " of " + e.t.String() + " mapping: " + e.err.Error() 167 | } 168 | 169 | func (m *structMapping) setDefaults(v reflect.Value) error { 170 | for _, it := range m.fields { 171 | if it.def != nil { 172 | v.Field(it.index).Set(*it.def) 173 | } 174 | } 175 | return nil 176 | } 177 | 178 | func (m *structMapping) unmarshal(r *Reader, v reflect.Value, opt *DecodeOptions) error { 179 | for { 180 | id, elem, err := r.ReadElement() 181 | if err != nil { 182 | if err == io.EOF { 183 | break 184 | } 185 | return err 186 | } 187 | if f, ok := m.ids[id]; ok { 188 | err = f.unmarshal(elem, v.Field(f.index), opt) 189 | } else if opt.DecodeUnknown != nil { 190 | err = opt.DecodeUnknown(id, elem) 191 | } 192 | if err != nil { 193 | if opt.SkipDamaged { 194 | continue 195 | } 196 | return err 197 | } 198 | } 199 | return nil 200 | } 201 | 202 | type field struct { 203 | id uint32 204 | seq []uint32 205 | index int 206 | name string 207 | def *reflect.Value 208 | omitempty bool 209 | } 210 | 211 | func newField(t reflect.StructField, index int, tag string) (*field, error) { 212 | v := strings.Split(tag, ",") 213 | seq := strings.Split(v[0], ">") 214 | // TODO: omitempty 215 | 216 | f := &field{ 217 | index: index, 218 | name: t.Name, 219 | } 220 | for i, s := range seq { 221 | id, err := strconv.ParseUint(s, 16, 32) 222 | if err != nil { 223 | return nil, err 224 | } 225 | if i == 0 { 226 | f.id = uint32(id) 227 | } else { 228 | f.seq = append(f.seq, uint32(id)) 229 | } 230 | } 231 | for _, it := range v[1:] { 232 | switch it { 233 | case "omitempty": 234 | f.omitempty = true 235 | default: 236 | def, err := newDefault(t.Type, it) 237 | if err != nil { 238 | return nil, err 239 | } 240 | v := reflect.ValueOf(def).Convert(t.Type) 241 | f.def = &v 242 | } 243 | } 244 | 245 | return f, nil 246 | } 247 | 248 | func newDefault(t reflect.Type, v string) (interface{}, error) { 249 | switch t.Kind() { 250 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 251 | return strconv.ParseInt(v, 10, int(t.Size())*8) 252 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 253 | return strconv.ParseUint(v, 10, int(t.Size())*8) 254 | case reflect.Bool: 255 | return strings.ToLower(v) != "false", nil 256 | case reflect.Float32, reflect.Float64: 257 | return strconv.ParseFloat(v, int(t.Size())*8) 258 | case reflect.String: 259 | return v, nil 260 | default: 261 | return nil, errors.New("default value is not supported") 262 | } 263 | } 264 | 265 | func (f *field) unmarshal(r *Reader, v reflect.Value, opt *DecodeOptions) error { 266 | if len(f.seq) > 0 { 267 | return f.unmarshalSeq(r, v, opt, f.seq) 268 | } 269 | return unmarshal(r, v, opt) 270 | } 271 | 272 | func (f *field) unmarshalSeq(r *Reader, v reflect.Value, opt *DecodeOptions, seq []uint32) error { 273 | for { 274 | id, elem, err := r.ReadElement() 275 | if err != nil { 276 | if err == io.EOF { 277 | break 278 | } 279 | return err 280 | } 281 | if id == seq[0] { 282 | if len(seq) > 1 { 283 | err = f.unmarshalSeq(elem, v, opt, seq[1:]) 284 | } else { 285 | err = unmarshal(elem, v, opt) 286 | } 287 | } else if opt.DecodeUnknown != nil { 288 | err = opt.DecodeUnknown(id, elem) 289 | } 290 | if err != nil { 291 | if opt.SkipDamaged { 292 | continue 293 | } 294 | return err 295 | } 296 | } 297 | return nil 298 | } 299 | 300 | var timeType = reflect.TypeOf(time.Time{}) 301 | var timeAbs = time.Date(2001, time.January, 1, 0, 0, 0, 0, time.UTC) 302 | 303 | var ( 304 | mappingCacheLock sync.RWMutex 305 | mappingCache = make(map[reflect.Type]*structMapping) 306 | ) 307 | 308 | func getStructMapping(t reflect.Type) (*structMapping, error) { 309 | m := &mappingCacheLock 310 | m.RLock() 311 | s, ok := mappingCache[t] 312 | if m.RUnlock(); ok { 313 | return s, nil 314 | } 315 | m.Lock() 316 | defer m.Unlock() 317 | if s, ok = mappingCache[t]; ok { 318 | return s, nil 319 | } 320 | s, err := newStructMapping(t) 321 | if err != nil { 322 | return nil, err 323 | } 324 | mappingCache[t] = s 325 | return s, nil 326 | } 327 | -------------------------------------------------------------------------------- /matroska/matroska.go: -------------------------------------------------------------------------------- 1 | package matroska 2 | 3 | import ( 4 | "github.com/pixelbender/go-matroska/ebml" 5 | "log" 6 | "os" 7 | "time" 8 | ) 9 | 10 | func Decode(file string) (*File, error) { 11 | r, err := os.Open(file) 12 | if err != nil { 13 | return nil, err 14 | } 15 | dec := ebml.NewReader(r, &ebml.DecodeOptions{ 16 | SkipDamaged: true, 17 | }) 18 | v := new(File) 19 | if err = dec.Decode(&v); err != nil { 20 | return nil, err 21 | } 22 | return v, nil 23 | } 24 | 25 | // File represents a Matroska encoded file. 26 | // See the specification https://matroska.org/technical/specs/index.html 27 | type File struct { 28 | EBML *EBML `ebml:"1A45DFA3"` 29 | Segment *Segment `ebml:"18538067"` 30 | } 31 | 32 | func NewFile(doctype string) *File { 33 | return &File{ 34 | EBML: &EBML{1, 1, 4, 8, doctype, 1, 1}, 35 | Segment: &Segment{}, 36 | } 37 | } 38 | 39 | // The EBML is a top level element contains a description of the file type. 40 | type EBML struct { 41 | Version int `ebml:"4286,1"` 42 | ReadVersion int `ebml:"42F7,1"` 43 | MaxIDLength int `ebml:"42F2,4"` 44 | MaxSizeLength int `ebml:"42F3,8"` 45 | DocType string `ebml:"4282,matroska"` 46 | DocTypeVersion int `ebml:"4287,1"` 47 | DocTypeReadVersion int `ebml:"4285,1"` 48 | } 49 | 50 | // ID is a binary EBML element identifier. 51 | type ID uint32 52 | 53 | // SegmentID is a randomly generated unique 128bit identifier of Segment/SegmentFamily. 54 | type SegmentID []byte 55 | 56 | // EditionID is a unique identifier of Edition. 57 | type EditionID []byte 58 | 59 | type Position int64 60 | type Time int64 61 | type Duration int64 62 | 63 | // Segment is the Root Element that contains all other Top-Level Elements. 64 | type Segment struct { 65 | SeekHead []*SeekHead `ebml:"114D9B74,omitempty" json:",omitempty"` 66 | Info []*Info `ebml:"1549A966" json:",omitempty"` 67 | Cluster []*Cluster `ebml:"1F43B675,omitempty" json:",omitempty"` 68 | Tracks []*Track `ebml:"1654AE6B,omitempty" json:",omitempty"` 69 | Cues []*CuePoint `ebml:"1C53BB6B>BB,omitempty" json:",omitempty"` 70 | Attachments []*Attachment `ebml:"1941A469>61A7"` 71 | Chapters []*Edition `ebml:"1043A770>45B9"` 72 | Tags []*Tag `ebml:"1254C367>7373"` 73 | } 74 | 75 | // SeekHead contains the position of other Top-Level Elements. 76 | type SeekHead struct { 77 | Seeks []*Seek `ebml:"4DBB"` 78 | } 79 | 80 | // Seek contains a single seek entry to an EBML Element. 81 | type Seek struct { 82 | ID ID `ebml:"53AB"` 83 | Position Position `ebml:"53AC"` 84 | } 85 | 86 | // Info contains miscellaneous general information and statistics on the file. 87 | type Info struct { 88 | ID SegmentID `ebml:"73A4,omitempty" json:",omitempty"` 89 | Filename string `ebml:"7384,omitempty" json:",omitempty"` 90 | PrevID SegmentID `ebml:"3CB923,omitempty" json:",omitempty"` 91 | PrevFilename string `ebml:"3C83AB,omitempty" json:",omitempty"` 92 | NextID SegmentID `ebml:"3EB923,omitempty" json:",omitempty"` 93 | NextFilename string `ebml:"3E83BB,omitempty" json:",omitempty"` 94 | SegmentFamily SegmentID `ebml:"4444,omitempty" json:",omitempty"` 95 | ChapterTranslate []*ChapterTranslate `ebml:"6924,omitempty" json:",omitempty"` 96 | TimecodeScale time.Duration `ebml:"2AD7B1,1000000"` 97 | Duration float64 `ebml:"4489,omitempty" json:",omitempty"` 98 | Date time.Time `ebml:"4461,omitempty" json:",omitempty"` 99 | Title string `ebml:"7BA9,omitempty" json:",omitempty"` 100 | MuxingApp string `ebml:"4D80"` 101 | WritingApp string `ebml:"5741"` 102 | } 103 | 104 | // ChapterTranslate contains tuple of corresponding ID used by chapter codecs to represent a Segment. 105 | type ChapterTranslate struct { 106 | EditionIDs []EditionID `ebml:"69FC,omitempty" json:",omitempty"` 107 | Codec ChapterCodec `ebml:"69BF"` 108 | ID TranslateID `ebml:"69A5"` 109 | } 110 | 111 | type TranslateID []byte 112 | type ChapterCodec uint8 113 | 114 | const ( 115 | ChapterCodecMatroska ChapterCodec = iota 116 | ChapterCodecDVD 117 | ) 118 | 119 | // Cluster is a Top-Level Element containing the Block structure. 120 | type Cluster struct { 121 | Timecode Time `ebml:"E7"` 122 | SilentTracks []TrackNumber `ebml:"5854>58D7,omitempty" json:",omitempty"` 123 | Position Position `ebml:"A7,omitempty" json:",omitempty"` 124 | PrevSize int64 `ebml:"AB,omitempty" json:",omitempty"` 125 | SimpleBlock []*Block `ebml:"A3,omitempty" json:",omitempty"` 126 | BlockGroup []*BlockGroup `ebml:"A0,omitempty" json:",omitempty"` 127 | } 128 | 129 | type ClusterID uint64 130 | 131 | // Block contains the actual data to be rendered and a timestamp. 132 | type Block struct { 133 | TrackNumber TrackNumber 134 | Timecode int16 135 | Flags uint8 136 | Frames int 137 | //Data []byte 138 | } 139 | 140 | const ( 141 | LacingNone uint8 = iota 142 | LacingXiph 143 | LacingFixedSize 144 | LacingEBML 145 | ) 146 | 147 | func (b *Block) UnmarshalEBML(r *ebml.Reader) error { 148 | //log.Printf("\t Block %#v", r.Len()) 149 | v, err := r.ReadVInt() 150 | if err != nil { 151 | log.Println(err) 152 | return err 153 | } 154 | b.TrackNumber = TrackNumber(v) 155 | p, err := r.Next(3) 156 | if err != nil { 157 | log.Println(err) 158 | return err 159 | } 160 | b.Timecode = int16(p[0])<<8 | int16(p[1]) 161 | b.Flags = p[2] 162 | if (b.Flags>>1)&3 == 0 { 163 | return nil 164 | } 165 | log.Printf("\t Block %#v", b) 166 | p, err = r.Next(1) 167 | if err != nil { 168 | log.Println(err) 169 | return err 170 | } 171 | b.Frames = int(p[0]) 172 | log.Printf("\t Block %#v", b) 173 | return nil 174 | } 175 | 176 | // BlockGroup contains a single Block and a relative information. 177 | type BlockGroup struct { 178 | Block *Block `ebml:"A1" json:",omitempty"` 179 | Additions []*BlockAddition `ebml:"75A1>A6,omitempty" json:",omitempty"` 180 | Duration Duration `ebml:"9B,omitempty" json:",omitempty"` 181 | ReferencePriority int64 `ebml:"FA"` 182 | ReferenceBlock []Time `ebml:"FB,omitempty" json:",omitempty"` 183 | CodecState []byte `ebml:"A4,omitempty" json:",omitempty"` 184 | DiscardPadding time.Duration `ebml:"75A2,omitempty" json:",omitempty"` 185 | Slices []*TimeSlice `ebml:"8E>E8,omitempty" json:",omitempty"` 186 | } 187 | 188 | type TimeSlice struct { 189 | LaceNumber int64 `ebml:"CC"` 190 | } 191 | 192 | // BlockAdd contains additional blocks to complete the main one. 193 | type BlockAddition struct { 194 | ID BlockAdditionID `ebml:"EE,1"` 195 | Data []byte `ebml:"A5"` 196 | } 197 | 198 | type BlockAdditionID uint64 199 | 200 | // Track is a Top-Level Element of information with track description. 201 | type Track struct { 202 | Entries []*TrackEntry `ebml:"AE"` 203 | } 204 | 205 | // TrackEntry describes a track with all Elements. 206 | type TrackEntry struct { 207 | Number TrackNumber `ebml:"D7"` 208 | ID TrackID `ebml:"73C5"` 209 | Type TrackType `ebml:"83"` 210 | Enabled bool `ebml:"B9,true"` 211 | Default bool `ebml:"88,true"` 212 | Forced bool `ebml:"55AA"` 213 | Lacing bool `ebml:"9C,true"` 214 | MinCache int `ebml:"6DE7"` 215 | MaxCache int `ebml:"6DF8,omitempty" json:",omitempty"` 216 | DefaultDuration time.Duration `ebml:"23E383,omitempty" json:",omitempty"` 217 | DefaultDecodedFieldDuration time.Duration `ebml:"234E7A,omitempty" json:",omitempty"` 218 | MaxBlockAdditionID BlockAdditionID `ebml:"55EE"` 219 | Name string `ebml:"536E,omitempty" json:",omitempty"` 220 | Language string `ebml:"22B59C,eng,omitempty" json:",omitempty"` 221 | CodecID string `ebml:"86"` 222 | CodecPrivate []byte `ebml:"63A2,omitempty" json:",omitempty"` 223 | CodecName string `ebml:"258688,omitempty" json:",omitempty"` 224 | AttachmentLink AttachmentID `ebml:"7446,omitempty" json:",omitempty"` 225 | CodecDecodeAll bool `ebml:"AA,true"` 226 | TrackOverlay []TrackNumber `ebml:"6FAB,omitempty" json:",omitempty"` 227 | CodecDelay time.Duration `ebml:"56AA,omitempty" json:",omitempty"` 228 | SeekPreRoll time.Duration `ebml:"56BB"` 229 | TrackTranslate []*TrackTranslate `ebml:"6624,omitempty" json:",omitempty"` 230 | Video *VideoTrack `ebml:"E0,omitempty" json:",omitempty"` 231 | Audio *AudioTrack `ebml:"E1,omitempty" json:",omitempty"` 232 | TrackOperation *TrackOperation `ebml:"E2,omitempty" json:",omitempty"` 233 | ContentEncodings []*ContentEncoding `ebml:"6D80>6240,omitempty" json:",omitempty"` 234 | } 235 | 236 | type TrackID uint64 237 | type TrackNumber int 238 | type AttachmentID uint8 239 | type TrackType uint8 240 | 241 | const ( 242 | TrackTypeVideo TrackType = 0x01 243 | TrackTypeAudio TrackType = 0x02 244 | TrackTypeComplex TrackType = 0x03 245 | TrackTypeLogo TrackType = 0x10 246 | TrackTypeSubtitle TrackType = 0x11 247 | TrackTypeButton TrackType = 0x12 248 | TrackTypeControl TrackType = 0x20 249 | ) 250 | 251 | // TrackTranslate describes a track identification for the given Chapter Codec. 252 | type TrackTranslate struct { 253 | EditionIDs []EditionID `ebml:"66FC,omitempty" json:",omitempty"` 254 | Codec ChapterCodec `ebml:"66BF"` 255 | TranslateTrackID `ebml:"66A5"` 256 | } 257 | 258 | type TranslateTrackID []byte 259 | 260 | // VideoTrack contains information that is specific for video tracks. 261 | type VideoTrack struct { 262 | Interlaced InterlaceType `ebml:"9A"` 263 | FieldOrder FieldOrder `ebml:"9D,2"` 264 | StereoMode StereoMode `ebml:"53B8,omitempty" json:"stereoMode,omitempty"` 265 | AlphaMode *AlphaMode `ebml:"53C0,omitempty" json:"alphaMode,omitempty"` 266 | Width int `ebml:"B0"` 267 | Height int `ebml:"BA"` 268 | CropBottom int `ebml:"54AA,omitempty" json:",omitempty"` 269 | CropTop int `ebml:"54BB,omitempty" json:",omitempty"` 270 | CropLeft int `ebml:"54CC,omitempty" json:",omitempty"` 271 | CropRight int `ebml:"54DD,omitempty" json:",omitempty"` 272 | DisplayWidth int `ebml:"54B0,omitempty" json:",omitempty"` 273 | DisplayHeight int `ebml:"54BA,omitempty" json:",omitempty"` 274 | DisplayUnit DisplayUnit `ebml:"54B2,omitempty" json:",omitempty"` 275 | AspectRatioType AspectRatioType `ebml:"54B3,omitempty" json:",omitempty"` 276 | ColourSpace uint32 `ebml:"2EB524,omitempty" json:",omitempty"` 277 | Colour *Colour `ebml:"55B0,omitempty" json:",omitempty"` 278 | } 279 | 280 | type InterlaceType uint8 281 | 282 | // InterlaceTypes 283 | const ( 284 | InterlaceTypeInterlaced InterlaceType = 1 285 | InterlaceTypeProgressive InterlaceType = 2 286 | ) 287 | 288 | type FieldOrder uint8 289 | 290 | // FieldOrders 291 | const ( 292 | FieldOrderProgressive FieldOrder = 0 293 | FieldOrderTop FieldOrder = 1 294 | FieldOrderUndetermined FieldOrder = 2 295 | FieldOrderBottom FieldOrder = 6 296 | FieldOrderDisplayBottomStoreTop FieldOrder = 9 297 | FieldOrderDisplayTopStoreBottom FieldOrder = 14 298 | ) 299 | 300 | type StereoMode uint8 301 | 302 | // StereoModes 303 | const ( 304 | StereoModeMono StereoMode = iota 305 | StereoModeHorizontalLeft 306 | StereoModeVerticalRight 307 | StereoModeVerticalLeft 308 | StereoModeCheckboardRight 309 | StereoModeCheckboardLeft 310 | StereoModeInterleavedRight 311 | StereoModeInterleavedLeft 312 | StereoModeColumnInterleavedRight 313 | StereoModeAnaglyphCyanRed 314 | StereoModeHorizontalRight 315 | StereoModeAnaglyphGreenMagenta 316 | StereoModeLacedLeft 317 | StereoModeLacedRight 318 | ) 319 | 320 | type AlphaMode struct { 321 | } 322 | 323 | type DisplayUnit uint8 324 | 325 | // DisplayUnits 326 | const ( 327 | DisplayUnitPixels DisplayUnit = iota 328 | DisplayUnitCentimeters 329 | DisplayUnitInches 330 | DisplayUnitAspectRatio 331 | ) 332 | 333 | type AspectRatioType uint8 334 | 335 | // AspectRatioTypes 336 | const ( 337 | AspectRatioFreeResizing AspectRatioType = iota 338 | AspectRatioKeep 339 | AspectRatioFixed 340 | ) 341 | 342 | // Colour describes the colour format settings. 343 | type Colour struct { 344 | MatrixCoefficients MatrixCoefficients `ebml:"55B1,2,omitempty" json:",omitempty"` 345 | BitsPerChannel int `ebml:"55B2,omitempty" json:",omitempty"` 346 | ChromaSubsamplingHorz int `ebml:"55B3,omitempty" json:",omitempty"` 347 | ChromaSubsamplingVert int `ebml:"55B4,omitempty" json:",omitempty"` 348 | CbSubsamplingHorz int `ebml:"55B5,omitempty" json:",omitempty"` 349 | CbSubsamplingVert int `ebml:"55B6,omitempty" json:",omitempty"` 350 | ChromaSitingHorz ChromaSiting `ebml:"55B7,omitempty" json:",omitempty"` 351 | ChromaSitingVert ChromaSiting `ebml:"55B8,omitempty" json:",omitempty"` 352 | ColourRange ColourRange `ebml:"55B9,omitempty" json:",omitempty"` 353 | TransferCharacteristics TransferCharacteristics `ebml:"55BA,omitempty" json:",omitempty"` 354 | Primaries Primaries `ebml:"55BB,2,omitempty" json:",omitempty"` 355 | MaxCLL int64 `ebml:"55BC,omitempty" json:",omitempty"` 356 | MaxFALL int64 `ebml:"55BD,omitempty" json:",omitempty"` 357 | MasteringMetadata *MasteringMetadata `ebml:"55D0"` 358 | } 359 | 360 | // MatrixCoefficients, see Table 4 of ISO/IEC 23001-8:2013/DCOR1 361 | type MatrixCoefficients uint8 362 | 363 | // TransferCharacteristics, see Table 3 of ISO/IEC 23001-8:2013/DCOR1 364 | type TransferCharacteristics uint8 365 | 366 | // Primaries, see Table 2 of ISO/IEC 23001-8:2013/DCOR1 367 | type Primaries uint8 368 | 369 | type ChromaSiting uint8 370 | 371 | // ChromaSitings 372 | const ( 373 | ChromaSitingUnspecified ChromaSiting = iota 374 | ChromaSitingCollocated 375 | ChromaSitingHalf 376 | ) 377 | 378 | type ColourRange uint8 379 | 380 | // ColourRange 381 | const ( 382 | ColourRangeUnspecified ColourRange = iota 383 | ColourRangeBroadcast 384 | ColourRangeFull 385 | ColourRangeDefined 386 | ) 387 | 388 | // MasteringMetadata represents SMPTE 2086 mastering data. 389 | type MasteringMetadata struct { 390 | PrimaryRChromaX float64 `ebml:"55D1,omitempty" json:",omitempty"` 391 | PrimaryRChromaY float64 `ebml:"55D2,omitempty" json:",omitempty"` 392 | PrimaryGChromaX float64 `ebml:"55D3,omitempty" json:",omitempty"` 393 | PrimaryGChromaY float64 `ebml:"55D4,omitempty" json:",omitempty"` 394 | PrimaryBChromaX float64 `ebml:"55D5,omitempty" json:",omitempty"` 395 | PrimaryBChromaY float64 `ebml:"55D6,omitempty" json:",omitempty"` 396 | WhitePointChromaX float64 `ebml:"55D7,omitempty" json:",omitempty"` 397 | WhitePointChromaY float64 `ebml:"55D8,omitempty" json:",omitempty"` 398 | LuminanceMax float64 `ebml:"55D9,omitempty" json:",omitempty"` 399 | LuminanceMin float64 `ebml:"55DA,omitempty" json:",omitempty"` 400 | } 401 | 402 | // AudioTrack contains information that is specific for audio tracks. 403 | type AudioTrack struct { 404 | SamplingFreq float64 `ebml:"B5,8000"` 405 | OutputSamplingFreq float64 `ebml:"78B5,omitempty" json:",omitempty"` 406 | Channels int `ebml:"9F,1"` 407 | BitDepth int `ebml:"6264,omitempty" json:",omitempty"` 408 | } 409 | 410 | // TrackOperation describes an operation that needs to be applied on tracks 411 | // to create the virtual track. 412 | type TrackOperation struct { 413 | CombinePlanes []*TrackPlane `ebml:"E3>E4,omitempty" json:",omitempty"` 414 | JoinBlocks []TrackID `ebml:"E9>ED,omitempty" json:",omitempty"` 415 | } 416 | 417 | // TrackPlane contains a video plane track that need to be combined to create this track. 418 | type TrackPlane struct { 419 | ID TrackID `ebml:"E5"` 420 | Type PlaneType `ebml:"E6"` 421 | } 422 | 423 | type PlaneType uint8 424 | 425 | // PlaneTypes 426 | const ( 427 | PlaneTypeLeft PlaneType = iota 428 | PlaneTypeRight 429 | PlaneTypeBackground 430 | ) 431 | 432 | // ContentEncoding contains settings for several content encoding mechanisms 433 | // like compression or encryption. 434 | type ContentEncoding struct { 435 | Order int `ebml:"5031"` 436 | Scope EncodingScope `ebml:"5032,1"` 437 | Type EncodingType `ebml:"5033"` 438 | Compression *Compression `ebml:"5034,omitempty" json:",omitempty"` 439 | Encryption *Encryption `ebml:"5035,omitempty" json:",omitempty"` 440 | } 441 | 442 | type EncodingScope uint8 443 | 444 | // EncodingScopes 445 | const ( 446 | EncodingScopeAll EncodingScope = 1 447 | EncodingScopePrivate EncodingScope = 2 448 | EncodingScopeNext EncodingScope = 4 449 | ) 450 | 451 | type EncodingType uint8 452 | 453 | const ( 454 | EncodingTypeCompression EncodingType = iota 455 | EncodingTypeEncryption 456 | ) 457 | 458 | // Compression describes the compression used. 459 | type Compression struct { 460 | Algo CompressionAlgo `ebml:"4254"` 461 | Settings []byte `ebml:"4255,omitempty" json:",omitempty"` 462 | } 463 | 464 | type CompressionAlgo uint8 465 | 466 | const ( 467 | CompressionAlgoZlib CompressionAlgo = 0 468 | CompressionAlgoHeaderStripping CompressionAlgo = 3 469 | ) 470 | 471 | // Encryption describes the encryption used. 472 | type Encryption struct { 473 | Algo uint8 `ebml:"47E1,omitempty" json:",omitempty"` 474 | KeyID []byte `ebml:"47E2,omitempty" json:",omitempty"` 475 | Signature []byte `ebml:"47E3,omitempty" json:",omitempty"` 476 | SignKeyID []byte `ebml:"47E4,omitempty" json:",omitempty"` 477 | SignAlgo uint8 `ebml:"47E5,omitempty" json:",omitempty"` 478 | SignHashAlgo uint8 `ebml:"47E6,omitempty" json:",omitempty"` 479 | } 480 | 481 | // CuePoint contains all information relative to a seek point in the Segment. 482 | type CuePoint struct { 483 | Time Time `ebml:"B3"` 484 | TrackPositions []*CueTrackPosition `ebml:"B7"` 485 | } 486 | 487 | // CueTrackPosition contains positions for different tracks corresponding to the timestamp. 488 | type CueTrackPosition struct { 489 | Track TrackNumber `ebml:"F7"` 490 | ClusterPosition Position `ebml:"F1"` 491 | RelativePosition Position `ebml:"F0,omitempty" json:",omitempty"` 492 | Duration Duration `ebml:"B2,omitempty" json:",omitempty"` 493 | BlockNumber int `ebml:"5378,1,omitempty" json:",omitempty"` 494 | CodecState Position `ebml:"EA,omitempty" json:",omitempty"` 495 | References []Time `ebml:"DB>96,omitempty" json:",omitempty"` 496 | } 497 | 498 | // Attachment describes attached files. 499 | type Attachment struct { 500 | ID AttachmentID `ebml:"46AE"` 501 | Description string `ebml:"467E,omitempty" json:",omitempty"` 502 | Name string `ebml:"466E"` 503 | MimeType string `ebml:"4660"` 504 | Data []byte `ebml:"465C"` 505 | } 506 | 507 | // Edition contains all information about a Segment edition. 508 | type Edition struct { 509 | ID EditionID `ebml:"45BC,omitempty" json:",omitempty"` 510 | Hidden bool `ebml:"45BD"` 511 | Default bool `ebml:"45DB"` 512 | Ordered bool `ebml:"45DD,omitempty" json:",omitempty"` 513 | Atoms []*ChapterAtom `ebml:"B6"` 514 | } 515 | 516 | // ChapterAtom contains the atom information to use as the chapter atom. 517 | type ChapterAtom struct { 518 | ID ChapterID `ebml:"73C4"` 519 | StringID string `ebml:"5654,omitempty" json:",omitempty"` 520 | TimeStart Time `ebml:"91"` 521 | TimeEnd Time `ebml:"92,omitempty" json:",omitempty"` 522 | Hidden bool `ebml:"98"` 523 | Enabled bool `ebml:"4598,true"` 524 | SegmentID SegmentID `ebml:"6E67,omitempty" json:",omitempty"` 525 | EditionID EditionID `ebml:"6EBC,omitempty" json:",omitempty"` 526 | PhysicalEquiv int `ebml:"63C3,omitempty" json:",omitempty"` 527 | Tracks []TrackID `ebml:"8F>89,omitempty" json:",omitempty"` 528 | Displays []*ChapterDisplay `ebml:"80,omitempty" json:",omitempty"` 529 | Processes []*ChapterProcess `ebml:"6944,omitempty" json:",omitempty"` 530 | } 531 | 532 | type ChapterID uint64 533 | 534 | // ChapterDisplay contains all possible strings to use for the chapter display. 535 | type ChapterDisplay struct { 536 | String string `ebml:"85"` 537 | Language string `ebml:"437C,eng"` // See ISO-639-2 538 | Country string `ebml:"437E,omitempty" json:",omitempty"` // See IANA ccTLDs 539 | } 540 | 541 | // ChapterProcess describes the atom processing commands. 542 | type ChapterProcess struct { 543 | CodecID ChapterCodec `ebml:"6955"` 544 | Private []byte `ebml:"450D,omitempty" json:",omitempty"` 545 | Command []*ChapterCommand `ebml:"6911,omitempty" json:",omitempty"` 546 | } 547 | 548 | // ChapterCommand contains all the commands associated to the atom. 549 | type ChapterCommand struct { 550 | Time Time `ebml:"6922"` 551 | Data []byte `ebml:"6933"` 552 | } 553 | 554 | // Tag contains Elements specific to Tracks/Chapters. 555 | type Tag struct { 556 | Targets []*Target `ebml:"63C0"` 557 | SimpleTags []*SimpleTag `ebml:"67C8"` 558 | } 559 | 560 | // Target contains all IDs where the specified meta data apply. 561 | type Target struct { 562 | TypeValue int `ebml:"68CA,50,omitempty" json:",omitempty"` 563 | Type string `ebml:"63CA,omitempty" json:",omitempty"` 564 | TrackIDs []TrackID `ebml:"63C5,omitempty" json:",omitempty"` 565 | EditionIDs []EditionID `ebml:"63C9,omitempty" json:",omitempty"` 566 | ChapterIDs []ChapterID `ebml:"63C4,omitempty" json:",omitempty"` 567 | AttachmentIDs []AttachmentID `ebml:"63C6,omitempty" json:",omitempty"` 568 | } 569 | 570 | // SimpleTag contains general information about the target. 571 | type SimpleTag struct { 572 | Name string `ebml:"45A3"` 573 | Language string `ebml:"447A,und"` 574 | Default bool `ebml:"4484,true"` 575 | String string `ebml:"4487,omitempty" json:",omitempty"` 576 | Binary []byte `ebml:"4485,omitempty" json:",omitempty"` 577 | } 578 | 579 | func NewSimpleTag(name, text string) *SimpleTag { 580 | return &SimpleTag{ 581 | Name: name, 582 | String: text, 583 | Language: "und", 584 | Default: true, 585 | } 586 | } 587 | -------------------------------------------------------------------------------- /matroska/matroska_test.go: -------------------------------------------------------------------------------- 1 | package matroska 2 | 3 | import ( 4 | "archive/zip" 5 | "encoding/json" 6 | "io" 7 | "net/http" 8 | "os" 9 | "path/filepath" 10 | "reflect" 11 | "testing" 12 | ) 13 | 14 | func TestMatroskaTestSuite(t *testing.T) { 15 | if testing.Short() { 16 | t.Skip("Skipping Matroska Test Suite in short mode") 17 | } 18 | _, err := os.Stat("testdata") 19 | if err != nil && os.IsNotExist(err) { 20 | if err = download("https://downloads.sourceforge.net/project/matroska/test_files/matroska_test_w1_1.zip", "testdata.zip"); err != nil { 21 | t.Fatal(err) 22 | } 23 | defer os.Remove("testdata.zip") 24 | if err = unpack("testdata.zip", "testdata"); err != nil { 25 | t.Fatal(err) 26 | } 27 | _, err = os.Stat("testdata") 28 | } 29 | if err != nil { 30 | t.Fatal(err) 31 | } 32 | tags := map[string][]*Tag{ 33 | "test1.mkv": newTestTags("Big Buck Bunny - test 1", "Matroska Validation File1, basic MPEG4.2 and MP3 with only SimpleBlock"), 34 | "test2.mkv": newTestTags("Elephant Dream - test 2", "Matroska Validation File 2, 100,000 timecode scale, odd aspect ratio, and CRC-32. Codecs are AVC and AAC"), 35 | "test3.mkv": newTestTags("Elephant Dream - test 3", "Matroska Validation File 3, header stripping on the video track and no SimpleBlock"), 36 | "test4.mkv": nil, 37 | "test5.mkv": newTestTags("Big Buck Bunny - test 8", "Matroska Validation File 8, secondary audio commentary track, misc subtitle tracks"), 38 | "test6.mkv": newTestTags("Big Buck Bunny - test 6", "Matroska Validation File 6, random length to code the size of Clusters and Blocks, no Cues for seeking"), 39 | "test7.mkv": newTestTags("Big Buck Bunny - test 7", "Matroska Validation File 7, junk elements are present at the beggining or end of clusters, the parser should skip it. There is also a damaged element at 451418"), 40 | "test8.mkv": newTestTags("Big Buck Bunny - test 8", "Matroska Validation File 8, audio missing between timecodes 6.019s and 6.360s"), 41 | } 42 | for name, it := range tags { 43 | file := filepath.Join("testdata", name) 44 | want := it 45 | t.Run(name, func(t *testing.T) { 46 | t.Parallel() 47 | doc, err := Decode(file) 48 | if err != nil { 49 | t.Fatal(err) 50 | } 51 | got := doc.Segment.Tags 52 | if !reflect.DeepEqual(want, got) { 53 | t.Errorf("Unexpected tags, want: %s\ngot: %s", dump(want), dump(got)) 54 | } 55 | //for _, c := range doc.Segment.Cluster { 56 | // log.Printf(">>>>>> %s", dump(c)) 57 | //} 58 | }) 59 | } 60 | } 61 | 62 | func newTestTags(title, comment string) []*Tag { 63 | return []*Tag{{ 64 | Targets: []*Target{ 65 | {TypeValue: 50}, 66 | }, 67 | SimpleTags: []*SimpleTag{ 68 | NewSimpleTag("TITLE", title), 69 | NewSimpleTag("DATE_RELEASED", "2010"), 70 | NewSimpleTag("COMMENT", comment), 71 | }, 72 | }} 73 | } 74 | 75 | func dump(v interface{}) string { 76 | b, _ := json.Marshal(v) 77 | return string(b) 78 | } 79 | 80 | func download(url, file string) error { 81 | out, err := os.Create(file) 82 | if err != nil { 83 | return err 84 | } 85 | defer out.Close() 86 | resp, err := http.Get(url) 87 | if err != nil { 88 | return err 89 | } 90 | defer resp.Body.Close() 91 | _, err = io.Copy(out, resp.Body) 92 | return err 93 | } 94 | 95 | func unpack(file, dir string) error { 96 | in, err := zip.OpenReader(file) 97 | if err != nil { 98 | return err 99 | } 100 | if err := os.MkdirAll(dir, 0755); err != nil { 101 | return err 102 | } 103 | for _, it := range in.File { 104 | path := filepath.Join(dir, it.Name) 105 | if it.FileInfo().IsDir() { 106 | os.MkdirAll(path, it.Mode()) 107 | continue 108 | } 109 | in, err := it.Open() 110 | if err != nil { 111 | return err 112 | } 113 | defer in.Close() 114 | out, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, it.Mode()) 115 | if err != nil { 116 | return err 117 | } 118 | defer out.Close() 119 | if _, err := io.Copy(out, in); err != nil { 120 | return err 121 | } 122 | } 123 | return nil 124 | } 125 | -------------------------------------------------------------------------------- /matroska/stream.go: -------------------------------------------------------------------------------- 1 | package matroska 2 | 3 | import "io" 4 | 5 | type Reader struct { 6 | } 7 | 8 | func NewReader(r io.Reader) *Reader { 9 | return &Reader{} 10 | } 11 | --------------------------------------------------------------------------------