// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "media/base/video_codec_string_parsers.h"

#include <array>
#include <string_view>

#include "base/logging.h"
#include "base/notreached.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "media/base/video_color_space.h"
#include "ui/gfx/color_space.h"

namespace {

bool IsDolbyVisionAVCCodecId(std::string_view codec_id) {
  return base::StartsWith(codec_id, "dva1.", base::CompareCase::SENSITIVE) ||
         base::StartsWith(codec_id, "dvav.", base::CompareCase::SENSITIVE);
}

bool IsDolbyVisionHEVCCodecId(std::string_view codec_id) {
  return base::StartsWith(codec_id, "dvh1.", base::CompareCase::SENSITIVE) ||
         base::StartsWith(codec_id, "dvhe.", base::CompareCase::SENSITIVE);
}

bool IsDolbyVisionAV1CodecId(std::string_view codec_id) {
  return base::StartsWith(codec_id, "dav1.", base::CompareCase::SENSITIVE);
}

}  // namespace

namespace media {

// TODO(crbug.com/40232176): Remove after rollout.
// Allow parsing HEVC range extension codec string.
BASE_FEATURE(kHEVCRextCodecStringParsing, base::FEATURE_ENABLED_BY_DEFAULT);

std::optional<VideoType> ParseNewStyleVp9CodecID(std::string_view codec_id) {
  // Initialize optional fields to their defaults.
  VideoType result = {
      .codec = VideoCodec::kVP9,
      .color_space = VideoColorSpace::REC709(),
      .subsampling = VideoChromaSampling::k420,
  };

  std::vector<std::string_view> fields = base::SplitStringPiece(
      codec_id, ".", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);

  // First four fields are mandatory. No more than 9 fields are expected.
  if (fields.size() < 4 || fields.size() > 9) {
    DVLOG(3) << __func__ << " Invalid number of fields (" << fields.size()
             << ")";
    return std::nullopt;
  }

  if (fields[0] != "vp09") {
    DVLOG(3) << __func__ << " Invalid 4CC (" << fields[0] << ")";
    return std::nullopt;
  }

  std::vector<int> values;
  values.reserve(fields.size() - 1);
  for (size_t i = 1; i < fields.size(); ++i) {
    // Missing value is not allowed.
    if (fields[i] == "") {
      DVLOG(3) << __func__ << " Invalid missing field (position:" << i << ")";
      return std::nullopt;
    }
    int value;
    if (!base::StringToInt(fields[i], &value) || value < 0) {
      DVLOG(3) << __func__ << " Invalid field value (" << value << ")";
      return std::nullopt;
    }
    values.push_back(value);
  }

  const int profile_idc = values[0];
  switch (profile_idc) {
    case 0:
      result.profile = VP9PROFILE_PROFILE0;
      break;
    case 1:
      result.profile = VP9PROFILE_PROFILE1;
      break;
    case 2:
      result.profile = VP9PROFILE_PROFILE2;
      break;
    case 3:
      result.profile = VP9PROFILE_PROFILE3;
      break;
    default:
      DVLOG(3) << __func__ << " Invalid profile (" << profile_idc << ")";
      return std::nullopt;
  }

  result.level = values[1];
  switch (result.level) {
    case 10:
    case 11:
    case 20:
    case 21:
    case 30:
    case 31:
    case 40:
    case 41:
    case 50:
    case 51:
    case 52:
    case 60:
    case 61:
    case 62:
      break;
    default:
      DVLOG(3) << __func__ << " Invalid level (" << result.level << ")";
      return std::nullopt;
  }

  result.bit_depth = values[2];
  if (result.bit_depth != 8 && result.bit_depth != 10 &&
      result.bit_depth != 12) {
    DVLOG(3) << __func__ << " Invalid bit-depth (" << *result.bit_depth << ")";
    return std::nullopt;
  }

  // 4:2:0 isn't supported in profiles 1, 3.
  if (profile_idc == 1 || profile_idc == 3) {
    result.subsampling = VideoChromaSampling::k422;
  }

  if (values.size() < 4) {
    return result;
  }
  const int chroma_subsampling = values[3];
  switch (chroma_subsampling) {
    case 0:
    case 1:
      result.subsampling = VideoChromaSampling::k420;
      break;
    case 2:
      result.subsampling = VideoChromaSampling::k422;
      break;
    case 3:
      result.subsampling = VideoChromaSampling::k444;
      break;
    default:
      DVLOG(3) << __func__ << " Invalid chroma subsampling ("
               << chroma_subsampling << ")";
      return std::nullopt;
  }

  if (result.subsampling != VideoChromaSampling::k420 && profile_idc != 1 &&
      profile_idc != 3) {
    DVLOG(3) << __func__
             << " 4:2:2 and 4:4:4 are only supported in profile 1, 3";

    // Ideally this would be an error, but even Netflix broke when we tried...
    result.subsampling = VideoChromaSampling::k420;
  }

  if (values.size() < 5) {
    return result;
  }
  auto primaries = VideoColorSpace::GetPrimaryID(values[4]);
  if (primaries == VideoColorSpace::PrimaryID::INVALID) {
    DVLOG(3) << __func__ << " Invalid color primaries (" << values[4] << ")";
    return std::nullopt;
  }

  if (values.size() < 6) {
    // Default missing transfer/matrix to BT709 (per REC709 initialization).
    result.color_space = VideoColorSpace(
        primaries, VideoColorSpace::TransferID::BT709,
        VideoColorSpace::MatrixID::BT709, gfx::ColorSpace::RangeID::LIMITED);
    return result;
  }
  auto transfer = VideoColorSpace::GetTransferID(values[5]);
  if (transfer == VideoColorSpace::TransferID::INVALID) {
    DVLOG(3) << __func__ << " Invalid transfer function (" << values[5] << ")";
    return std::nullopt;
  }

  if (values.size() < 7) {
    result.color_space =
        VideoColorSpace(primaries, transfer, VideoColorSpace::MatrixID::BT709,
                        gfx::ColorSpace::RangeID::LIMITED);
    return result;
  }
  auto matrix = VideoColorSpace::GetMatrixID(values[6]);
  if (matrix == VideoColorSpace::MatrixID::INVALID) {
    DVLOG(3) << __func__ << " Invalid matrix coefficients (" << values[6]
             << ")";
    return std::nullopt;
  }
  if (matrix == VideoColorSpace::MatrixID::RGB && chroma_subsampling != 3) {
    DVLOG(3) << __func__ << " Invalid combination of chroma_subsampling ("
             << ") and matrix coefficients (" << values[6] << ")";
  }

  if (values.size() < 8) {
    result.color_space = VideoColorSpace(primaries, transfer, matrix,
                                         gfx::ColorSpace::RangeID::LIMITED);
    return result;
  }
  const int video_full_range_flag = values[7];
  if (video_full_range_flag > 1) {
    DVLOG(3) << __func__ << " Invalid full range flag ("
             << video_full_range_flag << ")";
    return std::nullopt;
  }
  auto range = video_full_range_flag == 1 ? gfx::ColorSpace::RangeID::FULL
                                          : gfx::ColorSpace::RangeID::LIMITED;
  result.color_space = VideoColorSpace(primaries, transfer, matrix, range);

  return result;
}

std::optional<VideoType> ParseLegacyVp9CodecID(std::string_view codec_id) {
  if (codec_id == "vp9" || codec_id == "vp9.0") {
    // Profile is not included in the codec string. Consumers of parsed codec
    // should handle by rejecting ambiguous string or resolving to a default
    // profile.
    VideoType result{.codec = VideoCodec::kVP9,
                     .profile = VIDEO_CODEC_PROFILE_UNKNOWN,
                     .level = kNoVideoCodecLevel};
    return result;
  }

  return std::nullopt;
}

std::optional<VideoType> ParseAv1CodecId(std::string_view codec_id) {
  // The codecs parameter string for the AOM AV1 codec is as follows:
  // See https://aomediacodec.github.io/av1-isobmff/#codecsparam.
  //
  // <sample entry4CC>.<profile>.<level><tier>.<bitDepth>.<monochrome>.
  // <chromaSubsampling>.<colorPrimaries>.<transferCharacteristics>.
  // <matrixCoefficients>.<videoFullRangeFlag>

  std::vector<std::string_view> fields = base::SplitStringPiece(
      codec_id, ".", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);

  // The parameters sample entry 4CC, profile, level, tier, and bitDepth are all
  // mandatory fields. If any of these fields are empty, or not within their
  // allowed range, the processing device SHOULD treat it as an error.
  if (fields.size() < 4 || fields.size() > 10) {
    DVLOG(3) << __func__ << " Invalid number of fields (" << fields.size()
             << ")";
    return std::nullopt;
  }

  // All the other fields (including their leading '.') are optional, mutually
  // inclusive (all or none) fields. If not specified then the values listed in
  // the table below are assumed.
  //
  // mono_chrome              0
  // chromaSubsampling        112 (4:2:0 colocated with luma (0,0))
  // colorPrimaries           1 (ITU-R BT.709)
  // transferCharacteristics  1 (ITU-R BT.709)
  // matrixCoefficients       1 (ITU-R BT.709)
  // videoFullRangeFlag       0 (studio swing representation)
  // Initialize optional fields to their defaults.
  VideoType result = {
      .codec = VideoCodec::kAV1,
      .color_space = VideoColorSpace::REC709(),
      .subsampling = VideoChromaSampling::k420,
  };

  if (fields[0] != "av01") {
    DVLOG(3) << __func__ << " Invalid AV1 4CC (" << fields[0] << ")";
    return std::nullopt;
  }

  // The level parameter value SHALL equal the first level value indicated by
  // seq_level_idx in the Sequence Header. The tier parameter value SHALL be
  // equal to M when the first seq_tier value in the Sequence Header is equal to
  // 0, and H when it is equal to 1.
  if (fields[2].size() != 3 || (fields[2][2] != 'M' && fields[2][2] != 'H')) {
    DVLOG(3) << __func__ << " Invalid level+tier (" << fields[2] << ")";
    return std::nullopt;
  }

  // Since tier has been validated, strip the trailing tier indicator to allow
  // int conversion below.
  fields[2] = fields[2].substr(0, 2);

  // Fill with dummy values to ensure parallel indices with fields.
  std::vector<int> values(fields.size(), 0);
  for (size_t i = 1; i < fields.size(); ++i) {
    if (fields[i].empty()) {
      DVLOG(3) << __func__ << " Invalid empty field (position:" << i << ")";
      return std::nullopt;
    }

    if (!base::StringToInt(fields[i], &values[i]) || values[i] < 0) {
      DVLOG(3) << __func__ << " Invalid field value (" << values[i] << ")";
      return std::nullopt;
    }
  }

  // The profile parameter value, represented by a single digit decimal, SHALL
  // equal the value of seq_profile in the Sequence Header.
  const int profile_idc = fields[1].size() == 1 ? values[1] : -1;
  switch (profile_idc) {
    case 0:
      result.profile = AV1PROFILE_PROFILE_MAIN;
      break;
    case 1:
      result.subsampling = VideoChromaSampling::k444;
      result.profile = AV1PROFILE_PROFILE_HIGH;
      break;
    case 2:
      result.profile = AV1PROFILE_PROFILE_PRO;
      break;
    default:
      DVLOG(3) << __func__ << " Invalid profile (" << fields[1] << ")";
      return std::nullopt;
  }

  // The level parameter value SHALL equal the first level value indicated by
  // seq_level_idx in the Sequence Header. Note: We validate that this field has
  // the required leading zeros above.
  result.level = values[2];
  if (result.level > 31) {
    DVLOG(3) << __func__ << " Invalid level (" << result.level << ")";
    return std::nullopt;
  }

  // The bitDepth parameter value SHALL equal the value of BitDepth variable as
  // defined in [AV1] derived from the Sequence Header. Leading zeros required.
  result.bit_depth = values[3];
  if (fields[3].size() != 2 ||
      (result.bit_depth != 8 && result.bit_depth != 10 &&
       result.bit_depth != 12)) {
    DVLOG(3) << __func__ << " Invalid bit-depth (" << fields[3] << ")";
    return std::nullopt;
  }

  if (values.size() <= 4) {
    return result;
  }

  // The monochrome parameter value, represented by a single digit decimal,
  // SHALL equal the value of mono_chrome in the Sequence Header.
  const int monochrome = values[4];
  if (fields[4].size() != 1 || monochrome > 1) {
    DVLOG(3) << __func__ << " Invalid monochrome (" << fields[4] << ")";
    return std::nullopt;
  }
  if (monochrome == 1 && result.profile == AV1PROFILE_PROFILE_HIGH) {
    DVLOG(3) << "Monochrome isn't supported in high profile.";
    return std::nullopt;
  }

  if (values.size() <= 5) {
    if (monochrome == 1) {
      result.subsampling = VideoChromaSampling::k400;
    }
    return result;
  }

  // The chromaSubsampling parameter value, represented by a three-digit
  // decimal, SHALL have its first digit equal to subsampling_x and its second
  // digit equal to subsampling_y. If both subsampling_x and subsampling_y are
  // set to 1, then the third digit SHALL be equal to chroma_sample_position,
  // otherwise it SHALL be set to 0.
  if (fields[5].size() != 3) {
    DVLOG(3) << __func__ << " Invalid chroma subsampling (" << fields[5] << ")";
    return std::nullopt;
  }

  const char subsampling_x = fields[5][0];
  const char subsampling_y = fields[5][1];
  const char chroma_sample_position = fields[5][2];
  if ((subsampling_x < '0' || subsampling_x > '1') ||
      (subsampling_y < '0' || subsampling_y > '1') ||
      (chroma_sample_position < '0' || chroma_sample_position > '3')) {
    DVLOG(3) << __func__ << " Invalid chroma subsampling (" << fields[5] << ")";
    return std::nullopt;
  }

  if (((subsampling_x == '0' || subsampling_y == '0') &&
       chroma_sample_position != '0')) {
    DVLOG(3) << __func__ << " Invalid chroma subsampling (" << fields[5] << ")";
    return std::nullopt;
  }

  if (subsampling_x == '0' && subsampling_y == '0' && monochrome == 0) {
    if (result.profile == AV1PROFILE_PROFILE_MAIN) {
      DVLOG(3) << __func__ << "4:4:4 isn't supported in main profile.";
    } else {
      result.subsampling = VideoChromaSampling::k444;
    }
  } else if (subsampling_x == '1' && subsampling_y == '0' && monochrome == 0) {
    if (result.profile != AV1PROFILE_PROFILE_PRO) {
      DVLOG(3) << __func__ << "4:2:2 is only supported in pro profile.";
    } else {
      result.subsampling = VideoChromaSampling::k422;
    }
  } else if (subsampling_x == '1' && subsampling_y == '1' && monochrome == 0) {
    result.subsampling = VideoChromaSampling::k420;
  } else if (subsampling_x == '1' && subsampling_y == '1' && monochrome == 1) {
    result.subsampling = VideoChromaSampling::k400;
  }

  if (values.size() <= 6) {
    return result;
  }

  // The colorPrimaries, transferCharacteristics, matrixCoefficients and
  // videoFullRangeFlag parameter values SHALL equal the value of matching
  // fields in the Sequence Header, if color_description_present_flag is set to
  // 1, otherwise they SHOULD not be set, defaulting to the values below. The
  // videoFullRangeFlag is represented by a single digit.
  auto primaries = VideoColorSpace::GetPrimaryID(values[6]);
  if (fields[6].size() != 2 ||
      primaries == VideoColorSpace::PrimaryID::INVALID) {
    DVLOG(3) << __func__ << " Invalid color primaries (" << fields[6] << ")";
    return std::nullopt;
  }

  if (values.size() <= 7) {
    // Default missing transfer/matrix to BT709 (AV1 ISOBMFF spec default).
    result.color_space = VideoColorSpace(
        primaries, VideoColorSpace::TransferID::BT709,
        VideoColorSpace::MatrixID::BT709, gfx::ColorSpace::RangeID::LIMITED);
    return result;
  }

  auto transfer = VideoColorSpace::GetTransferID(values[7]);
  if (fields[7].size() != 2 ||
      transfer == VideoColorSpace::TransferID::INVALID) {
    DVLOG(3) << __func__ << " Invalid transfer function (" << fields[7] << ")";
    return std::nullopt;
  }

  if (values.size() <= 8) {
    result.color_space =
        VideoColorSpace(primaries, transfer, VideoColorSpace::MatrixID::BT709,
                        gfx::ColorSpace::RangeID::LIMITED);
    return result;
  }

  auto matrix = VideoColorSpace::GetMatrixID(values[8]);
  if (fields[8].size() != 2 || matrix == VideoColorSpace::MatrixID::INVALID) {
    // TODO(dalecurtis): AV1 allows a few matrices we don't support yet.
    // https://crbug.com/854290
    if (values[8] == 12 || values[8] == 13 || values[8] == 14) {
      DVLOG(3) << __func__ << " Unsupported matrix coefficients (" << fields[8]
               << ")";
    } else {
      DVLOG(3) << __func__ << " Invalid matrix coefficients (" << fields[8]
               << ")";
    }
    return std::nullopt;
  }

  if (values.size() <= 9) {
    result.color_space = VideoColorSpace(primaries, transfer, matrix,
                                         gfx::ColorSpace::RangeID::LIMITED);
    return result;
  }

  const int video_full_range_flag = values[9];
  if (fields[9].size() != 1 || video_full_range_flag > 1) {
    DVLOG(3) << __func__ << " Invalid full range flag (" << fields[9] << ")";
    return std::nullopt;
  }
  gfx::ColorSpace::RangeID range = video_full_range_flag == 1
                                       ? gfx::ColorSpace::RangeID::FULL
                                       : gfx::ColorSpace::RangeID::LIMITED;
  result.color_space = VideoColorSpace(primaries, transfer, matrix, range);

  return result;
}

std::optional<VideoType> ParseAVCCodecId(std::string_view codec_id) {
  // Make sure we have avc1.xxxxxx or avc3.xxxxxx , where xxxxxx are hex digits
  if (!base::StartsWith(codec_id, "avc1.", base::CompareCase::SENSITIVE) &&
      !base::StartsWith(codec_id, "avc3.", base::CompareCase::SENSITIVE)) {
    return std::nullopt;
  }
  uint32_t elem = 0;
  if (codec_id.size() != 11 ||
      !base::HexStringToUInt(codec_id.substr(5), &elem)) {
    DVLOG(4) << __func__ << ": invalid avc codec id (" << codec_id << ")";
    return std::nullopt;
  }

  uint8_t level_byte = elem & 0xFF;
  uint8_t constraints_byte = (elem >> 8) & 0xFF;
  uint8_t profile_idc = (elem >> 16) & 0xFF;

  // Check that the lower two bits of |constraints_byte| are zero (those are
  // reserved and must be zero according to ISO IEC 14496-10).
  if (constraints_byte & 3) {
    DVLOG(4) << __func__ << ": non-zero reserved bits in codec id " << codec_id;
    return std::nullopt;
  }

  VideoCodecProfile out_profile = VIDEO_CODEC_PROFILE_UNKNOWN;
  // profile_idc values for each profile are taken from ISO IEC 14496-10 and
  // https://en.wikipedia.org/wiki/H.264/MPEG-4_AVC#Profiles
  switch (profile_idc) {
    case 66:
      out_profile = H264PROFILE_BASELINE;
      break;
    case 77:
      out_profile = H264PROFILE_MAIN;
      break;
    case 83:
      out_profile = H264PROFILE_SCALABLEBASELINE;
      break;
    case 86:
      out_profile = H264PROFILE_SCALABLEHIGH;
      break;
    case 88:
      out_profile = H264PROFILE_EXTENDED;
      break;
    case 100:
      out_profile = H264PROFILE_HIGH;
      break;
    case 110:
      out_profile = H264PROFILE_HIGH10PROFILE;
      break;
    case 118:
      out_profile = H264PROFILE_MULTIVIEWHIGH;
      break;
    case 122:
      out_profile = H264PROFILE_HIGH422PROFILE;
      break;
    case 128:
      out_profile = H264PROFILE_STEREOHIGH;
      break;
    case 244:
      out_profile = H264PROFILE_HIGH444PREDICTIVEPROFILE;
      break;
    default:
      DVLOG(1) << "Warning: unrecognized AVC/H.264 profile " << profile_idc;
      return std::nullopt;
  }

  // TODO(servolk): Take into account also constraint set flags 3 through 5.
  uint8_t constraint_set0_flag = (constraints_byte >> 7) & 1;
  uint8_t constraint_set1_flag = (constraints_byte >> 6) & 1;
  uint8_t constraint_set2_flag = (constraints_byte >> 5) & 1;
  if (constraint_set2_flag && out_profile > H264PROFILE_EXTENDED) {
    out_profile = H264PROFILE_EXTENDED;
  }
  if (constraint_set1_flag && out_profile > H264PROFILE_MAIN) {
    out_profile = H264PROFILE_MAIN;
  }
  if (constraint_set0_flag && out_profile > H264PROFILE_BASELINE) {
    out_profile = H264PROFILE_BASELINE;
  }

  VideoType result = {
      .codec = VideoCodec::kH264,
      .profile = out_profile,
      .level = level_byte,
  };
  return result;
}

// The specification for HEVC codec id strings can be found in ISO IEC 14496-15
// dated 2012 or newer in the Annex E.3
std::optional<VideoType> ParseHEVCCodecId(std::string_view codec_id) {
  if (!base::StartsWith(codec_id, "hev1.", base::CompareCase::SENSITIVE) &&
      !base::StartsWith(codec_id, "hvc1.", base::CompareCase::SENSITIVE)) {
    return std::nullopt;
  }

  // HEVC codec id consists of:
  const int kMaxHevcCodecIdLength =
      5 +  // 'hev1.' or 'hvc1.' prefix (5 chars)
      4 +  // profile, e.g. '.A12' (max 4 chars)
      9 +  // profile_compatibility, dot + 32-bit hex number (max 9 chars)
      5 +  // tier and level, e.g. '.H120' (max 5 chars)
      18;  // up to 6 constraint bytes, bytes are dot-separated and hex-encoded.

  if (codec_id.size() > kMaxHevcCodecIdLength) {
    DVLOG(4) << __func__ << ": Codec id is too long (" << codec_id << ")";
    return std::nullopt;
  }

  std::vector<std::string_view> elem = base::SplitStringPiece(
      codec_id, ".", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
  DCHECK(elem[0] == "hev1" || elem[0] == "hvc1");

  if (elem.size() < 4) {
    DVLOG(4) << __func__ << ": invalid HEVC codec id " << codec_id;
    return std::nullopt;
  }

  uint8_t general_profile_space = 0;
  if (elem[1].size() > 0 &&
      (elem[1][0] == 'A' || elem[1][0] == 'B' || elem[1][0] == 'C')) {
    general_profile_space = 1 + (elem[1][0] - 'A');
    elem[1] = elem[1].substr(1);
  }
  DCHECK(general_profile_space >= 0 && general_profile_space <= 3);

  unsigned general_profile_idc = 0;
  if (!base::StringToUint(elem[1], &general_profile_idc) ||
      general_profile_idc > 0x1f) {
    DVLOG(4) << __func__ << ": invalid general_profile_idc=" << elem[1];
    return std::nullopt;
  }

  uint32_t general_profile_compatibility_flags = 0;
  if (!base::HexStringToUInt(elem[2], &general_profile_compatibility_flags)) {
    DVLOG(4) << __func__
             << ": invalid general_profile_compatibility_flags=" << elem[2];
    return std::nullopt;
  }

  VideoCodecProfile out_profile = VIDEO_CODEC_PROFILE_UNKNOWN;
  // Spec A.3.8
  if (general_profile_idc == 11 ||
      (general_profile_compatibility_flags & 2048)) {
    out_profile = HEVCPROFILE_HIGH_THROUGHPUT_SCREEN_EXTENDED;
  }
  // Spec H.11.1.2
  if (general_profile_idc == 10 ||
      (general_profile_compatibility_flags & 1024)) {
    out_profile = HEVCPROFILE_SCALABLE_REXT;
  }
  // Spec A.3.7
  if (general_profile_idc == 9 || (general_profile_compatibility_flags & 512)) {
    out_profile = HEVCPROFILE_SCREEN_EXTENDED;
  }
  // Spec I.11.1.1
  if (general_profile_idc == 8 || (general_profile_compatibility_flags & 256)) {
    out_profile = HEVCPROFILE_3D_MAIN;
  }
  // Spec H.11.1.1
  if (general_profile_idc == 7 || (general_profile_compatibility_flags & 128)) {
    out_profile = HEVCPROFILE_SCALABLE_MAIN;
  }
  // Spec G.11.1.1
  if (general_profile_idc == 6 || (general_profile_compatibility_flags & 64)) {
    out_profile = HEVCPROFILE_MULTIVIEW_MAIN;
  }
  // Spec A.3.6
  if (general_profile_idc == 5 || (general_profile_compatibility_flags & 32)) {
    out_profile = HEVCPROFILE_HIGH_THROUGHPUT;
  }
  // Spec A.3.5
  if (general_profile_idc == 4 || (general_profile_compatibility_flags & 16)) {
    out_profile = HEVCPROFILE_REXT;
  }
  // Spec A.3.3
  // NOTICE: Do not change the order of below sections
  if (general_profile_idc == 2 || (general_profile_compatibility_flags & 4)) {
    out_profile = HEVCPROFILE_MAIN10;
  }
  // Spec A.3.2
  // When general_profile_compatibility_flag[1] is equal to 1,
  // general_profile_compatibility_flag[2] should be equal to 1 as well.
  if (general_profile_idc == 1 || (general_profile_compatibility_flags & 2)) {
    out_profile = HEVCPROFILE_MAIN;
  }
  // Spec A.3.4
  // When general_profile_compatibility_flag[3] is equal to 1,
  // general_profile_compatibility_flag[1] and
  // general_profile_compatibility_flag[2] should be equal to 1 as well.
  if (general_profile_idc == 3 || (general_profile_compatibility_flags & 8)) {
    out_profile = HEVCPROFILE_MAIN_STILL_PICTURE;
  }

  if (out_profile == VIDEO_CODEC_PROFILE_UNKNOWN) {
    DVLOG(1) << "Warning: unrecognized HEVC/H.265 general_profile_idc: "
             << general_profile_idc << ", general_profile_compatibility_flags: "
             << general_profile_compatibility_flags;
    return std::nullopt;
  }

  uint8_t general_tier_flag;
  if (elem[3].size() > 0 && (elem[3][0] == 'L' || elem[3][0] == 'H')) {
    general_tier_flag = (elem[3][0] == 'L') ? 0 : 1;
    elem[3] = elem[3].substr(1);
  } else {
    DVLOG(4) << __func__ << ": invalid general_tier_flag=" << elem[3];
    return std::nullopt;
  }
  DCHECK(general_tier_flag == 0 || general_tier_flag == 1);

  unsigned general_level_idc = 0;
  if (!base::StringToUint(elem[3], &general_level_idc) ||
      general_level_idc > 0xff) {
    DVLOG(4) << __func__ << ": invalid general_level_idc=" << elem[3];
    return std::nullopt;
  }

  std::array<uint8_t, 6> constraint_flags = {};

  if (elem.size() > 10) {
    DVLOG(4) << __func__ << ": unexpected number of trailing bytes in HEVC "
             << "codec id " << codec_id;
    return std::nullopt;
  }

  // ISO/IEC 14496-15, section E.3:
  // Each of the 6 bytes of the constraint flags from SPS profile-tier-level
  // syntax, starting from the byte containing the
  // general_progressive_source_flag, each encoded as a hex number, and the
  // encoding of each byte is separated by a period; trailing bytes that are
  // zero may be omitted.
  for (size_t i = 4; i < elem.size(); ++i) {
    unsigned constr_byte = 0;
    if (!base::HexStringToUInt(elem[i], &constr_byte) || constr_byte > 0xFF) {
      DVLOG(4) << __func__ << ": invalid constraint byte=" << elem[i];
      return std::nullopt;
    }
    constraint_flags[i - 4] = constr_byte;
  }

  std::optional<uint8_t> bit_depth;
  std::optional<VideoChromaSampling> subsampling;

  // HEVC spec 7.3.3:
  // For range extension, low 4-bits of constraint_flags[0] contain the max
  // bit constraint flags for 12b/10b/8b, followed by yuv422 chroma sampling
  // constraint flag; constraint_flags[1] contains yuv420 chroma sampling, and
  // monochrome constraint flags, which we use to determine the bit depth and
  // chroma subsampling formats.
  if (base::FeatureList::IsEnabled(kHEVCRextCodecStringParsing) &&
      out_profile == HEVCPROFILE_REXT) {
    uint8_t max_bits_flag = (constraint_flags[0] >> 1) & 0b111;
    bool max_422_flag = constraint_flags[0] & 0b1;
    bool max_420_flag = constraint_flags[1] >> 7;
    bool max_monochrome_flag = (constraint_flags[1] >> 6) & 0b1;

    // Spec Table A.2
    switch (max_bits_flag) {
      case 0b000:
        bit_depth = 16;
        break;
      case 0b100:
        bit_depth = 12;
        break;
      case 0b110:
        bit_depth = 10;
        break;
      case 0b111:
        bit_depth = 8;
        break;
      default:
        DVLOG(4) << __func__ << "Invalid max_bits_flag=" << max_bits_flag;
        return std::nullopt;
    }

    if (max_monochrome_flag && (!max_422_flag || !max_420_flag)) {
      DVLOG(4) << __func__ << "When max_monochrome_flag is set, both "
               << "max_422_flag and max_420_flag should be set.";
      return std::nullopt;
    }
    if (max_420_flag && !max_422_flag) {
      DVLOG(4) << __func__ << "When max_420_flag is set, max_422_flag must "
               << "be set.";
      return std::nullopt;
    }

    if (max_monochrome_flag) {
      subsampling = VideoChromaSampling::k400;
    } else if (max_420_flag) {
      subsampling = VideoChromaSampling::k420;
    } else if (max_422_flag) {
      subsampling = VideoChromaSampling::k422;
    } else {
      subsampling = VideoChromaSampling::k444;
    }
  }

  // We only configure bit depth and chroma subsampling for range extension, as
  // it is only for range extension that we further check platform support on
  // specific bit depth and chroma subsampling formats for decode/encode; and we
  // don't support general_profile_idc > 4 with hardware encoders/decoders.
  // TODO(crbug.com/413659852) HEVC has color space information that should be
  // parsed. It's specified in the ISO 14496-15:2024 spec.
  VideoType result = {
      .codec = VideoCodec::kHEVC,
      .profile = out_profile,
      .level = general_level_idc,
      .color_space = VideoColorSpace(VideoColorSpace::PrimaryID::UNSPECIFIED,
                                     VideoColorSpace::TransferID::UNSPECIFIED,
                                     VideoColorSpace::MatrixID::UNSPECIFIED,
                                     gfx::ColorSpace::RangeID::DERIVED),
      .subsampling = subsampling,
      .bit_depth = bit_depth,
  };

  return result;
}

// The specification for Dolby Vision codec id strings can be found in Dolby
// Vision streams within the MPEG-DASH format:
// https://professional.dolby.com/siteassets/content-creation/dolby-vision-for-content-creators/dolbyvisioninmpegdashspecification_v2_0_public_20190107.pdf
std::optional<VideoType> ParseDolbyVisionCodecId(std::string_view codec_id) {
  if (!IsDolbyVisionAVCCodecId(codec_id) &&
      !IsDolbyVisionHEVCCodecId(codec_id) &&
      !IsDolbyVisionAV1CodecId(codec_id)) {
    return std::nullopt;
  }

  const int kMaxDvCodecIdLength = 5     // FOURCC string
                                  + 1   // delimiting period
                                  + 2   // profile id as 2 digit string
                                  + 1   // delimiting period
                                  + 2;  // level id as 2 digit string.

  if (codec_id.size() > kMaxDvCodecIdLength) {
    DVLOG(4) << __func__ << ": Codec id is too long (" << codec_id << ")";
    return std::nullopt;
  }

  std::vector<std::string_view> elem = base::SplitStringPiece(
      codec_id, ".", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
  DCHECK(elem[0] == "dvh1" || elem[0] == "dvhe" || elem[0] == "dva1" ||
         elem[0] == "dvav" || elem[0] == "dav1");

  if (elem.size() != 3) {
    DVLOG(4) << __func__ << ": invalid dolby vision codec id " << codec_id;
    return std::nullopt;
  }

  // Profile string should be two digits.
  unsigned profile_id = 0;
  if (elem[1].size() != 2 || !base::StringToUint(elem[1], &profile_id) ||
      profile_id > 20) {
    DVLOG(4) << __func__ << ": invalid format or profile_id=" << elem[1];
    return std::nullopt;
  }

  VideoType result = {
      .codec = VideoCodec::kDolbyVision,
  };

  // Only profiles 0, 4, 5, 7, 8, 9, 10 and 20 are valid. Profile 0 and 9 are
  // encoded based on AVC, profile 4, 5, 7, 8 and 20 are based on HEVC, profile
  // 10 is based on AV1.
  switch (profile_id) {
    case 0:
    case 9:
      if (!IsDolbyVisionAVCCodecId(codec_id)) {
        DVLOG(4) << __func__
                 << ": codec id is mismatched with profile_id=" << profile_id;
        return std::nullopt;
      }
      if (profile_id == 0) {
        result.profile = DOLBYVISION_PROFILE0;
      } else if (profile_id == 9) {
        result.profile = DOLBYVISION_PROFILE9;
      }
      break;
    case 5:
    case 7:
    case 8:
    case 20:
      if (!IsDolbyVisionHEVCCodecId(codec_id)) {
        DVLOG(4) << __func__
                 << ": codec id is mismatched with profile_id=" << profile_id;
        return std::nullopt;
      }
      if (profile_id == 5) {
        result.profile = DOLBYVISION_PROFILE5;
      } else if (profile_id == 7) {
        result.profile = DOLBYVISION_PROFILE7;
      } else if (profile_id == 8) {
        result.profile = DOLBYVISION_PROFILE8;
      } else if (profile_id == 20) {
        result.profile = DOLBYVISION_PROFILE20;
      }
      break;
    case 10:
      if (!IsDolbyVisionAV1CodecId(codec_id)) {
        DVLOG(4) << __func__
                 << ": codec id is mismatched with profile_id=" << profile_id;
        return std::nullopt;
      }
      if (profile_id == 10) {
        result.profile = DOLBYVISION_PROFILE10;
      }
      break;
    default:
      DVLOG(4) << __func__
               << ": depecrated and not supported profile_id=" << profile_id;
      return std::nullopt;
  }

  // Level string should be two digits.
  unsigned level_id = 0;
  if (elem[2].size() != 2 || !base::StringToUint(elem[2], &level_id) ||
      level_id > 13 || level_id < 1) {
    DVLOG(4) << __func__ << ": invalid format level_id=" << elem[2];
    return std::nullopt;
  }

  result.level = level_id;
  return result;
}

std::optional<VideoType> ParseCodec(std::string_view codec_id) {
  std::vector<std::string_view> elem = base::SplitStringPiece(
      codec_id, ".", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
  if (elem.empty()) {
    return std::nullopt;
  }

  if (codec_id == "vp8" || codec_id == "vp8.0") {
    VideoType result = {
        .codec = VideoCodec::kVP8,
        .profile = VP8PROFILE_ANY,
        .level = kNoVideoCodecLevel,
    };
    return result;
  }

  if (codec_id == "theora") {
    VideoType result = {
        .codec = VideoCodec::kTheora,
        .profile = THEORAPROFILE_ANY,
        .level = kNoVideoCodecLevel,
    };
    return result;
  }

  if (auto result = ParseNewStyleVp9CodecID(codec_id)) {
    return result;
  }

  if (auto result = ParseLegacyVp9CodecID(codec_id)) {
    return result;
  }

  if (auto result = ParseAv1CodecId(codec_id)) {
    return result;
  }

  if (auto result = ParseAVCCodecId(TranslateLegacyAvc1CodecIds(codec_id))) {
    return result;
  }

  if (auto result = ParseHEVCCodecId(codec_id)) {
    return result;
  }

  if (auto result = ParseDolbyVisionCodecId(codec_id)) {
    return result;
  }

  return std::nullopt;
}

VideoCodec StringToVideoCodec(std::string_view codec_id) {
  auto result = ParseCodec(codec_id);
  return result ? result->codec : VideoCodec::kUnknown;
}

std::string TranslateLegacyAvc1CodecIds(std::string_view codec_id) {
  // Special handling for old, pre-RFC 6381 format avc1 strings, which are still
  // being used by some HLS apps to preserve backward compatibility with older
  // iOS devices. The old format was avc1.<profile>.<level>
  // Where <profile> is H.264 profile_idc encoded as a decimal number, i.e.
  // 66 is baseline profile (0x42)
  // 77 is main profile (0x4d)
  // 100 is high profile (0x64)
  // And <level> is H.264 level multiplied by 10, also encoded as decimal number
  // E.g. <level> 31 corresponds to H.264 level 3.1
  // See, for example, http://qtdevseed.apple.com/qadrift/testcases/tc-0133.php
  uint32_t level_start = 0;
  std::string result;
  if (base::StartsWith(codec_id, "avc1.66.", base::CompareCase::SENSITIVE)) {
    level_start = 8;
    result = "avc1.4200";
  } else if (base::StartsWith(codec_id, "avc1.77.",
                              base::CompareCase::SENSITIVE)) {
    level_start = 8;
    result = "avc1.4D00";
  } else if (base::StartsWith(codec_id, "avc1.100.",
                              base::CompareCase::SENSITIVE)) {
    level_start = 9;
    result = "avc1.6400";
  }

  uint32_t level = 0;
  if (level_start > 0 &&
      base::StringToUint(codec_id.substr(level_start), &level) && level < 256) {
    // This is a valid legacy avc1 codec id - return the codec id translated
    // into RFC 6381 format.
    base::AppendHexEncodedByte(static_cast<uint8_t>(level), result);
    return result;
  }

  // This is not a valid legacy avc1 codec id - return the original codec id.
  return std::string(codec_id);
}

}  // namespace media
