<filename>edid2json.py
#!/usr/bin/env python3

# Copyright 2014 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

#############################################################
# Json output parser
# Takes in binary blob, creates EDID object, and outputs Json
# This parser is primarily based on the following:
# 1. "VESA Enhanced Extended Display Identification Data Standard", Release A,
#   Revision 2, Sept. 25, 2006.
# 2. "A DTV Profile for Uncompressed High Speed Digital Interfaces",
#   ANSI/CEA-861-F, Aug., 2013.
# 3. HDMI spec.
#############################################################

"""Parses EDID into python object and outputs Json form of EDID object."""


import json
import sys

import edid.data_block as data_block
import edid.descriptor as descriptor
import edid.edid as edid
import edid.extensions as extensions
from edid.tools import BytesFromFile


def _XYDict(x_value, y_value, first='x', second='y'):
  """Take in x and y coordinates and return in dictionary form.

  Args:
    x_value: The value of the x coordinate.
    y_value: The value of the y coordinate.
    first: The key for first field.
    second: The key for second field.

  Returns:
    A dict with a x key/value pair and a y key/value pair.
  """
  return {first: x_value, second: y_value}


def GetManufacturerInfo(e):
  """Organize the manufacturer information of an EDID.

  Args:
    e: The edid.Edid object.

  Returns:
    A dictionary of manufacturer information.
  """
  return {
      'Manufacturer ID': e.manufacturer_id,
      'ID Product Code': e.product_code,
      'Serial number': e.serial_number,
      'Week of manufacture': e.manufacturing_week,
      'Year of manufacture': e.manufacturing_year,
      'Model year': e.model_year
  }


def GetBasicDisplay(e):
  """Organizes the basic display information of an EDID.

  Args:
    e: The edid.Edid object.

  Returns:
    A dictionary of basic display information.
  """
  bd = e.basic_display
  jdict = {'Video input type': 'Digital' if bd.video_input_type else 'Analog'}

  if bd.video_input_type:  # Digital

    jdict.update({
        'Color Bit Depth': bd.color_bit_depth,
        'Digital Video Interface Standard Support': bd.digital_supports
    })

  elif not bd.video_input_type:  # Analog

    jdict.update({
        'Video white and sync levels': bd.signal_level,
        'Blank-to-black setup expected': bd.blank_black,
        'Separate sync supported': bd.separate_sync,
        'Composite sync (on HSync) supported': bd.composite_sync,
        'Sync on green supported': bd.green_sync,
        'VSync serrated when composite/sync-on-green used': bd.vsync_pulse
    })

  # Shared basic display properties (both analog/digital)
  if not bd.horizontal_dim or not bd.vertical_dim:
    max_dim = None
  else:
    max_dim = _XYDict(bd.horizontal_dim, bd.vertical_dim)

  pix_str = ('Preferred timing includes native timing pixel format and refresh '
             'rate')

  jdict.update({
      'Maximum dimensions (cm)': max_dim,
      'Aspect ratio (portrait)': bd.aspect_ratio_portrait,
      'Aspect ratio (landscape)': bd.aspect_ratio_landscape,
      'Display gamma': bd.display_gamma,
      'DPM standby supported': bd.dpm_standby,
      'DPM suspend supported': bd.dpm_suspend,
      'DPM active-off supported': bd.active_off,
      'Display color type': bd.display_type,
      'sRGB Standard is default colour space': bd.srgb_as_default,
      pix_str: bd.native_preferred_timing_mode,
      'Continuous frequency supported': bd.cont_freq_support
  })

  return jdict


def GetChromaticity(e):
  """Organizes the chromaticity information of an EDID.

  Args:
    e: The edid.Edid object.

  Returns:
    A dictionary of chromaticity information.
  """
  chrom = e.chromaticity

  return {
      'Red': _XYDict(chrom.red_x, chrom.red_y),
      'Green': _XYDict(chrom.grn_x, chrom.grn_y),
      'Blue': _XYDict(chrom.blue_x, chrom.blue_y),
      'White': _XYDict(chrom.wht_x, chrom.wht_y)
  }


def GetEstablishedTiming(e):
  """Organizes the established timing information of an EDID.

  Args:
    e: The edid.Edid object.

  Returns:
    A list of established timing information.
  """
  return e.established_timings.supported_timings


def GetBaseStandardTiming(e):
  """Organizes the standard timing information of an EDID.

  Args:
    e: The edid.Edid object.

  Returns:
    A list of standard timing information.
  """
  return [BuildSt(s) for s in e.standard_timings]


def BuildSt(st):
  """Organizes information in a single standard_timings.StandardTiming object.

  Args:
    st: A standard_timings.StandardTiming object.

  Returns:
    A dictionary of standard timings information.
  """
  return {
      'X resolution': st.x_resolution,
      'Ratio': st.xy_pixel_ratio,
      'Frequency': st.vertical_freq
  }


def GetDescriptorBlocks(e):
  """Organizes the descriptor blocks information of an EDID.

  Calls BuildBlockAnalysis on each block for detailed organization.

  Args:
    e: The edid.Edid object.

  Returns:
    A list of dictionaries of descriptor block information.
  """
  return [BuildBlockAnalysis(d) for d in e.descriptors]


def BuildBlockAnalysis(desc):
  """Organizes a single 18-byte descriptor's information.

  Called up to 4 times in a base EDID.
  Uses descriptor module to determine descriptor type.

  Args:
    desc: The descriptor being parsed.

  Returns:
    A dictionary of descriptor information.
  """
  mydict = {'Type': desc.type}

  if desc.type in (descriptor.TYPE_PRODUCT_SERIAL_NUMBER,
                   descriptor.TYPE_ALPHANUM_DATA_STRING,
                   descriptor.TYPE_DISPLAY_PRODUCT_NAME):

    mydict['Data string'] = desc.string

  elif desc.type == descriptor.TYPE_DISPLAY_RANGE_LIMITS:

    mydict['Subtype'] = desc.subtype

    vert_rate = _XYDict(desc.min_vertical_rate,
                        desc.max_vertical_rate, 'Minimum', 'Maximum')
    hor_rate = _XYDict(desc.min_horizontal_rate,
                       desc.max_horizontal_rate, 'Minimum', 'Maximum')

    mydict.update({
        'Vertical rate (Hz)': vert_rate,
        'Horizontal rate (kHz)': hor_rate,
        'Pixel clock (MHz)': desc.pixel_clock
    })

    if desc.subtype == descriptor.SUBTYPE_DISPLAY_RANGE_CVT:

      mydict.update({
          'Supported aspect ratios': desc.supported_aspect_ratios,
          'CVT blanking support': desc.cvt_blanking_support,
          'Display scaling support': desc.display_scaling_support,
          'CVT Version': desc.cvt_version,
          'Additional Pixel Clock (MHz)': desc.additional_pixel_clock,
          'Maximum active pixels': desc.max_active_pixels,
          'Preferred aspect ratio': desc.preferred_aspect_ratio,
          'Preferred vertical refresh (Hz)': desc.preferred_vert_refresh
      })

    elif desc.subtype == descriptor.SUBTYPE_DISPLAY_RANGE_2ND_GTF:

      mydict.update({
          'Start break frequency': desc.start_break_freq,
          'C': desc.c,
          'M': desc.m,
          'K': desc.k,
          'J': desc.j
      })

  elif desc.type == descriptor.TYPE_COLOR_POINT_DATA:
    cp_1 = desc.first_color_point
    cp_2 = desc.second_color_point

    mydict['Color Point'] = BuildCp(cp_1)
    mydict['Color Point'] = BuildCp(cp_2)

  elif desc.type == descriptor.TYPE_STANDARD_TIMING:

    mydict['Standard Timings'] = desc.standard_timings

  elif desc.type == descriptor.TYPE_DISPLAY_COLOR_MANAGEMENT:

    mydict['Display color management'] = {
        'Red a3:': desc.red_a3,
        'Red a2:': desc.red_a2,
        'Green a3:': desc.green_a3,
        'Green a2:': desc.green_a2,
        'Blue a3:': desc.blue_a3,
        'Blue a2:': desc.blue_a2
    }

  elif desc.type == descriptor.TYPE_CVT_TIMING:

    cvtlist = [BuildCvt(c) for c in desc.coordinated_video_timings]
    mydict['Coordinated Video Timings'] = cvtlist

  elif desc.type == descriptor.TYPE_ESTABLISHED_TIMINGS_III:

    mydict['Established Timings'] = desc.established_timings

  elif desc.type == descriptor.TYPE_MANUFACTURER_SPECIFIED:

    mydict['Blob'] = desc.GetBlob()

  elif desc.type == descriptor.TYPE_DETAILED_TIMING:

    return BuildDtd(desc)

  return mydict


def BuildCp(cp):
  """Organizes information about a single descriptor.ColorPoint object.

  Args:
    cp: A descriptor.ColorPoint object.

  Returns:
    A dictionary of color point information.
  """
  return {
      'Index number': cp.index_number,
      'White point coordinates': _XYDict(cp.white_x, cp.white_y),
      'Gamma': cp.gamma
  }


def BuildDtd(desc):
  """Organizes information about a single descriptor.DetailedTimingDescriptor.

  Used in the base EDID analysis as well as certain extensions (i.e., VTB).

  Args:
    desc: The descriptor.DetailedTimingDescriptor.

  Returns:
    A dictionary of detailed timing descriptor information.
  """
  return {
      'Type': desc.type,
      'Pixel clock (MHz)': desc.pixel_clock,
      'Addressable': _XYDict(desc.h_active_pixels, desc.v_active_lines),
      'Blanking': _XYDict(desc.h_blanking_pixels, desc.v_blanking_lines),
      'Front porch': _XYDict(desc.h_sync_offset, desc.v_sync_offset),
      'Sync pulse': _XYDict(desc.h_sync_pulse, desc.v_sync_pulse),
      'Image size (mm)': _XYDict(desc.h_display_size, desc.v_display_size),
      'Border': _XYDict(desc.h_border_pixels, desc.v_border_lines),
      'Interlace': desc.interlaced,
      'Stereo viewing': desc.stereo_mode,
      'Sync type': desc.sync_type
  }


def AnalyzeExtension(e, block_num):
  """Organizes an extension of an EDID.

  Args:
    e: The edid.Edid object.
    block_num: The index of the extension being analyzed.

  Returns:
    A dictionary of extension information.
  """
  ext = e.GetExtension(block_num)
  mydict = {'Type': ext.type}

  if ext.type == extensions.TYPE_CEA_861:

    # BASIC CEA INFO
    mydict.update({
        'Version': ext.version,
        'Underscan': ext.underscan_support,
        'Basic audio': ext.basic_audio_support,
        'YCbCr 4:4:4': ext.ycbcr444_support,
        'YCbCr 4:2:2': ext.ycbcr422_support,
        'Native DTD count': ext.native_dtd_count
    })

    # DATA BLOCKS

    dblist = []

    for db in ext.data_blocks:

      dbdict = {'Type': db.type}

      if db.type in (data_block.DB_TYPE_VIDEO,
                     data_block.DB_TYPE_YCBCR420_VIDEO):

        svd_list = []
        for svd in db.short_video_descriptors:
          svd_list.append({'Nativity': svd.nativity, 'VIC': svd.vic})

        dbdict['Short video descriptors'] = svd_list

      elif db.type == data_block.DB_TYPE_AUDIO:

        adlist = []
        for ad in db.short_audio_descriptors:

          addict = {
              'Type': ad.type,
              'Max channel count': ad.max_channel_count,
              'Supported sampling': ad.supported_sampling_freqs,
          }

          if ad.type == data_block.AUDIO_TYPE_LPCM:
            addict['Bit depth'] = ad.bit_depth
          elif ad.type == data_block.AUDIO_TYPE_DRA:
            addict['DRA value'] = ad.value
          elif ad.format_code <= 8 and ad.format_code >= 2:
            addict['Max bit rate'] = ad.max_bit_rate
          elif ad.format_code <= 14 and ad.format_code <= 9:
            addict['Value'] = ad.value
          else:
            addict['Extension code'] = ad.ext_code
            addict['Frame length'] = ad.frame_length
            if ad.mps_support:
              addict['MPS support'] = ad.mps_support

          adlist.append(addict)

        dbdict['Short audio descriptors'] = adlist

      elif db.type == data_block.DB_TYPE_SPEAKER_ALLOCATION:

        dbdict['Speaker allocation'] = db.allocation

      elif db.type in (data_block.DB_TYPE_VENDOR_SPECIFIC,
                       data_block.DB_TYPE_VENDOR_SPECIFIC_AUDIO,
                       data_block.DB_TYPE_VENDOR_SPECIFIC_VIDEO):
        dbdict['IEEE OUI'] = db.ieee_oui
        dbdict['Data payload'] = db.payload

      elif db.type == data_block.DB_TYPE_COLORIMETRY:

        dbdict['Colorimetry'] = db.colorimetry
        dbdict['Metadata'] = db.metadata

      elif db.type == data_block.DB_TYPE_VIDEO_CAPABILITY:

        dbdict.update({
            'YCC Quantization range': db.selectable_quantization_range_ycc,
            'RGB Quantization range': db.selectable_quantization_range_rgb,
            'PT behavior': db.pt_behavior,
            'IT behavior': db.it_behavior,
            'CE behavior': db.ce_behavior,
        })

      elif db.type == data_block.DB_TYPE_INFO_FRAME:

        if_proc = db.if_processing
        dbdict['InfoFrame Processing Descriptor'] = {'Data payload':
                                                     if_proc.payload}

        vsiflist = []
        for vsif in db.vsifs:

          vsifdict = {
              'Type': vsif.type,
              'Data payload': vsif.payload
          }

          if vsif.type == data_block.INFO_FRAME_TYPE_VENDOR_SPECIFIC:
            vsifdict['IEEE OUI'] = vsif.ieee_oui

          vsiflist.append(vsifdict)

        dbdict['Vendor-Specific Info Frames'] = vsiflist

      elif db.type == data_block.DB_TYPE_YCBCR420_CAPABILITY_MAP:

        dbdict['Supported descriptor indices'] = db.supported_descriptor_indices

      elif db.type == data_block.DB_TYPE_VIDEO_FORMAT_PREFERENCE:

        vps_list = []
        for vp in db.video_preferences:
          vp_json = {'Type': vp.type}
          if vp.type == 'Video Preference VIC':
            vp_json['VIC'] = vp.vic
          elif vp.type == 'Video Preference DTD':
            vp_json['DTD index'] = vp.dtd_index
          else:  # vp.type == 'Video Preference Reserved'
            vp_json['SVR'] = vp.svr

          vps_list.append(vp_json)

        dbdict['Video preferences'] = vps_list

      elif db.type == data_block.DB_TYPE_RESERVED:
        dbdict['Tag'] = db.tag
        dbdict['Data payload'] = db.GetBlob()

      else:
        raise RuntimeError(
            "Can't convert a datablock of type {}, tag {}!".format(
                db.type, db.tag))

      dblist.append(dbdict)

    mydict['Data blocks'] = dblist

    # DETAILED TIMING DESCRIPTORS

    mydict['Descriptors'] = [BuildDtd(dtd) for dtd in ext.dtds]

  elif ext.type == extensions.TYPE_VIDEO_TIMING_BLOCK:

    mydict.update({
        'Version': ext.version,
        'Detailed Timing Descriptors': [BuildDtd(d) for d in ext.dtbs],
        'Coordinated Video Timings': [BuildCvt(c) for c in ext.cvts],
        'Standard Timings': [BuildSt(s) for s in ext.sts]
    })

  elif ext.type == extensions.TYPE_EXTENSION_BLOCK_MAP:

    mydict['Tags'] = ext.all_tags

  return mydict


def BuildCvt(cvt):
  """Organizes information about a single CoordinatedVideoTiming object.

  Full object name: coordinated_video_timings.CoordinatedVideoTiming.

  Args:
    cvt: A single CoordinatedVideoTiming object.

  Returns:
    A dictionary of coordinated video timing information.
  """
  return {
      'Active vertical lines': cvt.active_vertical_lines,
      'Aspect ratio': cvt.aspect_ratio,
      'Preferred refresh rate': cvt.preferred_vertical_rate,
      'Supported refresh rates': cvt.supported_vertical_rates
  }


def BuildBase(e):
  """Organizes all information of the base EDID.

  Args:
    e: The edid.Edid object.

  Returns:
    A dictionary of EDID base block information.
  """
  return {
      'Manufacturer Info': GetManufacturerInfo(e),
      'Basic Display': GetBasicDisplay(e),
      'Chromaticity': GetChromaticity(e),
      'Established Timing': GetEstablishedTiming(e),
      'Standard Timing': GetBaseStandardTiming(e),
      'Descriptors': GetDescriptorBlocks(e)
  }


def BuildExtensions(e):
  """Organize all information of one or more extensions.

  Args:
    e: The edid.Edid object.

  Returns:
    A list of extension information.
  """
  return [AnalyzeExtension(e, x + 1)
          for x in list(range(0, e.extension_count))]


def ParseEdid(filename):
  """Create an EDID object from binary blob and convert to dictionary form.

  Args:
    filename: The name of the file containing the binary blob.

  Returns:
    A dictionary of information about the EDID object.
  """
  # Fill the edid list with bytes from binary blob
  edid_obj = edid.Edid(BytesFromFile(filename))
  errors = edid_obj.GetErrors()
  if not errors:
    return {
        'Base': BuildBase(edid_obj),
        'Extensions': BuildExtensions(edid_obj),
        'Version': edid_obj.edid_version
    }
  else:
    print('Found %d errors\n' % len(errors))
    for error in errors:
      print('At %s: %s' % (error.location, error.message))
      if error.expected:
        print('\tExpected\t%s' % error.expected)
        print('\tFound\t\t\t%s' % error.found)


_USAGE = """
Usage: %s <inputfile>

The program takes an EDID file, organizes it into a JSON object and prints
it out in the stdout.
"""

####################
# CODE STARTS HERE #
####################
if __name__ == '__main__':
  if len(sys.argv) < 2:
    print(_USAGE % sys.argv[0])
  else:
    edid_json = ParseEdid(sys.argv[1])
    if edid_json:
      print(json.dumps(edid_json, sort_keys=True, indent=4))

<filename>edid2json.py
#!/usr/bin/env python3

# Copyright 2014 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

#############################################################
# Json output parser
# Takes in binary blob, creates EDID object, and outputs Json
# This parser is primarily based on the following:
# 1. "VESA Enhanced Extended Display Identification Data Standard", Release A,
#   Revision 2, Sept. 25, 2006.
# 2. "A DTV Profile for Uncompressed High Speed Digital Interfaces",
#   ANSI/CEA-861-F, Aug., 2013.
# 3. HDMI spec.
#############################################################

"""Parses EDID into python object and outputs Json form of EDID object."""


import json
import sys

import edid.data_block as data_block
import edid.descriptor as descriptor
import edid.edid as edid
import edid.extensions as extensions
from edid.tools import BytesFromFile


def _XYDict(x_value, y_value, first='x', second='y'):
  """Take in x and y coordinates and return in dictionary form.

  Args:
    x_value: The value of the x coordinate.
    y_value: The value of the y coordinate.
    first: The key for first field.
    second: The key for second field.

  Returns:
    A dict with a x key/value pair and a y key/value pair.
  """
  return {first: x_value, second: y_value}


def GetManufacturerInfo(e):
  """Organize the manufacturer information of an EDID.

  Args:
    e: The edid.Edid object.

  Returns:
    A dictionary of manufacturer information.
  """
  return {
      'Manufacturer ID': e.manufacturer_id,
      'ID Product Code': e.product_code,
      'Serial number': e.serial_number,
      'Week of manufacture': e.manufacturing_week,
      'Year of manufacture': e.manufacturing_year,
      'Model year': e.model_year
  }


def GetBasicDisplay(e):
  """Organizes the basic display information of an EDID.

  Args:
    e: The edid.Edid object.

  Returns:
    A dictionary of basic display information.
  """
  bd = e.basic_display
  jdict = {'Video input type': 'Digital' if bd.video_input_type else 'Analog'}

  if bd.video_input_type:  # Digital

    jdict.update({
        'Color Bit Depth': bd.color_bit_depth,
        'Digital Video Interface Standard Support': bd.digital_supports
    })

  elif not bd.video_input_type:  # Analog

    jdict.update({
        'Video white and sync levels': bd.signal_level,
        'Blank-to-black setup expected': bd.blank_black,
        'Separate sync supported': bd.separate_sync,
        'Composite sync (on HSync) supported': bd.composite_sync,
        'Sync on green supported': bd.green_sync,
        'VSync serrated when composite/sync-on-green used': bd.vsync_pulse
    })

  # Shared basic display properties (both analog/digital)
  if not bd.horizontal_dim or not bd.vertical_dim:
    max_dim = None
  else:
    max_dim = _XYDict(bd.horizontal_dim, bd.vertical_dim)

  pix_str = ('Preferred timing includes native timing pixel format and refresh '
             'rate')

  jdict.update({
      'Maximum dimensions (cm)': max_dim,
      'Aspect ratio (portrait)': bd.aspect_ratio_portrait,
      'Aspect ratio (landscape)': bd.aspect_ratio_landscape,
      'Display gamma': bd.display_gamma,
      'DPM standby supported': bd.dpm_standby,
      'DPM suspend supported': bd.dpm_suspend,
      'DPM active-off supported': bd.active_off,
      'Display color type': bd.display_type,
      'sRGB Standard is default colour space': bd.srgb_as_default,
      pix_str: bd.native_preferred_timing_mode,
      'Continuous frequency supported': bd.cont_freq_support
  })

  return jdict


def GetChromaticity(e):
  """Organizes the chromaticity information of an EDID.

  Args:
    e: The edid.Edid object.

  Returns:
    A dictionary of chromaticity information.
  """
  chrom = e.chromaticity

  return {
      'Red': _XYDict(chrom.red_x, chrom.red_y),
      'Green': _XYDict(chrom.grn_x, chrom.grn_y),
      'Blue': _XYDict(chrom.blue_x, chrom.blue_y),
      'White': _XYDict(chrom.wht_x, chrom.wht_y)
  }


def GetEstablishedTiming(e):
  """Organizes the established timing information of an EDID.

  Args:
    e: The edid.Edid object.

  Returns:
    A list of established timing information.
  """
  return e.established_timings.supported_timings


def GetBaseStandardTiming(e):
  """Organizes the standard timing information of an EDID.

  Args:
    e: The edid.Edid object.

  Returns:
    A list of standard timing information.
  """
  return [BuildSt(s) for s in e.standard_timings]


def BuildSt(st):
  """Organizes information in a single standard_timings.StandardTiming object.

  Args:
    st: A standard_timings.StandardTiming object.

  Returns:
    A dictionary of standard timings information.
  """
  return {
      'X resolution': st.x_resolution,
      'Ratio': st.xy_pixel_ratio,
      'Frequency': st.vertical_freq
  }


def GetDescriptorBlocks(e):
  """Organizes the descriptor blocks information of an EDID.

  Calls BuildBlockAnalysis on each block for detailed organization.

  Args:
    e: The edid.Edid object.

  Returns:
    A list of dictionaries of descriptor block information.
  """
  return [BuildBlockAnalysis(d) for d in e.descriptors]


def BuildBlockAnalysis(desc):
  """Organizes a single 18-byte descriptor's information.

  Called up to 4 times in a base EDID.
  Uses descriptor module to determine descriptor type.

  Args:
    desc: The descriptor being parsed.

  Returns:
    A dictionary of descriptor information.
  """
  mydict = {'Type': desc.type}

  if desc.type in (descriptor.TYPE_PRODUCT_SERIAL_NUMBER,
                   descriptor.TYPE_ALPHANUM_DATA_STRING,
                   descriptor.TYPE_DISPLAY_PRODUCT_NAME):

    mydict['Data string'] = desc.string

  elif desc.type == descriptor.TYPE_DISPLAY_RANGE_LIMITS:

    mydict['Subtype'] = desc.subtype

    vert_rate = _XYDict(desc.min_vertical_rate,
                        desc.max_vertical_rate, 'Minimum', 'Maximum')
    hor_rate = _XYDict(desc.min_horizontal_rate,
                       desc.max_horizontal_rate, 'Minimum', 'Maximum')

    mydict.update({
        'Vertical rate (Hz)': vert_rate,
        'Horizontal rate (kHz)': hor_rate,
        'Pixel clock (MHz)': desc.pixel_clock
    })

    if desc.subtype == descriptor.SUBTYPE_DISPLAY_RANGE_CVT:

      mydict.update({
          'Supported aspect ratios': desc.supported_aspect_ratios,
          'CVT blanking support': desc.cvt_blanking_support,
          'Display scaling support': desc.display_scaling_support,
          'CVT Version': desc.cvt_version,
          'Additional Pixel Clock (MHz)': desc.additional_pixel_clock,
          'Maximum active pixels': desc.max_active_pixels,
          'Preferred aspect ratio': desc.preferred_aspect_ratio,
          'Preferred vertical refresh (Hz)': desc.preferred_vert_refresh
      })

    elif desc.subtype == descriptor.SUBTYPE_DISPLAY_RANGE_2ND_GTF:

      mydict.update({
          'Start break frequency': desc.start_break_freq,
          'C': desc.c,
          'M': desc.m,
          'K': desc.k,
          'J': desc.j
      })

  elif desc.type == descriptor.TYPE_COLOR_POINT_DATA:
    cp_1 = desc.first_color_point
    cp_2 = desc.second_color_point

    mydict['Color Point'] = BuildCp(cp_1)
    mydict['Color Point'] = BuildCp(cp_2)

  elif desc.type == descriptor.TYPE_STANDARD_TIMING:

    mydict['Standard Timings'] = desc.standard_timings

  elif desc.type == descriptor.TYPE_DISPLAY_COLOR_MANAGEMENT:

    mydict['Display color management'] = {
        'Red a3:': desc.red_a3,
        'Red a2:': desc.red_a2,
        'Green a3:': desc.green_a3,
        'Green a2:': desc.green_a2,
        'Blue a3:': desc.blue_a3,
        'Blue a2:': desc.blue_a2
    }

  elif desc.type == descriptor.TYPE_CVT_TIMING:

    cvtlist = [BuildCvt(c) for c in desc.coordinated_video_timings]
    mydict['Coordinated Video Timings'] = cvtlist

  elif desc.type == descriptor.TYPE_ESTABLISHED_TIMINGS_III:

    mydict['Established Timings'] = desc.established_timings

  elif desc.type == descriptor.TYPE_MANUFACTURER_SPECIFIED:

    mydict['Blob'] = desc.GetBlob()

  elif desc.type == descriptor.TYPE_DETAILED_TIMING:

    return BuildDtd(desc)

  return mydict


def BuildCp(cp):
  """Organizes information about a single descriptor.ColorPoint object.

  Args:
    cp: A descriptor.ColorPoint object.

  Returns:
    A dictionary of color point information.
  """
  return {
      'Index number': cp.index_number,
      'White point coordinates': _XYDict(cp.white_x, cp.white_y),
      'Gamma': cp.gamma
  }


def BuildDtd(desc):
  """Organizes information about a single descriptor.DetailedTimingDescriptor.

  Used in the base EDID analysis as well as certain extensions (i.e., VTB).

  Args:
    desc: The descriptor.DetailedTimingDescriptor.

  Returns:
    A dictionary of detailed timing descriptor information.
  """
  return {
      'Type': desc.type,
      'Pixel clock (MHz)': desc.pixel_clock,
      'Addressable': _XYDict(desc.h_active_pixels, desc.v_active_lines),
      'Blanking': _XYDict(desc.h_blanking_pixels, desc.v_blanking_lines),
      'Front porch': _XYDict(desc.h_sync_offset, desc.v_sync_offset),
      'Sync pulse': _XYDict(desc.h_sync_pulse, desc.v_sync_pulse),
      'Image size (mm)': _XYDict(desc.h_display_size, desc.v_display_size),
      'Border': _XYDict(desc.h_border_pixels, desc.v_border_lines),
      'Interlace': desc.interlaced,
      'Stereo viewing': desc.stereo_mode,
      'Sync type': desc.sync_type
  }


def AnalyzeExtension(e, block_num):
  """Organizes an extension of an EDID.

  Args:
    e: The edid.Edid object.
    block_num: The index of the extension being analyzed.

  Returns:
    A dictionary of extension information.
  """
  ext = e.GetExtension(block_num)
  mydict = {'Type': ext.type}

  if ext.type == extensions.TYPE_CEA_861:

    # BASIC CEA INFO
    mydict.update({
        'Version': ext.version,
        'Underscan': ext.underscan_support,
        'Basic audio': ext.basic_audio_support,
        'YCbCr 4:4:4': ext.ycbcr444_support,
        'YCbCr 4:2:2': ext.ycbcr422_support,
        'Native DTD count': ext.native_dtd_count
    })

    # DATA BLOCKS

    dblist = []

    for db in ext.data_blocks:

      dbdict = {'Type': db.type}

      if db.type in (data_block.DB_TYPE_VIDEO,
                     data_block.DB_TYPE_YCBCR420_VIDEO):

        svd_list = []
        for svd in db.short_video_descriptors:
          svd_list.append({'Nativity': svd.nativity, 'VIC': svd.vic})

        dbdict['Short video descriptors'] = svd_list

      elif db.type == data_block.DB_TYPE_AUDIO:

        adlist = []
        for ad in db.short_audio_descriptors:

          addict = {
              'Type': ad.type,
              'Max channel count': ad.max_channel_count,
              'Supported sampling': ad.supported_sampling_freqs,
          }

          if ad.type == data_block.AUDIO_TYPE_LPCM:
            addict['Bit depth'] = ad.bit_depth
          elif ad.type == data_block.AUDIO_TYPE_DRA:
            addict['DRA value'] = ad.value
          elif ad.format_code <= 8 and ad.format_code >= 2:
            addict['Max bit rate'] = ad.max_bit_rate
          elif ad.format_code <= 14 and ad.format_code <= 9:
            addict['Value'] = ad.value
          else:
            addict['Extension code'] = ad.ext_code
            addict['Frame length'] = ad.frame_length
            if ad.mps_support:
              addict['MPS support'] = ad.mps_support

          adlist.append(addict)

        dbdict['Short audio descriptors'] = adlist

      elif db.type == data_block.DB_TYPE_SPEAKER_ALLOCATION:

        dbdict['Speaker allocation'] = db.allocation

      elif db.type in (data_block.DB_TYPE_VENDOR_SPECIFIC,
                       data_block.DB_TYPE_VENDOR_SPECIFIC_AUDIO,
                       data_block.DB_TYPE_VENDOR_SPECIFIC_VIDEO):
        dbdict['IEEE OUI'] = db.ieee_oui
        dbdict['Data payload'] = db.payload

      elif db.type == data_block.DB_TYPE_COLORIMETRY:

        dbdict['Colorimetry'] = db.colorimetry
        dbdict['Metadata'] = db.metadata

      elif db.type == data_block.DB_TYPE_VIDEO_CAPABILITY:

        dbdict.update({
            'YCC Quantization range': db.selectable_quantization_range_ycc,
            'RGB Quantization range': db.selectable_quantization_range_rgb,
            'PT behavior': db.pt_behavior,
            'IT behavior': db.it_behavior,
            'CE behavior': db.ce_behavior,
        })

      elif db.type == data_block.DB_TYPE_INFO_FRAME:

        if_proc = db.if_processing
        dbdict['InfoFrame Processing Descriptor'] = {'Data payload':
                                                     if_proc.payload}

        vsiflist = []
        for vsif in db.vsifs:

          vsifdict = {
              'Type': vsif.type,
              'Data payload': vsif.payload
          }

          if vsif.type == data_block.INFO_FRAME_TYPE_VENDOR_SPECIFIC:
            vsifdict['IEEE OUI'] = vsif.ieee_oui

          vsiflist.append(vsifdict)

        dbdict['Vendor-Specific Info Frames'] = vsiflist

      elif db.type == data_block.DB_TYPE_YCBCR420_CAPABILITY_MAP:

        dbdict['Supported descriptor indices'] = db.supported_descriptor_indices

      elif db.type == data_block.DB_TYPE_VIDEO_FORMAT_PREFERENCE:

        vps_list = []
        for vp in db.video_preferences:
          vp_json = {'Type': vp.type}
          if vp.type == 'Video Preference VIC':
            vp_json['VIC'] = vp.vic
          elif vp.type == 'Video Preference DTD':
            vp_json['DTD index'] = vp.dtd_index
          else:  # vp.type == 'Video Preference Reserved'
            vp_json['SVR'] = vp.svr

          vps_list.append(vp_json)

        dbdict['Video preferences'] = vps_list

      elif db.type == data_block.DB_TYPE_RESERVED:
        dbdict['Tag'] = db.tag
        dbdict['Data payload'] = db.GetBlob()

      else:
        raise RuntimeError(
            "Can't convert a datablock of type {}, tag {}!".format(
                db.type, db.tag))

      dblist.append(dbdict)

    mydict['Data blocks'] = dblist

    # DETAILED TIMING DESCRIPTORS

    mydict['Descriptors'] = [BuildDtd(dtd) for dtd in ext.dtds]

  elif ext.type == extensions.TYPE_VIDEO_TIMING_BLOCK:

    mydict.update({
        'Version': ext.version,
        'Detailed Timing Descriptors': [BuildDtd(d) for d in ext.dtbs],
        'Coordinated Video Timings': [BuildCvt(c) for c in ext.cvts],
        'Standard Timings': [BuildSt(s) for s in ext.sts]
    })

  elif ext.type == extensions.TYPE_EXTENSION_BLOCK_MAP:

    mydict['Tags'] = ext.all_tags

  return mydict


def BuildCvt(cvt):
  """Organizes information about a single CoordinatedVideoTiming object.

  Full object name: coordinated_video_timings.CoordinatedVideoTiming.

  Args:
    cvt: A single CoordinatedVideoTiming object.

  Returns:
    A dictionary of coordinated video timing information.
  """
  return {
      'Active vertical lines': cvt.active_vertical_lines,
      'Aspect ratio': cvt.aspect_ratio,
      'Preferred refresh rate': cvt.preferred_vertical_rate,
      'Supported refresh rates': cvt.supported_vertical_rates
  }


def BuildBase(e):
  """Organizes all information of the base EDID.

  Args:
    e: The edid.Edid object.

  Returns:
    A dictionary of EDID base block information.
  """
  return {
      'Manufacturer Info': GetManufacturerInfo(e),
      'Basic Display': GetBasicDisplay(e),
      'Chromaticity': GetChromaticity(e),
      'Established Timing': GetEstablishedTiming(e),
      'Standard Timing': GetBaseStandardTiming(e),
      'Descriptors': GetDescriptorBlocks(e)
  }


def BuildExtensions(e):
  """Organize all information of one or more extensions.

  Args:
    e: The edid.Edid object.

  Returns:
    A list of extension information.
  """
  return [AnalyzeExtension(e, x + 1)
          for x in list(range(0, e.extension_count))]


def ParseEdid(filename):
  """Create an EDID object from binary blob and convert to dictionary form.

  Args:
    filename: The name of the file containing the binary blob.

  Returns:
    A dictionary of information about the EDID object.
  """
  # Fill the edid list with bytes from binary blob
  edid_obj = edid.Edid(BytesFromFile(filename))
  errors = edid_obj.GetErrors()
  if not errors:
    return {
        'Base': BuildBase(edid_obj),
        'Extensions': BuildExtensions(edid_obj),
        'Version': edid_obj.edid_version
    }
  else:
    print('Found %d errors\n' % len(errors))
    for error in errors:
      print('At %s: %s' % (error.location, error.message))
      if error.expected:
        print('\tExpected\t%s' % error.expected)
        print('\tFound\t\t\t%s' % error.found)


_USAGE = """
Usage: %s <inputfile>

The program takes an EDID file, organizes it into a JSON object and prints
it out in the stdout.
"""

####################
# CODE STARTS HERE #
####################
if __name__ == '__main__':
  if len(sys.argv) < 2:
    print(_USAGE % sys.argv[0])
  else:
    edid_json = ParseEdid(sys.argv[1])
    if edid_json:
      print(json.dumps(edid_json, sort_keys=True, indent=4))

#!/usr/bin/env python3 # Copyright 2014 The Chromium OS Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. ############################################################# # Json output parser # Takes in binary blob, creates EDID object, and outputs Json # This parser is primarily based on the following: # 1. "VESA Enhanced Extended Display Identification Data Standard", Release A, #   Revision 2, Sept. 25, 2006. # 2. "A DTV Profile for Uncompressed High Speed Digital Interfaces", #   ANSI/CEA-861-F, Aug., 2013. # 3. HDMI spec. ############################################################# Parses EDID into python object and outputs Json form of EDID object. Take in x and y coordinates and return in dictionary form.    Args:     x_value: The value of the x coordinate.     y_value: The value of the y coordinate.     first: The key for first field.     second: The key for second field.    Returns:     A dict with a x key/value pair and a y key/value pair. Organize the manufacturer information of an EDID.    Args:     e: The edid.Edid object.    Returns:     A dictionary of manufacturer information. Organizes the basic display information of an EDID.    Args:     e: The edid.Edid object.    Returns:     A dictionary of basic display information. # Digital # Analog # Shared basic display properties (both analog/digital) Organizes the chromaticity information of an EDID.    Args:     e: The edid.Edid object.    Returns:     A dictionary of chromaticity information. Organizes the established timing information of an EDID.    Args:     e: The edid.Edid object.    Returns:     A list of established timing information. Organizes the standard timing information of an EDID.    Args:     e: The edid.Edid object.    Returns:     A list of standard timing information. Organizes information in a single standard_timings.StandardTiming object.    Args:     st: A standard_timings.StandardTiming object.    Returns:     A dictionary of standard timings information. Organizes the descriptor blocks information of an EDID.    Calls BuildBlockAnalysis on each block for detailed organization.    Args:     e: The edid.Edid object.    Returns:     A list of dictionaries of descriptor block information. Organizes a single 18-byte descriptor's information.    Called up to 4 times in a base EDID.   Uses descriptor module to determine descriptor type.    Args:     desc: The descriptor being parsed.    Returns:     A dictionary of descriptor information. Organizes information about a single descriptor.ColorPoint object.    Args:     cp: A descriptor.ColorPoint object.    Returns:     A dictionary of color point information. Organizes information about a single descriptor.DetailedTimingDescriptor.    Used in the base EDID analysis as well as certain extensions (i.e., VTB).    Args:     desc: The descriptor.DetailedTimingDescriptor.    Returns:     A dictionary of detailed timing descriptor information. Organizes an extension of an EDID.    Args:     e: The edid.Edid object.     block_num: The index of the extension being analyzed.    Returns:     A dictionary of extension information. # BASIC CEA INFO # DATA BLOCKS # vp.type == 'Video Preference Reserved' # DETAILED TIMING DESCRIPTORS Organizes information about a single CoordinatedVideoTiming object.    Full object name: coordinated_video_timings.CoordinatedVideoTiming.    Args:     cvt: A single CoordinatedVideoTiming object.    Returns:     A dictionary of coordinated video timing information. Organizes all information of the base EDID.    Args:     e: The edid.Edid object.    Returns:     A dictionary of EDID base block information. Organize all information of one or more extensions.    Args:     e: The edid.Edid object.    Returns:     A list of extension information. Create an EDID object from binary blob and convert to dictionary form.    Args:     filename: The name of the file containing the binary blob.    Returns:     A dictionary of information about the EDID object. # Fill the edid list with bytes from binary blob Usage: %s <inputfile>  The program takes an EDID file, organizes it into a JSON object and prints it out in the stdout. #################### # CODE STARTS HERE # ####################