├── src ├── pkg │ ├── mp4 │ │ ├── Makefile │ │ └── mp4.go │ └── Makefile ├── cmd │ ├── mp4_stream │ │ ├── Makefile │ │ └── mp4_stream.go │ └── Makefile ├── clean.sh └── all.sh └── README.md /src/pkg/mp4/Makefile: -------------------------------------------------------------------------------- 1 | include $(GOROOT)/src/Make.inc 2 | 3 | TARG=mp4 4 | GOFILES=\ 5 | mp4.go\ 6 | 7 | include $(GOROOT)/src/Make.pkg 8 | -------------------------------------------------------------------------------- /src/cmd/mp4_stream/Makefile: -------------------------------------------------------------------------------- 1 | include $(GOROOT)/src/Make.inc 2 | 3 | TARG=mp4_stream 4 | GOFILES=\ 5 | mp4_stream.go\ 6 | 7 | include $(GOROOT)/src/Make.cmd -------------------------------------------------------------------------------- /src/pkg/Makefile: -------------------------------------------------------------------------------- 1 | all: install 2 | 3 | DIRS=\ 4 | mp4\ 5 | 6 | %.install: 7 | +cd $* && gomake install 8 | 9 | install: $(addsuffix .install, $(DIRS)) 10 | 11 | %.clean: 12 | +cd $* && gomake clean 13 | 14 | clean: $(addsuffix .clean, $(DIRS)) 15 | -------------------------------------------------------------------------------- /src/cmd/Makefile: -------------------------------------------------------------------------------- 1 | all: install 2 | 3 | DIRS=\ 4 | mp4_stream\ 5 | 6 | %.install: 7 | +cd $* && gomake install 8 | 9 | install: $(addsuffix .install, $(DIRS)) 10 | 11 | %.clean: 12 | +cd $* && gomake clean 13 | 14 | clean: $(addsuffix .clean, $(DIRS)) 15 | -------------------------------------------------------------------------------- /src/clean.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | DIRS=" 5 | cmd 6 | pkg 7 | " 8 | 9 | xcd() { 10 | echo 11 | cd $1 12 | echo --- cd $1 13 | } 14 | 15 | clean() { 16 | xcd $1 17 | gomake clean 18 | } 19 | 20 | for dir in $DIRS 21 | do (clean $dir) 22 | done 23 | 24 | echo " --- DONE" 25 | -------------------------------------------------------------------------------- /src/cmd/mp4_stream/mp4_stream.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "mp4" 5 | "fmt" 6 | "flag" 7 | "os" 8 | ) 9 | 10 | var inputFile string 11 | var f mp4.File 12 | 13 | func init() { 14 | flag.StringVar(&inputFile, "i", "", "-i input_file.mp4") 15 | flag.Parse() 16 | } 17 | 18 | func main() { 19 | if inputFile == "" { 20 | flag.Usage() 21 | return 22 | } 23 | f, err := mp4.Open(inputFile) 24 | if err != nil { 25 | fmt.Fprintln(os.Stderr, err) 26 | return 27 | } 28 | defer f.Close() 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | PKGS=" 5 | mp4 6 | " 7 | 8 | CMDS=" 9 | mp4_stream 10 | " 11 | 12 | xcd() { 13 | echo 14 | cd $1 15 | echo --- cd $1 16 | } 17 | 18 | mk() { 19 | xcd $1 20 | gomake clean 21 | gomake 22 | gomake install 23 | } 24 | 25 | rm -rf $GOROOT/pkg/${GOOS}_${GOARCH}/mp4 26 | rm -rf $GOROOT/pkg/${GOOS}_${GOARCH}/mp4.a 27 | rm -rf $GOBIN/mp4_stream 28 | 29 | for pkg in $PKGS 30 | do (mk pkg/$pkg) 31 | done 32 | 33 | for cmd in $CMDS 34 | do (mk cmd/$cmd) 35 | done 36 | 37 | echo " --- DONE" 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MP4 Stream 2 | ## A utility (written in Go) for parsing MP4 files and outputting portions of the streams. It is intended to be used for pseudo-streaming. 3 | 4 | This is my first attempt at a Go program. It is very rough and incomplete right now. At this point, it only parses the boxes/atoms of an MP4 container. I plan to continue working on the functionality until it is capable of using the moov box information to generate a new MP4 consisting of a specific time portion of the original MP4. Patches and suggestions welcome! 5 | 6 | ## Installing Go 7 | 8 | See . 9 | 10 | ## Installing MP4 Stream 11 | 12 | $ git clone git@github.com:bgentry/mp4_stream.git 13 | $ cd mp4_stream/src 14 | $ ./all.sh 15 | 16 | ## Try It Out 17 | 18 | $ mp4_stream -i ~/Movies/input_file.mp4 19 | 20 | -------------------------------------------------------------------------------- /src/pkg/mp4/mp4.go: -------------------------------------------------------------------------------- 1 | package mp4 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "encoding/binary" 7 | ) 8 | 9 | const ( 10 | BOX_HEADER_SIZE = int64(8) 11 | ) 12 | 13 | func Open(path string) (f *File, err os.Error) { 14 | // fmt.Println(flag.Args()) 15 | fmt.Println(path) 16 | 17 | file, err := os.OpenFile(path, os.O_RDONLY, 0400) 18 | if err != nil { 19 | fmt.Fprintln(os.Stderr, err) 20 | return nil, err 21 | } 22 | 23 | f = &File{ 24 | File: file, 25 | } 26 | 27 | return f, f.parse() 28 | } 29 | 30 | func (f *File) parse() (os.Error) { 31 | info, err := f.Stat() 32 | if err != nil { 33 | fmt.Fprintln(os.Stderr, err) 34 | return err 35 | } 36 | fmt.Printf("File size: %v \n", info.Size) 37 | f.size = info.Size 38 | 39 | // Loop through top-level Boxes 40 | boxes := readBoxes(f, int64(0), f.size) 41 | for box := range boxes { 42 | switch box.Name() { 43 | case "ftyp": 44 | f.ftyp = &FtypBox{ Box:box } 45 | f.ftyp.parse() 46 | case "moov": 47 | f.moov = &MoovBox{ Box:box } 48 | f.moov.parse() 49 | case "mdat": 50 | f.mdat = box 51 | default: 52 | fmt.Printf("Unhandled Box: %v \n", box.Name()) 53 | } 54 | } 55 | 56 | // Make sure we have all 3 required boxes 57 | if f.ftyp == nil || f.moov == nil || f.mdat == nil { 58 | return os.NewError("Missing a required box (ftyp, moov, or mdat)") 59 | } 60 | 61 | // Build chunk & sample tables 62 | fmt.Println("Building trak tables...") 63 | if err = f.buildTrakTables(); err != nil { 64 | fmt.Fprintln(os.Stderr, err) 65 | return err 66 | } 67 | fmt.Println("Chunk and Sample tables built.") 68 | 69 | return nil 70 | } 71 | 72 | func (f *File) buildTrakTables() (os.Error) { 73 | for _, trak := range f.moov.traks { 74 | trak.chunks = make([]Chunk, trak.mdia.minf.stbl.stco.entry_count) 75 | for i, offset := range trak.mdia.minf.stbl.stco.chunk_offset { 76 | trak.chunks[i].offset = offset 77 | } 78 | 79 | sample_num := uint32(1) 80 | next_chunk_id := 1 81 | for i := 0; i < int(trak.mdia.minf.stbl.stsc.entry_count); i++ { 82 | if i + 1 < int(trak.mdia.minf.stbl.stsc.entry_count) { 83 | next_chunk_id = int(trak.mdia.minf.stbl.stsc.first_chunk[i+1]) 84 | } else 85 | { 86 | next_chunk_id = len(trak.chunks) 87 | } 88 | first_chunk_id := trak.mdia.minf.stbl.stsc.first_chunk[i] 89 | n_samples := trak.mdia.minf.stbl.stsc.samples_per_chunk[i] 90 | sdi := trak.mdia.minf.stbl.stsc.sample_description_index[i] 91 | for j := int(first_chunk_id-1); j < next_chunk_id; j++ { 92 | trak.chunks[j].sample_count = n_samples 93 | trak.chunks[j].sample_description_index = sdi 94 | trak.chunks[j].start_sample = sample_num 95 | sample_num += n_samples 96 | } 97 | } 98 | 99 | sample_count := int(trak.mdia.minf.stbl.stsz.sample_count) 100 | trak.samples = make([]Sample, sample_count) 101 | sample_size := trak.mdia.minf.stbl.stsz.sample_size 102 | for i := 0; i < sample_count; i++ { 103 | if sample_size == uint32(0) { 104 | trak.samples[i].size = trak.mdia.minf.stbl.stsz.entry_size[i] 105 | } else 106 | { 107 | trak.samples[i].size = sample_size 108 | } 109 | } 110 | 111 | // Calculate file offset for each sample 112 | sample_id := 0 113 | for i := 0; i < len(trak.chunks); i++ { 114 | sample_offset := trak.chunks[i].offset 115 | for j := 0; j < int(trak.chunks[i].sample_count); j++ { 116 | sample_offset += trak.samples[sample_id].size 117 | sample_id++ 118 | } 119 | } 120 | 121 | // Calculate decoding time for each sample 122 | sample_id, sample_time := 0, uint32(0) 123 | for i := 0; i < int(trak.mdia.minf.stbl.stts.entry_count); i++ { 124 | sample_duration := trak.mdia.minf.stbl.stts.sample_delta[i] 125 | for j := 0; j < int(trak.mdia.minf.stbl.stts.sample_count[i]); j++ { 126 | trak.samples[sample_id].start_time = sample_time 127 | trak.samples[sample_id].duration = sample_duration 128 | sample_time += sample_duration 129 | sample_id++ 130 | } 131 | } 132 | // Calculate decoding to composition time offset, if ctts table exists 133 | if trak.mdia.minf.stbl.ctts != nil { 134 | sample_id = 0 135 | for i := 0; i < int(trak.mdia.minf.stbl.ctts.entry_count); i++ { 136 | count := int(trak.mdia.minf.stbl.ctts.sample_count[i]) 137 | cto := trak.mdia.minf.stbl.ctts.sample_offset[i] 138 | for j := 0; j < count; j++ { 139 | trak.samples[sample_id].cto = cto 140 | sample_id++ 141 | } 142 | } 143 | } 144 | } 145 | return nil 146 | } 147 | 148 | func readBoxes(f *File, start int64, n int64) (boxes chan *Box) { 149 | boxes = make(chan *Box, 100) 150 | go func() { 151 | for offset := start; offset < start + n; { 152 | size, name := f.ReadBoxAt(offset) 153 | fmt.Printf("Box found:\nType: %v \nSize (bytes): %v \n", name, size) 154 | 155 | box := &Box { 156 | name: name, 157 | size: int64(size), 158 | start: offset, 159 | file: f, 160 | } 161 | boxes <- box 162 | offset += int64(size) 163 | } 164 | close(boxes) 165 | } () 166 | return boxes 167 | } 168 | 169 | func readSubBoxes(f *File, start int64, n int64) (boxes chan *Box) { 170 | return readBoxes(f, start + BOX_HEADER_SIZE, n - BOX_HEADER_SIZE) 171 | } 172 | 173 | type File struct { 174 | *os.File 175 | ftyp *FtypBox 176 | moov *MoovBox 177 | mdat *Box 178 | size int64 179 | } 180 | 181 | func (f *File) ReadBoxAt(offset int64) (boxSize uint32, boxType string) { 182 | // Get Box size 183 | buf := f.ReadBytesAt(BOX_HEADER_SIZE, offset) 184 | boxSize = binary.BigEndian.Uint32(buf[0:4]) 185 | offset += BOX_HEADER_SIZE 186 | // Get Box name 187 | boxType = string(buf[4:8]) 188 | return boxSize, boxType 189 | } 190 | 191 | func (f *File) ReadBytesAt(n int64, offset int64) (word []byte) { 192 | buf := make([]byte, n) 193 | if _, error := f.ReadAt(buf, offset); error != nil { 194 | fmt.Println(error) 195 | return 196 | } 197 | return buf 198 | } 199 | 200 | type BoxInt interface { 201 | Name() string 202 | File() *File 203 | Size() int64 204 | Start() int64 205 | parse() os.Error 206 | } 207 | 208 | type Box struct { 209 | name string 210 | size, start int64 211 | file *File 212 | } 213 | 214 | func (b *Box) Name() (string) { return b.name } 215 | 216 | func (b *Box) Size() (int64) { return b.size } 217 | 218 | func (b *Box) File() (*File) { return b.file } 219 | 220 | func (b *Box) Start() (int64) { return b.start } 221 | 222 | func (b *Box) parse() (os.Error) { 223 | fmt.Printf("Default parser called; skip parsing. (%v)\n", b.name) 224 | return nil 225 | } 226 | 227 | func (b *Box) ReadBoxData() ([]byte) { 228 | if b.Size() <= BOX_HEADER_SIZE { 229 | return nil 230 | } 231 | return b.File().ReadBytesAt(b.Size() - BOX_HEADER_SIZE, b.Start() + BOX_HEADER_SIZE) 232 | } 233 | 234 | type FtypBox struct { 235 | *Box 236 | major_brand, minor_version string 237 | compatible_brands []string 238 | } 239 | 240 | func (b *FtypBox) parse() (os.Error) { 241 | data := b.ReadBoxData() 242 | b.major_brand, b.minor_version = string(data[0:4]), string(data[4:8]) 243 | if len(data) > 8 { 244 | for i := 8; i < len(data); i += 4 { 245 | b.compatible_brands = append(b.compatible_brands, string(data[i:i+4])) 246 | } 247 | } 248 | return nil 249 | } 250 | 251 | type MoovBox struct { 252 | *Box 253 | mvhd *MvhdBox 254 | iods *IodsBox 255 | traks []*TrakBox 256 | udta *UdtaBox 257 | } 258 | 259 | func (b *MoovBox) parse() (os.Error) { 260 | boxes := readSubBoxes(b.File(), b.Start(), b.Size()) 261 | for subBox := range boxes { 262 | switch subBox.Name() { 263 | case "mvhd": 264 | b.mvhd = &MvhdBox{ Box:subBox } 265 | b.mvhd.parse() 266 | case "iods": 267 | b.iods = &IodsBox{ Box:subBox } 268 | b.iods.parse() 269 | case "trak": 270 | trak := &TrakBox{ Box:subBox } 271 | trak.parse() 272 | b.traks = append(b.traks, trak) 273 | case "udta": 274 | b.udta = &UdtaBox{ Box:subBox } 275 | b.udta.parse() 276 | default: 277 | fmt.Printf("Unhandled Moov Sub-Box: %v \n", subBox.Name()) 278 | } 279 | } 280 | return nil 281 | } 282 | 283 | type MvhdBox struct { 284 | *Box 285 | version uint8 286 | flags [3]byte 287 | creation_time, modification_time, timescale, duration, next_track_id uint32 288 | rate Fixed32 289 | volume Fixed16 290 | other_data []byte 291 | } 292 | 293 | func (b *MvhdBox) parse() (err os.Error) { 294 | data := b.ReadBoxData() 295 | b.version = data[0] 296 | b.flags = [3]byte{data[1], data[2], data[3]} 297 | b.creation_time = binary.BigEndian.Uint32(data[4:8]) 298 | b.modification_time = binary.BigEndian.Uint32(data[8:12]) 299 | b.timescale = binary.BigEndian.Uint32(data[12:16]) 300 | b.duration = binary.BigEndian.Uint32(data[16:20]) 301 | b.rate, err = MakeFixed32(data[20:24]) 302 | if err != nil { 303 | return err 304 | } 305 | b.volume, err = MakeFixed16(data[24:26]) 306 | if err != nil { 307 | return err 308 | } 309 | b.other_data = data[26:] 310 | return nil 311 | } 312 | 313 | type IodsBox struct { 314 | *Box 315 | data []byte 316 | } 317 | 318 | func (b *IodsBox) parse() (os.Error) { 319 | b.data = b.ReadBoxData() 320 | return nil 321 | } 322 | 323 | type TrakBox struct { 324 | *Box 325 | tkhd *TkhdBox 326 | mdia *MdiaBox 327 | edts *EdtsBox 328 | chunks []Chunk 329 | samples []Sample 330 | } 331 | 332 | func (b *TrakBox) parse() (os.Error) { 333 | boxes := readSubBoxes(b.File(), b.Start(), b.Size()) 334 | for subBox := range boxes { 335 | switch subBox.Name() { 336 | case "tkhd": 337 | b.tkhd = &TkhdBox{ Box:subBox } 338 | b.tkhd.parse() 339 | case "mdia": 340 | b.mdia = &MdiaBox{ Box:subBox } 341 | b.mdia.parse() 342 | case "edts": 343 | b.edts = &EdtsBox{ Box:subBox } 344 | b.edts.parse() 345 | default: 346 | fmt.Printf("Unhandled Trak Sub-Box: %v \n", subBox.Name()) 347 | } 348 | } 349 | return nil 350 | } 351 | 352 | type TkhdBox struct { 353 | *Box 354 | version uint8 355 | flags [3]byte 356 | creation_time, modification_time, track_id, duration uint32 357 | layer, alternate_group uint16 // This should really be int16 but not sure how to parse 358 | volume Fixed16 359 | matrix []byte 360 | width, height Fixed32 361 | } 362 | 363 | func (b *TkhdBox) parse() (err os.Error) { 364 | data := b.ReadBoxData() 365 | b.version = data[0] 366 | b.flags = [3]byte{ data[1], data[2], data[3] } 367 | b.creation_time = binary.BigEndian.Uint32(data[4:8]) 368 | b.modification_time = binary.BigEndian.Uint32(data[8:12]) 369 | b.track_id = binary.BigEndian.Uint32(data[12:16]) 370 | // Skip 4 bytes for reserved space (uint32) 371 | b.duration = binary.BigEndian.Uint32(data[20:24]) 372 | // Skip 8 bytes for reserved space (2 uint32) 373 | b.layer = binary.BigEndian.Uint16(data[32:34]) 374 | b.alternate_group = binary.BigEndian.Uint16(data[34:36]) 375 | b.volume, err = MakeFixed16(data[36:38]) 376 | if err != nil { 377 | return err 378 | } 379 | // Skip 2 bytes for reserved space (uint16) 380 | b.matrix = data[40:76] 381 | b.width, err = MakeFixed32(data[76:80]) 382 | if err != nil { 383 | return err 384 | } 385 | b.height, err = MakeFixed32(data[80:84]) 386 | if err != nil { 387 | return err 388 | } 389 | return nil 390 | } 391 | 392 | type EdtsBox struct { 393 | *Box 394 | elst *ElstBox 395 | } 396 | 397 | func (b *EdtsBox) parse() (err os.Error) { 398 | boxes := readSubBoxes(b.File(), b.Start(), b.Size()) 399 | for subBox := range boxes { 400 | switch subBox.Name() { 401 | case "elst": 402 | b.elst = &ElstBox{ Box:subBox } 403 | err = b.elst.parse() 404 | default: 405 | fmt.Printf("Unhandled Edts Sub-Box: %v \n", subBox.Name()) 406 | } 407 | if err != nil { 408 | return err 409 | } 410 | } 411 | return nil 412 | } 413 | 414 | type ElstBox struct { 415 | *Box 416 | version uint8 417 | flags [3]byte 418 | entry_count uint32 419 | segment_duration, media_time []uint32 420 | media_rate_integer, media_rate_fraction []uint16 // This should really be int16 but not sure how to parse 421 | } 422 | 423 | func (b *ElstBox) parse() (err os.Error) { 424 | data := b.ReadBoxData() 425 | b.version = data[0] 426 | b.flags = [3]byte{ data[1], data[2], data[3] } 427 | b.entry_count = binary.BigEndian.Uint32(data[4:8]) 428 | for i := 0; i < int(b.entry_count); i++ { 429 | sd := binary.BigEndian.Uint32(data[(8+12*i):(12+12*i)]) 430 | mt := binary.BigEndian.Uint32(data[(12+12*i):(16+12*i)]) 431 | mri := binary.BigEndian.Uint16(data[(16+12*i):(18+12*i)]) 432 | mrf := binary.BigEndian.Uint16(data[(18+12*i):(20+12*i)]) 433 | b.segment_duration = append(b.segment_duration, sd) 434 | b.media_time = append(b.media_time, mt) 435 | b.media_rate_integer = append(b.media_rate_integer, mri) 436 | b.media_rate_fraction = append(b.media_rate_fraction, mrf) 437 | } 438 | return nil 439 | } 440 | 441 | type MdiaBox struct { 442 | *Box 443 | mdhd *MdhdBox 444 | hdlr *HdlrBox 445 | minf *MinfBox 446 | } 447 | 448 | func (b *MdiaBox) parse() (os.Error) { 449 | boxes := readSubBoxes(b.File(), b.Start(), b.Size()) 450 | for subBox := range boxes { 451 | switch subBox.Name() { 452 | case "mdhd": 453 | b.mdhd = &MdhdBox{ Box:subBox } 454 | b.mdhd.parse() 455 | case "hdlr": 456 | b.hdlr = &HdlrBox{ Box:subBox } 457 | b.hdlr.parse() 458 | case "minf": 459 | b.minf = &MinfBox{ Box:subBox } 460 | b.minf.parse() 461 | default: 462 | fmt.Printf("Unhandled Mdia Sub-Box: %v \n", subBox.Name()) 463 | } 464 | } 465 | return nil 466 | } 467 | 468 | type MdhdBox struct { 469 | *Box 470 | version uint8 471 | flags [3]byte 472 | creation_time, modification_time, timescale, duration uint32 473 | language uint16 // Combine 1-bit padding w/ 15-bit language data 474 | } 475 | 476 | func (b *MdhdBox) parse() (err os.Error) { 477 | data := b.ReadBoxData() 478 | b.version = data[0] 479 | b.flags = [3]byte{ data[1], data[2], data[3] } 480 | b.creation_time = binary.BigEndian.Uint32(data[4:8]) 481 | b.modification_time = binary.BigEndian.Uint32(data[8:12]) 482 | b.timescale = binary.BigEndian.Uint32(data[12:16]) 483 | b.duration = binary.BigEndian.Uint32(data[16:20]) 484 | // language includes 1 padding bit 485 | b.language = binary.BigEndian.Uint16(data[20:22]) 486 | return nil 487 | } 488 | 489 | type HdlrBox struct { 490 | *Box 491 | version uint8 492 | flags [3]byte 493 | pre_defined uint32 494 | handler_type, track_name string 495 | } 496 | 497 | func (b *HdlrBox) parse() (err os.Error) { 498 | data := b.ReadBoxData() 499 | b.version = data[0] 500 | b.flags = [3]byte{ data[1], data[2], data[3] } 501 | b.pre_defined = binary.BigEndian.Uint32(data[4:8]) 502 | b.handler_type = string(data[8:12]) 503 | // Skip 12 bytes for reserved space (3 uint32) 504 | b.track_name = string(data[24:]) 505 | return nil 506 | } 507 | 508 | type MinfBox struct { 509 | *Box 510 | vmhd *VmhdBox 511 | smhd *SmhdBox 512 | stbl *StblBox 513 | dinf *DinfBox 514 | hdlr *HdlrBox 515 | } 516 | 517 | func (b *MinfBox) parse() (err os.Error) { 518 | boxes := readSubBoxes(b.File(), b.Start(), b.Size()) 519 | for subBox := range boxes { 520 | switch subBox.Name() { 521 | case "vmhd": 522 | b.vmhd = &VmhdBox{ Box:subBox } 523 | err = b.vmhd.parse() 524 | case "smhd": 525 | b.smhd = &SmhdBox{ Box:subBox } 526 | err = b.smhd.parse() 527 | case "stbl": 528 | b.stbl = &StblBox{ Box:subBox } 529 | err = b.stbl.parse() 530 | case "dinf": 531 | b.dinf = &DinfBox{ Box:subBox } 532 | err = b.dinf.parse() 533 | case "hdlr": 534 | b.hdlr = &HdlrBox{ Box:subBox } 535 | err = b.hdlr.parse() 536 | default: 537 | fmt.Printf("Unhandled Minf Sub-Box: %v \n", subBox.Name()) 538 | } 539 | if err != nil { 540 | return err 541 | } 542 | } 543 | return nil 544 | } 545 | 546 | type VmhdBox struct { 547 | *Box 548 | version uint8 549 | flags [3]byte 550 | graphicsmode uint16 551 | opcolor [3]uint16 552 | } 553 | 554 | func (b *VmhdBox) parse() (err os.Error) { 555 | data := b.ReadBoxData() 556 | b.version = data[0] 557 | b.flags = [3]byte{ data[1], data[2], data[3] } 558 | b.graphicsmode = binary.BigEndian.Uint16(data[4:6]) 559 | for i := 0; i < 3; i++ { 560 | b.opcolor[i] = binary.BigEndian.Uint16(data[(6+2*i):(8+2*i)]) 561 | } 562 | return nil 563 | } 564 | 565 | type SmhdBox struct { 566 | *Box 567 | version uint8 568 | flags [3]byte 569 | balance uint16 // This should really be int16 but not sure how to parse 570 | } 571 | 572 | func (b *SmhdBox) parse() (err os.Error) { 573 | data := b.ReadBoxData() 574 | b.version = data[0] 575 | b.flags = [3]byte{ data[1], data[2], data[3] } 576 | b.balance = binary.BigEndian.Uint16(data[4:6]) 577 | return nil 578 | } 579 | 580 | type StblBox struct { 581 | *Box 582 | stsd *StsdBox 583 | stts *SttsBox 584 | stss *StssBox 585 | stsc *StscBox 586 | stsz *StszBox 587 | stco *StcoBox 588 | ctts *CttsBox 589 | } 590 | 591 | func (b *StblBox) parse() (err os.Error) { 592 | boxes := readSubBoxes(b.File(), b.Start(), b.Size()) 593 | for subBox := range boxes { 594 | switch subBox.Name() { 595 | case "stsd": 596 | b.stsd = &StsdBox{ Box:subBox } 597 | err = b.stsd.parse() 598 | case "stts": 599 | b.stts = &SttsBox{ Box:subBox } 600 | err = b.stts.parse() 601 | case "stss": 602 | b.stss = &StssBox{ Box:subBox } 603 | err = b.stss.parse() 604 | case "stsc": 605 | b.stsc = &StscBox{ Box:subBox } 606 | err = b.stsc.parse() 607 | case "stsz": 608 | b.stsz = &StszBox{ Box:subBox } 609 | err = b.stsz.parse() 610 | case "stco": 611 | b.stco = &StcoBox{ Box:subBox } 612 | err = b.stco.parse() 613 | case "ctts": 614 | b.ctts = &CttsBox{ Box:subBox } 615 | err = b.ctts.parse() 616 | default: 617 | fmt.Printf("Unhandled Stbl Sub-Box: %v \n", subBox.Name()) 618 | } 619 | if err != nil { 620 | return err 621 | } 622 | } 623 | return nil 624 | } 625 | 626 | type StsdBox struct { 627 | *Box 628 | version uint8 629 | flags [3]byte 630 | entry_count uint32 631 | other_data []byte 632 | } 633 | 634 | func (b *StsdBox) parse() (err os.Error) { 635 | data := b.ReadBoxData() 636 | b.version = data[0] 637 | b.flags = [3]byte{ data[1], data[2], data[3] } 638 | b.entry_count = binary.BigEndian.Uint32(data[4:8]) 639 | b.other_data = data[8:] 640 | fmt.Println("stsd box parsing not yet finished") 641 | return nil 642 | } 643 | 644 | type SttsBox struct { 645 | *Box 646 | version uint8 647 | flags [3]byte 648 | entry_count uint32 649 | sample_count []uint32 650 | sample_delta []uint32 651 | } 652 | 653 | func (b *SttsBox) parse() (err os.Error) { 654 | data := b.ReadBoxData() 655 | b.version = data[0] 656 | b.flags = [3]byte{ data[1], data[2], data[3] } 657 | b.entry_count = binary.BigEndian.Uint32(data[4:8]) 658 | for i := 0; i < int(b.entry_count); i++ { 659 | s_count := binary.BigEndian.Uint32(data[(8+8*i):(12+8*i)]) 660 | s_delta := binary.BigEndian.Uint32(data[(12+8*i):(16+8*i)]) 661 | b.sample_count = append(b.sample_count, s_count) 662 | b.sample_delta = append(b.sample_delta, s_delta) 663 | } 664 | return nil 665 | } 666 | 667 | type StssBox struct { 668 | *Box 669 | version uint8 670 | flags [3]byte 671 | entry_count uint32 672 | sample_number []uint32 673 | } 674 | 675 | func (b *StssBox) parse() (err os.Error) { 676 | data := b.ReadBoxData() 677 | b.version = data[0] 678 | b.flags = [3]byte{ data[1], data[2], data[3] } 679 | b.entry_count = binary.BigEndian.Uint32(data[4:8]) 680 | for i := 0; i < int(b.entry_count); i++ { 681 | sample := binary.BigEndian.Uint32(data[(8+4*i):(12+4*i)]) 682 | b.sample_number = append(b.sample_number, sample) 683 | } 684 | return nil 685 | } 686 | 687 | type StscBox struct { 688 | *Box 689 | version uint8 690 | flags [3]byte 691 | entry_count uint32 692 | first_chunk []uint32 693 | samples_per_chunk []uint32 694 | sample_description_index []uint32 695 | } 696 | 697 | func (b *StscBox) parse() (err os.Error) { 698 | data := b.ReadBoxData() 699 | b.version = data[0] 700 | b.flags = [3]byte{ data[1], data[2], data[3] } 701 | b.entry_count = binary.BigEndian.Uint32(data[4:8]) 702 | for i := 0; i < int(b.entry_count); i++ { 703 | fc := binary.BigEndian.Uint32(data[(8+12*i):(12+12*i)]) 704 | spc := binary.BigEndian.Uint32(data[(12+12*i):(16+12*i)]) 705 | sdi := binary.BigEndian.Uint32(data[(16+12*i):(20+12*i)]) 706 | b.first_chunk = append(b.first_chunk, fc) 707 | b.samples_per_chunk = append(b.samples_per_chunk, spc) 708 | b.sample_description_index = append(b.sample_description_index, sdi) 709 | } 710 | return nil 711 | } 712 | 713 | type StszBox struct { 714 | *Box 715 | version uint8 716 | flags [3]byte 717 | sample_size uint32 718 | sample_count uint32 719 | entry_size []uint32 720 | } 721 | 722 | func (b *StszBox) parse() (err os.Error) { 723 | data := b.ReadBoxData() 724 | b.version = data[0] 725 | b.flags = [3]byte{ data[1], data[2], data[3] } 726 | b.sample_size = binary.BigEndian.Uint32(data[4:8]) 727 | b.sample_count = binary.BigEndian.Uint32(data[8:12]) 728 | if b.sample_size == uint32(0) { 729 | for i := 0; i < int(b.sample_count); i++ { 730 | entry := binary.BigEndian.Uint32(data[(12+4*i):(16+4*i)]) 731 | b.entry_size = append(b.entry_size, entry) 732 | } 733 | } 734 | return nil 735 | } 736 | 737 | type StcoBox struct { 738 | *Box 739 | version uint8 740 | flags [3]byte 741 | entry_count uint32 742 | chunk_offset []uint32 743 | } 744 | 745 | func (b *StcoBox) parse() (err os.Error) { 746 | data := b.ReadBoxData() 747 | b.version = data[0] 748 | b.flags = [3]byte{ data[1], data[2], data[3] } 749 | b.entry_count = binary.BigEndian.Uint32(data[4:8]) 750 | for i := 0; i < int(b.entry_count); i++ { 751 | chunk := binary.BigEndian.Uint32(data[(8+4*i):(12+4*i)]) 752 | b.chunk_offset = append(b.chunk_offset, chunk) 753 | } 754 | return nil 755 | } 756 | 757 | type CttsBox struct { 758 | *Box 759 | version uint8 760 | flags [3]byte 761 | entry_count uint32 762 | sample_count []uint32 763 | sample_offset []uint32 764 | } 765 | 766 | func (b *CttsBox) parse() (err os.Error) { 767 | data := b.ReadBoxData() 768 | b.version = data[0] 769 | b.flags = [3]byte{ data[1], data[2], data[3] } 770 | b.entry_count = binary.BigEndian.Uint32(data[4:8]) 771 | for i := 0; i < int(b.entry_count); i++ { 772 | s_count := binary.BigEndian.Uint32(data[(8+8*i):(12+8*i)]) 773 | s_offset := binary.BigEndian.Uint32(data[(12+8*i):(16+8*i)]) 774 | b.sample_count = append(b.sample_count, s_count) 775 | b.sample_offset = append(b.sample_offset, s_offset) 776 | } 777 | return nil 778 | } 779 | 780 | type DinfBox struct { 781 | *Box 782 | dref *DrefBox 783 | } 784 | 785 | func (b *DinfBox) parse() (err os.Error) { 786 | boxes := readSubBoxes(b.File(), b.Start(), b.Size()) 787 | for subBox := range boxes { 788 | switch subBox.Name() { 789 | case "dref": 790 | b.dref = &DrefBox{ Box:subBox } 791 | err = b.dref.parse() 792 | default: 793 | fmt.Printf("Unhandled Dinf Sub-Box: %v \n", subBox.Name()) 794 | } 795 | if err != nil { 796 | return err 797 | } 798 | } 799 | return nil 800 | } 801 | 802 | type DrefBox struct { 803 | *Box 804 | version uint8 805 | flags [3]byte 806 | entry_count uint32 807 | other_data []byte 808 | } 809 | 810 | func (b *DrefBox) parse() (err os.Error) { 811 | data := b.ReadBoxData() 812 | b.version = data[0] 813 | b.flags = [3]byte{ data[1], data[2], data[3] } 814 | b.entry_count = binary.BigEndian.Uint32(data[4:8]) 815 | b.other_data = data[8:] 816 | fmt.Println("dref box parsing not yet finished") 817 | return nil 818 | } 819 | 820 | type UdtaBox struct { 821 | *Box 822 | meta *MetaBox 823 | } 824 | 825 | func (b *UdtaBox) parse() (err os.Error) { 826 | boxes := readSubBoxes(b.File(), b.Start(), b.Size()) 827 | for subBox := range boxes { 828 | switch subBox.Name() { 829 | case "meta": 830 | b.meta = &MetaBox{ Box:subBox } 831 | err = b.meta.parse() 832 | default: 833 | fmt.Printf("Unhandled Udta Sub-Box: %v \n", subBox.Name()) 834 | } 835 | if err != nil { 836 | return err 837 | } 838 | } 839 | return nil 840 | } 841 | 842 | type MetaBox struct { 843 | *Box 844 | version uint8 845 | flags [3]byte 846 | hdlr *HdlrBox 847 | } 848 | 849 | func (b *MetaBox) parse() (err os.Error) { 850 | data := b.ReadBoxData() 851 | b.version = data[0] 852 | b.flags = [3]byte{ data[1], data[2], data[3] } 853 | boxes := readSubBoxes(b.File(), b.Start()+4, b.Size()-4) 854 | for subBox := range boxes { 855 | switch subBox.Name() { 856 | case "hdlr": 857 | b.hdlr = &HdlrBox{ Box:subBox } 858 | err = b.hdlr.parse() 859 | default: 860 | fmt.Printf("Unhandled Meta Sub-Box: %v \n", subBox.Name()) 861 | } 862 | if err != nil { 863 | return err 864 | } 865 | } 866 | return nil 867 | } 868 | 869 | // An 8.8 Fixed Point Decimal notation 870 | type Fixed16 uint16 871 | 872 | func (f Fixed16) String() string { 873 | return fmt.Sprintf("%v", uint16(f) >> 8) 874 | } 875 | 876 | func MakeFixed16(bytes []byte) (Fixed16, os.Error) { 877 | if len(bytes) != 2 { 878 | return Fixed16(0), os.NewError("Invalid number of bytes for Fixed16. Need 2, got " + string(len(bytes))) 879 | } 880 | return Fixed16(binary.BigEndian.Uint16(bytes)), nil 881 | } 882 | 883 | // A 16.16 Fixed Point Decimal notation 884 | type Fixed32 uint32 885 | 886 | func (f Fixed32) String() string { 887 | return fmt.Sprintf("%v", uint32(f) >> 16) 888 | } 889 | 890 | func MakeFixed32(bytes []byte) (Fixed32, os.Error) { 891 | if len(bytes) != 4 { 892 | return Fixed32(0), os.NewError("Invalid number of bytes for Fixed32. Need 4, got " + string(len(bytes))) 893 | } 894 | return Fixed32(binary.BigEndian.Uint32(bytes)), nil 895 | } 896 | 897 | type Chunk struct { 898 | sample_description_index, start_sample, sample_count, offset uint32 899 | } 900 | 901 | type Sample struct { 902 | size, offset, start_time, duration, cto uint32 903 | } 904 | --------------------------------------------------------------------------------