| #!/usr/bin/python |
| |
| # 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.""" |
| |
| from __future__ import print_function |
| |
| 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 |
| |
| |
| def BytesFromFile(filename): |
| """Reads the EDID from binary blob form into list form. |
| |
| Args: |
| filename: The name of the binary blob. |
| |
| Returns: |
| The list of bytes that make up the EDID. |
| """ |
| with open(filename, 'rb') as f: |
| chunk = f.read() |
| return map(ord, chunk) |
| |
| |
| def _XYDict(x_value, y_value, first='x', second='y'): |
| """Takes in x and y coordinates and returns 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): |
| """Organizes 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 == descriptor.TYPE_PRODUCT_SERIAL_NUMBER or |
| desc.type == descriptor.TYPE_ALPHANUM_DATA_STRING or |
| desc.type == 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 == data_block.DB_TYPE_VIDEO or |
| db.type == 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 == data_block.DB_TYPE_VENDOR_SPECIFIC or |
| db.type == data_block.DB_TYPE_VENDOR_SPECIFIC_AUDIO or |
| db.type == 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 |
| |
| 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): |
| """Organizes 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(xrange(0, |
| e.extension_count))] |
| |
| |
| def ParseEdid(filename): |
| """Creates an EDID object from binary blob and converts 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)) |