npcpy 1.2.34__tar.gz → 1.2.35__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.
- {npcpy-1.2.34/npcpy.egg-info → npcpy-1.2.35}/PKG-INFO +1 -1
- {npcpy-1.2.34 → npcpy-1.2.35}/npcpy/data/audio.py +35 -1
- npcpy-1.2.35/npcpy/data/load.py +296 -0
- npcpy-1.2.35/npcpy/data/video.py +100 -0
- npcpy-1.2.35/npcpy/ft/diff.py +371 -0
- {npcpy-1.2.34 → npcpy-1.2.35}/npcpy/gen/image_gen.py +120 -23
- npcpy-1.2.35/npcpy/gen/ocr.py +187 -0
- {npcpy-1.2.34 → npcpy-1.2.35}/npcpy/memory/command_history.py +231 -40
- {npcpy-1.2.34 → npcpy-1.2.35}/npcpy/npc_compiler.py +14 -5
- {npcpy-1.2.34 → npcpy-1.2.35}/npcpy/serve.py +1206 -547
- {npcpy-1.2.34 → npcpy-1.2.35/npcpy.egg-info}/PKG-INFO +1 -1
- {npcpy-1.2.34 → npcpy-1.2.35}/npcpy.egg-info/SOURCES.txt +1 -0
- {npcpy-1.2.34 → npcpy-1.2.35}/setup.py +1 -1
- {npcpy-1.2.34 → npcpy-1.2.35}/tests/test_load.py +54 -33
- npcpy-1.2.34/npcpy/data/load.py +0 -154
- npcpy-1.2.34/npcpy/data/video.py +0 -28
- npcpy-1.2.34/npcpy/ft/diff.py +0 -110
- {npcpy-1.2.34 → npcpy-1.2.35}/LICENSE +0 -0
- {npcpy-1.2.34 → npcpy-1.2.35}/MANIFEST.in +0 -0
- {npcpy-1.2.34 → npcpy-1.2.35}/README.md +0 -0
- {npcpy-1.2.34 → npcpy-1.2.35}/npcpy/__init__.py +0 -0
- {npcpy-1.2.34 → npcpy-1.2.35}/npcpy/data/__init__.py +0 -0
- {npcpy-1.2.34 → npcpy-1.2.35}/npcpy/data/data_models.py +0 -0
- {npcpy-1.2.34 → npcpy-1.2.35}/npcpy/data/image.py +0 -0
- {npcpy-1.2.34 → npcpy-1.2.35}/npcpy/data/text.py +0 -0
- {npcpy-1.2.34 → npcpy-1.2.35}/npcpy/data/web.py +0 -0
- {npcpy-1.2.34 → npcpy-1.2.35}/npcpy/ft/__init__.py +0 -0
- {npcpy-1.2.34 → npcpy-1.2.35}/npcpy/ft/ge.py +0 -0
- {npcpy-1.2.34 → npcpy-1.2.35}/npcpy/ft/memory_trainer.py +0 -0
- {npcpy-1.2.34 → npcpy-1.2.35}/npcpy/ft/model_ensembler.py +0 -0
- {npcpy-1.2.34 → npcpy-1.2.35}/npcpy/ft/rl.py +0 -0
- {npcpy-1.2.34 → npcpy-1.2.35}/npcpy/ft/sft.py +0 -0
- {npcpy-1.2.34 → npcpy-1.2.35}/npcpy/ft/usft.py +0 -0
- {npcpy-1.2.34 → npcpy-1.2.35}/npcpy/gen/__init__.py +0 -0
- {npcpy-1.2.34 → npcpy-1.2.35}/npcpy/gen/audio_gen.py +0 -0
- {npcpy-1.2.34 → npcpy-1.2.35}/npcpy/gen/embeddings.py +0 -0
- {npcpy-1.2.34 → npcpy-1.2.35}/npcpy/gen/response.py +0 -0
- {npcpy-1.2.34 → npcpy-1.2.35}/npcpy/gen/video_gen.py +0 -0
- {npcpy-1.2.34 → npcpy-1.2.35}/npcpy/llm_funcs.py +0 -0
- {npcpy-1.2.34 → npcpy-1.2.35}/npcpy/main.py +0 -0
- {npcpy-1.2.34 → npcpy-1.2.35}/npcpy/memory/__init__.py +0 -0
- {npcpy-1.2.34 → npcpy-1.2.35}/npcpy/memory/kg_vis.py +0 -0
- {npcpy-1.2.34 → npcpy-1.2.35}/npcpy/memory/knowledge_graph.py +0 -0
- {npcpy-1.2.34 → npcpy-1.2.35}/npcpy/memory/memory_processor.py +0 -0
- {npcpy-1.2.34 → npcpy-1.2.35}/npcpy/memory/search.py +0 -0
- {npcpy-1.2.34 → npcpy-1.2.35}/npcpy/mix/__init__.py +0 -0
- {npcpy-1.2.34 → npcpy-1.2.35}/npcpy/mix/debate.py +0 -0
- {npcpy-1.2.34 → npcpy-1.2.35}/npcpy/npc_sysenv.py +0 -0
- {npcpy-1.2.34 → npcpy-1.2.35}/npcpy/npcs.py +0 -0
- {npcpy-1.2.34 → npcpy-1.2.35}/npcpy/sql/__init__.py +0 -0
- {npcpy-1.2.34 → npcpy-1.2.35}/npcpy/sql/ai_function_tools.py +0 -0
- {npcpy-1.2.34 → npcpy-1.2.35}/npcpy/sql/database_ai_adapters.py +0 -0
- {npcpy-1.2.34 → npcpy-1.2.35}/npcpy/sql/database_ai_functions.py +0 -0
- {npcpy-1.2.34 → npcpy-1.2.35}/npcpy/sql/model_runner.py +0 -0
- {npcpy-1.2.34 → npcpy-1.2.35}/npcpy/sql/npcsql.py +0 -0
- {npcpy-1.2.34 → npcpy-1.2.35}/npcpy/sql/sql_model_compiler.py +0 -0
- {npcpy-1.2.34 → npcpy-1.2.35}/npcpy/tools.py +0 -0
- {npcpy-1.2.34 → npcpy-1.2.35}/npcpy/work/__init__.py +0 -0
- {npcpy-1.2.34 → npcpy-1.2.35}/npcpy/work/desktop.py +0 -0
- {npcpy-1.2.34 → npcpy-1.2.35}/npcpy/work/plan.py +0 -0
- {npcpy-1.2.34 → npcpy-1.2.35}/npcpy/work/trigger.py +0 -0
- {npcpy-1.2.34 → npcpy-1.2.35}/npcpy.egg-info/dependency_links.txt +0 -0
- {npcpy-1.2.34 → npcpy-1.2.35}/npcpy.egg-info/requires.txt +0 -0
- {npcpy-1.2.34 → npcpy-1.2.35}/npcpy.egg-info/top_level.txt +0 -0
- {npcpy-1.2.34 → npcpy-1.2.35}/setup.cfg +0 -0
- {npcpy-1.2.34 → npcpy-1.2.35}/tests/test_audio.py +0 -0
- {npcpy-1.2.34 → npcpy-1.2.35}/tests/test_command_history.py +0 -0
- {npcpy-1.2.34 → npcpy-1.2.35}/tests/test_image.py +0 -0
- {npcpy-1.2.34 → npcpy-1.2.35}/tests/test_llm_funcs.py +0 -0
- {npcpy-1.2.34 → npcpy-1.2.35}/tests/test_npc_compiler.py +0 -0
- {npcpy-1.2.34 → npcpy-1.2.35}/tests/test_npcsql.py +0 -0
- {npcpy-1.2.34 → npcpy-1.2.35}/tests/test_response.py +0 -0
- {npcpy-1.2.34 → npcpy-1.2.35}/tests/test_serve.py +0 -0
- {npcpy-1.2.34 → npcpy-1.2.35}/tests/test_text.py +0 -0
- {npcpy-1.2.34 → npcpy-1.2.35}/tests/test_tools.py +0 -0
- {npcpy-1.2.34 → npcpy-1.2.35}/tests/test_web.py +0 -0
|
@@ -175,6 +175,41 @@ def run_transcription(audio_np):
|
|
|
175
175
|
return None
|
|
176
176
|
|
|
177
177
|
|
|
178
|
+
def transcribe_audio_file(file_path: str, language=None) -> str:
|
|
179
|
+
"""
|
|
180
|
+
File-based transcription helper that prefers the local faster-whisper/whisper
|
|
181
|
+
setup used elsewhere in this module.
|
|
182
|
+
"""
|
|
183
|
+
# Try faster-whisper first
|
|
184
|
+
try:
|
|
185
|
+
from faster_whisper import WhisperModel # type: ignore
|
|
186
|
+
try:
|
|
187
|
+
import torch # type: ignore
|
|
188
|
+
device = "cuda" if torch.cuda.is_available() else "cpu"
|
|
189
|
+
except Exception:
|
|
190
|
+
device = "cpu"
|
|
191
|
+
model = WhisperModel("small", device=device)
|
|
192
|
+
segments, _ = model.transcribe(file_path, language=language, beam_size=5)
|
|
193
|
+
text = " ".join(seg.text.strip() for seg in segments if seg.text).strip()
|
|
194
|
+
if text:
|
|
195
|
+
return text
|
|
196
|
+
except Exception:
|
|
197
|
+
pass
|
|
198
|
+
|
|
199
|
+
# Fallback to openai/whisper if available
|
|
200
|
+
try:
|
|
201
|
+
import whisper # type: ignore
|
|
202
|
+
model = whisper.load_model("small")
|
|
203
|
+
result = model.transcribe(file_path, language=language)
|
|
204
|
+
text = result.get("text", "").strip()
|
|
205
|
+
if text:
|
|
206
|
+
return text
|
|
207
|
+
except Exception:
|
|
208
|
+
pass
|
|
209
|
+
|
|
210
|
+
return ""
|
|
211
|
+
|
|
212
|
+
|
|
178
213
|
|
|
179
214
|
def load_history():
|
|
180
215
|
global history
|
|
@@ -431,4 +466,3 @@ def process_text_for_tts(text):
|
|
|
431
466
|
text = re.sub(r"([.!?])(\w)", r"\1 \2", text)
|
|
432
467
|
return text
|
|
433
468
|
|
|
434
|
-
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import fitz
|
|
2
|
+
import pandas as pd
|
|
3
|
+
import json
|
|
4
|
+
import io
|
|
5
|
+
from PIL import Image
|
|
6
|
+
import numpy as np
|
|
7
|
+
from typing import Optional, List
|
|
8
|
+
import os
|
|
9
|
+
import tempfile
|
|
10
|
+
import subprocess
|
|
11
|
+
|
|
12
|
+
try:
|
|
13
|
+
from docx import Document
|
|
14
|
+
except ImportError:
|
|
15
|
+
Document = None
|
|
16
|
+
|
|
17
|
+
try:
|
|
18
|
+
from pptx import Presentation
|
|
19
|
+
except ImportError:
|
|
20
|
+
Presentation = None
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
from bs4 import BeautifulSoup
|
|
24
|
+
except ImportError:
|
|
25
|
+
BeautifulSoup = None
|
|
26
|
+
|
|
27
|
+
def load_csv(file_path):
|
|
28
|
+
df = pd.read_csv(file_path)
|
|
29
|
+
return df
|
|
30
|
+
|
|
31
|
+
def load_json(file_path):
|
|
32
|
+
with open(file_path, "r", encoding='utf-8') as f:
|
|
33
|
+
data = json.load(f)
|
|
34
|
+
return data
|
|
35
|
+
|
|
36
|
+
def load_txt(file_path):
|
|
37
|
+
with open(file_path, "r", encoding='utf-8') as f:
|
|
38
|
+
text = f.read()
|
|
39
|
+
return text
|
|
40
|
+
|
|
41
|
+
def load_excel(file_path):
|
|
42
|
+
df = pd.read_excel(file_path)
|
|
43
|
+
return df
|
|
44
|
+
|
|
45
|
+
def load_image(file_path):
|
|
46
|
+
img = Image.open(file_path)
|
|
47
|
+
img_array = np.array(img)
|
|
48
|
+
df = pd.DataFrame(
|
|
49
|
+
{
|
|
50
|
+
"image_array": [img_array.tobytes()],
|
|
51
|
+
"shape": [img_array.shape],
|
|
52
|
+
"dtype": [img_array.dtype.str],
|
|
53
|
+
}
|
|
54
|
+
)
|
|
55
|
+
return df
|
|
56
|
+
|
|
57
|
+
def load_pdf(file_path):
|
|
58
|
+
pdf_document = fitz.open(file_path)
|
|
59
|
+
full_text = ""
|
|
60
|
+
for page in pdf_document:
|
|
61
|
+
full_text += page.get_text() + "\n"
|
|
62
|
+
return full_text
|
|
63
|
+
|
|
64
|
+
def load_docx(file_path):
|
|
65
|
+
if Document is None:
|
|
66
|
+
raise ImportError("Please install python-docx to load .docx files.")
|
|
67
|
+
doc = Document(file_path)
|
|
68
|
+
full_text = "\n".join([para.text for para in doc.paragraphs])
|
|
69
|
+
return full_text
|
|
70
|
+
|
|
71
|
+
def load_pptx(file_path):
|
|
72
|
+
if Presentation is None:
|
|
73
|
+
raise ImportError("Please install python-pptx to load .pptx files.")
|
|
74
|
+
prs = Presentation(file_path)
|
|
75
|
+
full_text = ""
|
|
76
|
+
for slide in prs.slides:
|
|
77
|
+
for shape in slide.shapes:
|
|
78
|
+
if hasattr(shape, "text"):
|
|
79
|
+
full_text += shape.text + "\n"
|
|
80
|
+
return full_text
|
|
81
|
+
|
|
82
|
+
def load_html(file_path):
|
|
83
|
+
if BeautifulSoup is None:
|
|
84
|
+
raise ImportError("Please install beautifulsoup4 to load .html files.")
|
|
85
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
|
86
|
+
soup = BeautifulSoup(f, 'html.parser')
|
|
87
|
+
return soup.get_text(separator='\n', strip=True)
|
|
88
|
+
|
|
89
|
+
extension_map = {
|
|
90
|
+
"PNG": "images",
|
|
91
|
+
"JPG": "images",
|
|
92
|
+
"JPEG": "images",
|
|
93
|
+
"GIF": "images",
|
|
94
|
+
"SVG": "images",
|
|
95
|
+
"WEBP": "images",
|
|
96
|
+
"BMP": "images",
|
|
97
|
+
"TIFF": "images",
|
|
98
|
+
"MP4": "videos",
|
|
99
|
+
"AVI": "videos",
|
|
100
|
+
"MOV": "videos",
|
|
101
|
+
"WMV": "videos",
|
|
102
|
+
"MPG": "videos",
|
|
103
|
+
"MPEG": "videos",
|
|
104
|
+
"WEBM": "videos",
|
|
105
|
+
"MKV": "videos",
|
|
106
|
+
"DOCX": "documents",
|
|
107
|
+
"PPTX": "documents",
|
|
108
|
+
"PDF": "documents",
|
|
109
|
+
"XLSX": "documents",
|
|
110
|
+
"TXT": "documents",
|
|
111
|
+
"CSV": "documents",
|
|
112
|
+
"MD": "documents",
|
|
113
|
+
"HTML": "documents",
|
|
114
|
+
"HTM": "documents",
|
|
115
|
+
"MP3": "audio",
|
|
116
|
+
"WAV": "audio",
|
|
117
|
+
"M4A": "audio",
|
|
118
|
+
"AAC": "audio",
|
|
119
|
+
"FLAC": "audio",
|
|
120
|
+
"OGG": "audio",
|
|
121
|
+
"ZIP": "archives",
|
|
122
|
+
"RAR": "archives",
|
|
123
|
+
"7Z": "archives",
|
|
124
|
+
"TAR": "archives",
|
|
125
|
+
"GZ": "archives",
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
def _chunk_text(full_content: str, chunk_size: int) -> List[str]:
|
|
129
|
+
"""Split long content into reasonably sized chunks for model input."""
|
|
130
|
+
chunks = []
|
|
131
|
+
for i in range(0, len(full_content), chunk_size):
|
|
132
|
+
chunk = full_content[i:i+chunk_size].strip()
|
|
133
|
+
if chunk:
|
|
134
|
+
chunks.append(chunk)
|
|
135
|
+
return chunks
|
|
136
|
+
|
|
137
|
+
def _transcribe_audio(file_path: str, language: Optional[str] = None) -> str:
|
|
138
|
+
"""
|
|
139
|
+
Best-effort audio transcription using optional dependencies.
|
|
140
|
+
Tries faster-whisper, then openai/whisper. Falls back to metadata only.
|
|
141
|
+
"""
|
|
142
|
+
# Prefer the existing audio module helper if present
|
|
143
|
+
try:
|
|
144
|
+
from npcpy.data.audio import transcribe_audio_file # type: ignore
|
|
145
|
+
text = transcribe_audio_file(file_path, language=language)
|
|
146
|
+
if text:
|
|
147
|
+
return text
|
|
148
|
+
except Exception:
|
|
149
|
+
pass
|
|
150
|
+
|
|
151
|
+
# Try faster-whisper first
|
|
152
|
+
try:
|
|
153
|
+
from faster_whisper import WhisperModel
|
|
154
|
+
try:
|
|
155
|
+
import torch
|
|
156
|
+
device = "cuda" if torch.cuda.is_available() else "cpu"
|
|
157
|
+
except Exception:
|
|
158
|
+
device = "cpu"
|
|
159
|
+
model = WhisperModel("small", device=device)
|
|
160
|
+
segments, _ = model.transcribe(file_path, language=language, beam_size=5)
|
|
161
|
+
return " ".join(seg.text.strip() for seg in segments if seg.text).strip()
|
|
162
|
+
except Exception:
|
|
163
|
+
pass
|
|
164
|
+
|
|
165
|
+
# Fallback: openai/whisper
|
|
166
|
+
try:
|
|
167
|
+
import whisper
|
|
168
|
+
model = whisper.load_model("small")
|
|
169
|
+
result = model.transcribe(file_path, language=language)
|
|
170
|
+
return result.get("text", "").strip()
|
|
171
|
+
except Exception:
|
|
172
|
+
pass
|
|
173
|
+
|
|
174
|
+
# Last resort metadata message
|
|
175
|
+
return f"[Audio file at {file_path}; install faster-whisper or whisper for transcription]"
|
|
176
|
+
|
|
177
|
+
def load_audio(file_path: str, language: Optional[str] = None) -> str:
|
|
178
|
+
"""Load and transcribe an audio file into text."""
|
|
179
|
+
transcript = _transcribe_audio(file_path, language=language)
|
|
180
|
+
if transcript:
|
|
181
|
+
return transcript
|
|
182
|
+
return f"[Audio file at {file_path}; no transcript available]"
|
|
183
|
+
|
|
184
|
+
def _extract_audio_from_video(file_path: str, max_duration: int = 600) -> Optional[str]:
|
|
185
|
+
"""
|
|
186
|
+
Use ffmpeg to dump the audio track from a video into a temp wav for transcription.
|
|
187
|
+
Returns the temp path or None.
|
|
188
|
+
"""
|
|
189
|
+
try:
|
|
190
|
+
temp_audio = tempfile.NamedTemporaryFile(delete=False, suffix=".wav")
|
|
191
|
+
temp_audio.close()
|
|
192
|
+
cmd = [
|
|
193
|
+
"ffmpeg",
|
|
194
|
+
"-y",
|
|
195
|
+
"-i",
|
|
196
|
+
file_path,
|
|
197
|
+
"-vn",
|
|
198
|
+
"-ac",
|
|
199
|
+
"1",
|
|
200
|
+
"-ar",
|
|
201
|
+
"16000",
|
|
202
|
+
"-t",
|
|
203
|
+
str(max_duration),
|
|
204
|
+
temp_audio.name,
|
|
205
|
+
]
|
|
206
|
+
subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
207
|
+
return temp_audio.name
|
|
208
|
+
except Exception:
|
|
209
|
+
return None
|
|
210
|
+
|
|
211
|
+
def load_video(file_path: str, language: Optional[str] = None, max_audio_seconds: int = 600) -> str:
|
|
212
|
+
"""
|
|
213
|
+
Summarize a video by reporting metadata and (optionally) transcribing its audio track.
|
|
214
|
+
"""
|
|
215
|
+
# Prefer the video module helper if present
|
|
216
|
+
try:
|
|
217
|
+
from npcpy.data.video import summarize_video_file # type: ignore
|
|
218
|
+
return summarize_video_file(file_path, language=language, max_audio_seconds=max_audio_seconds)
|
|
219
|
+
except Exception:
|
|
220
|
+
pass
|
|
221
|
+
|
|
222
|
+
# Fallback to minimal summary/transcription
|
|
223
|
+
meta_bits = []
|
|
224
|
+
try:
|
|
225
|
+
import cv2
|
|
226
|
+
video = cv2.VideoCapture(file_path)
|
|
227
|
+
fps = video.get(cv2.CAP_PROP_FPS)
|
|
228
|
+
frame_count = int(video.get(cv2.CAP_PROP_FRAME_COUNT))
|
|
229
|
+
width = int(video.get(cv2.CAP_PROP_FRAME_WIDTH))
|
|
230
|
+
height = int(video.get(cv2.CAP_PROP_FRAME_HEIGHT))
|
|
231
|
+
duration = frame_count / fps if fps else 0
|
|
232
|
+
meta_bits.append(
|
|
233
|
+
f"Video file: {os.path.basename(file_path)} | {width}x{height} | {fps:.2f} fps | {frame_count} frames | ~{duration:.1f}s"
|
|
234
|
+
)
|
|
235
|
+
video.release()
|
|
236
|
+
except Exception:
|
|
237
|
+
meta_bits.append(f"Video file: {os.path.basename(file_path)}")
|
|
238
|
+
|
|
239
|
+
audio_path = _extract_audio_from_video(file_path, max_duration=max_audio_seconds)
|
|
240
|
+
transcript = ""
|
|
241
|
+
if audio_path:
|
|
242
|
+
try:
|
|
243
|
+
transcript = _transcribe_audio(audio_path, language=language)
|
|
244
|
+
finally:
|
|
245
|
+
try:
|
|
246
|
+
os.remove(audio_path)
|
|
247
|
+
except Exception:
|
|
248
|
+
pass
|
|
249
|
+
|
|
250
|
+
if transcript:
|
|
251
|
+
meta_bits.append("Audio transcript:")
|
|
252
|
+
meta_bits.append(transcript)
|
|
253
|
+
else:
|
|
254
|
+
meta_bits.append("[No transcript extracted; ensure ffmpeg and faster-whisper/whisper are installed]")
|
|
255
|
+
|
|
256
|
+
return "\n".join(meta_bits)
|
|
257
|
+
|
|
258
|
+
def load_file_contents(file_path, chunk_size=None):
|
|
259
|
+
file_ext = os.path.splitext(file_path)[1].upper().lstrip('.')
|
|
260
|
+
full_content = ""
|
|
261
|
+
if not isinstance(chunk_size, int):
|
|
262
|
+
chunk_size=250
|
|
263
|
+
try:
|
|
264
|
+
if file_ext == 'PDF':
|
|
265
|
+
full_content = load_pdf(file_path)
|
|
266
|
+
elif file_ext == 'DOCX':
|
|
267
|
+
full_content = load_docx(file_path)
|
|
268
|
+
elif file_ext == 'PPTX':
|
|
269
|
+
full_content = load_pptx(file_path)
|
|
270
|
+
elif file_ext in ['HTML', 'HTM']:
|
|
271
|
+
full_content = load_html(file_path)
|
|
272
|
+
elif file_ext == 'CSV':
|
|
273
|
+
df = load_csv(file_path)
|
|
274
|
+
full_content = df.to_string()
|
|
275
|
+
elif file_ext in ['XLS', 'XLSX']:
|
|
276
|
+
df = load_excel(file_path)
|
|
277
|
+
full_content = df.to_string()
|
|
278
|
+
elif file_ext in ['TXT', 'MD', 'PY', 'JSX', 'TSX', 'TS', 'JS', 'JSON', 'SQL', 'NPC', 'JINX', 'LINE', 'YAML', 'DART', 'JAVA']:
|
|
279
|
+
full_content = load_txt(file_path)
|
|
280
|
+
elif file_ext == 'JSON':
|
|
281
|
+
data = load_json(file_path)
|
|
282
|
+
full_content = json.dumps(data, indent=2)
|
|
283
|
+
elif file_ext in ['MP3', 'WAV', 'M4A', 'AAC', 'FLAC', 'OGG']:
|
|
284
|
+
full_content = load_audio(file_path)
|
|
285
|
+
elif file_ext in ['MP4', 'AVI', 'MOV', 'WMV', 'MPG', 'MPEG', 'WEBM', 'MKV']:
|
|
286
|
+
full_content = load_video(file_path)
|
|
287
|
+
else:
|
|
288
|
+
return [f"Unsupported file format for content loading: {file_ext}"]
|
|
289
|
+
|
|
290
|
+
if not full_content:
|
|
291
|
+
return []
|
|
292
|
+
|
|
293
|
+
return _chunk_text(full_content, chunk_size)
|
|
294
|
+
|
|
295
|
+
except Exception as e:
|
|
296
|
+
return [f"Error loading file {file_path}: {str(e)}"]
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
|
|
2
|
+
import os
|
|
3
|
+
import tempfile
|
|
4
|
+
import subprocess
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def process_video(file_path, table_name):
|
|
8
|
+
|
|
9
|
+
import cv2
|
|
10
|
+
import base64
|
|
11
|
+
|
|
12
|
+
embeddings = []
|
|
13
|
+
texts = []
|
|
14
|
+
try:
|
|
15
|
+
video = cv2.VideoCapture(file_path)
|
|
16
|
+
fps = video.get(cv2.CAP_PROP_FPS)
|
|
17
|
+
frame_count = int(video.get(cv2.CAP_PROP_FRAME_COUNT))
|
|
18
|
+
|
|
19
|
+
for i in range(frame_count):
|
|
20
|
+
ret, frame = video.read()
|
|
21
|
+
if not ret:
|
|
22
|
+
break
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
n = 10
|
|
26
|
+
|
|
27
|
+
return embeddings, texts
|
|
28
|
+
|
|
29
|
+
except Exception as e:
|
|
30
|
+
print(f"Error processing video: {e}")
|
|
31
|
+
return [], []
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def summarize_video_file(file_path: str, language: str = None, max_audio_seconds: int = 600) -> str:
|
|
35
|
+
"""
|
|
36
|
+
Summarize a video using lightweight metadata plus optional audio transcript.
|
|
37
|
+
Prefers the audio transcription helper in npcpy.data.audio when available.
|
|
38
|
+
"""
|
|
39
|
+
meta_bits = []
|
|
40
|
+
try:
|
|
41
|
+
import cv2 # type: ignore
|
|
42
|
+
|
|
43
|
+
video = cv2.VideoCapture(file_path)
|
|
44
|
+
fps = video.get(cv2.CAP_PROP_FPS)
|
|
45
|
+
frame_count = int(video.get(cv2.CAP_PROP_FRAME_COUNT))
|
|
46
|
+
width = int(video.get(cv2.CAP_PROP_FRAME_WIDTH))
|
|
47
|
+
height = int(video.get(cv2.CAP_PROP_FRAME_HEIGHT))
|
|
48
|
+
duration = frame_count / fps if fps else 0
|
|
49
|
+
meta_bits.append(
|
|
50
|
+
f"Video file: {os.path.basename(file_path)} | {width}x{height} | {fps:.2f} fps | {frame_count} frames | ~{duration:.1f}s"
|
|
51
|
+
)
|
|
52
|
+
video.release()
|
|
53
|
+
except Exception:
|
|
54
|
+
meta_bits.append(f"Video file: {os.path.basename(file_path)}")
|
|
55
|
+
|
|
56
|
+
# Extract audio track with ffmpeg if available
|
|
57
|
+
audio_path = None
|
|
58
|
+
try:
|
|
59
|
+
temp_audio = tempfile.NamedTemporaryFile(delete=False, suffix=".wav")
|
|
60
|
+
temp_audio.close()
|
|
61
|
+
cmd = [
|
|
62
|
+
"ffmpeg",
|
|
63
|
+
"-y",
|
|
64
|
+
"-i",
|
|
65
|
+
file_path,
|
|
66
|
+
"-vn",
|
|
67
|
+
"-ac",
|
|
68
|
+
"1",
|
|
69
|
+
"-ar",
|
|
70
|
+
"16000",
|
|
71
|
+
"-t",
|
|
72
|
+
str(max_audio_seconds),
|
|
73
|
+
temp_audio.name,
|
|
74
|
+
]
|
|
75
|
+
subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
76
|
+
audio_path = temp_audio.name
|
|
77
|
+
except Exception:
|
|
78
|
+
audio_path = None
|
|
79
|
+
|
|
80
|
+
transcript = ""
|
|
81
|
+
if audio_path:
|
|
82
|
+
try:
|
|
83
|
+
try:
|
|
84
|
+
from npcpy.data.audio import transcribe_audio_file
|
|
85
|
+
transcript = transcribe_audio_file(audio_path, language=language) # type: ignore
|
|
86
|
+
except Exception:
|
|
87
|
+
transcript = ""
|
|
88
|
+
finally:
|
|
89
|
+
try:
|
|
90
|
+
os.remove(audio_path)
|
|
91
|
+
except Exception:
|
|
92
|
+
pass
|
|
93
|
+
|
|
94
|
+
if transcript:
|
|
95
|
+
meta_bits.append("Audio transcript:")
|
|
96
|
+
meta_bits.append(transcript)
|
|
97
|
+
else:
|
|
98
|
+
meta_bits.append("[No transcript extracted; ensure ffmpeg and a transcription backend are installed]")
|
|
99
|
+
|
|
100
|
+
return "\n".join(meta_bits)
|