Created
August 6, 2025 21:40
-
-
Save johnlindquist/ce8f2040f17fceb5f0714a51d3ab9c48 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Name: summarize-latest-claude-print-video | |
import "@johnlindquist/kit" | |
import { GoogleGenAI, createUserContent, createPartFromUri } from '@google/genai'; | |
const GEMINI_API_KEY = await env("GEMINI_API_KEY") | |
const ai = new GoogleGenAI({ apiKey: GEMINI_API_KEY }); | |
const basicMemoryPath = home("Documents", "screenflow", "claude-code", "print", "*.mp4") | |
const videoFiles = await globby(basicMemoryPath) | |
const videoPath = await arg("Pick a video", videoFiles.map(v => { | |
return { | |
name: path.parse(v).base, | |
description: v, | |
value: v | |
} | |
})) | |
await editor(videoPath) | |
if (!videoPath) { | |
await div(`<div class="p-4 text-red-500">No video files found in ${basicMemoryPath}</div>`) | |
} | |
if (!videoPath.endsWith(".mp4")) { | |
await div(`<div class="p-4 text-red-500">Please select a video file.</div>`) | |
exit() | |
} | |
// Upload video | |
console.log("Uploading video...") | |
let file = await ai.files.upload({ | |
file: videoPath, | |
config: { mimeType: 'video/mp4' } | |
}); | |
await hide() | |
let attempts = 0; | |
const maxAttempts = 60; // 2 minutes max wait | |
while (file.state !== 'ACTIVE' && attempts < maxAttempts) { | |
await new Promise(resolve => setTimeout(resolve, 2000)); // Wait 2 seconds | |
file = await ai.files.get({ name: file.name }); | |
attempts++; | |
console.log(`Checking file status (${attempts}/${maxAttempts}): ${file.state}`); | |
// Check for failure states | |
if (file.state === 'FAILED') { | |
throw new Error(`File processing failed: ${file.error?.message || 'Unknown error'}`); | |
} | |
} | |
if (file.state !== 'ACTIVE') { | |
throw new Error(`File did not become active after ${maxAttempts * 2} seconds`); | |
} | |
console.log("File is ready for use!"); | |
let prompt = await readFile(kenvPath("snippets", "summarizer.txt"), "utf8") | |
const lessonExample = await readFile(kenvPath("snippets", "lesson-example.txt"), "utf8") | |
prompt = `${prompt} | |
<CRITICAL> | |
Follow the <EXAMPLE/> template precisely. It is required that you only output the markdown according to the <EXAMPLE/>. | |
</CRITICAL> | |
<EXAMPLE> | |
${lessonExample} | |
</EXAMPLE> | |
` | |
console.log("Generating content...") | |
const response = await ai.models.generateContent({ | |
model: 'gemini-2.5-pro', | |
contents: createUserContent([ | |
createPartFromUri(file.uri, file.mimeType), | |
prompt | |
]) | |
}); | |
log(JSON.stringify(response, null, 2)) | |
const videoPathParsed = path.parse(videoPath) | |
const summaryPath = path.join(videoPathParsed.dir, videoPathParsed.name + ".md") | |
let summary = response.text | |
// File the line that starts with "title:", insert a line beneath it that starts with "video: ${videoPath}" | |
summary = summary.replace(/^title:.*$/m, (match) => `${match}\nvideo: ${videoPath}`) | |
await writeFile(summaryPath, summary) | |
await edit(summaryPath) | |
/* ─── Constants & Types ──────────────────────────────────────────────────── */ | |
/* ─── API Layer – CourseBuilderClient ────────────────────────────────────── */ | |
/* ─── VideoUploader ──────────────────────────────────────────────────────── */ | |
/* ─── Domain Helpers ─────────────────────────────────────────────────────── */ | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment