Skip to content

Commit 7647d55

Browse files
committed
v1.1.2
1 parent d1313f5 commit 7647d55

File tree

3 files changed

+97
-23
lines changed

3 files changed

+97
-23
lines changed

README.MD

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ This module uses web scraping and engineering techniques to interface with Bing'
1111
### 🔑 Key Features
1212

1313
- 🖼️ **Generate images** by providing a text prompt
14-
- 📸 **Get image URLs** up to 4 generated images
14+
- 🎥 **Generate videos** by providing a text prompt
15+
- 📸 **Get image URLs** up to 4 generated images
1516
- 🔐 **Authentication** via saved Bing cookies or auto-fetched from browsers
1617
- ⚠️ **Custom exceptions** for common issues
1718

@@ -46,8 +47,14 @@ bing_art = BingArt(auto=True)
4647

4748
Call `generate_images()` with your query text:
4849

49-
```python
50-
results = bing.generate_images("a cat painting in Picasso style")
50+
```python
51+
results = bing_art.generate_images("a cat painting in Picasso style")
52+
```
53+
54+
And for videos:
55+
56+
```python
57+
results = bing_art.generate_video("a dancing cat")
5158
```
5259

5360
The return value contains image URLs and original prompt:
@@ -61,6 +68,17 @@ The return value contains image URLs and original prompt:
6168
}
6269
```
6370

71+
For video generation, the output will be:
72+
73+
```json
74+
{
75+
"video": {
76+
"video_url": "https://..."
77+
},
78+
"prompt": "a dancing cat"
79+
}
80+
```
81+
6482
## 🚨 Exceptions
6583

6684
- `AuthCookieError`: Invalid authentication cookie

bingart/bingart.py

Lines changed: 74 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -51,33 +51,40 @@ def get_auth_cookies(self):
5151

5252
raise AuthCookieError('Failed to fetch authentication cookies automatically.')
5353

54-
def _prepare_headers(self):
54+
def _prepare_headers(self, content_type='image'):
5555
cookie_str = ''
5656
if self.auth_cookie_U:
5757
cookie_str += f'_U={self.auth_cookie_U};'
5858
if self.auth_cookie_KievRPSSecAuth:
5959
cookie_str += f' KievRPSSecAuth={self.auth_cookie_KievRPSSecAuth};'
6060

61-
return {
62-
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
63-
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
64-
'Referer': self.base_url,
65-
'Accept-Language': 'en-US,en;q=0.9',
61+
base_headers = {
62+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36',
63+
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
64+
'Accept-Language': 'ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7',
6665
'Cookie': cookie_str
6766
}
6867

68+
if content_type == 'video':
69+
base_headers['Referer'] = f'{self.base_url}?ctype=video'
70+
base_headers['Content-Type'] = 'application/x-www-form-urlencoded'
71+
else:
72+
base_headers['Referer'] = self.base_url
73+
74+
return base_headers
75+
6976
def _get_balance(self):
7077
response = self.session.get(self.base_url)
7178
try:
72-
coins = int(re.search(r'<div id="reward_c" data-tb="(\d+)"', response.text).group(1))
79+
coins = 15 - int(re.search(r'<div id="reward_c" data-tb="(\d+)"', response.text).group(1))
7380
except AttributeError:
7481
raise AuthCookieError('Auth cookie failed!')
7582
return coins
7683

7784
def _fetch_images(self, encoded_query, ID, IG):
7885
images = []
7986
while True:
80-
response = self.session.get(f'{self.base_url}/async/results/{ID}?{encoded_query}&IG={IG}&IID=images.as'.replace('&amp;nfy=1', ''))
87+
response = self.session.get(f'{self.base_url}/async/results/{ID}?{encoded_query}&IG={IG}&IID=images.as'.replace('&nfy=1', ''))
8188
if 'text/css' in response.text:
8289
src_urls = re.findall(r'src="([^"]+)"', response.text)
8390
for src_url in src_urls:
@@ -87,22 +94,71 @@ def _fetch_images(self, encoded_query, ID, IG):
8794
return images
8895
time.sleep(5)
8996

90-
def generate_images(self, query):
97+
def _fetch_video(self, encoded_query, ID):
98+
while True:
99+
response = self.session.get(f'{self.base_url}/async/results/{ID}?{encoded_query}&ctype=video&sm=1&girftp=1')
100+
try:
101+
result = response.json()
102+
if result.get('errorMessage') == 'Pending':
103+
time.sleep(5)
104+
continue
105+
elif result.get('showContent'):
106+
return {'video_url': result['showContent']}
107+
else:
108+
return None
109+
except ValueError:
110+
video_url = re.search(r'ourl="([^"]+)"', response.text)
111+
if video_url:
112+
return {'video_url': video_url.group(1)}
113+
else:
114+
time.sleep(5)
115+
continue
116+
117+
def _handle_creation_error(self, response):
118+
if 'data-clarity-tag="BlockedByContentPolicy"' in response.text or 'girer_center block_icon' in response.text:
119+
raise PromptRejectedError('Error! Your prompt has been rejected for content policy reasons.')
120+
else:
121+
raise AuthCookieError('Auth cookie failed!')
122+
123+
def generate(self, query, content_type='image'):
91124
encoded_query = urlencode({'q': query})
92-
self.session.headers.update(self.headers)
93-
coins = self._get_balance()
94-
rt = '4' if coins > 0 else '3'
95-
creation_url = f'{self.base_url}?{encoded_query}&rt={rt}&FORM=GENCRE'
125+
126+
if content_type == 'image':
127+
headers = self.headers
128+
coins = self._get_balance()
129+
rt = '4' if coins > 0 else '3'
130+
creation_url = f'{self.base_url}?{encoded_query}&rt={rt}&FORM=GENCRE'
131+
elif content_type == 'video':
132+
headers = self._prepare_headers(content_type='video')
133+
creation_url = f'{self.base_url}?{encoded_query}&rt=4&FORM=GENCRE&ctype=video&pt=4&sm=1'
134+
else:
135+
raise ValueError("content_type must be 'image' or 'video'")
96136

137+
self.session.headers.update(headers)
97138
response = self.session.post(creation_url, data={'q': query})
139+
98140
try:
99-
ID = re.search(';id=([^"]+)"', response.text).group(1)
100-
IG = re.search('IG:"([^"]+)"', response.text).group(1)
141+
if content_type == 'image':
142+
ID = re.search(';id=([^"]+)"', response.text).group(1)
143+
IG = re.search('IG:"([^"]+)"', response.text).group(1)
144+
else:
145+
ID = re.search(r'id=([^&]+)', response.url).group(1)
146+
IG = None
101147
except AttributeError:
102-
raise PromptRejectedError('Error! Your prompt has been rejected for ethical reasons.')
148+
self._handle_creation_error(response)
149+
150+
if content_type == 'image':
151+
result = self._fetch_images(encoded_query, ID, IG)
152+
return {'images': result, 'prompt': query}
153+
else:
154+
result = self._fetch_video(encoded_query, ID)
155+
return {'video': result, 'prompt': query}
156+
157+
def generate_images(self, query):
158+
return self.generate(query, content_type='image')
103159

104-
images = self._fetch_images(encoded_query, ID, IG)
105-
return {'images': images, 'prompt': query}
160+
def generate_video(self, query):
161+
return self.generate(query, content_type='video')
106162

107163
def close_session(self):
108164
self.session.close()

setup.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55

66
setup(
77
name='bingart',
8-
version='1.1.1',
8+
version='1.1.2',
99
author='Maehdakvan',
1010
author_email='visitanimation@google.com',
11-
description='bingart is an unofficial API wrapper for Bing Image Creator (based on DALL-E 3).',
11+
description='bingart is an unofficial API wrapper for Bing Image & Video Creator (based on DALL-E 3).',
1212
long_description=long_description,
1313
long_description_content_type='text/markdown',
1414
url='https://github.com/DedInc/bingart',

0 commit comments

Comments
 (0)