Title: Parser
Published: April 3, 2024
Last modified: April 28, 2025

---

# class Parser {}

## In this article

 * [Methods](https://developer.wordpress.org/reference/classes/avifinfo-parser/?output_format=md#methods)
 * [Source](https://developer.wordpress.org/reference/classes/avifinfo-parser/?output_format=md#source)

[ Back to top](https://developer.wordpress.org/reference/classes/avifinfo-parser/?output_format=md#wp--skip-link--target)

## 󠀁[Methods](https://developer.wordpress.org/reference/classes/avifinfo-parser/?output_format=md#methods)󠁿

| Name | Description | 
| [Parser::__construct](https://developer.wordpress.org/reference/classes/avifinfo-parser/__construct/) | – | 
| [Parser::parse_file](https://developer.wordpress.org/reference/classes/avifinfo-parser/parse_file/) | Parses a file stream. | 
| [Parser::parse_ftyp](https://developer.wordpress.org/reference/classes/avifinfo-parser/parse_ftyp/) | Parses a file stream. | 
| [Parser::parse_ipco](https://developer.wordpress.org/reference/classes/avifinfo-parser/parse_ipco/) | Parses an “ipco” box. | 
| [Parser::parse_iprp](https://developer.wordpress.org/reference/classes/avifinfo-parser/parse_iprp/) | Parses an “iprp” box. | 
| [Parser::parse_iref](https://developer.wordpress.org/reference/classes/avifinfo-parser/parse_iref/) | Parses an “iref” box. | 
| [Parser::parse_meta](https://developer.wordpress.org/reference/classes/avifinfo-parser/parse_meta/) | Parses a “meta” box. |

## 󠀁[Source](https://developer.wordpress.org/reference/classes/avifinfo-parser/?output_format=md#source)󠁿

    ```php
        // print_r( $this ); // Uncomment to print all boxes.
        return FOUND;
      }
    }

    //------------------------------------------------------------------------------

    class Parser {
      private $handle; // Input stream.
      private $num_parsed_boxes = 0;
      private $data_was_skipped = false;
      public $features;

      function __construct( $handle ) {
        $this->handle   = $handle;
        $this->features = new Features();
      }

      /**
       * Parses an "ipco" box.
       *
       * "ispe" is used for width and height, "pixi" and "av1C" are used for bit depth
       * and number of channels, and "auxC" is used for alpha.
       *
       * @param stream  $handle              The resource the box will be parsed from.
       * @param int     $num_remaining_bytes The number of bytes that should be available from the resource.
       * @return Status                      FOUND on success or an error on failure.
       */
      private function parse_ipco( $num_remaining_bytes ) {
        $box_index = 1; // 1-based index. Used for iterating over properties.
        do {
          $box    = new Box();
          $status = $box->parse( $this->handle, $this->num_parsed_boxes, $num_remaining_bytes );
          if ( $status != FOUND ) {
            return $status;
          }

          if ( $box->type == 'ispe' ) {
            // See ISO/IEC 23008-12:2017(E) 6.5.3.2
            if ( $box->content_size < 8 ) {
              return INVALID;
            }
            if ( !( $data = read( $this->handle, 8 ) ) ) {
              return TRUNCATED;
            }
            $width  = read_big_endian( substr( $data, 0, 4 ), 4 );
            $height = read_big_endian( substr( $data, 4, 4 ), 4 );
            if ( $width == 0 || $height == 0 ) {
              return INVALID;
            }
            if ( count( $this->features->dim_props ) <= MAX_FEATURES &&
                 $box_index <= MAX_VALUE ) {
              $dim_prop_count = count( $this->features->dim_props );
              $this->features->dim_props[$dim_prop_count]                 = new Dim_Prop();
              $this->features->dim_props[$dim_prop_count]->property_index = $box_index;
              $this->features->dim_props[$dim_prop_count]->width          = $width;
              $this->features->dim_props[$dim_prop_count]->height         = $height;
            } else {
              $this->data_was_skipped = true;
            }
            if ( !skip( $this->handle, $box->content_size - 8 ) ) {
              return TRUNCATED;
            }
          } else if ( $box->type == 'pixi' ) {
            // See ISO/IEC 23008-12:2017(E) 6.5.6.2
            if ( $box->content_size < 1 ) {
              return INVALID;
            }
            if ( !( $data = read( $this->handle, 1 ) ) ) {
              return TRUNCATED;
            }
            $num_channels = read_big_endian( $data, 1 );
            if ( $num_channels < 1 ) {
              return INVALID;
            }
            if ( $box->content_size < 1 + $num_channels ) {
              return INVALID;
            }
            if ( !( $data = read( $this->handle, 1 ) ) ) {
              return TRUNCATED;
            }
            $bit_depth = read_big_endian( $data, 1 );
            if ( $bit_depth < 1 ) {
              return INVALID;
            }
            for ( $i = 1; $i < $num_channels; ++$i ) {
              if ( !( $data = read( $this->handle, 1 ) ) ) {
                return TRUNCATED;
              }
              // Bit depth should be the same for all channels.
              if ( read_big_endian( $data, 1 ) != $bit_depth ) {
                return INVALID;
              }
              if ( $i > 32 ) {
                return ABORTED; // Be reasonable.
              }
            }
            if ( count( $this->features->chan_props ) <= MAX_FEATURES &&
                 $box_index <= MAX_VALUE && $bit_depth <= MAX_VALUE &&
                 $num_channels <= MAX_VALUE ) {
              $chan_prop_count = count( $this->features->chan_props );
              $this->features->chan_props[$chan_prop_count]                 = new Chan_Prop();
              $this->features->chan_props[$chan_prop_count]->property_index = $box_index;
              $this->features->chan_props[$chan_prop_count]->bit_depth      = $bit_depth;
              $this->features->chan_props[$chan_prop_count]->num_channels   = $num_channels;
            } else {
              $this->data_was_skipped = true;
            }
            if ( !skip( $this->handle, $box->content_size - ( 1 + $num_channels ) ) ) {
              return TRUNCATED;
            }
          } else if ( $box->type == 'av1C' ) {
            // See AV1 Codec ISO Media File Format Binding 2.3.1
            // at https://aomediacodec.github.io/av1-isobmff/#av1c
            // Only parse the necessary third byte. Assume that the others are valid.
            if ( $box->content_size < 3 ) {
              return INVALID;
            }
            if ( !( $data = read( $this->handle, 3 ) ) ) {
              return TRUNCATED;
            }
            $byte          = read_big_endian( substr( $data, 2, 1 ), 1 );
            $high_bitdepth = ( $byte & 0x40 ) != 0;
            $twelve_bit    = ( $byte & 0x20 ) != 0;
            $monochrome    = ( $byte & 0x10 ) != 0;
            if ( $twelve_bit && !$high_bitdepth ) {
                return INVALID;
            }
            if ( count( $this->features->chan_props ) <= MAX_FEATURES &&
                 $box_index <= MAX_VALUE ) {
              $chan_prop_count = count( $this->features->chan_props );
              $this->features->chan_props[$chan_prop_count]                 = new Chan_Prop();
              $this->features->chan_props[$chan_prop_count]->property_index = $box_index;
              $this->features->chan_props[$chan_prop_count]->bit_depth      =
                  $high_bitdepth ? $twelve_bit ? 12 : 10 : 8;
              $this->features->chan_props[$chan_prop_count]->num_channels   = $monochrome ? 1 : 3;
            } else {
              $this->data_was_skipped = true;
            }
            if ( !skip( $this->handle, $box->content_size - 3 ) ) {
              return TRUNCATED;
            }
          } else if ( $box->type == 'auxC' ) {
            // See AV1 Image File Format (AVIF) 4
            // at https://aomediacodec.github.io/av1-avif/#auxiliary-images
            $kAlphaStr       = "urn:mpeg:mpegB:cicp:systems:auxiliary:alpha\0";
            $kAlphaStrLength = 44; // Includes terminating character.
            if ( $box->content_size >= $kAlphaStrLength ) {
              if ( !( $data = read( $this->handle, $kAlphaStrLength ) ) ) {
                return TRUNCATED;
              }
              if ( substr( $data, 0, $kAlphaStrLength ) == $kAlphaStr ) {
                // Note: It is unlikely but it is possible that this alpha plane does
                //       not belong to the primary item or a tile. Ignore this issue.
                $this->features->has_alpha = true;
              }
              if ( !skip( $this->handle, $box->content_size - $kAlphaStrLength ) ) {
                return TRUNCATED;
              }
            } else {
              if ( !skip( $this->handle, $box->content_size ) ) {
                return TRUNCATED;
              }
            }
          } else {
            if ( !skip( $this->handle, $box->content_size ) ) {
              return TRUNCATED;
            }
          }
          ++$box_index;
          $num_remaining_bytes -= $box->size;
        } while ( $num_remaining_bytes > 0 );
        return NOT_FOUND;
      }

      /**
       * Parses an "iprp" box.
       *
       * The "ipco" box contains the properties which are linked to items by the "ipma" box.
       *
       * @param stream  $handle              The resource the box will be parsed from.
       * @param int     $num_remaining_bytes The number of bytes that should be available from the resource.
       * @return Status                      FOUND on success or an error on failure.
       */
      private function parse_iprp( $num_remaining_bytes ) {
        do {
          $box    = new Box();
          $status = $box->parse( $this->handle, $this->num_parsed_boxes, $num_remaining_bytes );
          if ( $status != FOUND ) {
            return $status;
          }

          if ( $box->type == 'ipco' ) {
            $status = $this->parse_ipco( $box->content_size );
            if ( $status != NOT_FOUND ) {
              return $status;
            }
          } else if ( $box->type == 'ipma' ) {
            // See ISO/IEC 23008-12:2017(E) 9.3.2
            $num_read_bytes = 4;
            if ( $box->content_size < $num_read_bytes ) {
              return INVALID;
            }
            if ( !( $data = read( $this->handle, $num_read_bytes ) ) ) {
              return TRUNCATED;
            }
            $entry_count        = read_big_endian( $data, 4 );
            $id_num_bytes       = ( $box->version < 1 ) ? 2 : 4;
            $index_num_bytes    = ( $box->flags & 1 ) ? 2 : 1;
            $essential_bit_mask = ( $box->flags & 1 ) ? 0x8000 : 0x80;

            for ( $entry = 0; $entry < $entry_count; ++$entry ) {
              if ( $entry >= MAX_PROPS ||
                   count( $this->features->props ) >= MAX_PROPS ) {
                $this->data_was_skipped = true;
                break;
              }
              $num_read_bytes += $id_num_bytes + 1;
              if ( $box->content_size < $num_read_bytes ) {
                return INVALID;
              }
              if ( !( $data = read( $this->handle, $id_num_bytes + 1 ) ) ) {
                return TRUNCATED;
              }
              $item_id           = read_big_endian(
                  substr( $data, 0, $id_num_bytes ), $id_num_bytes );
              $association_count = read_big_endian(
                  substr( $data, $id_num_bytes, 1 ), 1 );

              for ( $property = 0; $property < $association_count; ++$property ) {
                if ( $property >= MAX_PROPS ||
                     count( $this->features->props ) >= MAX_PROPS ) {
                  $this->data_was_skipped = true;
                  break;
                }
                $num_read_bytes += $index_num_bytes;
                if ( $box->content_size < $num_read_bytes ) {
                  return INVALID;
                }
                if ( !( $data = read( $this->handle, $index_num_bytes ) ) ) {
                  return TRUNCATED;
                }
                $value          = read_big_endian( $data, $index_num_bytes );
                // $essential = ($value & $essential_bit_mask);  // Unused.
                $property_index = ( $value & ~$essential_bit_mask );
                if ( $property_index <= MAX_VALUE && $item_id <= MAX_VALUE ) {
                  $prop_count = count( $this->features->props );
                  $this->features->props[$prop_count]                 = new Prop();
                  $this->features->props[$prop_count]->property_index = $property_index;
                  $this->features->props[$prop_count]->item_id        = $item_id;
                } else {
                  $this->data_was_skipped = true;
                }
              }
              if ( $property < $association_count ) {
                break; // Do not read garbage.
              }
            }

            // If all features are available now, do not look further.
            $status = $this->features->get_primary_item_features();
            if ( $status != NOT_FOUND ) {
              return $status;
            }

            // Mostly if 'data_was_skipped'.
            if ( !skip( $this->handle, $box->content_size - $num_read_bytes ) ) {
              return TRUNCATED;
            }
          } else {
            if ( !skip( $this->handle, $box->content_size ) ) {
              return TRUNCATED;
            }
          }
          $num_remaining_bytes -= $box->size;
        } while ( $num_remaining_bytes > 0 );
        return NOT_FOUND;
      }

      /**
       * Parses an "iref" box.
       *
       * The "dimg" boxes contain links between tiles and their parent items, which
       * can be used to infer bit depth and number of channels for the primary item
       * when the latter does not have these properties.
       *
       * @param stream  $handle              The resource the box will be parsed from.
       * @param int     $num_remaining_bytes The number of bytes that should be available from the resource.
       * @return Status                      FOUND on success or an error on failure.
       */
      private function parse_iref( $num_remaining_bytes ) {
        while ( $num_remaining_bytes > 0 ) {
          $box    = new Box();
          $status = $box->parse( $this->handle, $this->num_parsed_boxes, $num_remaining_bytes );
          if ( $status != FOUND ) {
            return $status;
          }

          if ( $box->type == 'dimg' ) {
            // See ISO/IEC 14496-12:2015(E) 8.11.12.2
            $num_bytes_per_id = ( $box->version == 0 ) ? 2 : 4;
            $num_read_bytes   = $num_bytes_per_id + 2;
            if ( $box->content_size < $num_read_bytes ) {
              return INVALID;
            }
            if ( !( $data = read( $this->handle, $num_read_bytes ) ) ) {
              return TRUNCATED;
            }
            $from_item_id    = read_big_endian( $data, $num_bytes_per_id );
            $reference_count = read_big_endian( substr( $data, $num_bytes_per_id, 2 ), 2 );

            for ( $i = 0; $i < $reference_count; ++$i ) {
              if ( $i >= MAX_TILES ) {
                $this->data_was_skipped = true;
                break;
              }
              $num_read_bytes += $num_bytes_per_id;
              if ( $box->content_size < $num_read_bytes ) {
                return INVALID;
              }
              if ( !( $data = read( $this->handle, $num_bytes_per_id ) ) ) {
                return TRUNCATED;
              }
              $to_item_id = read_big_endian( $data, $num_bytes_per_id );
              $tile_count = count( $this->features->tiles );
              if ( $from_item_id <= MAX_VALUE && $to_item_id <= MAX_VALUE &&
                   $tile_count < MAX_TILES ) {
                $this->features->tiles[$tile_count]                 = new Tile();
                $this->features->tiles[$tile_count]->tile_item_id   = $to_item_id;
                $this->features->tiles[$tile_count]->parent_item_id = $from_item_id;
              } else {
                $this->data_was_skipped = true;
              }
            }

            // If all features are available now, do not look further.
            $status = $this->features->get_primary_item_features();
            if ( $status != NOT_FOUND ) {
              return $status;
            }

            // Mostly if 'data_was_skipped'.
            if ( !skip( $this->handle, $box->content_size - $num_read_bytes ) ) {
              return TRUNCATED;
            }
          } else {
            if ( !skip( $this->handle, $box->content_size ) ) {
              return TRUNCATED;
            }
          }
          $num_remaining_bytes -= $box->size;
        }
        return NOT_FOUND;
      }

      /**
       * Parses a "meta" box.
       *
       * It looks for the primary item ID in the "pitm" box and recurses into other boxes
       * to find its features.
       *
       * @param stream  $handle              The resource the box will be parsed from.
       * @param int     $num_remaining_bytes The number of bytes that should be available from the resource.
       * @return Status                      FOUND on success or an error on failure.
       */
      private function parse_meta( $num_remaining_bytes ) {
        do {
          $box    = new Box();
          $status = $box->parse( $this->handle, $this->num_parsed_boxes, $num_remaining_bytes );
          if ( $status != FOUND ) {
            return $status;
          }

          if ( $box->type == 'pitm' ) {
            // See ISO/IEC 14496-12:2015(E) 8.11.4.2
            $num_bytes_per_id = ( $box->version == 0 ) ? 2 : 4;
            if ( $num_bytes_per_id > $num_remaining_bytes ) {
              return INVALID;
            }
            if ( !( $data = read( $this->handle, $num_bytes_per_id ) ) ) {
              return TRUNCATED;
            }
            $primary_item_id = read_big_endian( $data, $num_bytes_per_id );
            if ( $primary_item_id > MAX_VALUE ) {
              return ABORTED;
            }
            $this->features->has_primary_item = true;
            $this->features->primary_item_id  = $primary_item_id;
            if ( !skip( $this->handle, $box->content_size - $num_bytes_per_id ) ) {
              return TRUNCATED;
            }
          } else if ( $box->type == 'iprp' ) {
            $status = $this->parse_iprp( $box->content_size );
            if ( $status != NOT_FOUND ) {
              return $status;
            }
          } else if ( $box->type == 'iref' ) {
            $status = $this->parse_iref( $box->content_size );
            if ( $status != NOT_FOUND ) {
              return $status;
            }
          } else {
            if ( !skip( $this->handle, $box->content_size ) ) {
              return TRUNCATED;
            }
          }
          $num_remaining_bytes -= $box->size;
        } while ( $num_remaining_bytes != 0 );
        // According to ISO/IEC 14496-12:2012(E) 8.11.1.1 there is at most one "meta".
        return INVALID;
      }

      /**
       * Parses a file stream.
       *
       * The file type is checked through the "ftyp" box.
       *
       * @return bool True if the input stream is an AVIF bitstream or false.
       */
      public function parse_ftyp() {
        $box    = new Box();
        $status = $box->parse( $this->handle, $this->num_parsed_boxes );
        if ( $status != FOUND ) {
          return false;
        }

        if ( $box->type != 'ftyp' ) {
          return false;
        }
        // Iterate over brands. See ISO/IEC 14496-12:2012(E) 4.3.1
        if ( $box->content_size < 8 ) {
          return false;
        }
        for ( $i = 0; $i + 4 <= $box->content_size; $i += 4 ) {
          if ( !( $data = read( $this->handle, 4 ) ) ) {
            return false;
          }
          if ( $i == 4 ) {
            continue; // Skip minor_version.
          }
          if ( substr( $data, 0, 4 ) == 'avif' || substr( $data, 0, 4 ) == 'avis' ) {
            return skip( $this->handle, $box->content_size - ( $i + 4 ) );
          }
          if ( $i > 32 * 4 ) {
            return false; // Be reasonable.
          }

        }
        return false; // No AVIF brand no good.
      }

      /**
       * Parses a file stream.
       *
       * Features are extracted from the "meta" box.
       *
       * @return bool True if the main features of the primary item were parsed or false.
       */
      public function parse_file() {
        $box = new Box();
        while ( $box->parse( $this->handle, $this->num_parsed_boxes ) == FOUND ) {
          if ( $box->type === 'meta' ) {
            if ( $this->parse_meta( $box->content_size ) != FOUND ) {
              return false;
            }
            return true;
          }
    ```

[View all references](https://developer.wordpress.org/reference/files/wp-includes/class-avif-info.php/)
[View on Trac](https://core.trac.wordpress.org/browser/tags/7.0/src/wp-includes/class-avif-info.php#L315)
[View on GitHub](https://github.com/WordPress/wordpress-develop/blob/7.0/src/wp-includes/class-avif-info.php#L315-L781)

## User Contributed Notes

You must [log in](https://login.wordpress.org/?redirect_to=https%3A%2F%2Fdeveloper.wordpress.org%2Freference%2Fclasses%2Favifinfo-parser%2F)
before being able to contribute a note or feedback.