| #!/usr/bin/env python3 |
| |
| import os |
| import subprocess |
| import sys |
| |
| def parse_hex_byte(s): |
| assert(s.endswith("h")) |
| s = s[:-1] |
| assert(0 <= int(s, 16) <= 255) |
| return "0x" + s |
| |
| def parse_hex_list(s): |
| if s == "n/a": |
| return None |
| if s.startswith("("): |
| assert(s.endswith(")h")) |
| s = s[1:-2] |
| l = [b.strip() for b in s.split(",")] |
| l = [b[:-1] if b.endswith("h") else b for b in l] |
| for b in l: |
| assert(0 <= int(b, 16) <= 255) |
| return "0x" + "".join(l) |
| |
| def parse_pixel_param(l): |
| l = l[2].split(" ") |
| assert(len(l) >= 2 and l[1] == "Pixels") |
| return int(l[0]) |
| |
| def parse_line_param(l): |
| l = l[1].split(" ") |
| assert(len(l) >= 2 and l[1] == "lines") |
| return int(l[0]) |
| |
| def parse_timing(page): |
| lines = [l.strip() for l in page.splitlines()] |
| if len(lines) < 6: |
| return None |
| try: |
| i = lines.index("Detailed Timing Parameters") |
| except ValueError: |
| return None |
| raw_metadata = lines[1:i] |
| raw_params = lines[i+1:] |
| |
| metadata = {} |
| for l in raw_metadata: |
| if not ": " in l: |
| continue |
| k, v = l.split(":", 1) |
| metadata[k.strip()] = v.strip() |
| assert("Resolution" in metadata and "EDID ID" in metadata) |
| |
| if "Proposed" in metadata and not "Adopted" in metadata: |
| print("skipping proposed but not adopted timing: " + metadata["Resolution"], |
| file=sys.stderr) |
| return None |
| |
| params = {} |
| for l in raw_params: |
| if not " = " in l: |
| continue |
| tokens = [l.strip() for l in l.split("=")] |
| params[tokens[0]] = tokens[1:] |
| assert("Timing Name" in params) |
| |
| res = metadata["Resolution"] |
| size, rest = res.split(" at ", 1) |
| horiz_video, vert_video = size.split("x", 1) |
| refresh_rate_hz, rest = rest.split(" Hz ", 1) |
| reduced_blanking = "reduced blanking" in rest.lower() |
| |
| horiz_video = int(horiz_video.strip()) |
| vert_video = int(vert_video.strip()) |
| refresh_rate_hz = float(refresh_rate_hz.strip()) |
| |
| ids = {} |
| for kv in metadata["EDID ID"].split(";"): |
| k, v = kv.split(":") |
| ids[k.strip()] = v.strip() |
| assert("DMT ID" in ids) |
| |
| dmt_id = parse_hex_byte(ids["DMT ID"]) |
| edid_std_id = parse_hex_list(ids["Std. 2 Byte Code"]) |
| cvt_id = parse_hex_list(ids["CVT 3 Byte Code"]) |
| |
| pixel_clock_mhz = float(params["Pixel Clock"][0].split(";")[0]) |
| horiz_blank = parse_pixel_param(params["Hor Blank Time"]) |
| horiz_front_porch = parse_pixel_param(params["// H Front Porch"]) |
| horiz_sync_pulse = parse_pixel_param(params["Hor Sync Time"]) |
| horiz_border = parse_pixel_param(params["// H Right Border"]) |
| assert(horiz_border == parse_pixel_param(params["// H Left Border"])) |
| vert_blank = parse_line_param(params["Ver Blank Time"]) |
| vert_front_porch = parse_line_param(params["// V Front Porch"]) |
| vert_sync_pulse = parse_line_param(params["Ver Sync Time"]) |
| vert_border = parse_line_param(params["// V Bottom Border"]) |
| assert(vert_border == parse_line_param(params["// V Top Border"])) |
| |
| return { |
| "dmt_id": dmt_id, |
| "edid_std_id": 0 if edid_std_id is None else edid_std_id, |
| "cvt_id": 0 if cvt_id is None else cvt_id, |
| "horiz_video": horiz_video, |
| "vert_video": vert_video, |
| "refresh_rate_hz": refresh_rate_hz, |
| "pixel_clock_hz": int(pixel_clock_mhz * 1000 * 1000), |
| "horiz_blank": horiz_blank, |
| "horiz_front_porch": horiz_front_porch, |
| "horiz_sync_pulse": horiz_sync_pulse, |
| "horiz_border": horiz_border, |
| "vert_blank": vert_blank, |
| "vert_front_porch": vert_front_porch, |
| "vert_sync_pulse": vert_sync_pulse, |
| "vert_border": vert_border, |
| "reduced_blanking": "true" if reduced_blanking else "false", |
| } |
| |
| if len(sys.argv) != 2: |
| print("usage: gen-dmt.py <DMT PDF>", file=sys.stderr) |
| sys.exit(1) |
| in_path = sys.argv[1] |
| in_basename = os.path.basename(in_path) |
| |
| tool_dir = os.path.dirname(os.path.realpath(__file__)) |
| out_path = tool_dir + "/../dmt-table.c" |
| |
| cmd = ["pdftotext", "-nodiag", "-layout", in_path, "-"] |
| page = "" |
| timings = [] |
| for l in subprocess.check_output(cmd, text=True): |
| if l.startswith("\f"): |
| t = parse_timing(page) |
| if t is not None: |
| timings.append(t) |
| page = l[1:] |
| else: |
| page += l |
| |
| with open(out_path, "w+") as f: |
| f.write("/* DO NOT EDIT! This file has been generated by gen-dmt.py from {}. */\n\n".format(in_basename)) |
| f.write('#include "dmt.h"\n\n') |
| f.write("const struct di_dmt_timing _di_dmt_timings[] = {\n") |
| for t in timings: |
| f.write("\t{\n") |
| for k, v in t.items(): |
| f.write("\t\t.{} = {},\n".format(k, v)) |
| f.write("\t},\n") |
| f.write("};\n\n") |
| f.write("const size_t _di_dmt_timings_len = {};\n".format(len(timings))) |