upload_image.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. """
  2. Module to handle image uploading and processing for Bing AI integrations.
  3. """
  4. from __future__ import annotations
  5. import json
  6. import math
  7. from aiohttp import ClientSession, FormData
  8. from ...typing import ImageType, Tuple
  9. from ...image import to_image, process_image, to_base64_jpg, ImageRequest, Image
  10. from ...requests import raise_for_status
  11. IMAGE_CONFIG = {
  12. "maxImagePixels": 360000,
  13. "imageCompressionRate": 0.7,
  14. "enableFaceBlurDebug": False,
  15. }
  16. async def upload_image(
  17. session: ClientSession,
  18. image_data: ImageType,
  19. tone: str,
  20. headers: dict
  21. ) -> ImageRequest:
  22. """
  23. Uploads an image to Bing's AI service and returns the image response.
  24. Args:
  25. session (ClientSession): The active session.
  26. image_data (bytes): The image data to be uploaded.
  27. tone (str): The tone of the conversation.
  28. proxy (str, optional): Proxy if any. Defaults to None.
  29. Raises:
  30. RuntimeError: If the image upload fails.
  31. Returns:
  32. ImageRequest: The response from the image upload.
  33. """
  34. image = to_image(image_data)
  35. new_width, new_height = calculate_new_dimensions(image)
  36. image = process_image(image, new_width, new_height)
  37. img_binary_data = to_base64_jpg(image, IMAGE_CONFIG['imageCompressionRate'])
  38. data = build_image_upload_payload(img_binary_data, tone)
  39. async with session.post("https://www.bing.com/images/kblob", data=data, headers=prepare_headers(headers)) as response:
  40. await raise_for_status(response, "Failed to upload image")
  41. return parse_image_response(await response.json())
  42. def calculate_new_dimensions(image: Image) -> Tuple[int, int]:
  43. """
  44. Calculates the new dimensions for the image based on the maximum allowed pixels.
  45. Args:
  46. image (Image): The PIL Image object.
  47. Returns:
  48. Tuple[int, int]: The new width and height for the image.
  49. """
  50. width, height = image.size
  51. max_image_pixels = IMAGE_CONFIG['maxImagePixels']
  52. if max_image_pixels / (width * height) < 1:
  53. scale_factor = math.sqrt(max_image_pixels / (width * height))
  54. return int(width * scale_factor), int(height * scale_factor)
  55. return width, height
  56. def build_image_upload_payload(image_bin: str, tone: str) -> FormData:
  57. """
  58. Builds the payload for image uploading.
  59. Args:
  60. image_bin (str): Base64 encoded image binary data.
  61. tone (str): The tone of the conversation.
  62. Returns:
  63. Tuple[str, str]: The data and boundary for the payload.
  64. """
  65. data = FormData()
  66. knowledge_request = json.dumps(build_knowledge_request(tone), ensure_ascii=False)
  67. data.add_field('knowledgeRequest', knowledge_request, content_type="application/json")
  68. data.add_field('imageBase64', image_bin)
  69. return data
  70. def build_knowledge_request(tone: str) -> dict:
  71. """
  72. Builds the knowledge request payload.
  73. Args:
  74. tone (str): The tone of the conversation.
  75. Returns:
  76. dict: The knowledge request payload.
  77. """
  78. return {
  79. "imageInfo": {},
  80. "knowledgeRequest": {
  81. 'invokedSkills': ["ImageById"],
  82. 'subscriptionId': "Bing.Chat.Multimodal",
  83. 'invokedSkillsRequestData': {
  84. 'enableFaceBlur': True
  85. },
  86. 'convoData': {
  87. 'convoid': "",
  88. 'convotone': tone
  89. }
  90. }
  91. }
  92. def prepare_headers(headers: dict) -> dict:
  93. """
  94. Prepares the headers for the image upload request.
  95. Args:
  96. session (ClientSession): The active session.
  97. boundary (str): The boundary string for the multipart/form-data.
  98. Returns:
  99. dict: The headers for the request.
  100. """
  101. headers["Referer"] = 'https://www.bing.com/search?q=Bing+AI&showconv=1&FORM=hpcodx'
  102. headers["Origin"] = 'https://www.bing.com'
  103. return headers
  104. def parse_image_response(response: dict) -> ImageRequest:
  105. """
  106. Parses the response from the image upload.
  107. Args:
  108. response (dict): The response dictionary.
  109. Raises:
  110. RuntimeError: If parsing the image info fails.
  111. Returns:
  112. ImageRequest: The parsed image response.
  113. """
  114. if not response.get('blobId'):
  115. raise RuntimeError("Failed to parse image info.")
  116. result = {'bcid': response.get('blobId', ""), 'blurredBcid': response.get('processedBlobId', "")}
  117. result["imageUrl"] = f"https://www.bing.com/images/blob?bcid={result['blurredBcid'] or result['bcid']}"
  118. result['originalImageUrl'] = (
  119. f"https://www.bing.com/images/blob?bcid={result['blurredBcid']}"
  120. if IMAGE_CONFIG["enableFaceBlurDebug"] else
  121. f"https://www.bing.com/images/blob?bcid={result['bcid']}"
  122. )
  123. return ImageRequest(result)