hugpy-media 0.2.2__tar.gz

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 (92) hide show
  1. hugpy_media-0.2.2/LICENSE +41 -0
  2. hugpy_media-0.2.2/PKG-INFO +79 -0
  3. hugpy_media-0.2.2/README.md +9 -0
  4. hugpy_media-0.2.2/pyproject.toml +96 -0
  5. hugpy_media-0.2.2/setup.cfg +4 -0
  6. hugpy_media-0.2.2/src/hugpy_media/__init__.py +32 -0
  7. hugpy_media-0.2.2/src/hugpy_media/chunking.py +111 -0
  8. hugpy_media-0.2.2/src/hugpy_media/comfy/__init__.py +1 -0
  9. hugpy_media-0.2.2/src/hugpy_media/comfy/comfy_runner.py +536 -0
  10. hugpy_media-0.2.2/src/hugpy_media/embed/__init__.py +6 -0
  11. hugpy_media-0.2.2/src/hugpy_media/embed/embed_runner.py +154 -0
  12. hugpy_media-0.2.2/src/hugpy_media/extract.py +504 -0
  13. hugpy_media-0.2.2/src/hugpy_media/hooks.py +158 -0
  14. hugpy_media-0.2.2/src/hugpy_media/imagegen/__init__.py +6 -0
  15. hugpy_media-0.2.2/src/hugpy_media/imagegen/imagegen_runner.py +1156 -0
  16. hugpy_media-0.2.2/src/hugpy_media/imagegen/schemas.py +91 -0
  17. hugpy_media-0.2.2/src/hugpy_media/imagegen/vram_retry.py +89 -0
  18. hugpy_media-0.2.2/src/hugpy_media/keywords/__init__.py +6 -0
  19. hugpy_media-0.2.2/src/hugpy_media/keywords/keybert_model.py +608 -0
  20. hugpy_media-0.2.2/src/hugpy_media/keywords/keywords_runner.py +163 -0
  21. hugpy_media-0.2.2/src/hugpy_media/keywords/schemas.py +67 -0
  22. hugpy_media-0.2.2/src/hugpy_media/model_battery.py +349 -0
  23. hugpy_media-0.2.2/src/hugpy_media/pdfs/__init__.py +0 -0
  24. hugpy_media-0.2.2/src/hugpy_media/pdfs/utils.py +166 -0
  25. hugpy_media-0.2.2/src/hugpy_media/plugin.py +210 -0
  26. hugpy_media-0.2.2/src/hugpy_media/plugin_builders.py +534 -0
  27. hugpy_media-0.2.2/src/hugpy_media/py.typed +0 -0
  28. hugpy_media-0.2.2/src/hugpy_media/schemas/__init__.py +0 -0
  29. hugpy_media-0.2.2/src/hugpy_media/schemas/embeded_schemas.py +57 -0
  30. hugpy_media-0.2.2/src/hugpy_media/schemas/summarizer_schemas.py +176 -0
  31. hugpy_media-0.2.2/src/hugpy_media/schemas/whisper_schemas.py +175 -0
  32. hugpy_media-0.2.2/src/hugpy_media/seo/__init__.py +0 -0
  33. hugpy_media-0.2.2/src/hugpy_media/seo/pdf_utils.py +227 -0
  34. hugpy_media-0.2.2/src/hugpy_media/summarizers/__init__.py +21 -0
  35. hugpy_media-0.2.2/src/hugpy_media/summarizers/generation.py +56 -0
  36. hugpy_media-0.2.2/src/hugpy_media/summarizers/media.py +47 -0
  37. hugpy_media-0.2.2/src/hugpy_media/summarizers/summarize_runner.py +98 -0
  38. hugpy_media-0.2.2/src/hugpy_media/summarizers/summarizers.py +516 -0
  39. hugpy_media-0.2.2/src/hugpy_media/text/__init__.py +0 -0
  40. hugpy_media-0.2.2/src/hugpy_media/text/combined.py +281 -0
  41. hugpy_media-0.2.2/src/hugpy_media/tts/__init__.py +34 -0
  42. hugpy_media-0.2.2/src/hugpy_media/tts/_backend_main.py +103 -0
  43. hugpy_media-0.2.2/src/hugpy_media/tts/chatterbox_runner.py +610 -0
  44. hugpy_media-0.2.2/src/hugpy_media/tts/paths.py +9 -0
  45. hugpy_media-0.2.2/src/hugpy_media/tts/schemas.py +75 -0
  46. hugpy_media-0.2.2/src/hugpy_media/tts/seat.py +141 -0
  47. hugpy_media-0.2.2/src/hugpy_media/tts/tts_runner.py +263 -0
  48. hugpy_media-0.2.2/src/hugpy_media/vision/__init__.py +6 -0
  49. hugpy_media-0.2.2/src/hugpy_media/vision/schemas.py +107 -0
  50. hugpy_media-0.2.2/src/hugpy_media/vision/utils.py +26 -0
  51. hugpy_media-0.2.2/src/hugpy_media/vision/vision_backends.py +100 -0
  52. hugpy_media-0.2.2/src/hugpy_media/vision/vision_coder.py +377 -0
  53. hugpy_media-0.2.2/src/hugpy_media/vision/vision_runner.py +27 -0
  54. hugpy_media-0.2.2/src/hugpy_media/vision_analysis/__init__.py +8 -0
  55. hugpy_media-0.2.2/src/hugpy_media/vision_analysis/runner.py +233 -0
  56. hugpy_media-0.2.2/src/hugpy_media/vision_analysis/schemas.py +63 -0
  57. hugpy_media-0.2.2/src/hugpy_media/whisper_model/__init__.py +27 -0
  58. hugpy_media-0.2.2/src/hugpy_media/whisper_model/constants.py +35 -0
  59. hugpy_media-0.2.2/src/hugpy_media/whisper_model/src/__init__.py +6 -0
  60. hugpy_media-0.2.2/src/hugpy_media/whisper_model/src/model/__init__.py +7 -0
  61. hugpy_media-0.2.2/src/hugpy_media/whisper_model/src/model/execute.py +218 -0
  62. hugpy_media-0.2.2/src/hugpy_media/whisper_model/src/model/model.py +56 -0
  63. hugpy_media-0.2.2/src/hugpy_media/whisper_model/src/model/utils/__init__.py +5 -0
  64. hugpy_media-0.2.2/src/hugpy_media/whisper_model/src/model/utils/audio.py +81 -0
  65. hugpy_media-0.2.2/src/hugpy_media/whisper_model/src/model/utils/files/__init__.py +1 -0
  66. hugpy_media-0.2.2/src/hugpy_media/whisper_model/src/model/utils/files/artifacts/__init__.py +1 -0
  67. hugpy_media-0.2.2/src/hugpy_media/whisper_model/src/model/utils/files/artifacts/workspace.py +55 -0
  68. hugpy_media-0.2.2/src/hugpy_media/whisper_model/src/model/utils/files/frames/__init__.py +6 -0
  69. hugpy_media-0.2.2/src/hugpy_media/whisper_model/src/model/utils/files/frames/extract.py +92 -0
  70. hugpy_media-0.2.2/src/hugpy_media/whisper_model/src/model/utils/files/frames/utils.py +45 -0
  71. hugpy_media-0.2.2/src/hugpy_media/whisper_model/src/model/utils/files/save.py +17 -0
  72. hugpy_media-0.2.2/src/hugpy_media/whisper_model/src/runner.py +115 -0
  73. hugpy_media-0.2.2/src/hugpy_media/whisper_model/src/stream.py +168 -0
  74. hugpy_media-0.2.2/src/hugpy_media.egg-info/PKG-INFO +79 -0
  75. hugpy_media-0.2.2/src/hugpy_media.egg-info/SOURCES.txt +90 -0
  76. hugpy_media-0.2.2/src/hugpy_media.egg-info/dependency_links.txt +1 -0
  77. hugpy_media-0.2.2/src/hugpy_media.egg-info/entry_points.txt +2 -0
  78. hugpy_media-0.2.2/src/hugpy_media.egg-info/requires.txt +56 -0
  79. hugpy_media-0.2.2/src/hugpy_media.egg-info/scm_file_list.json +86 -0
  80. hugpy_media-0.2.2/src/hugpy_media.egg-info/scm_version.json +8 -0
  81. hugpy_media-0.2.2/src/hugpy_media.egg-info/top_level.txt +1 -0
  82. hugpy_media-0.2.2/tests/conftest.py +23 -0
  83. hugpy_media-0.2.2/tests/test_comfy_vram_retry.py +229 -0
  84. hugpy_media-0.2.2/tests/test_imagegen_device.py +93 -0
  85. hugpy_media-0.2.2/tests/test_imagegen_evict_to_fit.py +232 -0
  86. hugpy_media-0.2.2/tests/test_imagegen_footprint.py +332 -0
  87. hugpy_media-0.2.2/tests/test_imagegen_placement.py +233 -0
  88. hugpy_media-0.2.2/tests/test_imagegen_ram_only.py +272 -0
  89. hugpy_media-0.2.2/tests/test_import_policy.py +106 -0
  90. hugpy_media-0.2.2/tests/test_media_extract_readability.py +485 -0
  91. hugpy_media-0.2.2/tests/test_plugin.py +192 -0
  92. hugpy_media-0.2.2/tests/test_vision_coder_placement.py +226 -0
@@ -0,0 +1,41 @@
1
+ hugpy — Source-Available License
2
+
3
+ Copyright (c) 2026 putkoff (hugpy.ai). All rights reserved.
4
+
5
+ Permission is granted, free of charge, to use this software ("hugpy") for
6
+ personal and non-commercial purposes, and for time-limited commercial
7
+ evaluation, subject to the following conditions:
8
+
9
+ 1. Non-commercial use means use by an individual for personal purposes, or
10
+ use by a non-profit or educational institution for its own internal
11
+ purposes. Any use by, for, or on behalf of a for-profit business or in
12
+ connection with revenue-generating activity is commercial use — including
13
+ internal business use, use in producing goods or services, and use on
14
+ paid engagements.
15
+
16
+ 2. Commercial use requires a commercial license from the copyright holder.
17
+ Exception: a business may evaluate the software internally for up to
18
+ thirty (30) days free of charge; continued use after that requires a
19
+ commercial license.
20
+
21
+ 3. Redistribution of this software, in source or binary form, modified or
22
+ unmodified, is not permitted without prior written permission from the
23
+ copyright holder. Downloading the software from an official distribution
24
+ channel (PyPI, npm, hugpy.ai) is not redistribution.
25
+
26
+ 4. Modification for personal use or internal evaluation is permitted;
27
+ distribution of modified versions is not.
28
+
29
+ 5. This notice must be retained in all copies or substantial portions of
30
+ the software.
31
+
32
+ 6. Any use outside these terms automatically terminates this license.
33
+
34
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
35
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
36
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
37
+ COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY ARISING
38
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
39
+ IN THE SOFTWARE.
40
+
41
+ For commercial licensing or redistribution permission: https://hugpy.ai
@@ -0,0 +1,79 @@
1
+ Metadata-Version: 2.4
2
+ Name: hugpy-media
3
+ Version: 0.2.2
4
+ Summary: Hugpy media: engine task plugins for speech recognition, TTS, embeddings, keywords, summaries, vision, image generation, ComfyUI and document/URL extraction
5
+ Author-email: putkoff <support@hugpy.ai>
6
+ License-Expression: LicenseRef-Proprietary
7
+ Project-URL: Homepage, https://hugpy.ai
8
+ Project-URL: Documentation, https://github.com/hugpy/hugpy/blob/main/py/inference/hugpy_media/README.md
9
+ Project-URL: Repository, https://github.com/hugpy/hugpy
10
+ Project-URL: Source, https://github.com/hugpy/hugpy/tree/main/py/inference/hugpy_media
11
+ Project-URL: Issues, https://github.com/hugpy/hugpy/issues
12
+ Project-URL: Changelog, https://github.com/hugpy/hugpy/releases
13
+ Project-URL: Architecture, https://github.com/hugpy/hugpy/blob/main/PARTITION.md
14
+ Keywords: hugpy,llm,self-hosted,whisper,embeddings,vision,image-generation,tts,media-intelligence
15
+ Classifier: Development Status :: 3 - Alpha
16
+ Classifier: Intended Audience :: Developers
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3 :: Only
20
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
21
+ Requires-Python: >=3.10
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Requires-Dist: hugpy-platform
25
+ Requires-Dist: hugpy-storage
26
+ Requires-Dist: hugpy-engine
27
+ Requires-Dist: abstract_essentials
28
+ Requires-Dist: pydantic>=2
29
+ Requires-Dist: requests
30
+ Requires-Dist: beautifulsoup4
31
+ Requires-Dist: PyPDF2
32
+ Requires-Dist: pdfplumber
33
+ Requires-Dist: pillow
34
+ Provides-Extra: test
35
+ Requires-Dist: pytest>=8; extra == "test"
36
+ Requires-Dist: pytest-timeout; extra == "test"
37
+ Provides-Extra: audio
38
+ Requires-Dist: openai-whisper; extra == "audio"
39
+ Provides-Extra: imagegen
40
+ Requires-Dist: diffusers>=0.39; extra == "imagegen"
41
+ Requires-Dist: accelerate; extra == "imagegen"
42
+ Requires-Dist: transformers>=5.12; extra == "imagegen"
43
+ Requires-Dist: bitsandbytes>=0.48.0; extra == "imagegen"
44
+ Provides-Extra: embed
45
+ Requires-Dist: sentence-transformers; extra == "embed"
46
+ Provides-Extra: transformers
47
+ Requires-Dist: torch; extra == "transformers"
48
+ Requires-Dist: transformers; extra == "transformers"
49
+ Requires-Dist: accelerate; extra == "transformers"
50
+ Requires-Dist: sentencepiece; extra == "transformers"
51
+ Requires-Dist: timm; extra == "transformers"
52
+ Provides-Extra: vision
53
+ Requires-Dist: opencv-python-headless; extra == "vision"
54
+ Requires-Dist: onnxruntime; extra == "vision"
55
+ Requires-Dist: aiohttp; extra == "vision"
56
+ Provides-Extra: extract
57
+ Requires-Dist: python-docx; extra == "extract"
58
+ Requires-Dist: openpyxl; extra == "extract"
59
+ Requires-Dist: brotli; extra == "extract"
60
+ Requires-Dist: zstandard; extra == "extract"
61
+ Provides-Extra: keywords
62
+ Requires-Dist: keybert; extra == "keywords"
63
+ Provides-Extra: tts
64
+ Requires-Dist: chatterbox-tts; extra == "tts"
65
+ Provides-Extra: comfy
66
+ Requires-Dist: aiohttp; extra == "comfy"
67
+ Provides-Extra: all
68
+ Requires-Dist: hugpy-media[audio,comfy,embed,extract,imagegen,keywords,transformers,vision]; extra == "all"
69
+ Dynamic: license-file
70
+
71
+ # hugpy-media
72
+
73
+ `hugpy_media` — extracted from `abstract_hugpy_dev` as part of the Hugpy
74
+ partition. Ownership and allowed dependencies are declared in
75
+ `py/partition.toml`; see `PARTITION.md` at the workspace root.
76
+
77
+ Allowed Python dependencies inside the ecosystem: hugpy_platform, hugpy_storage, hugpy_engine.
78
+
79
+ Register the media tasks with the engine via `hugpy_media.plugin.register()` (also the `hugpy_engine.tasks` entry point `media`). Heavy stacks are extras: `keywords`, `audio`, `imagegen`, `embed`, `transformers`, `vision`, `extract`, `tts`, `comfy`, umbrella `all`.
@@ -0,0 +1,9 @@
1
+ # hugpy-media
2
+
3
+ `hugpy_media` — extracted from `abstract_hugpy_dev` as part of the Hugpy
4
+ partition. Ownership and allowed dependencies are declared in
5
+ `py/partition.toml`; see `PARTITION.md` at the workspace root.
6
+
7
+ Allowed Python dependencies inside the ecosystem: hugpy_platform, hugpy_storage, hugpy_engine.
8
+
9
+ Register the media tasks with the engine via `hugpy_media.plugin.register()` (also the `hugpy_engine.tasks` entry point `media`). Heavy stacks are extras: `keywords`, `audio`, `imagegen`, `embed`, `transformers`, `vision`, `extract`, `tts`, `comfy`, umbrella `all`.
@@ -0,0 +1,96 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77", "setuptools-scm>=8"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "hugpy-media"
7
+ dynamic = ["version"]
8
+ description = "Hugpy media: engine task plugins for speech recognition, TTS, embeddings, keywords, summaries, vision, image generation, ComfyUI and document/URL extraction"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "LicenseRef-Proprietary"
12
+ license-files = ["LICENSE"]
13
+ authors = [{ name = "putkoff", email = "support@hugpy.ai" }]
14
+ keywords = [
15
+ "hugpy",
16
+ "llm",
17
+ "self-hosted",
18
+ "whisper",
19
+ "embeddings",
20
+ "vision",
21
+ "image-generation",
22
+ "tts",
23
+ "media-intelligence",
24
+ ]
25
+ classifiers = [
26
+ "Development Status :: 3 - Alpha",
27
+ "Intended Audience :: Developers",
28
+ "Operating System :: OS Independent",
29
+ "Programming Language :: Python :: 3",
30
+ "Programming Language :: Python :: 3 :: Only",
31
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
32
+ ]
33
+ # Base = importable and able to build requests / run the model-less extractors
34
+ # on any box. Every model stack is an extra, imported lazily inside its runner.
35
+ dependencies = [
36
+ "hugpy-platform",
37
+ "hugpy-storage",
38
+ "hugpy-engine",
39
+ "abstract_essentials", # derive_media_type / read_from_file / safe_dump_to_json
40
+ "pydantic>=2",
41
+ "requests",
42
+ "beautifulsoup4", # url-extraction, seo/text helpers
43
+ "PyPDF2", # pure-python PDF reading (pdfs/, text/)
44
+ "pdfplumber", # document-extraction PDF page text
45
+ "pillow", # image decoding in vision/imagegen request paths
46
+ ]
47
+
48
+ [project.urls]
49
+ Homepage = "https://hugpy.ai"
50
+ Documentation = "https://github.com/hugpy/hugpy/blob/main/py/inference/hugpy_media/README.md"
51
+ Repository = "https://github.com/hugpy/hugpy"
52
+ Source = "https://github.com/hugpy/hugpy/tree/main/py/inference/hugpy_media"
53
+ Issues = "https://github.com/hugpy/hugpy/issues"
54
+ Changelog = "https://github.com/hugpy/hugpy/releases"
55
+ Architecture = "https://github.com/hugpy/hugpy/blob/main/PARTITION.md"
56
+
57
+ [project.optional-dependencies]
58
+ test = ["pytest>=8", "pytest-timeout"]
59
+ # Mirrors the monolith's extras, one per media stack.
60
+ audio = ["openai-whisper"]
61
+ imagegen = ["diffusers>=0.39", "accelerate", "transformers>=5.12", "bitsandbytes>=0.48.0"]
62
+ embed = ["sentence-transformers"]
63
+ transformers = ["torch", "transformers", "accelerate", "sentencepiece", "timm"]
64
+ vision = ["opencv-python-headless", "onnxruntime", "aiohttp"]
65
+ extract = ["python-docx", "openpyxl", "brotli", "zstandard"]
66
+ keywords = ["keybert"]
67
+ # tts is opt-in ONLY: chatterbox-tts pins torch/numpy/transformers versions that
68
+ # conflict with the rest of this set (numpy<2 / transformers==5.2 vs >=5.12), so
69
+ # `hugpy[gpu-worker]` (-> hugpy-media[all]) was unresolvable while it was in
70
+ # `all` (found at the 0.2.0 worker cutover, 2026-09-23).
71
+ tts = ["chatterbox-tts"]
72
+ comfy = ["aiohttp"]
73
+ all = ["hugpy-media[keywords,audio,imagegen,embed,transformers,vision,extract,comfy]"]
74
+
75
+ [project.entry-points."hugpy_engine.tasks"]
76
+ media = "hugpy_media.plugin:register"
77
+
78
+ [tool.setuptools.packages.find]
79
+ where = ["src"]
80
+
81
+ [tool.setuptools.package-data]
82
+ hugpy_media = ["py.typed", "**/*.json", "**/*.sh", "**/*.md", "**/*.txt"]
83
+
84
+ [tool.pytest.ini_options]
85
+ testpaths = ["tests"]
86
+ timeout = 120
87
+
88
+ # ---------------------------------------------------------------------------
89
+ # Lockstep workspace version (2026-09-22): every in-tree hugpy-* distribution
90
+ # takes ONE version from the workspace git tag (vX.Y.Z at the repo root), so a
91
+ # release is a tag and a build always carries the commit it came from
92
+ # (X.Y.Z.devN+g<sha>[.dirty] between tags). A checkout without git metadata
93
+ # builds as 0.0.0+unknown, which central refuses to advertise to workers.
94
+ [tool.setuptools_scm]
95
+ root = "../../.."
96
+ fallback_version = "0.0.0+unknown"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,32 @@
1
+ """hugpy-media: task plugins for the Hugpy engine.
2
+
3
+ Embeddings/similarity, summaries, keywords, speech recognition, TTS, vision
4
+ chat, vision analysis, image generation, ComfyUI and document/URL extraction.
5
+ Heavy frameworks are extras imported only inside the selected runner, so this
6
+ package imports without torch, diffusers, whisper, keybert,
7
+ sentence-transformers, OpenCV or llama.cpp installed.
8
+
9
+ Wiring: ``hugpy_media.plugin.register()`` (also the ``hugpy_engine.tasks``
10
+ entry point ``media``) puts every media task into ``hugpy_engine.tasks``;
11
+ ``hugpy_media.hooks`` is where the fleet worker observes spawned processes.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ try: # the installed distribution's version: the workspace tag/commit, never a literal
17
+ from importlib.metadata import version as _dist_version
18
+ __version__ = _dist_version("hugpy-media")
19
+ except Exception: # noqa: BLE001 — source tree without metadata
20
+ __version__ = "0.0.0+unknown"
21
+
22
+ __all__ = ["__version__", "hooks", "plugin", "extract", "chunking"]
23
+
24
+
25
+ def __getattr__(name: str):
26
+ # Submodules are exposed by name but imported on demand, keeping
27
+ # ``import hugpy_media`` free of even the pydantic schema imports.
28
+ if name in ("hooks", "plugin", "extract", "chunking"):
29
+ import importlib
30
+
31
+ return importlib.import_module(f"hugpy_media.{name}")
32
+ raise AttributeError(f"module 'hugpy_media' has no attribute {name!r}")
@@ -0,0 +1,111 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ from typing import List, Optional
5
+
6
+ from hugpy_platform.module_imports import get_tiktoken
7
+ def get_encoder(model_name: str = "gpt-4", encoding_name: Optional[str] = None):
8
+ """Return a tiktoken encoder for your model/encoding, or None when tiktoken
9
+ isn't installed. The package lazy-imports tiktoken, so on lean installs
10
+ get_tiktoken() yields a stub whose calls return None — handle that here
11
+ rather than crashing downstream (callers fall back to a heuristic)."""
12
+ tiktoken = get_tiktoken()
13
+ try:
14
+ enc = (tiktoken.get_encoding(encoding_name) if encoding_name
15
+ else tiktoken.encoding_for_model(model_name))
16
+ except Exception:
17
+ return None
18
+ return enc if hasattr(enc, "encode") else None
19
+
20
+ def count_tokens(text: str, encoder=None) -> int:
21
+ """Count how many tokens `text` encodes to. Falls back to a ~4-chars/token
22
+ heuristic when no usable encoder is available, so chunking (and the
23
+ summarizer that drives it) still works on installs without tiktoken."""
24
+ s = str(text)
25
+ enc = getattr(encoder, "encode", None)
26
+ if callable(enc):
27
+ try:
28
+ return len(enc(s))
29
+ except Exception:
30
+ pass
31
+ return max(len(s) // 4, len(s.split()), 1)
32
+
33
+ def recursive_chunk(
34
+ text: str,
35
+ desired_tokens: int,
36
+ model_name: str = "gpt-4",
37
+ separators: Optional[List[str]] = None,
38
+ overlap: int = 0
39
+ ) -> List[str]:
40
+ """
41
+ Split `text` into chunks as close to `desired_tokens` tokens as possible,
42
+ preserving contiguous blocks via `separators`, and *only* splitting inside
43
+ a block if it can’t possibly fit otherwise.
44
+
45
+ Args:
46
+ text: the full string to split
47
+ desired_tokens: target token count per chunk (never exceed)
48
+ encoder: a tiktoken encoder
49
+ separators: list of splitters, from largest to smallest logical unit
50
+ overlap: how many tokens to overlap between adjacent chunks
51
+ """
52
+ encoder = get_encoder(model_name)
53
+ if separators is None:
54
+ # from big (paragraphs) to small (words)
55
+ separators = ["\n\n", "\n", r"(?<=[\.\?\!])\s", ", ", " "]
56
+
57
+ # If it already fits, return it whole:
58
+ if count_tokens(text, encoder) <= desired_tokens:
59
+ return [text]
60
+
61
+ # Try splitting by each separator in turn
62
+ for sep in separators:
63
+ # use regex split when the separator is a lookbehind pattern
64
+ parts = re.split(sep, text) if sep.startswith("(?") else text.split(sep)
65
+ if len(parts) > 1:
66
+ chunks: List[str] = []
67
+ current = ""
68
+ current_tokens = 0
69
+
70
+ for part in parts:
71
+ part = part.strip()
72
+ if not part:
73
+ continue
74
+ part_tokens = count_tokens(part, encoder)
75
+
76
+ # If this block alone is too big, recurse into it with the next-level separators
77
+ if part_tokens > desired_tokens:
78
+ # flush current
79
+ if current:
80
+ chunks.extend(recursive_chunk(
81
+ current, desired_tokens, model_name, separators[1:], overlap
82
+ ))
83
+ current, current_tokens = "", 0
84
+ # now chunk the oversized block
85
+ chunks.extend(recursive_chunk(
86
+ part, desired_tokens, model_name, separators[1:], overlap
87
+ ))
88
+ else:
89
+ # can we add it to the current chunk?
90
+ if current_tokens + part_tokens <= desired_tokens:
91
+ # include the separator back in
92
+ current = sep.join([current, part]) if current else part
93
+ current_tokens += part_tokens
94
+ else:
95
+ # flush current, start new
96
+ chunks.append(current)
97
+ current, current_tokens = part, part_tokens
98
+
99
+ if current:
100
+ chunks.append(current)
101
+ return chunks
102
+
103
+ # Fallback: pure token sliding window (the only time we’ll split “inside” a block)
104
+ tokens = encoder.encode(text)
105
+ stride = desired_tokens - overlap
106
+ return [
107
+ encoder.decode(tokens[i : i + desired_tokens])
108
+ for i in range(0, len(tokens), stride)
109
+ ]
110
+
111
+
@@ -0,0 +1 @@
1
+ from hugpy_media.comfy.comfy_runner import ComfyRunner