pygpt-net 2.6.65__py3-none-any.whl → 2.6.66__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. pygpt_net/CHANGELOG.txt +11 -0
  2. pygpt_net/__init__.py +3 -3
  3. pygpt_net/app.py +2 -0
  4. pygpt_net/controller/chat/chat.py +0 -0
  5. pygpt_net/controller/chat/handler/openai_stream.py +137 -7
  6. pygpt_net/controller/chat/render.py +0 -0
  7. pygpt_net/controller/config/field/checkbox_list.py +34 -1
  8. pygpt_net/controller/media/media.py +20 -1
  9. pygpt_net/controller/presets/presets.py +4 -1
  10. pygpt_net/controller/ui/mode.py +14 -10
  11. pygpt_net/controller/ui/ui.py +18 -1
  12. pygpt_net/core/image/image.py +34 -1
  13. pygpt_net/core/tabs/tabs.py +0 -0
  14. pygpt_net/core/types/image.py +61 -3
  15. pygpt_net/data/config/config.json +4 -3
  16. pygpt_net/data/config/models.json +629 -41
  17. pygpt_net/data/locale/locale.de.ini +4 -0
  18. pygpt_net/data/locale/locale.en.ini +4 -0
  19. pygpt_net/data/locale/locale.es.ini +4 -0
  20. pygpt_net/data/locale/locale.fr.ini +4 -0
  21. pygpt_net/data/locale/locale.it.ini +4 -0
  22. pygpt_net/data/locale/locale.pl.ini +4 -0
  23. pygpt_net/data/locale/locale.uk.ini +4 -0
  24. pygpt_net/data/locale/locale.zh.ini +4 -0
  25. pygpt_net/item/model.py +15 -19
  26. pygpt_net/provider/agents/openai/agent.py +0 -0
  27. pygpt_net/provider/api/google/__init__.py +20 -9
  28. pygpt_net/provider/api/google/image.py +161 -28
  29. pygpt_net/provider/api/google/video.py +73 -36
  30. pygpt_net/provider/api/openai/__init__.py +21 -11
  31. pygpt_net/provider/api/openai/agents/client.py +0 -0
  32. pygpt_net/provider/api/openai/video.py +562 -0
  33. pygpt_net/provider/core/config/patch.py +7 -0
  34. pygpt_net/provider/core/model/patch.py +29 -3
  35. pygpt_net/provider/vector_stores/qdrant.py +117 -0
  36. pygpt_net/ui/layout/toolbox/raw.py +7 -1
  37. pygpt_net/ui/widget/option/checkbox_list.py +14 -2
  38. {pygpt_net-2.6.65.dist-info → pygpt_net-2.6.66.dist-info}/METADATA +66 -25
  39. {pygpt_net-2.6.65.dist-info → pygpt_net-2.6.66.dist-info}/RECORD +37 -35
  40. {pygpt_net-2.6.65.dist-info → pygpt_net-2.6.66.dist-info}/LICENSE +0 -0
  41. {pygpt_net-2.6.65.dist-info → pygpt_net-2.6.66.dist-info}/WHEEL +0 -0
  42. {pygpt_net-2.6.65.dist-info → pygpt_net-2.6.66.dist-info}/entry_points.txt +0 -0
@@ -6,7 +6,7 @@
6
6
  # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
7
  # MIT License #
8
8
  # Created By : Marcin Szczygliński #
9
- # Updated Date: 2025.09.01 23:00:00 #
9
+ # Updated Date: 2025.12.25 20:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import base64, datetime, os, requests
@@ -150,11 +150,10 @@ class VideoWorker(QRunnable):
150
150
  self.fps = 24
151
151
  self.seed: Optional[int] = None
152
152
  self.negative_prompt: Optional[str] = None
153
- self.generate_audio: bool = False # Veo 3 only
154
- self.resolution: str = "720p" # Veo 3 supports 720p/1080p
153
+ self.generate_audio: bool = False # generation includes audio by default on Veo 3.x
154
+ self.resolution: str = "720p" # Veo supports 720p/1080p depending on variant
155
155
 
156
156
  # limits / capabilities
157
- # self.veo_max_num = 4 # Veo returns up to 4 videos
158
157
  self.veo_max_num = 1 # limit to 1 in Gemini API
159
158
 
160
159
  # fallbacks
@@ -187,42 +186,52 @@ class VideoWorker(QRunnable):
187
186
  num = min(self.num, self.veo_max_num)
188
187
  cfg_kwargs = {
189
188
  "number_of_videos": num,
190
- #"duration_seconds": self._duration_for_model(self.model, self.duration_seconds),
191
189
  }
192
- if self.aspect_ratio:
193
- cfg_kwargs["aspect_ratio"] = self.aspect_ratio
190
+
191
+ # normalize and set aspect ratio
192
+ ar = self._normalize_aspect_ratio(self.aspect_ratio)
193
+ if ar:
194
+ cfg_kwargs["aspect_ratio"] = ar
195
+
196
+ # normalize and set resolution if supported
197
+ res = self._normalize_resolution(self.resolution)
198
+ if res:
199
+ cfg_kwargs["resolution"] = res
200
+
201
+ # set optional controls
194
202
  if self.seed is not None:
195
203
  cfg_kwargs["seed"] = int(self.seed)
196
204
  if self.negative_prompt:
197
205
  cfg_kwargs["negative_prompt"] = self.negative_prompt
198
- if self._is_veo3(self.model):
199
- # Veo 3 supports audio and resolution
200
- # WARN: but not Gemini API:
201
- pass
202
- """
203
- cfg_kwargs["generate_audio"] = bool(self.generate_audio)
204
- if self.resolution:
205
- cfg_kwargs["resolution"] = self.resolution
206
- """
207
-
208
- config = gtypes.GenerateVideosConfig(**cfg_kwargs)
209
-
210
- # build request
211
- req_kwargs = {
212
- "model": self.model or self.DEFAULT_VEO_MODEL,
213
- "prompt": self.input_prompt or "",
214
- "config": config,
215
- }
216
206
 
217
- # image-to-video if an image attachment is present and supported
218
- base_img = self._first_image_attachment(self.attachments)
219
- if self.mode == Video.MODE_IMAGE_TO_VIDEO and base_img is not None and self._supports_image_to_video(self.model):
220
- req_kwargs["image"] = gtypes.Image.from_file(location=base_img)
207
+ # set durationSeconds when supported; fall back gracefully if rejected by model
208
+ cfg_try = dict(cfg_kwargs)
209
+ cfg_try["duration_seconds"] = int(self._duration_for_model(self.model, self.duration_seconds))
221
210
 
222
211
  self.signals.status.emit(trans('vid.status.generating') + f": {self.input_prompt}...")
223
212
 
224
- # start long-running operation
225
- operation = self.client.models.generate_videos(**req_kwargs)
213
+ try:
214
+ config = gtypes.GenerateVideosConfig(**cfg_try)
215
+ operation = self.client.models.generate_videos(
216
+ model=self.model or self.DEFAULT_VEO_MODEL,
217
+ prompt=self.input_prompt or "",
218
+ config=config,
219
+ image=self._image_part_if_needed(),
220
+ video=None,
221
+ )
222
+ except Exception as e:
223
+ if "durationSeconds isn't supported" in str(e) or "Unrecognized" in str(e):
224
+ # retry without duration_seconds
225
+ config = gtypes.GenerateVideosConfig(**cfg_kwargs)
226
+ operation = self.client.models.generate_videos(
227
+ model=self.model or self.DEFAULT_VEO_MODEL,
228
+ prompt=self.input_prompt or "",
229
+ config=config,
230
+ image=self._image_part_if_needed(),
231
+ video=None,
232
+ )
233
+ else:
234
+ raise
226
235
 
227
236
  # poll until done
228
237
  while not getattr(operation, "done", False):
@@ -258,6 +267,22 @@ class VideoWorker(QRunnable):
258
267
 
259
268
  # ---------- helpers ----------
260
269
 
270
+ def _normalize_aspect_ratio(self, ar: str) -> str:
271
+ """Normalize aspect ratio to Veo-supported values."""
272
+ val = (ar or "").strip()
273
+ return val if val in ("16:9", "9:16") else "16:9"
274
+
275
+ def _normalize_resolution(self, res: str) -> Optional[str]:
276
+ """Normalize resolution to '720p' or '1080p'."""
277
+ val = (res or "").lower().replace(" ", "")
278
+ if val in ("720p", "1080p"):
279
+ return val
280
+ if val in ("1280x720", "720x1280"):
281
+ return "720p"
282
+ if val in ("1920x1080", "1080x1920"):
283
+ return "1080p"
284
+ return None
285
+
261
286
  def _is_veo3(self, model_id: str) -> bool:
262
287
  mid = str(model_id or "").lower()
263
288
  return mid.startswith("veo-3.")
@@ -265,20 +290,32 @@ class VideoWorker(QRunnable):
265
290
  def _supports_image_to_video(self, model_id: str) -> bool:
266
291
  """Return True if the model supports image->video."""
267
292
  mid = str(model_id or "").lower()
268
- # Official support for image-to-video on veo-2 and veo-3 preview; keep extendable.
269
- return ("veo-2.0" in mid) or ("veo-3.0-generate-preview" in mid) or ("veo-3.0-fast-generate-preview" in mid)
293
+ return any(p in mid for p in (
294
+ "veo-2.0",
295
+ "veo-3.0-generate",
296
+ "veo-3.0-fast-generate",
297
+ "veo-3.1-generate",
298
+ "veo-3.1-fast-generate",
299
+ ))
270
300
 
271
301
  def _duration_for_model(self, model_id: str, requested: int) -> int:
272
302
  """Adjust duration constraints to model-specific limits."""
273
303
  mid = str(model_id or "").lower()
274
304
  if "veo-2.0" in mid:
275
- # Veo 2 supports 5–8s, default 8s.
276
305
  return max(5, min(8, int(requested or 8)))
306
+ if "veo-3.1" in mid:
307
+ return max(4, min(8, int(requested or 8)))
277
308
  if "veo-3.0" in mid:
278
- # Veo 3 commonly uses 8s clips; honor request if provided, otherwise 8s.
279
- return int(requested or 8)
309
+ return max(4, min(8, int(requested or 8)))
280
310
  return int(requested or 8)
281
311
 
312
+ def _image_part_if_needed(self) -> Optional[gtypes.Image]:
313
+ """Return Image part when in image-to-video mode and supported."""
314
+ if self.mode != Video.MODE_IMAGE_TO_VIDEO:
315
+ return None
316
+ base_img = self._first_image_attachment(self.attachments)
317
+ return gtypes.Image.from_file(location=base_img) if base_img else None
318
+
282
319
  def _first_image_attachment(self, attachments: Dict[str, Any]) -> Optional[str]:
283
320
  """Return path of the first image attachment, if any."""
284
321
  for _, att in (attachments or {}).items():
@@ -6,7 +6,7 @@
6
6
  # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
7
  # MIT License #
8
8
  # Created By : Marcin Szczygliński #
9
- # Updated Date: 2025.09.12 20:00:00 #
9
+ # Updated Date: 2025.12.25 20:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from openai import OpenAI
@@ -38,6 +38,7 @@ from .store import Store
38
38
  from .summarizer import Summarizer
39
39
  from .tools import Tools
40
40
  from .vision import Vision
41
+ from .video import Video
41
42
 
42
43
 
43
44
  class ApiOpenAI:
@@ -63,6 +64,7 @@ class ApiOpenAI:
63
64
  self.summarizer = Summarizer(window)
64
65
  self.tools = Tools(window)
65
66
  self.vision = Vision(window)
67
+ self.video = Video(window)
66
68
  self.client = None
67
69
  self.locked = False
68
70
  self.last_client_args = None # last client args used, for debug purposes
@@ -87,7 +89,7 @@ class ApiOpenAI:
87
89
  self,
88
90
  context: BridgeContext,
89
91
  extra: dict = None,
90
- rt_signals = None
92
+ rt_signals=None
91
93
  ) -> bool:
92
94
  """
93
95
  Call OpenAI API
@@ -157,7 +159,7 @@ class ApiOpenAI:
157
159
  if is_realtime:
158
160
  return True
159
161
 
160
- if fixtures.is_enabled("stream"): # fake stream for testing
162
+ if fixtures.is_enabled("stream"): # fake stream for testing
161
163
  use_responses_api = False
162
164
  response = fixtures.get_stream_generator(ctx)
163
165
  else:
@@ -181,12 +183,20 @@ class ApiOpenAI:
181
183
 
182
184
  self.vision.append_images(ctx) # append images to ctx if provided
183
185
 
184
- # image
186
+ # image / video
185
187
  elif mode == MODE_IMAGE:
186
- return self.image.generate(
187
- context=context,
188
- extra=extra,
189
- ) # return here, async handled
188
+ media_mode = self.window.controller.media.get_mode()
189
+ if media_mode == "video":
190
+ if context.model and context.model.is_video_output():
191
+ return self.video.generate(
192
+ context=context,
193
+ extra=extra,
194
+ ) # async handled if allowed
195
+ elif media_mode == "image":
196
+ return self.image.generate(
197
+ context=context,
198
+ extra=extra,
199
+ )
190
200
 
191
201
  # vision
192
202
  elif mode == MODE_VISION:
@@ -294,13 +304,13 @@ class ApiOpenAI:
294
304
  messages.append({"role": "user", "content": prompt})
295
305
  additional_kwargs = {}
296
306
  # if max_tokens > 0:
297
- # additional_kwargs["max_tokens"] = max_tokens
307
+ # additional_kwargs["max_tokens"] = max_tokens
298
308
 
299
309
  # tools / functions
300
310
  tools = self.window.core.api.openai.tools.prepare(model, functions)
301
311
  if len(tools) > 0 and "disable_tools" not in extra:
302
312
  additional_kwargs["tools"] = tools
303
-
313
+
304
314
  try:
305
315
  response = client.chat.completions.create(
306
316
  messages=messages,
@@ -349,4 +359,4 @@ class ApiOpenAI:
349
359
  self.client = None
350
360
  except Exception as e:
351
361
  self.window.core.debug.log(e)
352
- print("Error closing client:", e)
362
+ print("Error closing client:", e)
File without changes