import flet as ft
import cv2
import tempfile
import os
import requests
import threading
import time
import base64
def main(page: ft.Page):
page.title = "Azure Custom Vision Image Capture"
page.vertical_alignment = ft.MainAxisAlignment.CENTER
page.horizontal_alignment = ft.CrossAxisAlignment.CENTER
page
.padding
= 0 # Ensure there
is no padding around the edges of the page
page.scroll = ft.ScrollMode.ALWAYS # Allow scrolling for any overflow
title_text = ft.Text("NITTO CORP", size=50, weight=ft.FontWeight.BOLD, text_align=ft.TextAlign.CENTER)
camera_image = ft.Image(width=640, height=480, fit=ft.ImageFit.CONTAIN)
capture_button = ft.ElevatedButton("Capture", bgcolor=ft.colors.BLUE, color=ft.colors.WHITE)
upload_button = ft.FilePicker(on_result=lambda e: upload_image(e))
upload_button_trigger = ft.ElevatedButton("Upload", bgcolor=ft.colors.ORANGE, color=ft.colors.WHITE, on_click=lambda e: upload_button.pick_files(allow_multiple=False))
cancel_button
= ft
.ElevatedButton
("Cancel"
, bgcolor
=ft
.colors
.RED
, color
=ft
.colors
.WHITE
, disabled
=True) send_button
= ft
.ElevatedButton
("Send to Azure"
, bgcolor
=ft
.colors
.GREEN
, color
=ft
.colors
.WHITE
, disabled
=True) result_text = ft.Text("", text_align=ft.TextAlign.CENTER)
temp_image_path = None
cap = None
stop_thread = False
is_uploaded_image = False
def update_camera_image():
nonlocal cap, stop_thread, stream_active, is_uploaded_image
try:
cap = cv2.VideoCapture(0)
if not cap.isOpened():
result_text
.value
= "Error
: Could not
open Camera"
page.update()
return
while not stop_thread and stream_active and not is_uploaded_image:
if ret:
_, imencode_image = cv2.imencode(".jpg", frame)
base64_image = base64.b64encode(imencode_image.tobytes()).decode("utf-8")
camera_image.src_base64 = base64_image
page.update()
time.sleep(0.03)
cap.release()
except Exception as e:
print(f"Camera stream error: {e}")
def capture_image(e):
nonlocal temp_image_path, cap, stop_thread, stream_active, is_uploaded_image
is_uploaded_image = False
time.sleep(1)
try:
if ret:
with tempfile.NamedTemporaryFile(suffix=".jpg", delete=False) as temp_file:
temp_image_path = temp_file.name
cv2.imwrite(temp_image_path, frame)
with
open(temp_image_path
, "rb"
) as image_file
: base64_image
= base64
.b64encode
(image_file
.read()).decode
("utf
-8"
) camera_image.src_base64 = base64_image
send_button.disabled = False
cancel_button.disabled = False
stream_active = False
result_text.value = "Image Captured"
else:
result_text.value = "Error: Could not capture image."
except Exception as ex:
result_text.value = f"An error occurred: {ex}"
page.update()
def upload_image(e):
nonlocal temp_image_path, is_uploaded_image, stream_active
if e.files:
temp_image_path = e.files[0].path
with
open(temp_image_path
, "rb"
) as image_file
: base64_image
= base64
.b64encode
(image_file
.read()).decode
("utf
-8"
) camera_image.src_base64 = base64_image
send_button.disabled = False
cancel_button.disabled = False
result_text.value = "Image Uploaded"
stream_active = False # Stop camera preview
else:
result_text.value = "No file selected."
page.update()
def send_to_azure(e):
nonlocal temp_image_path, stream_active, is_uploaded_image
if not temp_image_path:
result_text.value = "No image captured to send."
page.update()
return
try:
prediction_endpoint = "https://w...content-available-to-author-only...e.com/customvision/v3.0/Prediction/8df0db3e-dd98-4f2a-99f7-a50b767905e6/classify/iterations/Iteration2/image"
prediction_key = "64RvUlO1xbEvmvZwk5cHxmhbRjaN0Qy6A8YEZAzaoNIB9ze4BHsDJQQJ99BCACi0881XJ3w3AAAIACOGFGRQ"
with
open(temp_image_path
, "rb"
) as image_file
: image_data
= image_file
.read() headers = {
"Prediction-Key": prediction_key,
"Content-Type": "application/octet-stream",
}
response = requests.post(prediction_endpoint, headers=headers, data=image_data)
response.raise_for_status()
result_text.value = f"Azure response: {response.json()}"
except requests.exceptions.RequestException as ex:
result_text.value = f"Error Sending to Azure: {ex}"
except Exception as ex:
result_text.value = f"An unexpected error occurred: {ex}"
page.update()
def cancel_capture(e):
nonlocal temp_image_path, stream_active, is_uploaded_image
if temp_image_path and os.path.exists(temp_image_path):
os.remove(temp_image_path)
temp_image_path = None
camera_image.src_base64 = ""
send_button
.disabled
= True cancel_button
.disabled
= True result_text.value = "Capture Cancelled"
is_uploaded_image = False
threading
.Thread
(target
=update_camera_image
, daemon
=True).start
() page.update()
# Set the background image path
background_image_path = "C:/Users/RoboController/Downloads/名称未設定のデザイン (2).jpg"
# Create a container for the background image
background_image = ft.Image(src=background_image_path, fit=ft.ImageFit.COVER)
# Function to update background image size based on page size
def update_background():
background_image.width = page.width
background_image.height = page.height
page.update()
#
Call update_background when the page
is resized
page.on_resize = lambda e: update_background()
# Create a stack to layer the background image and the other controls
stack = ft.Stack(
controls=[
ft.Container(
content=background_image,
width=page.width,
height=page.height,
),
ft.Column([
title_text,
camera_image,
ft.Row([
capture_button,
upload_button_trigger,
cancel_button,
send_button,
], alignment=ft.MainAxisAlignment.CENTER),
result_text,
upload_button,
], horizontal_alignment=ft.CrossAxisAlignment.CENTER)
]
)
page.add(stack)
# Set initial background size
update_background()
threading
.Thread
(target
=update_camera_image
, daemon
=True).start
()
ft.app(target=main)
aW1wb3J0IGZsZXQgYXMgZnQKaW1wb3J0IGN2MgppbXBvcnQgdGVtcGZpbGUKaW1wb3J0IG9zCmltcG9ydCByZXF1ZXN0cwppbXBvcnQgdGhyZWFkaW5nCmltcG9ydCB0aW1lCmltcG9ydCBiYXNlNjQKCmRlZiBtYWluKHBhZ2U6IGZ0LlBhZ2UpOgogICAgcGFnZS50aXRsZSA9ICJBenVyZSBDdXN0b20gVmlzaW9uIEltYWdlIENhcHR1cmUiCiAgICBwYWdlLnZlcnRpY2FsX2FsaWdubWVudCA9IGZ0Lk1haW5BeGlzQWxpZ25tZW50LkNFTlRFUgogICAgcGFnZS5ob3Jpem9udGFsX2FsaWdubWVudCA9IGZ0LkNyb3NzQXhpc0FsaWdubWVudC5DRU5URVIKICAgIHBhZ2UucGFkZGluZyA9IDAgICMgRW5zdXJlIHRoZXJlIGlzIG5vIHBhZGRpbmcgYXJvdW5kIHRoZSBlZGdlcyBvZiB0aGUgcGFnZQogICAgcGFnZS5zY3JvbGwgPSBmdC5TY3JvbGxNb2RlLkFMV0FZUyAgIyBBbGxvdyBzY3JvbGxpbmcgZm9yIGFueSBvdmVyZmxvdwoKICAgIHRpdGxlX3RleHQgPSBmdC5UZXh0KCJOSVRUTyBDT1JQIiwgc2l6ZT01MCwgd2VpZ2h0PWZ0LkZvbnRXZWlnaHQuQk9MRCwgdGV4dF9hbGlnbj1mdC5UZXh0QWxpZ24uQ0VOVEVSKQogICAgCiAgICBjYW1lcmFfaW1hZ2UgPSBmdC5JbWFnZSh3aWR0aD02NDAsIGhlaWdodD00ODAsIGZpdD1mdC5JbWFnZUZpdC5DT05UQUlOKQogICAgY2FwdHVyZV9idXR0b24gPSBmdC5FbGV2YXRlZEJ1dHRvbigiQ2FwdHVyZSIsIGJnY29sb3I9ZnQuY29sb3JzLkJMVUUsIGNvbG9yPWZ0LmNvbG9ycy5XSElURSkKICAgIHVwbG9hZF9idXR0b24gPSBmdC5GaWxlUGlja2VyKG9uX3Jlc3VsdD1sYW1iZGEgZTogdXBsb2FkX2ltYWdlKGUpKQogICAgdXBsb2FkX2J1dHRvbl90cmlnZ2VyID0gZnQuRWxldmF0ZWRCdXR0b24oIlVwbG9hZCIsIGJnY29sb3I9ZnQuY29sb3JzLk9SQU5HRSwgY29sb3I9ZnQuY29sb3JzLldISVRFLCBvbl9jbGljaz1sYW1iZGEgZTogdXBsb2FkX2J1dHRvbi5waWNrX2ZpbGVzKGFsbG93X211bHRpcGxlPUZhbHNlKSkKICAgIGNhbmNlbF9idXR0b24gPSBmdC5FbGV2YXRlZEJ1dHRvbigiQ2FuY2VsIiwgYmdjb2xvcj1mdC5jb2xvcnMuUkVELCBjb2xvcj1mdC5jb2xvcnMuV0hJVEUsIGRpc2FibGVkPVRydWUpCiAgICBzZW5kX2J1dHRvbiA9IGZ0LkVsZXZhdGVkQnV0dG9uKCJTZW5kIHRvIEF6dXJlIiwgYmdjb2xvcj1mdC5jb2xvcnMuR1JFRU4sIGNvbG9yPWZ0LmNvbG9ycy5XSElURSwgZGlzYWJsZWQ9VHJ1ZSkKICAgIHJlc3VsdF90ZXh0ID0gZnQuVGV4dCgiIiwgdGV4dF9hbGlnbj1mdC5UZXh0QWxpZ24uQ0VOVEVSKQoKICAgIHRlbXBfaW1hZ2VfcGF0aCA9IE5vbmUKICAgIGNhcCA9IE5vbmUKICAgIHN0b3BfdGhyZWFkID0gRmFsc2UKICAgIHN0cmVhbV9hY3RpdmUgPSBUcnVlCiAgICBpc191cGxvYWRlZF9pbWFnZSA9IEZhbHNlCgogICAgZGVmIHVwZGF0ZV9jYW1lcmFfaW1hZ2UoKToKICAgICAgICBub25sb2NhbCBjYXAsIHN0b3BfdGhyZWFkLCBzdHJlYW1fYWN0aXZlLCBpc191cGxvYWRlZF9pbWFnZQogICAgICAgIHRyeToKICAgICAgICAgICAgY2FwID0gY3YyLlZpZGVvQ2FwdHVyZSgwKQogICAgICAgICAgICBpZiBub3QgY2FwLmlzT3BlbmVkKCk6CiAgICAgICAgICAgICAgICByZXN1bHRfdGV4dC52YWx1ZSA9ICJFcnJvcjogQ291bGQgbm90IG9wZW4gQ2FtZXJhIgogICAgICAgICAgICAgICAgcGFnZS51cGRhdGUoKQogICAgICAgICAgICAgICAgcmV0dXJuCgogICAgICAgICAgICB3aGlsZSBub3Qgc3RvcF90aHJlYWQgYW5kIHN0cmVhbV9hY3RpdmUgYW5kIG5vdCBpc191cGxvYWRlZF9pbWFnZToKICAgICAgICAgICAgICAgIHJldCwgZnJhbWUgPSBjYXAucmVhZCgpCiAgICAgICAgICAgICAgICBpZiByZXQ6CiAgICAgICAgICAgICAgICAgICAgXywgaW1lbmNvZGVfaW1hZ2UgPSBjdjIuaW1lbmNvZGUoIi5qcGciLCBmcmFtZSkKICAgICAgICAgICAgICAgICAgICBiYXNlNjRfaW1hZ2UgPSBiYXNlNjQuYjY0ZW5jb2RlKGltZW5jb2RlX2ltYWdlLnRvYnl0ZXMoKSkuZGVjb2RlKCJ1dGYtOCIpCiAgICAgICAgICAgICAgICAgICAgY2FtZXJhX2ltYWdlLnNyY19iYXNlNjQgPSBiYXNlNjRfaW1hZ2UKICAgICAgICAgICAgICAgICAgICBwYWdlLnVwZGF0ZSgpCiAgICAgICAgICAgICAgICB0aW1lLnNsZWVwKDAuMDMpCiAgICAgICAgICAgIGNhcC5yZWxlYXNlKCkKICAgICAgICBleGNlcHQgRXhjZXB0aW9uIGFzIGU6CiAgICAgICAgICAgIHByaW50KGYiQ2FtZXJhIHN0cmVhbSBlcnJvcjoge2V9IikKCiAgICBkZWYgY2FwdHVyZV9pbWFnZShlKToKICAgICAgICBub25sb2NhbCB0ZW1wX2ltYWdlX3BhdGgsIGNhcCwgc3RvcF90aHJlYWQsIHN0cmVhbV9hY3RpdmUsIGlzX3VwbG9hZGVkX2ltYWdlCiAgICAgICAgaXNfdXBsb2FkZWRfaW1hZ2UgPSBGYWxzZQogICAgICAgIHRpbWUuc2xlZXAoMSkKICAgICAgICB0cnk6CiAgICAgICAgICAgIHJldCwgZnJhbWUgPSBjYXAucmVhZCgpCiAgICAgICAgICAgIGlmIHJldDoKICAgICAgICAgICAgICAgIHdpdGggdGVtcGZpbGUuTmFtZWRUZW1wb3JhcnlGaWxlKHN1ZmZpeD0iLmpwZyIsIGRlbGV0ZT1GYWxzZSkgYXMgdGVtcF9maWxlOgogICAgICAgICAgICAgICAgICAgIHRlbXBfaW1hZ2VfcGF0aCA9IHRlbXBfZmlsZS5uYW1lCiAgICAgICAgICAgICAgICAgICAgY3YyLmltd3JpdGUodGVtcF9pbWFnZV9wYXRoLCBmcmFtZSkKICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgd2l0aCBvcGVuKHRlbXBfaW1hZ2VfcGF0aCwgInJiIikgYXMgaW1hZ2VfZmlsZToKICAgICAgICAgICAgICAgICAgICBiYXNlNjRfaW1hZ2UgPSBiYXNlNjQuYjY0ZW5jb2RlKGltYWdlX2ZpbGUucmVhZCgpKS5kZWNvZGUoInV0Zi04IikKICAgICAgICAgICAgICAgICAgICBjYW1lcmFfaW1hZ2Uuc3JjX2Jhc2U2NCA9IGJhc2U2NF9pbWFnZQogICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgc2VuZF9idXR0b24uZGlzYWJsZWQgPSBGYWxzZQogICAgICAgICAgICAgICAgY2FuY2VsX2J1dHRvbi5kaXNhYmxlZCA9IEZhbHNlCiAgICAgICAgICAgICAgICBzdHJlYW1fYWN0aXZlID0gRmFsc2UKICAgICAgICAgICAgICAgIHJlc3VsdF90ZXh0LnZhbHVlID0gIkltYWdlIENhcHR1cmVkIgogICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgcmVzdWx0X3RleHQudmFsdWUgPSAiRXJyb3I6IENvdWxkIG5vdCBjYXB0dXJlIGltYWdlLiIKICAgICAgICBleGNlcHQgRXhjZXB0aW9uIGFzIGV4OgogICAgICAgICAgICByZXN1bHRfdGV4dC52YWx1ZSA9IGYiQW4gZXJyb3Igb2NjdXJyZWQ6IHtleH0iCiAgICAgICAgcGFnZS51cGRhdGUoKQoKICAgIGRlZiB1cGxvYWRfaW1hZ2UoZSk6CiAgICAgICAgbm9ubG9jYWwgdGVtcF9pbWFnZV9wYXRoLCBpc191cGxvYWRlZF9pbWFnZSwgc3RyZWFtX2FjdGl2ZQogICAgICAgIGlmIGUuZmlsZXM6CiAgICAgICAgICAgIHRlbXBfaW1hZ2VfcGF0aCA9IGUuZmlsZXNbMF0ucGF0aAogICAgICAgICAgICB3aXRoIG9wZW4odGVtcF9pbWFnZV9wYXRoLCAicmIiKSBhcyBpbWFnZV9maWxlOgogICAgICAgICAgICAgICAgYmFzZTY0X2ltYWdlID0gYmFzZTY0LmI2NGVuY29kZShpbWFnZV9maWxlLnJlYWQoKSkuZGVjb2RlKCJ1dGYtOCIpCiAgICAgICAgICAgICAgICBjYW1lcmFfaW1hZ2Uuc3JjX2Jhc2U2NCA9IGJhc2U2NF9pbWFnZQogICAgICAgICAgICBzZW5kX2J1dHRvbi5kaXNhYmxlZCA9IEZhbHNlCiAgICAgICAgICAgIGNhbmNlbF9idXR0b24uZGlzYWJsZWQgPSBGYWxzZQogICAgICAgICAgICByZXN1bHRfdGV4dC52YWx1ZSA9ICJJbWFnZSBVcGxvYWRlZCIKCSAgICBpc191cGxvYWRlZF9pbWFnZSA9IFRydWUKICAgICAgICAgICAgc3RyZWFtX2FjdGl2ZSA9IEZhbHNlICAjIFN0b3AgY2FtZXJhIHByZXZpZXcKICAgICAgICBlbHNlOgogICAgICAgICAgICByZXN1bHRfdGV4dC52YWx1ZSA9ICJObyBmaWxlIHNlbGVjdGVkLiIKICAgICAgICBwYWdlLnVwZGF0ZSgpCgogICAgZGVmIHNlbmRfdG9fYXp1cmUoZSk6CiAgICAgICAgbm9ubG9jYWwgdGVtcF9pbWFnZV9wYXRoLCBzdHJlYW1fYWN0aXZlLCBpc191cGxvYWRlZF9pbWFnZQogICAgICAgIGlmIG5vdCB0ZW1wX2ltYWdlX3BhdGg6CiAgICAgICAgICAgIHJlc3VsdF90ZXh0LnZhbHVlID0gIk5vIGltYWdlIGNhcHR1cmVkIHRvIHNlbmQuIgogICAgICAgICAgICBwYWdlLnVwZGF0ZSgpCiAgICAgICAgICAgIHJldHVybgogICAgICAgIHRyeToKICAgICAgICAgICAgcHJlZGljdGlvbl9lbmRwb2ludCA9ICJodHRwczovL3cuLi5jb250ZW50LWF2YWlsYWJsZS10by1hdXRob3Itb25seS4uLmUuY29tL2N1c3RvbXZpc2lvbi92My4wL1ByZWRpY3Rpb24vOGRmMGRiM2UtZGQ5OC00ZjJhLTk5ZjctYTUwYjc2NzkwNWU2L2NsYXNzaWZ5L2l0ZXJhdGlvbnMvSXRlcmF0aW9uMi9pbWFnZSIKICAgICAgICAgICAgcHJlZGljdGlvbl9rZXkgPSAiNjRSdlVsTzF4YkV2bXZad2s1Y0h4bWhiUmphTjBReTZBOFlFWkF6YW9OSUI5emU0QkhzREpRUUo5OUJDQUNpMDg4MVhKM3czQUFBSUFDT0dGR1JRIgogICAgICAgICAgICB3aXRoIG9wZW4odGVtcF9pbWFnZV9wYXRoLCAicmIiKSBhcyBpbWFnZV9maWxlOgogICAgICAgICAgICAgICAgaW1hZ2VfZGF0YSA9IGltYWdlX2ZpbGUucmVhZCgpCiAgICAgICAgICAgIGhlYWRlcnMgPSB7CiAgICAgICAgICAgICAgICAiUHJlZGljdGlvbi1LZXkiOiBwcmVkaWN0aW9uX2tleSwKICAgICAgICAgICAgICAgICJDb250ZW50LVR5cGUiOiAiYXBwbGljYXRpb24vb2N0ZXQtc3RyZWFtIiwKICAgICAgICAgICAgfQogICAgICAgICAgICByZXNwb25zZSA9IHJlcXVlc3RzLnBvc3QocHJlZGljdGlvbl9lbmRwb2ludCwgaGVhZGVycz1oZWFkZXJzLCBkYXRhPWltYWdlX2RhdGEpCiAgICAgICAgICAgIHJlc3BvbnNlLnJhaXNlX2Zvcl9zdGF0dXMoKQogICAgICAgICAgICByZXN1bHRfdGV4dC52YWx1ZSA9IGYiQXp1cmUgcmVzcG9uc2U6IHtyZXNwb25zZS5qc29uKCl9IgogICAgICAgIGV4Y2VwdCByZXF1ZXN0cy5leGNlcHRpb25zLlJlcXVlc3RFeGNlcHRpb24gYXMgZXg6CiAgICAgICAgICAgIHJlc3VsdF90ZXh0LnZhbHVlID0gZiJFcnJvciBTZW5kaW5nIHRvIEF6dXJlOiB7ZXh9IgogICAgICAgIGV4Y2VwdCBFeGNlcHRpb24gYXMgZXg6CiAgICAgICAgICAgIHJlc3VsdF90ZXh0LnZhbHVlID0gZiJBbiB1bmV4cGVjdGVkIGVycm9yIG9jY3VycmVkOiB7ZXh9IgogICAgICAgIHBhZ2UudXBkYXRlKCkKCiAgICBkZWYgY2FuY2VsX2NhcHR1cmUoZSk6CiAgICAgICAgbm9ubG9jYWwgdGVtcF9pbWFnZV9wYXRoLCBzdHJlYW1fYWN0aXZlLCBpc191cGxvYWRlZF9pbWFnZQogICAgICAgIGlmIHRlbXBfaW1hZ2VfcGF0aCBhbmQgb3MucGF0aC5leGlzdHModGVtcF9pbWFnZV9wYXRoKToKICAgICAgICAgICAgb3MucmVtb3ZlKHRlbXBfaW1hZ2VfcGF0aCkKICAgICAgICB0ZW1wX2ltYWdlX3BhdGggPSBOb25lCiAgICAgICAgY2FtZXJhX2ltYWdlLnNyY19iYXNlNjQgPSAiIgogICAgICAgIHNlbmRfYnV0dG9uLmRpc2FibGVkID0gVHJ1ZQogICAgICAgIGNhbmNlbF9idXR0b24uZGlzYWJsZWQgPSBUcnVlCiAgICAgICAgcmVzdWx0X3RleHQudmFsdWUgPSAiQ2FwdHVyZSBDYW5jZWxsZWQiCiAgICAgICAgaXNfdXBsb2FkZWRfaW1hZ2UgPSBGYWxzZQogICAgICAgIHN0cmVhbV9hY3RpdmUgPSBUcnVlCiAgICAgICAgdGhyZWFkaW5nLlRocmVhZCh0YXJnZXQ9dXBkYXRlX2NhbWVyYV9pbWFnZSwgZGFlbW9uPVRydWUpLnN0YXJ0KCkKICAgICAgICBwYWdlLnVwZGF0ZSgpCgogICAgIyBTZXQgdGhlIGJhY2tncm91bmQgaW1hZ2UgcGF0aAogICAgYmFja2dyb3VuZF9pbWFnZV9wYXRoID0gIkM6L1VzZXJzL1JvYm9Db250cm9sbGVyL0Rvd25sb2Fkcy/lkI3np7DmnKroqK3lrprjga7jg4fjgrbjgqTjg7MgKDIpLmpwZyIKICAgIAogICAgIyBDcmVhdGUgYSBjb250YWluZXIgZm9yIHRoZSBiYWNrZ3JvdW5kIGltYWdlCiAgICBiYWNrZ3JvdW5kX2ltYWdlID0gZnQuSW1hZ2Uoc3JjPWJhY2tncm91bmRfaW1hZ2VfcGF0aCwgZml0PWZ0LkltYWdlRml0LkNPVkVSKQoKICAgICMgRnVuY3Rpb24gdG8gdXBkYXRlIGJhY2tncm91bmQgaW1hZ2Ugc2l6ZSBiYXNlZCBvbiBwYWdlIHNpemUKICAgIGRlZiB1cGRhdGVfYmFja2dyb3VuZCgpOgogICAgICAgIGJhY2tncm91bmRfaW1hZ2Uud2lkdGggPSBwYWdlLndpZHRoCiAgICAgICAgYmFja2dyb3VuZF9pbWFnZS5oZWlnaHQgPSBwYWdlLmhlaWdodAogICAgICAgIHBhZ2UudXBkYXRlKCkKCiAgICAjIENhbGwgdXBkYXRlX2JhY2tncm91bmQgd2hlbiB0aGUgcGFnZSBpcyByZXNpemVkCiAgICBwYWdlLm9uX3Jlc2l6ZSA9IGxhbWJkYSBlOiB1cGRhdGVfYmFja2dyb3VuZCgpCgogICAgIyBDcmVhdGUgYSBzdGFjayB0byBsYXllciB0aGUgYmFja2dyb3VuZCBpbWFnZSBhbmQgdGhlIG90aGVyIGNvbnRyb2xzCiAgICBzdGFjayA9IGZ0LlN0YWNrKAogICAgICAgIGNvbnRyb2xzPVsKICAgICAgICAgICAgZnQuQ29udGFpbmVyKAogICAgICAgICAgICAgICAgY29udGVudD1iYWNrZ3JvdW5kX2ltYWdlLAogICAgICAgICAgICAgICAgd2lkdGg9cGFnZS53aWR0aCwKICAgICAgICAgICAgICAgIGhlaWdodD1wYWdlLmhlaWdodCwKICAgICAgICAgICAgKSwKICAgICAgICAgICAgZnQuQ29sdW1uKFsKICAgICAgICAgICAgICAgIHRpdGxlX3RleHQsCiAgICAgICAgICAgICAgICBjYW1lcmFfaW1hZ2UsCiAgICAgICAgICAgICAgICBmdC5Sb3coWwogICAgICAgICAgICAgICAgICAgIGNhcHR1cmVfYnV0dG9uLAogICAgICAgICAgICAgICAgICAgIHVwbG9hZF9idXR0b25fdHJpZ2dlciwKICAgICAgICAgICAgICAgICAgICBjYW5jZWxfYnV0dG9uLAogICAgICAgICAgICAgICAgICAgIHNlbmRfYnV0dG9uLAogICAgICAgICAgICAgICAgXSwgYWxpZ25tZW50PWZ0Lk1haW5BeGlzQWxpZ25tZW50LkNFTlRFUiksCiAgICAgICAgICAgICAgICByZXN1bHRfdGV4dCwKICAgICAgICAgICAgICAgIHVwbG9hZF9idXR0b24sCiAgICAgICAgICAgIF0sIGhvcml6b250YWxfYWxpZ25tZW50PWZ0LkNyb3NzQXhpc0FsaWdubWVudC5DRU5URVIpCiAgICAgICAgXQogICAgKQoKICAgIHBhZ2UuYWRkKHN0YWNrKQoKICAgICMgU2V0IGluaXRpYWwgYmFja2dyb3VuZCBzaXplCiAgICB1cGRhdGVfYmFja2dyb3VuZCgpCgogICAgdGhyZWFkaW5nLlRocmVhZCh0YXJnZXQ9dXBkYXRlX2NhbWVyYV9pbWFnZSwgZGFlbW9uPVRydWUpLnN0YXJ0KCkKCmZ0LmFwcCh0YXJnZXQ9bWFpbik=