tuna-agent 0.1.125 → 0.1.126

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.
@@ -174,30 +174,50 @@ export async function analyzeVideo(url, onProgress) {
174
174
  const finalSlots = sceneSlots.slice(0, MAX_SCENES);
175
175
  progress(`Đang cắt ${finalSlots.length} frames và phân tích...`);
176
176
  console.log('[analyze_video] Building', finalSlots.length, 'scenes (segments:', segments.length, ', duration:', durationSec, 's)');
177
- const scenes = [];
177
+ // Step 1: Extract all frames sequentially (ffmpeg can't run in parallel on same file efficiently)
178
+ const frameBuffers = [];
178
179
  for (let i = 0; i < finalSlots.length; i++) {
179
180
  const slot = finalSlots[i];
180
181
  const midpoint = (slot.start + slot.end) / 2;
181
182
  const framePath = path.join(framesDir, `scene-${String(i).padStart(3, '0')}.jpg`);
182
183
  try {
183
- progress(`Đang phân tích scene ${i + 1}/${finalSlots.length}...`);
184
184
  await run(FFMPEG, ['-y', '-ss', String(midpoint), '-i', videoPath, '-vframes', '1', '-vf', 'scale=640:-1', '-q:v', '5', framePath, '-loglevel', 'error']);
185
185
  const buf = await fs.readFile(framePath);
186
- const visual_description = await visionDescribe(buf.toString('base64'), slot.voiceover);
187
- scenes.push({
188
- scene_number: i + 1,
189
- timestamp_start: Math.round(slot.start * 10) / 10,
190
- timestamp_end: Math.round(slot.end * 10) / 10,
191
- thumbnail_base64: buf.toString('base64'),
192
- voiceover: slot.voiceover,
193
- visual_description,
194
- });
186
+ frameBuffers.push({ idx: i, buf, slot });
195
187
  }
196
188
  catch (err) {
197
189
  const msg = err instanceof Error ? err.message : String(err);
198
190
  console.warn('[analyze_video] Frame extract failed for scene', i, msg);
199
191
  }
200
192
  }
193
+ // Step 2: Vision describe all frames in parallel (batch of 5)
194
+ progress(`Đang phân tích ${frameBuffers.length} scenes song song...`);
195
+ const BATCH_SIZE = 5;
196
+ const sceneResults = [];
197
+ for (let b = 0; b < frameBuffers.length; b += BATCH_SIZE) {
198
+ const batch = frameBuffers.slice(b, b + BATCH_SIZE);
199
+ progress(`Đang phân tích scene ${b + 1}-${Math.min(b + BATCH_SIZE, frameBuffers.length)}/${frameBuffers.length}...`);
200
+ const results = await Promise.all(batch.map(async ({ idx, buf, slot }) => {
201
+ try {
202
+ const visual_description = await visionDescribe(buf.toString('base64'), slot.voiceover);
203
+ return {
204
+ scene_number: idx + 1,
205
+ timestamp_start: Math.round(slot.start * 10) / 10,
206
+ timestamp_end: Math.round(slot.end * 10) / 10,
207
+ thumbnail_base64: buf.toString('base64'),
208
+ voiceover: slot.voiceover,
209
+ visual_description,
210
+ };
211
+ }
212
+ catch (err) {
213
+ const msg = err instanceof Error ? err.message : String(err);
214
+ console.warn('[analyze_video] Vision describe failed for scene', idx, msg);
215
+ return null;
216
+ }
217
+ }));
218
+ sceneResults.push(...results.filter((r) => r !== null));
219
+ }
220
+ const scenes = sceneResults.sort((a, b) => a.scene_number - b.scene_number);
201
221
  return {
202
222
  duration_sec: Math.round(durationSec),
203
223
  language: transcript.language || 'unknown',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tuna-agent",
3
- "version": "0.1.125",
3
+ "version": "0.1.126",
4
4
  "description": "Tuna Agent - Run AI coding tasks on your machine",
5
5
  "bin": {
6
6
  "tuna-agent": "dist/cli/index.js"