├── assets ├── logo.jpeg ├── step0.jpg ├── step1.jpg ├── step2.jpg └── demo_outputs.png ├── examples └── example.py ├── pyproject.toml ├── bing_brush ├── cli.py └── bing_brush.py ├── mkdocs.yml ├── README.md ├── docs └── index.md └── .gitignore /assets/logo.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vra/bing_brush/HEAD/assets/logo.jpeg -------------------------------------------------------------------------------- /assets/step0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vra/bing_brush/HEAD/assets/step0.jpg -------------------------------------------------------------------------------- /assets/step1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vra/bing_brush/HEAD/assets/step1.jpg -------------------------------------------------------------------------------- /assets/step2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vra/bing_brush/HEAD/assets/step2.jpg -------------------------------------------------------------------------------- /assets/demo_outputs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vra/bing_brush/HEAD/assets/demo_outputs.png -------------------------------------------------------------------------------- /examples/example.py: -------------------------------------------------------------------------------- 1 | from bing_brush import BingBrush 2 | 3 | 4 | def main(): 5 | brush = BingBrush( 6 | cookie="/path/to/cookie.txt", 7 | ) 8 | 9 | brush.process( 10 | prompt="a cute panda eating bamboos", 11 | ) 12 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["hatchling"] 3 | build-backend = "hatchling.build" 4 | 5 | [project] 6 | name = "bing_brush" 7 | version = "0.1.2" 8 | authors = [ 9 | { name="Yunfeng Wang", email="wyf.brz@gmail.com" }, 10 | ] 11 | description = "One-line Image Generating Program Based on the Bing Image Createor (Powered by DALL·E 3)" 12 | readme = "README.md" 13 | requires-python = ">=3.8" 14 | classifiers = [ 15 | "Programming Language :: Python :: 3", 16 | "License :: OSI Approved :: MIT License", 17 | "Operating System :: OS Independent", 18 | ] 19 | dependencies = [ 20 | "click", 21 | "requests", 22 | "regex", 23 | ] 24 | 25 | [project.scripts] 26 | bing_brush="bing_brush.cli:cli" 27 | 28 | [project.urls] 29 | "Homepage" = "https://github.com/vra/bing_brush" 30 | "Bug Tracker" = "https://github.com/vra/bing_brush/issues" 31 | -------------------------------------------------------------------------------- /bing_brush/cli.py: -------------------------------------------------------------------------------- 1 | import click 2 | 3 | from .bing_brush import BingBrush 4 | 5 | 6 | @click.command() 7 | @click.option( 8 | "-c", 9 | "--cookie", 10 | help="cookie for https://bing.com, could be a string or a fle path", 11 | ) 12 | @click.option("-p", "--prompt", help="prompt") 13 | @click.option( 14 | "-o", 15 | "--out_folder", 16 | default="bing_brush_out_folder", 17 | help="Location to save generated images", 18 | ) 19 | @click.option( 20 | "-v", 21 | "--verbose", 22 | is_flag=True, 23 | help="if set, enter verbose mode, show detailed logs", 24 | ) 25 | def cli( 26 | cookie, 27 | prompt, 28 | out_folder, 29 | verbose, 30 | ): 31 | """ 32 | image generator using the Bing Image Create API" 33 | 34 | Args: 35 | cookie (str): 36 | prompt (str): 37 | out_folder (str): 38 | verbose (bool): 39 | """ 40 | 41 | sdk = BingBrush(cookie=cookie, verbose=verbose) 42 | 43 | sdk.process( 44 | prompt=prompt, 45 | out_folder=out_folder, 46 | ) 47 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: BingBrush 2 | site_description: 3 | repo_name: vra/bing_brush 4 | repo_url: https://github.com/vra/bing_brush 5 | copyright: Copyright © 2023 Yunfeng Wang 6 | 7 | 8 | nav: 9 | - Home: index.md 10 | 11 | 12 | theme: 13 | name: material 14 | features: 15 | - header.autohide 16 | - navigation.instant 17 | - navigation.tracking 18 | - content.code.annotate 19 | - toc.integrate 20 | - toc.follow 21 | - navigation.path 22 | - navigation.top 23 | - navigation.tabs 24 | - navigation.footer 25 | - content.code.copy 26 | icon: 27 | repo: fontawesome/brands/github 28 | palette: 29 | 30 | # Palette toggle for light mode 31 | - media: "(prefers-color-scheme: light)" 32 | scheme: default 33 | toggle: 34 | icon: material/brightness-7 35 | name: Switch to dark mode 36 | primary: teal 37 | accent: cyan 38 | 39 | # Palette toggle for dark mode 40 | - media: "(prefers-color-scheme: dark)" 41 | scheme: slate 42 | primary: teal 43 | accent: cyan 44 | toggle: 45 | icon: material/brightness-4 46 | name: Switch to light mode 47 | 48 | markdown_extensions: 49 | - admonition 50 | - pymdownx.details 51 | - pymdownx.keys 52 | - pymdownx.superfences 53 | - pymdownx.tabbed: 54 | alternate_style: true 55 | - pymdownx.tasklist: 56 | custom_checkbox: true 57 | 58 | extra_css: 59 | - static/extra.css 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | ### One-line Image Generating Program Based on the Bing Image Createor (Powered by DALL·E 3) 5 | 6 |
7 | 8 | --- 9 | 10 | Credit: The solution of invoking Bing Image Creator API is from . 11 | 12 | ## Installation 13 | ```bash 14 | pip install bing_brush 15 | ``` 16 | 17 | ### Obtain your cookie 18 | 19 | The cookie of Bing.com is needed. You need to login to Bing.com first, then follow the steps below to write your cookie to a file (e.g., `cookie.txt`): 20 | 21 | #### Step0: 22 | Vist 23 |
24 | 25 |
26 | 27 | #### Step1: 28 | Press F12 to open dev tools, then refresh the web page to run all requests again: 29 |
30 | 31 |
32 | Select any rquest with type of xhr, then click the head of the request 33 | 34 | 35 | #### Step2: 36 | In the detail of the request header, find the `Cookie` section, copy the value of it to your file (e.g., cookie.txt) 37 |
38 | 39 |
40 | Then your cookie for Bing.com has successfully be stored. 41 | 42 | ## Usage 43 | ### CLI 44 | ```bash 45 | # -c is short for --cookie, -p is short for --prompt 46 | bing_brush -c cookie.txt -p 'a cute panda eating bamboos' -o output_folder 47 | ``` 48 | This command will generate 4 pictures located in `output_folder`, example outputs: 49 |
50 | 51 |
52 | 53 | ### Python API 54 | ```python 55 | brush = BingBrush(cookie='/path/to/cookie.txt') 56 | brush.process(prompt='a cute panda eating bamboos', out_folder='output_folder') 57 | ``` 58 | 59 | 60 | ## TODO 61 | + [ ] unit test 62 | + [ ] support for obtaining cookie from os.env 63 | 64 | 65 | ## Logo 66 | Logo of this project is generated by Bing Image, prompt: 67 | > A minimalist logo vector image, square-shaped, with a magical brush implemented in Python language in the center, colorful, digitial art 68 | 69 | ## Contribution 70 | 71 | ## 72 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | ### One-line Image Generating Program Based on the Bing Image Createor (Powered by DALL·E 3) 5 | 6 |
7 | 8 | --- 9 | 10 | Credit: The solution of invoking Bing Image Creator API is from . 11 | 12 | ## Installation 13 | ```bash 14 | pip install bing_brush 15 | ``` 16 | 17 | ### Obtain your cookie 18 | 19 | The cookie of Bing.com is needed. You need to login to Bing.com first, then follow the steps below to write your cookie to a file (e.g., `cookie.txt`): 20 | 21 | #### Step0: 22 | Vist 23 |
24 | 25 |
26 | 27 | #### Step1: 28 | Press F12 to open dev tools, then refresh the web page to run all requests again: 29 |
30 | 31 |
32 | Select any rquest with type of xhr, then click the head of the request 33 | 34 | 35 | #### Step2: 36 | In the detail of the request header, find the `Cookie` section, copy the value of it to your file (e.g., cookie.txt) 37 |
38 | 39 |
40 | Then your cookie for Bing.com has successfully be stored. 41 | 42 | ## Usage 43 | ### CLI 44 | ```bash 45 | # -c is short for --cookie, -p is short for --prompt 46 | bing_brush -c cookie.txt -p 'a cute panda eating bamboos' -o output_folder 47 | ``` 48 | This command will generate 4 pictures located in `output_folder`, example outputs: 49 |
50 | 51 |
52 | 53 | ### Python API 54 | ```python 55 | brush = BingBrush(cookie='/path/to/cookie.txt') 56 | brush.process(prompt='a cute panda eating bamboos', out_folder='output_folder') 57 | ``` 58 | 59 | 60 | ## TODO 61 | + [ ] unit test 62 | + [ ] support for obtaining cookie from os.env 63 | 64 | 65 | ## Logo 66 | Logo of this project is generated by Bing Image, prompt: 67 | > A minimalist logo vector image, square-shaped, with a magical brush implemented in Python language in the center, colorful, digitial art 68 | 69 | ## Contribution 70 | 71 | ## 72 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Custom settings 2 | tmp 3 | out* 4 | ossutil_output 5 | 6 | # Byte-compiled / optimized / DLL files 7 | __pycache__/ 8 | *.py[cod] 9 | *$py.class 10 | 11 | # C extensions 12 | *.so 13 | 14 | # Distribution / packaging 15 | .Python 16 | build/ 17 | develop-eggs/ 18 | dist/ 19 | downloads/ 20 | eggs/ 21 | .eggs/ 22 | lib64/ 23 | parts/ 24 | sdist/ 25 | var/ 26 | wheels/ 27 | share/python-wheels/ 28 | *.egg-info/ 29 | .installed.cfg 30 | *.egg 31 | MANIFEST 32 | 33 | # PyInstaller 34 | # Usually these files are written by a python script from a template 35 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 36 | *.manifest 37 | *.spec 38 | 39 | # Installer logs 40 | pip-log.txt 41 | pip-delete-this-directory.txt 42 | 43 | # Unit test / coverage reports 44 | htmlcov/ 45 | .tox/ 46 | .nox/ 47 | .coverage 48 | .coverage.* 49 | .cache 50 | nosetests.xml 51 | coverage.xml 52 | *.cover 53 | *.py,cover 54 | .hypothesis/ 55 | .pytest_cache/ 56 | cover/ 57 | 58 | # Translations 59 | *.mo 60 | *.pot 61 | 62 | # Django stuff: 63 | *.log 64 | local_settings.py 65 | db.sqlite3 66 | db.sqlite3-journal 67 | 68 | # Flask stuff: 69 | instance/ 70 | .webassets-cache 71 | 72 | # Scrapy stuff: 73 | .scrapy 74 | 75 | # Sphinx documentation 76 | docs/_build/ 77 | 78 | # PyBuilder 79 | .pybuilder/ 80 | target/ 81 | 82 | # Jupyter Notebook 83 | .ipynb_checkpoints 84 | 85 | # IPython 86 | profile_default/ 87 | ipython_config.py 88 | 89 | # pyenv 90 | # For a library or package, you might want to ignore these files since the code is 91 | # intended to run in multiple environments; otherwise, check them in: 92 | # .python-version 93 | 94 | # pipenv 95 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 96 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 97 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 98 | # install all needed dependencies. 99 | #Pipfile.lock 100 | 101 | # poetry 102 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 103 | # This is especially recommended for binary packages to ensure reproducibility, and is more 104 | # commonly ignored for libraries. 105 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 106 | #poetry.lock 107 | 108 | # pdm 109 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 110 | #pdm.lock 111 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 112 | # in version control. 113 | # https://pdm.fming.dev/#use-with-ide 114 | .pdm.toml 115 | 116 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 117 | __pypackages__/ 118 | 119 | # Celery stuff 120 | celerybeat-schedule 121 | celerybeat.pid 122 | 123 | # SageMath parsed files 124 | *.sage.py 125 | 126 | # Environments 127 | .env 128 | .venv 129 | env/ 130 | venv/ 131 | ENV/ 132 | env.bak/ 133 | venv.bak/ 134 | 135 | # Spyder project settings 136 | .spyderproject 137 | .spyproject 138 | 139 | # Rope project settings 140 | .ropeproject 141 | 142 | # mkdocs documentation 143 | /site 144 | 145 | # mypy 146 | .mypy_cache/ 147 | .dmypy.json 148 | dmypy.json 149 | 150 | # Pyre type checker 151 | .pyre/ 152 | 153 | # pytype static type analyzer 154 | .pytype/ 155 | 156 | # Cython debug symbols 157 | cython_debug/ 158 | 159 | # PyCharm 160 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 161 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 162 | # and can be added to the global gitignore or merged into this file. For a more nuclear 163 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 164 | #.idea/ 165 | -------------------------------------------------------------------------------- /bing_brush/bing_brush.py: -------------------------------------------------------------------------------- 1 | from http.cookies import SimpleCookie 2 | import os 3 | import random 4 | 5 | # import re 6 | import time 7 | 8 | # from functools import partial 9 | 10 | import requests 11 | from requests.utils import cookiejar_from_dict 12 | 13 | import regex 14 | 15 | 16 | class BingBrush: 17 | def __init__( 18 | self, 19 | cookie, 20 | verbose=False, 21 | max_wait_time=600, 22 | ): 23 | self.max_wait_time = max_wait_time 24 | self.verbose = verbose 25 | 26 | self.session = self.construct_requests_session(cookie) 27 | 28 | self.prepare_error_messages() 29 | 30 | def parse_cookie(self, cookie_string): 31 | cookie = SimpleCookie() 32 | if os.path.exists(cookie_string): 33 | with open(cookie_string) as f: 34 | cookie_string = f.read() 35 | 36 | cookie.load(cookie_string) 37 | cookies_dict = {} 38 | cookiejar = None 39 | for key, morsel in cookie.items(): 40 | cookies_dict[key] = morsel.value 41 | cookiejar = cookiejar_from_dict( 42 | cookies_dict, cookiejar=None, overwrite=True 43 | ) 44 | return cookiejar 45 | 46 | def prepare_error_messages(self): 47 | self.error_message_dict = { 48 | "error_blocked_prompt": "Your prompt has been blocked by Bing. Try to change any bad words and try again.", 49 | "error_being_reviewed_prompt": "Your prompt is being reviewed by Bing. Try to change any sensitive words and try again.", 50 | "error_noresults": "Could not get results.", 51 | "error_unsupported_lang": "this language is currently not supported by bing.", 52 | "error_timeout": "Your request has timed out.", 53 | "error_redirect": "Redirect failed", 54 | "error_bad_images": "Bad images", 55 | "error_no_images": "No images", 56 | } 57 | 58 | def construct_requests_session(self, cookie): 59 | # Generate random US IP 60 | FORWARDED_IP = f"100.{random.randint(43, 63)}.{random.randint(128, 255)}.{random.randint(0, 255)}" 61 | HEADERS = { 62 | "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", 63 | "accept-language": "en-US,en;q=0.9", 64 | "cache-control": "max-age=0", 65 | "content-type": "application/x-www-form-urlencoded", 66 | "referrer": "https://www.bing.com/images/create/", 67 | "origin": "https://www.bing.com", 68 | "user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Edg/110.0.1587.63", 69 | "x-forwarded-for": FORWARDED_IP, 70 | } 71 | 72 | session = requests.Session() 73 | session.headers = HEADERS 74 | session.cookies = self.parse_cookie(cookie) 75 | return session 76 | 77 | def process_error(self, response): 78 | for error_type, error_msg in self.error_message_dict.items(): 79 | if error_msg in response.text.lower(): 80 | return Exception(error_type) 81 | 82 | def request_result_urls(self, response, url_encoded_prompt): 83 | if "Location" not in response.headers: 84 | return None, None 85 | redirect_url = response.headers["Location"].replace("&nfy=1", "") 86 | request_id = redirect_url.split("id=")[-1] 87 | return redirect_url, request_id 88 | 89 | def obtaion_image_url(self, redirect_url, request_id, url_encoded_prompt): 90 | self.session.get(f"https://www.bing.com{redirect_url}") 91 | polling_url = f"https://www.bing.com/images/create/async/results/{request_id}?q={url_encoded_prompt}" 92 | # Poll for results 93 | start_wait = time.time() 94 | while True: 95 | if int(time.time() - start_wait) > self.max_wait_time: 96 | raise Exception(self.error_message_dict["error_timeout"]) 97 | response = self.session.get(polling_url) 98 | if response.status_code != 200: 99 | raise Exception(self.error_message_dict["error_noresults"]) 100 | if not response.text or response.text.find("errorMessage") != -1: 101 | time.sleep(1) 102 | continue 103 | else: 104 | break 105 | 106 | image_links = regex.findall(r'src="([^"]+)"', response.text) 107 | normal_image_links = [link.split("?w=")[0] for link in image_links] 108 | normal_image_links = list(set(normal_image_links)) 109 | 110 | return normal_image_links 111 | 112 | def send_request(self, prompt, rt_type=4): 113 | url_encoded_prompt = requests.utils.quote(prompt) 114 | payload = f"q={url_encoded_prompt}&qs=ds" 115 | url = f"https://www.bing.com/images/create?q={url_encoded_prompt}&rt={rt_type}&FORM=GENCRE" 116 | response = self.session.post( 117 | url, 118 | allow_redirects=False, 119 | data=payload, 120 | timeout=self.max_wait_time, 121 | ) 122 | return response, url_encoded_prompt 123 | 124 | def process(self, prompt, out_folder): 125 | # rt=4 means the reward pipeline, run faster than the pipeline without reward (rt=3) 126 | response, url_encoded_prompt = self.send_request(prompt, rt_type=4) 127 | 128 | if response.status_code != 302: 129 | self.process_error(response) 130 | 131 | print("==> Generating...") 132 | redirect_url, request_id = self.request_result_urls( 133 | response, url_encoded_prompt 134 | ) 135 | if redirect_url is None: 136 | # reward is empty, use rt=3 for slow response 137 | print( 138 | "==> Your boosts have run out, using the slow generating pipeline, please wait..." 139 | ) 140 | response, url_encoded_prompt = self.send_request(prompt, rt_type=3) 141 | redirect_url, request_id = self.request_result_urls( 142 | response, url_encoded_prompt 143 | ) 144 | if redirect_url is None: 145 | print( 146 | "==> Error occurs, please submit an issue at https://github.com/vra/bing_brush, I will fix it as soon as possible." 147 | ) 148 | return -1 149 | 150 | img_urls = self.obtaion_image_url(redirect_url, request_id, url_encoded_prompt) 151 | 152 | print("==> Downloading...") 153 | os.makedirs(out_folder, exist_ok=True) 154 | for url in img_urls: 155 | self.write_image(url, out_folder) 156 | print(f"==> Images are saved to {out_folder}") 157 | 158 | def write_image(self, url, out_folder): 159 | response = requests.get(url) 160 | if response.status_code == 200: 161 | file_name = url.split("/")[-1] 162 | save_path = os.path.join(out_folder, file_name) + ".jpg" 163 | 164 | with open(save_path, "wb") as file: 165 | file.write(response.content) 166 | if self.verbose: 167 | print(f"Save image to: {save_path}") 168 | else: 169 | print("Download failed!") 170 | --------------------------------------------------------------------------------