sunholo 0.106.0__tar.gz → 0.106.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 (166) hide show
  1. {sunholo-0.106.0 → sunholo-0.106.2}/PKG-INFO +2 -2
  2. {sunholo-0.106.0 → sunholo-0.106.2}/setup.py +1 -2
  3. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/discovery_engine/discovery_engine_client.py +9 -2
  4. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/senses/stream_voice.py +122 -96
  5. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo.egg-info/PKG-INFO +2 -2
  6. {sunholo-0.106.0 → sunholo-0.106.2}/LICENSE.txt +0 -0
  7. {sunholo-0.106.0 → sunholo-0.106.2}/MANIFEST.in +0 -0
  8. {sunholo-0.106.0 → sunholo-0.106.2}/README.md +0 -0
  9. {sunholo-0.106.0 → sunholo-0.106.2}/setup.cfg +0 -0
  10. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/__init__.py +0 -0
  11. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/agents/__init__.py +0 -0
  12. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/agents/chat_history.py +0 -0
  13. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/agents/dispatch_to_qa.py +0 -0
  14. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/agents/fastapi/__init__.py +0 -0
  15. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/agents/fastapi/base.py +0 -0
  16. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/agents/fastapi/qna_routes.py +0 -0
  17. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/agents/flask/__init__.py +0 -0
  18. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/agents/flask/base.py +0 -0
  19. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/agents/flask/qna_routes.py +0 -0
  20. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/agents/flask/vac_routes.py +0 -0
  21. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/agents/langserve.py +0 -0
  22. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/agents/pubsub.py +0 -0
  23. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/agents/route.py +0 -0
  24. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/agents/special_commands.py +0 -0
  25. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/agents/swagger.py +0 -0
  26. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/archive/__init__.py +0 -0
  27. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/archive/archive.py +0 -0
  28. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/auth/__init__.py +0 -0
  29. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/auth/gcloud.py +0 -0
  30. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/auth/refresh.py +0 -0
  31. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/auth/run.py +0 -0
  32. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/azure/__init__.py +0 -0
  33. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/azure/auth.py +0 -0
  34. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/azure/blobs.py +0 -0
  35. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/azure/event_grid.py +0 -0
  36. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/bots/__init__.py +0 -0
  37. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/bots/discord.py +0 -0
  38. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/bots/github_webhook.py +0 -0
  39. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/bots/webapp.py +0 -0
  40. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/chunker/__init__.py +0 -0
  41. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/chunker/azure.py +0 -0
  42. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/chunker/doc_handling.py +0 -0
  43. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/chunker/encode_metadata.py +0 -0
  44. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/chunker/images.py +0 -0
  45. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/chunker/loaders.py +0 -0
  46. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/chunker/message_data.py +0 -0
  47. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/chunker/pdfs.py +0 -0
  48. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/chunker/process_chunker_data.py +0 -0
  49. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/chunker/publish.py +0 -0
  50. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/chunker/pubsub.py +0 -0
  51. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/chunker/splitter.py +0 -0
  52. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/cli/__init__.py +0 -0
  53. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/cli/chat_vac.py +0 -0
  54. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/cli/cli.py +0 -0
  55. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/cli/cli_init.py +0 -0
  56. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/cli/configs.py +0 -0
  57. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/cli/deploy.py +0 -0
  58. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/cli/embedder.py +0 -0
  59. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/cli/merge_texts.py +0 -0
  60. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/cli/run_proxy.py +0 -0
  61. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/cli/sun_rich.py +0 -0
  62. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/cli/swagger.py +0 -0
  63. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/cli/vertex.py +0 -0
  64. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/components/__init__.py +0 -0
  65. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/components/llm.py +0 -0
  66. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/components/retriever.py +0 -0
  67. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/components/vectorstore.py +0 -0
  68. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/custom_logging.py +0 -0
  69. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/database/__init__.py +0 -0
  70. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/database/alloydb.py +0 -0
  71. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/database/alloydb_client.py +0 -0
  72. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/database/database.py +0 -0
  73. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/database/lancedb.py +0 -0
  74. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/database/sql/sb/create_function.sql +0 -0
  75. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/database/sql/sb/create_function_time.sql +0 -0
  76. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/database/sql/sb/create_table.sql +0 -0
  77. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/database/sql/sb/delete_source_row.sql +0 -0
  78. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/database/sql/sb/return_sources.sql +0 -0
  79. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/database/sql/sb/setup.sql +0 -0
  80. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/database/static_dbs.py +0 -0
  81. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/database/uuid.py +0 -0
  82. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/discovery_engine/__init__.py +0 -0
  83. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/discovery_engine/chunker_handler.py +0 -0
  84. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/discovery_engine/create_new.py +0 -0
  85. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/discovery_engine/get_ai_search_chunks.py +0 -0
  86. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/embedder/__init__.py +0 -0
  87. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/embedder/embed_chunk.py +0 -0
  88. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/excel/__init__.py +0 -0
  89. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/excel/plugin.py +0 -0
  90. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/gcs/__init__.py +0 -0
  91. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/gcs/add_file.py +0 -0
  92. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/gcs/download_folder.py +0 -0
  93. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/gcs/download_url.py +0 -0
  94. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/gcs/extract_and_sign.py +0 -0
  95. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/gcs/metadata.py +0 -0
  96. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/genai/__init__.py +0 -0
  97. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/genai/images.py +0 -0
  98. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/genai/init.py +0 -0
  99. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/genai/process_funcs_cls.py +0 -0
  100. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/genai/safety.py +0 -0
  101. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/invoke/__init__.py +0 -0
  102. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/invoke/async_class.py +0 -0
  103. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/invoke/direct_vac_func.py +0 -0
  104. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/invoke/invoke_vac_utils.py +0 -0
  105. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/langfuse/__init__.py +0 -0
  106. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/langfuse/callback.py +0 -0
  107. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/langfuse/evals.py +0 -0
  108. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/langfuse/prompts.py +0 -0
  109. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/llamaindex/__init__.py +0 -0
  110. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/llamaindex/get_files.py +0 -0
  111. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/llamaindex/import_files.py +0 -0
  112. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/llamaindex/llamaindex_class.py +0 -0
  113. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/llamaindex/user_history.py +0 -0
  114. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/lookup/__init__.py +0 -0
  115. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/lookup/model_lookup.yaml +0 -0
  116. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/patches/__init__.py +0 -0
  117. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/patches/langchain/__init__.py +0 -0
  118. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/patches/langchain/lancedb.py +0 -0
  119. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/patches/langchain/vertexai.py +0 -0
  120. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/pubsub/__init__.py +0 -0
  121. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/pubsub/process_pubsub.py +0 -0
  122. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/pubsub/pubsub_manager.py +0 -0
  123. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/qna/__init__.py +0 -0
  124. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/qna/parsers.py +0 -0
  125. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/qna/retry.py +0 -0
  126. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/senses/__init__.py +0 -0
  127. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/streaming/__init__.py +0 -0
  128. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/streaming/content_buffer.py +0 -0
  129. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/streaming/langserve.py +0 -0
  130. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/streaming/stream_lookup.py +0 -0
  131. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/streaming/streaming.py +0 -0
  132. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/summarise/__init__.py +0 -0
  133. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/summarise/summarise.py +0 -0
  134. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/terraform/__init__.py +0 -0
  135. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/terraform/tfvars_editor.py +0 -0
  136. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/tools/__init__.py +0 -0
  137. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/tools/web_browser.py +0 -0
  138. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/utils/__init__.py +0 -0
  139. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/utils/api_key.py +0 -0
  140. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/utils/big_context.py +0 -0
  141. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/utils/config.py +0 -0
  142. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/utils/config_class.py +0 -0
  143. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/utils/config_schema.py +0 -0
  144. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/utils/gcp.py +0 -0
  145. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/utils/gcp_project.py +0 -0
  146. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/utils/mime.py +0 -0
  147. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/utils/parsers.py +0 -0
  148. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/utils/timedelta.py +0 -0
  149. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/utils/user_ids.py +0 -0
  150. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/utils/version.py +0 -0
  151. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/vertex/__init__.py +0 -0
  152. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/vertex/extensions_call.py +0 -0
  153. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/vertex/extensions_class.py +0 -0
  154. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/vertex/genai_functions.py +0 -0
  155. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/vertex/init.py +0 -0
  156. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/vertex/memory_tools.py +0 -0
  157. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/vertex/safety.py +0 -0
  158. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo/vertex/type_dict_to_json.py +0 -0
  159. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo.egg-info/SOURCES.txt +0 -0
  160. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo.egg-info/dependency_links.txt +0 -0
  161. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo.egg-info/entry_points.txt +0 -0
  162. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo.egg-info/requires.txt +0 -0
  163. {sunholo-0.106.0 → sunholo-0.106.2}/sunholo.egg-info/top_level.txt +0 -0
  164. {sunholo-0.106.0 → sunholo-0.106.2}/tests/test_async.py +0 -0
  165. {sunholo-0.106.0 → sunholo-0.106.2}/tests/test_chat_history.py +0 -0
  166. {sunholo-0.106.0 → sunholo-0.106.2}/tests/test_config.py +0 -0
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sunholo
3
- Version: 0.106.0
3
+ Version: 0.106.2
4
4
  Summary: Large Language Model DevOps - a package to help deploy LLMs to the Cloud.
5
5
  Home-page: https://github.com/sunholo-data/sunholo-py
6
- Download-URL: https://github.com/sunholo-data/sunholo-py/archive/refs/tags/v0.106.0.tar.gz
6
+ Download-URL: https://github.com/sunholo-data/sunholo-py/archive/refs/tags/v0.106.2.tar.gz
7
7
  Author: Holosun ApS
8
8
  Author-email: multivac@sunholo.com
9
9
  License: Apache License, Version 2.0
@@ -1,6 +1,6 @@
1
1
  from setuptools import setup, find_packages
2
2
 
3
- version = '0.106.0'
3
+ version = '0.106.2'
4
4
 
5
5
  setup(
6
6
  name='sunholo',
@@ -56,7 +56,6 @@ setup(
56
56
  "google-cloud-pubsub",
57
57
  "google-cloud-discoveryengine",
58
58
  "google-cloud-texttospeech",
59
-
60
59
  "google-generativeai>=0.7.1",
61
60
  "gunicorn",
62
61
  "httpcore",
@@ -12,6 +12,7 @@ except ImportError:
12
12
 
13
13
  from ..custom_logging import log
14
14
  from typing import Optional, List
15
+ import asyncio
15
16
 
16
17
  class DiscoveryEngineClient:
17
18
  """
@@ -69,9 +70,15 @@ class DiscoveryEngineClient:
69
70
  self.store_client = discoveryengine.DataStoreServiceClient(client_options=client_options)
70
71
  self.doc_client = discoveryengine.DocumentServiceClient(client_options=client_options)
71
72
  self.search_client = discoveryengine.SearchServiceClient(client_options=client_options)
72
- self.async_search_client = discoveryengine.SearchServiceAsyncClient(client_options=client_options)
73
73
  self.engine_client = discoveryengine.EngineServiceClient(client_options=client_options)
74
-
74
+ # Initialize the async client only if there's an active event loop
75
+ try:
76
+ asyncio.get_running_loop()
77
+ self.async_search_client = discoveryengine.SearchServiceAsyncClient(client_options=client_options)
78
+ except RuntimeError:
79
+ # No event loop in non-async environment, set async client to None
80
+ log.info("No event loop detected; skipping async client initialization")
81
+ self.async_search_client = None
75
82
 
76
83
  @classmethod
77
84
  def my_retry(cls):
@@ -60,7 +60,10 @@ class StreamingTTS:
60
60
  self.voice_gender = texttospeech.SsmlVoiceGender.NEUTRAL
61
61
  self.voice_name = "en-GB-Journey-D"
62
62
  # Audio processing parameters
63
- self.fade_duration = 0.1 # 10ms fade in/out
63
+ # Separate fade durations for playback and file saving
64
+ self.playback_fade_duration = 0.05 # 50ms fade for real-time playback
65
+ self.file_fade_duration = 0.01 # 10ms fade for file saving
66
+ self.stream = None
64
67
  self._initialize_audio_device()
65
68
 
66
69
  def set_voice(self, voice_name: str):
@@ -137,111 +140,101 @@ class StreamingTTS:
137
140
  sd.default.channels = 1
138
141
  sd.default.dtype = np.int16
139
142
 
140
- # Start and stop the stream once to "warm up" the audio device
141
- dummy_audio = np.zeros(int(self.sample_rate * 1), dtype=np.int16)
142
- with sd.OutputStream(
143
+ # Initialize persistent output stream
144
+ self.stream = sd.OutputStream(
143
145
  samplerate=self.sample_rate,
144
146
  channels=1,
145
147
  dtype=np.int16,
146
148
  latency='low'
147
- ) as stream:
148
- stream.write(dummy_audio)
149
+ )
150
+ self.stream.start()
149
151
 
150
152
  log.info("Audio device initialized successfully")
151
153
  except Exception as e:
152
154
  log.error(f"Error initializing audio device: {e}")
153
155
  raise
154
156
 
155
- def _make_fade(x, in_length, out_length=None, type='l', copy=True):
156
- """Apply fade in/out to a signal.
157
-
158
- If `x` is two-dimenstional, this works along the columns (= first
159
- axis).
160
-
161
- This is based on the *fade* effect of SoX, see:
162
- http://sox.sourceforge.net/sox.html
163
-
164
- The C implementation can be found here:
165
- http://sourceforge.net/p/sox/code/ci/master/tree/src/fade.c
166
-
167
- Parameters
168
- ----------
169
- x : array_like
170
- Input signal.
171
- in_length : int
172
- Length of fade-in in samples (contrary to SoX, where this is
173
- specified in seconds).
174
- out_length : int, optional
175
- Length of fade-out in samples. If not specified, `fade_in` is
176
- used also for the fade-out.
177
- type : {'t', 'q', 'h', 'l', 'p'}, optional
178
- Select the shape of the fade curve: 'q' for quarter of a sine
179
- wave, 'h' for half a sine wave, 't' for linear ("triangular")
180
- slope, 'l' for logarithmic, and 'p' for inverted parabola.
181
- The default is logarithmic.
182
- copy : bool, optional
183
- If `False`, the fade is applied in-place and a reference to
184
- `x` is returned.
185
-
186
- """
187
- x = np.array(x, copy=copy)
188
-
189
- if out_length is None:
190
- out_length = in_length
191
-
192
- def make_fade(length, type):
193
- fade = np.arange(length) / length
194
- if type == 't': # triangle
195
- pass
196
- elif type == 'q': # quarter of sinewave
197
- fade = np.sin(fade * np.pi / 2)
198
- elif type == 'h': # half of sinewave... eh cosine wave
199
- fade = (1 - np.cos(fade * np.pi)) / 2
200
- elif type == 'l': # logarithmic
201
- fade = np.power(0.1, (1 - fade) * 5) # 5 means 100 db attenuation
202
- elif type == 'p': # inverted parabola
203
- fade = (1 - (1 - fade)**2)
204
- else:
205
- raise ValueError("Unknown fade type {0!r}".format(type))
206
- return fade
207
-
208
- # Using .T w/o [:] causes error: https://github.com/numpy/numpy/issues/2667
209
- x[:in_length].T[:] *= make_fade(in_length, type)
210
- x[len(x) - out_length:].T[:] *= make_fade(out_length, type)[::-1]
211
- return x
157
+ def _make_fade(self, length: int, fade_type: str='l') -> np.ndarray:
158
+ """Generate a fade curve of specified length and type."""
159
+ fade = np.arange(length, dtype=np.float32) / length
160
+
161
+ if fade_type == 't': # triangle
162
+ pass
163
+ elif fade_type == 'q': # quarter of sinewave
164
+ fade = np.sin(fade * np.pi / 2)
165
+ elif fade_type == 'h': # half of sinewave
166
+ fade = (1 - np.cos(fade * np.pi)) / 2
167
+ elif fade_type == 'l': # logarithmic
168
+ fade = np.power(0.1, (1 - fade) * 5)
169
+ elif fade_type == 'p': # inverted parabola
170
+ fade = (1 - (1 - fade)**2)
171
+ else:
172
+ raise ValueError(f"Unknown fade type {fade_type!r}")
173
+
174
+ return fade
212
175
 
213
- def _apply_fade(self, audio: np.ndarray, fade_in: bool = True, fade_out: bool = True) -> np.ndarray:
214
- """Apply fade in/out to audio to prevent clicks."""
215
- fade_length = int(self.fade_duration * self.sample_rate)
216
- audio = audio.astype(np.float32) # Convert to float for fade calculation
176
+ def _apply_fade(self, audio: np.ndarray, fade_duration: float, fade_in: bool = True, fade_out: bool = True) -> np.ndarray:
177
+ """Apply fade in/out to audio with specified duration."""
178
+ if audio.ndim != 1:
179
+ raise ValueError("Audio must be 1-dimensional")
180
+
181
+ fade_length = int(fade_duration * self.sample_rate)
182
+ audio = audio.astype(np.float32)
217
183
 
218
184
  if fade_in:
219
- fade_in_curve = np.linspace(0, 1, fade_length)
185
+ fade_in_curve = self._make_fade(fade_length, 'l')
220
186
  audio[:fade_length] *= fade_in_curve
221
187
 
222
188
  if fade_out:
223
- fade_out_curve = np.linspace(1, 0, fade_length)
224
- audio[-fade_length:] *= fade_out_curve
189
+ fade_out_curve = self._make_fade(fade_length, 'l')
190
+ audio[-fade_length:] *= fade_out_curve[::-1]
225
191
 
226
- return audio.astype(np.int16) # Convert back to int16
192
+ return audio.astype(np.int16)
193
+
227
194
 
228
- def _play_audio_chunk(self, audio_chunk: np.ndarray):
195
+ def _play_audio_chunk(self, audio_chunk: np.ndarray, is_final_chunk: bool = False):
229
196
  """Play a single audio chunk with proper device handling."""
230
197
  try:
231
- # Add small silence padding and apply fades
232
- padding = np.zeros(int(1 * self.sample_rate), dtype=np.int16)
233
- audio_with_padding = np.concatenate([padding, audio_chunk, padding])
234
- processed_audio = self._apply_fade(audio_with_padding)
198
+ # Add longer padding for the final chunk
199
+ padding_duration = 0.1 if is_final_chunk else 0.02
200
+ padding = np.zeros(int(padding_duration * self.sample_rate), dtype=np.int16)
235
201
 
236
- # Use context manager for proper stream handling
237
- with sd.OutputStream(
238
- samplerate=self.sample_rate,
239
- channels=1,
240
- dtype=np.int16,
241
- latency='low'
242
- ) as stream:
243
- stream.write(processed_audio)
244
- stream.write(np.zeros(int(0.1 * self.sample_rate), dtype=np.int16))
202
+ if is_final_chunk:
203
+ # For final chunk, add extra padding and longer fade
204
+ audio_with_padding = np.concatenate([
205
+ padding,
206
+ audio_chunk,
207
+ padding,
208
+ np.zeros(int(0.2 * self.sample_rate), dtype=np.int16) # Extra tail padding
209
+ ])
210
+ fade_duration = self.playback_fade_duration * 2 # Longer fade for end
211
+ else:
212
+ audio_with_padding = np.concatenate([padding, audio_chunk, padding])
213
+ fade_duration = self.playback_fade_duration
214
+
215
+ processed_audio = self._apply_fade(
216
+ audio_with_padding,
217
+ fade_duration=fade_duration,
218
+ fade_in=True,
219
+ fade_out=True
220
+ )
221
+
222
+ if self.stream and self.stream.active:
223
+ self.stream.write(processed_audio)
224
+ if is_final_chunk:
225
+ # Write a small buffer of silence at the end
226
+ final_silence = np.zeros(int(0.1 * self.sample_rate), dtype=np.int16)
227
+ self.stream.write(final_silence)
228
+ else:
229
+ with sd.OutputStream(
230
+ samplerate=self.sample_rate,
231
+ channels=1,
232
+ dtype=np.int16,
233
+ latency='low'
234
+ ) as temp_stream:
235
+ temp_stream.write(processed_audio)
236
+ if is_final_chunk:
237
+ temp_stream.write(np.zeros(int(0.1 * self.sample_rate), dtype=np.int16))
245
238
 
246
239
  except Exception as e:
247
240
  log.error(f"Error during audio playback: {e}")
@@ -250,11 +243,31 @@ class StreamingTTS:
250
243
  def audio_player(self):
251
244
  """Continuously play audio chunks from the queue."""
252
245
  log.info("Audio player started")
253
- while self.is_playing or not self.audio_queue.empty():
254
- if not self.audio_queue.empty():
255
- audio_chunk = self.audio_queue.get()
256
- self._play_audio_chunk(audio_chunk)
257
- time.sleep(0.1)
246
+ try:
247
+ while self.is_playing or not self.audio_queue.empty():
248
+ if not self.audio_queue.empty():
249
+ audio_chunk = self.audio_queue.get()
250
+ self._play_audio_chunk(audio_chunk)
251
+ time.sleep(0.005) # Reduced sleep time for more responsive playback
252
+ finally:
253
+ # Ensure stream is properly closed
254
+ if self.stream and self.stream.active:
255
+ self.stream.stop()
256
+ self.stream.close()
257
+ self.stream = None
258
+
259
+ def __del__(self):
260
+ """Cleanup method to ensure stream is closed."""
261
+ if hasattr(self, 'stream') and self.stream and self.stream.active:
262
+ # Write a small silence buffer before closing
263
+ final_silence = np.zeros(int(0.1 * self.sample_rate), dtype=np.int16)
264
+ try:
265
+ self.stream.write(final_silence)
266
+ time.sleep(0.1) # Let the final audio finish playing
267
+ except Exception:
268
+ pass # Ignore errors during cleanup
269
+ self.stream.stop()
270
+ self.stream.close()
258
271
 
259
272
  def process_text_stream(self, text_generator):
260
273
  """Process incoming text stream and convert to audio."""
@@ -281,24 +294,37 @@ class StreamingTTS:
281
294
  player_thread.join()
282
295
 
283
296
  def save_to_file(self, text_generator, output_path):
284
- """Save the audio to a WAV file instead of playing it."""
297
+ """Save the audio to a WAV file with minimal fading."""
285
298
  import wave
286
299
 
287
300
  all_audio = []
288
301
  for text_chunk in text_generator:
289
302
  audio_chunk = self.text_to_audio(text_chunk)
290
- processed_chunk = self._apply_fade(audio_chunk)
303
+ # Use shorter fade duration for file saving
304
+ processed_chunk = self._apply_fade(
305
+ audio_chunk,
306
+ fade_duration=self.file_fade_duration
307
+ )
291
308
  all_audio.append(processed_chunk)
292
309
 
293
- # Add small silence between chunks and at ends
294
- silence = np.zeros(int(0.1 * self.sample_rate), dtype=np.int16)
310
+ # Add minimal silence between chunks
311
+ silence = np.zeros(int(0.05 * self.sample_rate), dtype=np.int16)
295
312
  final_audio = silence
296
- for chunk in all_audio:
313
+
314
+ for i, chunk in enumerate(all_audio):
315
+ if i == len(all_audio) - 1:
316
+ # For the last chunk, use a slightly longer fade out
317
+ chunk = self._apply_fade(
318
+ chunk,
319
+ fade_duration=self.file_fade_duration * 2,
320
+ fade_in=False,
321
+ fade_out=True
322
+ )
297
323
  final_audio = np.concatenate([final_audio, chunk, silence])
298
324
 
299
325
  with wave.open(output_path, 'wb') as wav_file:
300
326
  wav_file.setnchannels(1)
301
- wav_file.setsampwidth(2) # 16-bit audio
327
+ wav_file.setsampwidth(2)
302
328
  wav_file.setframerate(self.sample_rate)
303
329
  wav_file.writeframes(final_audio.tobytes())
304
330
 
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sunholo
3
- Version: 0.106.0
3
+ Version: 0.106.2
4
4
  Summary: Large Language Model DevOps - a package to help deploy LLMs to the Cloud.
5
5
  Home-page: https://github.com/sunholo-data/sunholo-py
6
- Download-URL: https://github.com/sunholo-data/sunholo-py/archive/refs/tags/v0.106.0.tar.gz
6
+ Download-URL: https://github.com/sunholo-data/sunholo-py/archive/refs/tags/v0.106.2.tar.gz
7
7
  Author: Holosun ApS
8
8
  Author-email: multivac@sunholo.com
9
9
  License: Apache License, Version 2.0
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes