upload_image.py 4.7 KB

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