polytext 0.2.8b6__tar.gz → 0.2.8b7__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.
- {polytext-0.2.8b6 → polytext-0.2.8b7}/PKG-INFO +1 -1
- {polytext-0.2.8b6 → polytext-0.2.8b7}/polytext/converter/audio_to_text.py +37 -1
- {polytext-0.2.8b6 → polytext-0.2.8b7}/polytext/loader/base.py +8 -3
- {polytext-0.2.8b6 → polytext-0.2.8b7}/polytext.egg-info/PKG-INFO +1 -1
- {polytext-0.2.8b6 → polytext-0.2.8b7}/polytext.egg-info/SOURCES.txt +1 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/setup.py +1 -1
- polytext-0.2.8b7/tests/test_audio_chunk_quality.py +76 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/tests/test_base_loader_error_mapping.py +19 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/LICENSE +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/README.md +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/polytext/__init__.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/polytext/converter/__init__.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/polytext/converter/base.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/polytext/converter/beautiful_text.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/polytext/converter/document_ocr_to_text.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/polytext/converter/document_ocr_to_text_azure_oai.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/polytext/converter/gemini_quality_guards.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/polytext/converter/html_to_md.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/polytext/converter/md_to_text.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/polytext/converter/ocr_to_text.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/polytext/converter/ocr_to_text_azure_oai.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/polytext/converter/pdf.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/polytext/converter/text_to_md.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/polytext/converter/video_to_audio.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/polytext/exceptions/__init__.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/polytext/exceptions/base.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/polytext/generator/__init__.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/polytext/generator/pdf.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/polytext/loader/__init__.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/polytext/loader/audio.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/polytext/loader/aws_auth.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/polytext/loader/document.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/polytext/loader/document_ocr.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/polytext/loader/downloader/__init__.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/polytext/loader/downloader/downloader.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/polytext/loader/html.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/polytext/loader/markdown.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/polytext/loader/notebook.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/polytext/loader/ocr.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/polytext/loader/plain_text.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/polytext/loader/video.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/polytext/loader/xml_xbrl.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/polytext/loader/youtube.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/polytext/loader/youtube_llm.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/polytext/processor/__init__.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/polytext/processor/audio_chunker.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/polytext/processor/text_merger.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/polytext/processor/transcript_chunker.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/polytext/prompts/__init__.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/polytext/prompts/beautiful_text.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/polytext/prompts/ocr.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/polytext/prompts/text_merging.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/polytext/prompts/text_to_md.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/polytext/prompts/transcription.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/polytext/utils/__init__.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/polytext/utils/utils.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/polytext.egg-info/dependency_links.txt +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/polytext.egg-info/not-zip-safe +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/polytext.egg-info/requires.txt +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/polytext.egg-info/top_level.txt +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/pyproject.toml +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/setup.cfg +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/tests/test_audio_chunker.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/tests/test_audio_comparison_helpers.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/tests/test_audio_transcription_model_migration.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/tests/test_aws_auth.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/tests/test_beautiful_text_manual.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/tests/test_compare_audio_models.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/tests/test_compare_document_ocr_to_text_models.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/tests/test_compare_ocr_to_text_models.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/tests/test_compare_youtube_models.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/tests/test_dowload_audio_from_youtube.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/tests/test_dowload_audio_from_youtube_helpers.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/tests/test_extracted_text_whitespace.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/tests/test_gemini_quality_guards.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/tests/test_get_audio_transcript_from_gcs.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/tests/test_get_customized_pdf_from_markdown.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/tests/test_get_document_ocr.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/tests/test_get_document_ocr_azure_oai.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/tests/test_get_document_text.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/tests/test_get_document_text_from_gcs.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/tests/test_get_ocr_from_image.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/tests/test_get_text_from_markdown.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/tests/test_get_video_transcript_from_gcs.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/tests/test_library.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/tests/test_markdown_loader_gzip.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/tests/test_markitdown_html.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/tests/test_notebook_loader.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/tests/test_ocr_fallbacks.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/tests/test_ocr_image_descriptions.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/tests/test_pain_text.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/tests/test_pdf_conversion_error.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/tests/test_python_version_metadata.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/tests/test_split_audio_with_llm.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/tests/test_transcribe_s3_images_from_csv.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/tests/test_transcribe_s3_images_from_csv_script.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/tests/test_xml_xbrl_loader.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/tests/test_youtube_gemini_minimal_check.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/tests/test_youtube_llm_fallbacks.py +0 -0
- {polytext-0.2.8b6 → polytext-0.2.8b7}/tests/test_youtube_transcript.py +0 -0
|
@@ -63,6 +63,8 @@ AUDIO_DEFAULT_MAX_OUTPUT_TOKENS = 4096
|
|
|
63
63
|
AUDIO_MAX_ADAPTIVE_SPLIT_DEPTH = 1
|
|
64
64
|
AUDIO_ADAPTIVE_SPLIT_OVERLAP_MS = 2000
|
|
65
65
|
AUDIO_LONG_DURATION_THRESHOLD_MS = 80 * 60 * 1000
|
|
66
|
+
AUDIO_LOW_TEXT_THRESHOLD_WORDS_PER_MINUTE = 20
|
|
67
|
+
AUDIO_LOW_TEXT_MIN_DURATION_MS = 60_000
|
|
66
68
|
AUDIO_TAIL_REPETITION_LINES = int(os.getenv("AUDIO_TAIL_REPETITION_LINES", "200"))
|
|
67
69
|
AUDIO_TAIL_REPETITION_THRESHOLD = float(os.getenv("AUDIO_TAIL_REPETITION_THRESHOLD", "0.35"))
|
|
68
70
|
AUDIO_FALLBACK_SOURCE_PATTERN = os.getenv("AUDIO_FALLBACK_SOURCE_PATTERN", "flash-lite")
|
|
@@ -284,6 +286,37 @@ def normalize_no_human_speech_marker(text: str) -> tuple[str, bool]:
|
|
|
284
286
|
return cleaned_text, False
|
|
285
287
|
|
|
286
288
|
|
|
289
|
+
def summarize_audio_chunk_quality(chunks: list[dict], transcripts: list[str]) -> dict:
|
|
290
|
+
"""Report empty and unusually short transcripts without discarding any text.
|
|
291
|
+
|
|
292
|
+
Indices are one-based. Short audio chunks are excluded from the low-text
|
|
293
|
+
count because a brief spoken phrase can be a complete transcription.
|
|
294
|
+
"""
|
|
295
|
+
empty_indices = []
|
|
296
|
+
low_text_indices = []
|
|
297
|
+
for index, (chunk, transcript) in enumerate(zip(chunks, transcripts), start=1):
|
|
298
|
+
word_count = len(transcript.split())
|
|
299
|
+
if word_count == 0:
|
|
300
|
+
empty_indices.append(index)
|
|
301
|
+
else:
|
|
302
|
+
duration_ms = chunk.get("duration_ms", 0)
|
|
303
|
+
if (
|
|
304
|
+
duration_ms >= AUDIO_LOW_TEXT_MIN_DURATION_MS
|
|
305
|
+
and word_count * 60_000 < AUDIO_LOW_TEXT_THRESHOLD_WORDS_PER_MINUTE * duration_ms
|
|
306
|
+
):
|
|
307
|
+
low_text_indices.append(index)
|
|
308
|
+
|
|
309
|
+
return {
|
|
310
|
+
"total_chunks": len(chunks),
|
|
311
|
+
"empty_chunks": len(empty_indices),
|
|
312
|
+
"low_text_chunks": len(low_text_indices),
|
|
313
|
+
"empty_chunk_indices": empty_indices,
|
|
314
|
+
"low_text_chunk_indices": low_text_indices,
|
|
315
|
+
"low_text_threshold_words_per_minute": AUDIO_LOW_TEXT_THRESHOLD_WORDS_PER_MINUTE,
|
|
316
|
+
"low_text_min_duration_ms": AUDIO_LOW_TEXT_MIN_DURATION_MS,
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
|
|
287
320
|
def add_line_break_after_each_sentence(text: str) -> str:
|
|
288
321
|
if not text:
|
|
289
322
|
return text
|
|
@@ -1030,6 +1063,8 @@ class AudioToTextConverter:
|
|
|
1030
1063
|
- prompt_tokens (int): Total number of prompt tokens used
|
|
1031
1064
|
- completion_model (str): Name of the transcription model used
|
|
1032
1065
|
- completion_model_provider (str): Provider of the transcription model
|
|
1066
|
+
- audio_chunk_quality (dict): Counts of empty and low-text chunks,
|
|
1067
|
+
with one-based indices and the applied words-per-minute threshold
|
|
1033
1068
|
|
|
1034
1069
|
Raises:
|
|
1035
1070
|
ValueError: If the audio file format is not recognized
|
|
@@ -1121,7 +1156,8 @@ class AudioToTextConverter:
|
|
|
1121
1156
|
"completion_tokens": completion_tokens + full_text_merged_dict["completion_tokens"],
|
|
1122
1157
|
"prompt_tokens": prompt_tokens + full_text_merged_dict["prompt_tokens"],
|
|
1123
1158
|
"completion_model": self.transcription_model,
|
|
1124
|
-
"completion_model_provider": self.transcription_model_provider
|
|
1159
|
+
"completion_model_provider": self.transcription_model_provider,
|
|
1160
|
+
"audio_chunk_quality": summarize_audio_chunk_quality(chunks, transcript_chunks),
|
|
1125
1161
|
}
|
|
1126
1162
|
if len(chunk_results) == 1 and chunk_results[0]:
|
|
1127
1163
|
for key in (
|
|
@@ -42,6 +42,7 @@ logger = logging.getLogger(__name__)
|
|
|
42
42
|
|
|
43
43
|
MIN_DOC_TEXT_LENGTH_ACCEPTED = int(os.getenv("MIN_DOC_TEXT_LENGTH_ACCEPTED", "400"))
|
|
44
44
|
OCR_INCLUDE_IMAGE_DESCRIPTIONS_ENV = "OCR_INCLUDE_IMAGE_DESCRIPTIONS"
|
|
45
|
+
FALLBACK_OCR_ENV = "FALLBACK_OCR"
|
|
45
46
|
LLM_OUTPUT_ERROR_CODES = {
|
|
46
47
|
995: "INVALID_ARGUMENT",
|
|
47
48
|
996: "RECITATION",
|
|
@@ -119,7 +120,7 @@ class BaseLoader:
|
|
|
119
120
|
- target_size (int, optional): Target file size in bytes. Defaults to 1MB
|
|
120
121
|
- source (str): Source of the document. Must be either "cloud" or "local"
|
|
121
122
|
- fallback_ocr (bool, optional): If True, Optical Character Recognition (OCR) will be used as a fallback
|
|
122
|
-
if direct text extraction fails,
|
|
123
|
+
if direct text extraction fails. When omitted, defaults from FALLBACK_OCR and otherwise to False.
|
|
123
124
|
- save_transcript_chunks (bool, optional): Whether to save chunk transcripts in final output. Defaults to False.
|
|
124
125
|
- bitrate_quality (int, optional): Variable bitrate quality from 0-9 (9 being lowest). Defaults to 9
|
|
125
126
|
|
|
@@ -143,7 +144,10 @@ class BaseLoader:
|
|
|
143
144
|
self.kwargs = kwargs
|
|
144
145
|
self.target_size = kwargs.get("target_size", 1)
|
|
145
146
|
self.source = kwargs.get("source", "cloud")
|
|
146
|
-
self.fallback_ocr = kwargs.get(
|
|
147
|
+
self.fallback_ocr = kwargs.get(
|
|
148
|
+
"fallback_ocr",
|
|
149
|
+
_read_bool_env(FALLBACK_OCR_ENV),
|
|
150
|
+
)
|
|
147
151
|
self.save_transcript_chunks = kwargs.get("save_transcript_chunks", False)
|
|
148
152
|
self.bitrate_quality = kwargs.get("bitrate_quality", 9)
|
|
149
153
|
self.aws_auth_mode = kwargs.get("aws_auth_mode")
|
|
@@ -548,6 +552,7 @@ class BaseLoader:
|
|
|
548
552
|
- completion_model (str): Model name used, from the first processed source.
|
|
549
553
|
- completion_model_provider (str): Provider of the model, from the first processed source.
|
|
550
554
|
- text_chunks (list, optional): List of text chunks, if chunking was applied.
|
|
555
|
+
- audio_chunk_quality (dict, optional): Counts of empty and low-text audio chunks.
|
|
551
556
|
- type (str): Type of the processed source (e.g., ocr, video, audio, text).
|
|
552
557
|
- input (str): The input path or URL of the first processed source.
|
|
553
558
|
- output_list (list): List of individual extraction results (one per input).
|
|
@@ -608,7 +613,7 @@ class BaseLoader:
|
|
|
608
613
|
"output_list": [result_dict],
|
|
609
614
|
}
|
|
610
615
|
|
|
611
|
-
for metadata_key in ("ocr_failed_pages", "ocr_failed_pages_detail"):
|
|
616
|
+
for metadata_key in ("ocr_failed_pages", "ocr_failed_pages_detail", "audio_chunk_quality"):
|
|
612
617
|
if metadata_key in result_dict:
|
|
613
618
|
final_result[metadata_key] = result_dict[metadata_key]
|
|
614
619
|
|
|
@@ -56,6 +56,7 @@ polytext/prompts/text_to_md.py
|
|
|
56
56
|
polytext/prompts/transcription.py
|
|
57
57
|
polytext/utils/__init__.py
|
|
58
58
|
polytext/utils/utils.py
|
|
59
|
+
tests/test_audio_chunk_quality.py
|
|
59
60
|
tests/test_audio_chunker.py
|
|
60
61
|
tests/test_audio_comparison_helpers.py
|
|
61
62
|
tests/test_audio_transcription_model_migration.py
|
|
@@ -51,7 +51,7 @@ def get_requirements(*requirements_file):
|
|
|
51
51
|
|
|
52
52
|
setup(
|
|
53
53
|
name='polytext',
|
|
54
|
-
version='0.2.
|
|
54
|
+
version='0.2.8b7',
|
|
55
55
|
url='https://github.com/docsity/polytext',
|
|
56
56
|
# download_url='https://github.com/pualien/py-polytext/archive/0.1.23.tar.gz',
|
|
57
57
|
license='MIT',
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import tempfile
|
|
2
|
+
import unittest
|
|
3
|
+
from unittest.mock import MagicMock, patch
|
|
4
|
+
|
|
5
|
+
from polytext.converter import audio_to_text
|
|
6
|
+
from polytext.loader import BaseLoader
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class AudioChunkQualityTests(unittest.TestCase):
|
|
10
|
+
def test_counts_empty_and_low_text_chunks_separately(self):
|
|
11
|
+
chunks = [
|
|
12
|
+
{"duration_ms": 120_000},
|
|
13
|
+
{"duration_ms": 120_000},
|
|
14
|
+
{"duration_ms": 120_000},
|
|
15
|
+
{"duration_ms": 20_000},
|
|
16
|
+
{"duration_ms": 1_200_000},
|
|
17
|
+
]
|
|
18
|
+
transcripts = ["parola " * 40, " \n", "parola " * 12, "ciao", "parola " * 100]
|
|
19
|
+
|
|
20
|
+
quality = audio_to_text.summarize_audio_chunk_quality(chunks, transcripts)
|
|
21
|
+
|
|
22
|
+
self.assertEqual(quality, {
|
|
23
|
+
"total_chunks": 5,
|
|
24
|
+
"empty_chunks": 1,
|
|
25
|
+
"low_text_chunks": 2,
|
|
26
|
+
"empty_chunk_indices": [2],
|
|
27
|
+
"low_text_chunk_indices": [3, 5],
|
|
28
|
+
"low_text_threshold_words_per_minute": 20,
|
|
29
|
+
"low_text_min_duration_ms": 60_000,
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
@patch("polytext.converter.audio_to_text.TextMerger")
|
|
33
|
+
@patch("polytext.converter.audio_to_text.AudioChunker")
|
|
34
|
+
@patch.object(audio_to_text.AudioToTextConverter, "process_chunk")
|
|
35
|
+
def test_quality_is_returned_without_saving_chunk_transcripts(
|
|
36
|
+
self, mock_process_chunk, mock_chunker_cls, mock_merger_cls
|
|
37
|
+
):
|
|
38
|
+
chunker = mock_chunker_cls.return_value
|
|
39
|
+
chunker.duration_ms = 240_000
|
|
40
|
+
chunker.extract_chunks.return_value = [
|
|
41
|
+
{"file_path": "/tmp/chunk1.mp3", "duration_ms": 120_000},
|
|
42
|
+
{"file_path": "/tmp/chunk2.mp3", "duration_ms": 120_000},
|
|
43
|
+
]
|
|
44
|
+
mock_process_chunk.side_effect = lambda chunk, index: (
|
|
45
|
+
index,
|
|
46
|
+
{"transcript": "parola " * 40 if index == 0 else "", "completion_tokens": 4, "prompt_tokens": 5},
|
|
47
|
+
)
|
|
48
|
+
mock_merger_cls.return_value.merge_chunks_with_llm_sequential.return_value = {
|
|
49
|
+
"full_text_merged": "testo finale", "completion_tokens": 0, "prompt_tokens": 0
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
with tempfile.NamedTemporaryFile(suffix=".mp3") as source_audio:
|
|
53
|
+
converter = audio_to_text.AudioToTextConverter()
|
|
54
|
+
result = converter.transcribe_full_audio(source_audio.name)
|
|
55
|
+
|
|
56
|
+
self.assertEqual(result["text"], "testo finale")
|
|
57
|
+
self.assertEqual(result["audio_chunk_quality"]["total_chunks"], 2)
|
|
58
|
+
self.assertEqual(result["audio_chunk_quality"]["empty_chunks"], 1)
|
|
59
|
+
self.assertNotIn("text_chunks", result)
|
|
60
|
+
|
|
61
|
+
def test_quality_reaches_top_level_loader_response(self):
|
|
62
|
+
quality = {"total_chunks": 2, "empty_chunks": 1, "low_text_chunks": 0}
|
|
63
|
+
file_loader = MagicMock()
|
|
64
|
+
file_loader.load.return_value = {
|
|
65
|
+
"text": "trascrizione", "completion_tokens": 3, "prompt_tokens": 4,
|
|
66
|
+
"audio_chunk_quality": quality,
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
response = BaseLoader().run_loader_class(file_loader, ["audio.mp3"])
|
|
70
|
+
|
|
71
|
+
self.assertEqual(response["audio_chunk_quality"], quality)
|
|
72
|
+
self.assertEqual(response["output_list"][0]["audio_chunk_quality"], quality)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
if __name__ == "__main__":
|
|
76
|
+
unittest.main()
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import os
|
|
1
2
|
import unittest
|
|
2
3
|
from types import SimpleNamespace
|
|
3
4
|
from unittest.mock import Mock, patch
|
|
@@ -60,6 +61,24 @@ class _BeautifulTextLoader(BaseLoader):
|
|
|
60
61
|
|
|
61
62
|
|
|
62
63
|
class TestBaseLoaderErrorMapping(unittest.TestCase):
|
|
64
|
+
def test_document_ocr_fallback_is_enabled_by_environment(self):
|
|
65
|
+
with patch.dict(os.environ, {"FALLBACK_OCR": "true"}, clear=True):
|
|
66
|
+
loader = BaseLoader()
|
|
67
|
+
|
|
68
|
+
self.assertTrue(loader.fallback_ocr)
|
|
69
|
+
|
|
70
|
+
def test_explicit_document_ocr_fallback_setting_overrides_environment(self):
|
|
71
|
+
with patch.dict(os.environ, {"FALLBACK_OCR": "true"}, clear=True):
|
|
72
|
+
loader = BaseLoader(fallback_ocr=False)
|
|
73
|
+
|
|
74
|
+
self.assertFalse(loader.fallback_ocr)
|
|
75
|
+
|
|
76
|
+
def test_document_ocr_fallback_remains_disabled_without_configuration(self):
|
|
77
|
+
with patch.dict(os.environ, {}, clear=True):
|
|
78
|
+
loader = BaseLoader()
|
|
79
|
+
|
|
80
|
+
self.assertFalse(loader.fallback_ocr)
|
|
81
|
+
|
|
63
82
|
def test_llm_output_empty_document_codes_are_raised_as_loader_errors(self):
|
|
64
83
|
cases = [
|
|
65
84
|
(995, "INVALID_ARGUMENT"),
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|