├── 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 |
--------------------------------------------------------------------------------