github.com/camlistore/go4@v0.0.0-20200104003542-c7e774b10ea0/media/heif/bmff/bmff.go (about) 1 /* 2 Copyright 2018 The go4 Authors 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 // Package bmff reads ISO BMFF boxes, as used by HEIF, etc. 18 // 19 // This is not so much as a generic BMFF reader as it is a BMFF reader 20 // as needed by HEIF, though that may change in time. For now, only 21 // boxes necessary for the go4.org/media/heif package have explicit 22 // parsers. 23 // 24 // This package makes no API compatibility promises; it exists 25 // primarily for use by the go4.org/media/heif package. 26 package bmff 27 28 import ( 29 "bufio" 30 "bytes" 31 "encoding/binary" 32 "errors" 33 "fmt" 34 "io" 35 "io/ioutil" 36 "strings" 37 ) 38 39 func NewReader(r io.Reader) *Reader { 40 br, ok := r.(*bufio.Reader) 41 if !ok { 42 br = bufio.NewReader(r) 43 } 44 return &Reader{br: bufReader{Reader: br}} 45 } 46 47 type Reader struct { 48 br bufReader 49 lastBox Box // or nil 50 noMoreBoxes bool // a box with size 0 (the final box) was seen 51 } 52 53 type BoxType [4]byte 54 55 // Common box types. 56 var ( 57 TypeFtyp = BoxType{'f', 't', 'y', 'p'} 58 TypeMeta = BoxType{'m', 'e', 't', 'a'} 59 ) 60 61 func (t BoxType) String() string { return string(t[:]) } 62 63 func (t BoxType) EqualString(s string) bool { 64 // Could be cleaner, but see ohttps://github.com/golang/go/issues/24765 65 return len(s) == 4 && s[0] == t[0] && s[1] == t[1] && s[2] == t[2] && s[3] == t[3] 66 } 67 68 type parseFunc func(b box, br *bufio.Reader) (Box, error) 69 70 // Box represents a BMFF box. 71 type Box interface { 72 Size() int64 // 0 means unknown (will read to end of file) 73 Type() BoxType 74 75 // Parses parses the box, populating the fields 76 // in the returned concrete type. 77 // 78 // If Parse has already been called, Parse returns nil. 79 // If the box type is unknown, the returned error is ErrUnknownBox 80 // and it's guaranteed that no bytes have been read from the box. 81 Parse() (Box, error) 82 83 // Body returns the inner bytes of the box, ignoring the header. 84 // The body may start with the 4 byte header of a "Full Box" if the 85 // box's type derives from a full box. Most users will use Parse 86 // instead. 87 // Body will return a new reader at the beginning of the box if the 88 // outer box has already been parsed. 89 Body() io.Reader 90 } 91 92 // ErrUnknownBox is returned by Box.Parse for unrecognized box types. 93 var ErrUnknownBox = errors.New("heif: unknown box") 94 95 type parserFunc func(b *box, br *bufReader) (Box, error) 96 97 func boxType(s string) BoxType { 98 if len(s) != 4 { 99 panic("bogus boxType length") 100 } 101 return BoxType{s[0], s[1], s[2], s[3]} 102 } 103 104 var parsers = map[BoxType]parserFunc{ 105 boxType("dinf"): parseDataInformationBox, 106 boxType("dref"): parseDataReferenceBox, 107 boxType("ftyp"): parseFileTypeBox, 108 boxType("hdlr"): parseHandlerBox, 109 boxType("iinf"): parseItemInfoBox, 110 boxType("infe"): parseItemInfoEntry, 111 boxType("iloc"): parseItemLocationBox, 112 boxType("ipco"): parseItemPropertyContainerBox, 113 boxType("ipma"): parseItemPropertyAssociation, 114 boxType("iprp"): parseItemPropertiesBox, 115 boxType("irot"): parseImageRotation, 116 boxType("ispe"): parseImageSpatialExtentsProperty, 117 boxType("meta"): parseMetaBox, 118 boxType("pitm"): parsePrimaryItemBox, 119 } 120 121 type box struct { 122 size int64 // 0 means unknown, will read to end of file (box container) 123 boxType BoxType 124 body io.Reader 125 parsed Box // if non-nil, the Parsed result 126 slurp []byte // if non-nil, the contents slurped to memory 127 } 128 129 func (b *box) Size() int64 { return b.size } 130 func (b *box) Type() BoxType { return b.boxType } 131 132 func (b *box) Body() io.Reader { 133 if b.slurp != nil { 134 return bytes.NewReader(b.slurp) 135 } 136 return b.body 137 } 138 139 func (b *box) Parse() (Box, error) { 140 if b.parsed != nil { 141 return b.parsed, nil 142 } 143 parser, ok := parsers[b.Type()] 144 if !ok { 145 return nil, ErrUnknownBox 146 } 147 v, err := parser(b, &bufReader{Reader: bufio.NewReader(b.Body())}) 148 if err != nil { 149 return nil, err 150 } 151 b.parsed = v 152 return v, nil 153 } 154 155 type FullBox struct { 156 *box 157 Version uint8 158 Flags uint32 // 24 bits 159 } 160 161 // ReadBox reads the next box. 162 // 163 // If the previously read box was not read to completion, ReadBox consumes 164 // the rest of its data. 165 // 166 // At the end, the error is io.EOF. 167 func (r *Reader) ReadBox() (Box, error) { 168 if r.noMoreBoxes { 169 return nil, io.EOF 170 } 171 if r.lastBox != nil { 172 if _, err := io.Copy(ioutil.Discard, r.lastBox.Body()); err != nil { 173 return nil, err 174 } 175 } 176 var buf [8]byte 177 178 _, err := io.ReadFull(r.br, buf[:4]) 179 if err != nil { 180 return nil, err 181 } 182 box := &box{ 183 size: int64(binary.BigEndian.Uint32(buf[:4])), 184 } 185 186 _, err = io.ReadFull(r.br, box.boxType[:]) // 4 more bytes 187 if err != nil { 188 return nil, err 189 } 190 191 // Special cases for size: 192 var remain int64 193 switch box.size { 194 case 1: 195 // 1 means it's actually a 64-bit size, after the type. 196 _, err = io.ReadFull(r.br, buf[:8]) 197 if err != nil { 198 return nil, err 199 } 200 box.size = int64(binary.BigEndian.Uint64(buf[:8])) 201 if box.size < 0 { 202 // Go uses int64 for sizes typically, but BMFF uses uint64. 203 // We assume for now that nobody actually uses boxes larger 204 // than int64. 205 return nil, fmt.Errorf("unexpectedly large box %q", box.boxType) 206 } 207 remain = box.size - 2*4 - 8 208 case 0: 209 // 0 means unknown & to read to end of file. No more boxes. 210 r.noMoreBoxes = true 211 default: 212 remain = box.size - 2*4 213 } 214 if remain < 0 { 215 return nil, fmt.Errorf("Box header for %q has size %d, suggesting %d (negative) bytes remain", box.boxType, box.size, remain) 216 } 217 if box.size > 0 { 218 box.body = io.LimitReader(r.br, remain) 219 } else { 220 box.body = r.br 221 } 222 r.lastBox = box 223 return box, nil 224 } 225 226 // ReadAndParseBox wraps the ReadBox method, ensuring that the read box is of type typ 227 // and parses successfully. It returns the parsed box. 228 func (r *Reader) ReadAndParseBox(typ BoxType) (Box, error) { 229 box, err := r.ReadBox() 230 if err != nil { 231 return nil, fmt.Errorf("error reading %q box: %v", typ, err) 232 } 233 if box.Type() != typ { 234 return nil, fmt.Errorf("error reading %q box: got box type %q instead", typ, box.Type()) 235 } 236 pbox, err := box.Parse() 237 if err != nil { 238 return nil, fmt.Errorf("error parsing read %q box: %v", typ, err) 239 } 240 return pbox, nil 241 } 242 243 func readFullBox(outer *box, br *bufReader) (fb FullBox, err error) { 244 fb.box = outer 245 // Parse FullBox header. 246 buf, err := br.Peek(4) 247 if err != nil { 248 return FullBox{}, fmt.Errorf("failed to read 4 bytes of FullBox: %v", err) 249 } 250 fb.Version = buf[0] 251 buf[0] = 0 252 fb.Flags = binary.BigEndian.Uint32(buf[:4]) 253 br.Discard(4) 254 return fb, nil 255 } 256 257 type FileTypeBox struct { 258 *box 259 MajorBrand string // 4 bytes 260 MinorVersion string // 4 bytes 261 Compatible []string // all 4 bytes 262 } 263 264 func parseFileTypeBox(outer *box, br *bufReader) (Box, error) { 265 buf, err := br.Peek(8) 266 if err != nil { 267 return nil, err 268 } 269 ft := &FileTypeBox{ 270 box: outer, 271 MajorBrand: string(buf[:4]), 272 MinorVersion: string(buf[4:8]), 273 } 274 br.Discard(8) 275 for { 276 buf, err := br.Peek(4) 277 if err == io.EOF { 278 return ft, nil 279 } 280 if err != nil { 281 return nil, err 282 } 283 ft.Compatible = append(ft.Compatible, string(buf[:4])) 284 br.Discard(4) 285 } 286 } 287 288 type MetaBox struct { 289 FullBox 290 Children []Box 291 } 292 293 func parseMetaBox(outer *box, br *bufReader) (Box, error) { 294 fb, err := readFullBox(outer, br) 295 if err != nil { 296 return nil, err 297 } 298 mb := &MetaBox{FullBox: fb} 299 return mb, br.parseAppendBoxes(&mb.Children) 300 } 301 302 func (br *bufReader) parseAppendBoxes(dst *[]Box) error { 303 if br.err != nil { 304 return br.err 305 } 306 boxr := NewReader(br.Reader) 307 for { 308 inner, err := boxr.ReadBox() 309 if err == io.EOF { 310 return nil 311 } 312 if err != nil { 313 br.err = err 314 return err 315 } 316 slurp, err := ioutil.ReadAll(inner.Body()) 317 if err != nil { 318 br.err = err 319 return err 320 } 321 inner.(*box).slurp = slurp 322 *dst = append(*dst, inner) 323 } 324 } 325 326 // ItemInfoEntry represents an "infe" box. 327 // 328 // TODO: currently only parses Version 2 boxes. 329 type ItemInfoEntry struct { 330 FullBox 331 332 ItemID uint16 333 ProtectionIndex uint16 334 ItemType string // always 4 bytes 335 336 Name string 337 338 // If Type == "mime": 339 ContentType string 340 ContentEncoding string 341 342 // If Type == "uri ": 343 ItemURIType string 344 } 345 346 func parseItemInfoEntry(outer *box, br *bufReader) (Box, error) { 347 fb, err := readFullBox(outer, br) 348 if err != nil { 349 return nil, err 350 } 351 ie := &ItemInfoEntry{FullBox: fb} 352 if fb.Version != 2 { 353 return nil, fmt.Errorf("TODO: found version %d infe box. Only 2 is supported now.", fb.Version) 354 } 355 356 ie.ItemID, _ = br.readUint16() 357 ie.ProtectionIndex, _ = br.readUint16() 358 if !br.ok() { 359 return nil, br.err 360 } 361 buf, err := br.Peek(4) 362 if err != nil { 363 return nil, err 364 } 365 ie.ItemType = string(buf[:4]) 366 ie.Name, _ = br.readString() 367 368 switch ie.ItemType { 369 case "mime": 370 ie.ContentType, _ = br.readString() 371 if br.anyRemain() { 372 ie.ContentEncoding, _ = br.readString() 373 } 374 case "uri ": 375 ie.ItemURIType, _ = br.readString() 376 } 377 if !br.ok() { 378 return nil, br.err 379 } 380 return ie, nil 381 } 382 383 // ItemInfoBox represents an "iinf" box. 384 type ItemInfoBox struct { 385 FullBox 386 Count uint16 387 ItemInfos []*ItemInfoEntry 388 } 389 390 func parseItemInfoBox(outer *box, br *bufReader) (Box, error) { 391 fb, err := readFullBox(outer, br) 392 if err != nil { 393 return nil, err 394 } 395 ib := &ItemInfoBox{FullBox: fb} 396 397 ib.Count, _ = br.readUint16() 398 399 var itemInfos []Box 400 br.parseAppendBoxes(&itemInfos) 401 if br.ok() { 402 for _, box := range itemInfos { 403 pb, err := box.Parse() 404 if err != nil { 405 return nil, fmt.Errorf("error parsing ItemInfoEntry in ItemInfoBox: %v", err) 406 } 407 if iie, ok := pb.(*ItemInfoEntry); ok { 408 ib.ItemInfos = append(ib.ItemInfos, iie) 409 } 410 } 411 } 412 if !br.ok() { 413 return FullBox{}, br.err 414 } 415 return ib, nil 416 } 417 418 // bufReader adds some HEIF/BMFF-specific methods around a *bufio.Reader. 419 type bufReader struct { 420 *bufio.Reader 421 err error // sticky error 422 } 423 424 // ok reports whether all previous reads have been error-free. 425 func (br *bufReader) ok() bool { return br.err == nil } 426 427 func (br *bufReader) anyRemain() bool { 428 if br.err != nil { 429 return false 430 } 431 _, err := br.Peek(1) 432 return err == nil 433 } 434 435 func (br *bufReader) readUintN(bits uint8) (uint64, error) { 436 if br.err != nil { 437 return 0, br.err 438 } 439 if bits == 0 { 440 return 0, nil 441 } 442 nbyte := bits / 8 443 buf, err := br.Peek(int(nbyte)) 444 if err != nil { 445 br.err = err 446 return 0, err 447 } 448 defer br.Discard(int(nbyte)) 449 switch bits { 450 case 8: 451 return uint64(buf[0]), nil 452 case 16: 453 return uint64(binary.BigEndian.Uint16(buf[:2])), nil 454 case 32: 455 return uint64(binary.BigEndian.Uint32(buf[:4])), nil 456 case 64: 457 return binary.BigEndian.Uint64(buf[:8]), nil 458 default: 459 br.err = fmt.Errorf("invalid uintn read size") 460 return 0, br.err 461 } 462 } 463 464 func (br *bufReader) readUint8() (uint8, error) { 465 if br.err != nil { 466 return 0, br.err 467 } 468 v, err := br.ReadByte() 469 if err != nil { 470 br.err = err 471 return 0, err 472 } 473 return v, nil 474 } 475 476 func (br *bufReader) readUint16() (uint16, error) { 477 if br.err != nil { 478 return 0, br.err 479 } 480 buf, err := br.Peek(2) 481 if err != nil { 482 br.err = err 483 return 0, err 484 } 485 v := binary.BigEndian.Uint16(buf[:2]) 486 br.Discard(2) 487 return v, nil 488 } 489 490 func (br *bufReader) readUint32() (uint32, error) { 491 if br.err != nil { 492 return 0, br.err 493 } 494 buf, err := br.Peek(4) 495 if err != nil { 496 br.err = err 497 return 0, err 498 } 499 v := binary.BigEndian.Uint32(buf[:4]) 500 br.Discard(4) 501 return v, nil 502 } 503 504 func (br *bufReader) readString() (string, error) { 505 if br.err != nil { 506 return "", br.err 507 } 508 s0, err := br.ReadString(0) 509 if err != nil { 510 br.err = err 511 return "", err 512 } 513 s := strings.TrimSuffix(s0, "\x00") 514 if len(s) == len(s0) { 515 err = fmt.Errorf("unexpected non-null terminated string") 516 br.err = err 517 return "", err 518 } 519 return s, nil 520 } 521 522 // HEIF: ipco 523 type ItemPropertyContainerBox struct { 524 *box 525 Properties []Box // of ItemProperty or ItemFullProperty 526 } 527 528 func parseItemPropertyContainerBox(outer *box, br *bufReader) (Box, error) { 529 ipc := &ItemPropertyContainerBox{box: outer} 530 return ipc, br.parseAppendBoxes(&ipc.Properties) 531 } 532 533 // HEIF: iprp 534 type ItemPropertiesBox struct { 535 *box 536 PropertyContainer *ItemPropertyContainerBox 537 Associations []*ItemPropertyAssociation // at least 1 538 } 539 540 func parseItemPropertiesBox(outer *box, br *bufReader) (Box, error) { 541 ip := &ItemPropertiesBox{ 542 box: outer, 543 } 544 545 var boxes []Box 546 err := br.parseAppendBoxes(&boxes) 547 if err != nil { 548 return nil, err 549 } 550 if len(boxes) < 2 { 551 return nil, fmt.Errorf("expect at least 2 boxes in children; got 0") 552 } 553 554 cb, err := boxes[0].Parse() 555 if err != nil { 556 return nil, fmt.Errorf("failed to parse first box, %q: %v", boxes[0].Type(), err) 557 } 558 559 var ok bool 560 ip.PropertyContainer, ok = cb.(*ItemPropertyContainerBox) 561 if !ok { 562 return nil, fmt.Errorf("unexpected type %T for ItemPropertieBox.PropertyContainer", cb) 563 } 564 565 // Association boxes 566 ip.Associations = make([]*ItemPropertyAssociation, 0, len(boxes)-1) 567 for _, box := range boxes[1:] { 568 boxp, err := box.Parse() 569 if err != nil { 570 return nil, fmt.Errorf("failed to parse association box: %v", err) 571 } 572 ipa, ok := boxp.(*ItemPropertyAssociation) 573 if !ok { 574 return nil, fmt.Errorf("unexpected box %q instead of ItemPropertyAssociation", boxp.Type()) 575 } 576 ip.Associations = append(ip.Associations, ipa) 577 } 578 return ip, nil 579 } 580 581 type ItemPropertyAssociation struct { 582 FullBox 583 EntryCount uint32 584 Entries []ItemPropertyAssociationItem 585 } 586 587 // not a box 588 type ItemProperty struct { 589 Essential bool 590 Index uint16 591 } 592 593 // not a box 594 type ItemPropertyAssociationItem struct { 595 ItemID uint32 596 AssociationsCount int // as declared 597 Associations []ItemProperty // as parsed 598 } 599 600 func parseItemPropertyAssociation(outer *box, br *bufReader) (Box, error) { 601 fb, err := readFullBox(outer, br) 602 if err != nil { 603 return nil, err 604 } 605 ipa := &ItemPropertyAssociation{FullBox: fb} 606 count, _ := br.readUint32() 607 ipa.EntryCount = count 608 609 for i := uint64(0); i < uint64(count) && br.ok(); i++ { 610 var itemID uint32 611 if fb.Version < 1 { 612 itemID16, _ := br.readUint16() 613 itemID = uint32(itemID16) 614 } else { 615 itemID, _ = br.readUint32() 616 } 617 assocCount, _ := br.readUint8() 618 ipai := ItemPropertyAssociationItem{ 619 ItemID: itemID, 620 AssociationsCount: int(assocCount), 621 } 622 for j := 0; j < int(assocCount) && br.ok(); j++ { 623 first, _ := br.readUint8() 624 essential := first&(1<<7) != 0 625 first &^= byte(1 << 7) 626 627 var index uint16 628 if fb.Flags&1 != 0 { 629 second, _ := br.readUint8() 630 index = uint16(first)<<8 | uint16(second) 631 } else { 632 index = uint16(first) 633 } 634 ipai.Associations = append(ipai.Associations, ItemProperty{ 635 Essential: essential, 636 Index: index, 637 }) 638 } 639 ipa.Entries = append(ipa.Entries, ipai) 640 } 641 if !br.ok() { 642 return nil, br.err 643 } 644 return ipa, nil 645 } 646 647 type ImageSpatialExtentsProperty struct { 648 FullBox 649 ImageWidth uint32 650 ImageHeight uint32 651 } 652 653 func parseImageSpatialExtentsProperty(outer *box, br *bufReader) (Box, error) { 654 fb, err := readFullBox(outer, br) 655 if err != nil { 656 return nil, err 657 } 658 w, err := br.readUint32() 659 if err != nil { 660 return nil, err 661 } 662 h, err := br.readUint32() 663 if err != nil { 664 return nil, err 665 } 666 return &ImageSpatialExtentsProperty{ 667 FullBox: fb, 668 ImageWidth: w, 669 ImageHeight: h, 670 }, nil 671 } 672 673 type OffsetLength struct { 674 Offset, Length uint64 675 } 676 677 // not a box 678 type ItemLocationBoxEntry struct { 679 ItemID uint16 680 ConstructionMethod uint8 // actually uint4 681 DataReferenceIndex uint16 682 BaseOffset uint64 // uint32 or uint64, depending on encoding 683 ExtentCount uint16 684 Extents []OffsetLength 685 } 686 687 // box "iloc" 688 type ItemLocationBox struct { 689 FullBox 690 691 offsetSize, lengthSize, baseOffsetSize, indexSize uint8 // actually uint4 692 693 ItemCount uint16 694 Items []ItemLocationBoxEntry 695 } 696 697 func parseItemLocationBox(outer *box, br *bufReader) (Box, error) { 698 fb, err := readFullBox(outer, br) 699 if err != nil { 700 return nil, err 701 } 702 ilb := &ItemLocationBox{ 703 FullBox: fb, 704 } 705 buf, err := br.Peek(4) 706 if err != nil { 707 return nil, err 708 } 709 ilb.offsetSize = buf[0] >> 4 710 ilb.lengthSize = buf[0] & 15 711 ilb.baseOffsetSize = buf[1] >> 4 712 if fb.Version > 0 { // version 1 713 ilb.indexSize = buf[1] & 15 714 } 715 716 ilb.ItemCount = binary.BigEndian.Uint16(buf[2:4]) 717 br.Discard(4) 718 719 for i := 0; br.ok() && i < int(ilb.ItemCount); i++ { 720 var ent ItemLocationBoxEntry 721 ent.ItemID, _ = br.readUint16() 722 if fb.Version > 0 { // version 1 723 cmeth, _ := br.readUint16() 724 ent.ConstructionMethod = byte(cmeth & 15) 725 } 726 ent.DataReferenceIndex, _ = br.readUint16() 727 if br.ok() && ilb.baseOffsetSize > 0 { 728 br.Discard(int(ilb.baseOffsetSize) / 8) 729 } 730 ent.ExtentCount, _ = br.readUint16() 731 for j := 0; br.ok() && j < int(ent.ExtentCount); j++ { 732 var ol OffsetLength 733 ol.Offset, _ = br.readUintN(ilb.offsetSize * 8) 734 ol.Length, _ = br.readUintN(ilb.lengthSize * 8) 735 if br.err != nil { 736 return nil, br.err 737 } 738 ent.Extents = append(ent.Extents, ol) 739 } 740 ilb.Items = append(ilb.Items, ent) 741 } 742 if !br.ok() { 743 return nil, br.err 744 } 745 return ilb, nil 746 } 747 748 // a "hdlr" box. 749 type HandlerBox struct { 750 FullBox 751 HandlerType string // always 4 bytes; usually "pict" for iOS Camera images 752 Name string 753 } 754 755 func parseHandlerBox(gen *box, br *bufReader) (Box, error) { 756 fb, err := readFullBox(gen, br) 757 if err != nil { 758 return nil, err 759 } 760 hb := &HandlerBox{ 761 FullBox: fb, 762 } 763 buf, err := br.Peek(20) 764 if err != nil { 765 return nil, err 766 } 767 hb.HandlerType = string(buf[4:8]) 768 br.Discard(20) 769 770 hb.Name, _ = br.readString() 771 return hb, br.err 772 } 773 774 // a "dinf" box 775 type DataInformationBox struct { 776 *box 777 Children []Box 778 } 779 780 func parseDataInformationBox(gen *box, br *bufReader) (Box, error) { 781 dib := &DataInformationBox{box: gen} 782 return dib, br.parseAppendBoxes(&dib.Children) 783 } 784 785 // a "dref" box. 786 type DataReferenceBox struct { 787 FullBox 788 EntryCount uint32 789 Children []Box 790 } 791 792 func parseDataReferenceBox(gen *box, br *bufReader) (Box, error) { 793 fb, err := readFullBox(gen, br) 794 if err != nil { 795 return nil, err 796 } 797 drb := &DataReferenceBox{FullBox: fb} 798 drb.EntryCount, _ = br.readUint32() 799 return drb, br.parseAppendBoxes(&drb.Children) 800 } 801 802 // "pitm" box 803 type PrimaryItemBox struct { 804 FullBox 805 ItemID uint16 806 } 807 808 func parsePrimaryItemBox(gen *box, br *bufReader) (Box, error) { 809 fb, err := readFullBox(gen, br) 810 if err != nil { 811 return nil, err 812 } 813 pib := &PrimaryItemBox{FullBox: fb} 814 pib.ItemID, _ = br.readUint16() 815 if !br.ok() { 816 return nil, br.err 817 } 818 return pib, nil 819 } 820 821 // ImageRotation is a HEIF "irot" rotation property. 822 type ImageRotation struct { 823 *box 824 Angle uint8 // 1 means 90 degrees counter-clockwise, 2 means 180 counter-clockwise 825 } 826 827 func parseImageRotation(gen *box, br *bufReader) (Box, error) { 828 v, err := br.readUint8() 829 if err != nil { 830 return nil, err 831 } 832 return &ImageRotation{box: gen, Angle: v & 3}, nil 833 }