Skip to content

Commit

Permalink
added stat
Browse files Browse the repository at this point in the history
  • Loading branch information
pelikhan committed Jan 17, 2025
1 parent f50e73f commit 36248a5
Show file tree
Hide file tree
Showing 8 changed files with 88 additions and 32 deletions.
2 changes: 1 addition & 1 deletion docs/src/components/BuiltinTools.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ import { LinkCard } from '@astrojs/starlight/components';
<LinkCard title="user_input_select" description="Ask the user to select an option." href="/genaiscript/reference/scripts/system#systemuser_input" />
<LinkCard title="user_input_text" description="Ask the user to input text." href="/genaiscript/reference/scripts/system#systemuser_input" />
<LinkCard title="video_probe" description="Probe a video file and returns the metadata information" href="/genaiscript/reference/scripts/system#systemvideo" />
<LinkCard title="video_extract_audio" description="Extract audio from a video file into a .wav file. Returns the audio filename." href="/genaiscript/reference/scripts/system#systemvideo" />
<LinkCard title="video_extract_audio" description="Extract audio from a video file into an audio file. Returns the audio filename." href="/genaiscript/reference/scripts/system#systemvideo" />
<LinkCard title="video_extract_frames" description="Extract frames from a video file" href="/genaiscript/reference/scripts/system#systemvideo" />
<LinkCard title="vision_ask_images" description="Use vision model to run a query on multiple images" href="/genaiscript/reference/scripts/system#systemvision_ask_images" />

22 changes: 14 additions & 8 deletions docs/src/content/docs/reference/scripts/system.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3566,7 +3566,7 @@ defTool(
Video manipulation tools
- tool `video_probe`: Probe a video file and returns the metadata information
- tool `video_extract_audio`: Extract audio from a video file into a .wav file. Returns the audio filename.
- tool `video_extract_audio`: Extract audio from a video file into an audio file. Returns the audio filename.
- tool `video_extract_frames`: Extract frames from a video file
`````js wrap title="system.video"
Expand All @@ -3582,14 +3582,16 @@ defTool(
properties: {
filename: {
type: "string",
description: "The video filename or URL to probe",
description: "The video filename to probe",
},
},
required: ["filename"],
},
async (args) => {
const { context, filename } = args
if (!filename) return "No filename or url provided"
if (!filename) return "No filename provided"
if (!(await workspace.stat(filename)))
return `File ${filename} does not exist.`
context.log(`probing ${filename}`)
const info = await ffmpeg.probe(filename)
return YAML.stringify(info)
Expand All @@ -3598,20 +3600,22 @@ defTool(
defTool(
"video_extract_audio",
"Extract audio from a video file into a .wav file. Returns the audio filename.",
"Extract audio from a video file into an audio file. Returns the audio filename.",
{
type: "object",
properties: {
filename: {
type: "string",
description: "The video filename or URL to probe",
description: "The video filename to probe",
},
},
required: ["filename"],
},
async (args) => {
const { context, filename } = args
if (!filename) return "No filename or url provided"
if (!filename) return "No filename provided"
if (!(await workspace.stat(filename)))
return `File ${filename} does not exist.`
context.log(`extracting audio from ${filename}`)
const audioFile = await ffmpeg.extractAudio(filename)
return audioFile
Expand All @@ -3626,7 +3630,7 @@ defTool(
properties: {
filename: {
type: "string",
description: "The video filename or URL to probe",
description: "The video filename to probe",
},
keyframes: {
type: "boolean",
Expand Down Expand Up @@ -3655,7 +3659,9 @@ defTool(
},
async (args) => {
const { context, filename, transcription, ...options } = args
if (!filename) return "No filename or url provided"
if (!filename) return "No filename provided"
if (!(await workspace.stat(filename)))
return `File ${filename} does not exist.`
context.log(`extracting frames from ${filename}`)
if (transcription) {
Expand Down
14 changes: 7 additions & 7 deletions docs/src/content/docs/reference/scripts/videos.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,13 @@ or configure the `FFMPEG_PATH` / `FFPROBE_PATH` environment variables to point t
As mentionned above, multi-modal LLMs typically support images as a sequence
of frames (or screenshots).

The `ffmpeg.extractFrames` will render frames from a video file or url
The `ffmpeg.extractFrames` will render frames from a video file
and return them as an array of file paths. You can use the result with `defImages` directly.

- by default, extract keyframes (intra-frames)

```js
const frames = await ffmpeg.extractFrames("path_url_to_video")
const frames = await ffmpeg.extractFrames("path_to_video")
def("FRAMES", frames)
```

Expand Down Expand Up @@ -66,22 +66,22 @@ const transcript = await transcribe("...", { sceneThreshold: 0.3 })

## Extracting audio

The `ffmpeg.extractAudio` will extract the audio from a video file or url
The `ffmpeg.extractAudio` will extract the audio from a video file
as a `.wav` file.

```js
const audio = await ffmpeg.extractAudio("path_url_to_video")
const audio = await ffmpeg.extractAudio("path_to_video")
```

The conversion to audio happens automatically
for videos when using [transcribe](/genaiscript/reference/scripts/transcription).

## Probing videos

You can extract metadata from a video file or url using `ffmpeg.probe`.
You can extract metadata from a video file using `ffmpeg.probe`.

```js
const info = await ffmpeg.probe("path_url_to_video")
const info = await ffmpeg.probe("path_to_video")
const { duration } = info.streams[0]
console.log(`video duration: ${duration} seconds`)
```
Expand All @@ -92,7 +92,7 @@ You can further customize the `ffmpeg` configuration
by passing `outputOptions`.

```js 'outputOptions: "-b:a 16k",'
const audio = await ffmpeg.extractAudio("path_url_to_video", {
const audio = await ffmpeg.extractAudio("path_to_video", {
outputOptions: "-b:a 16k",
})
```
Expand Down
35 changes: 26 additions & 9 deletions packages/core/src/fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,15 @@ import {
import { errorMessage } from "./error"
import { logVerbose, toStringList } from "./util"
import { CancellationOptions, CancellationToken } from "./cancellation"
import { readText } from "./fs"
import { resolveHttpProxyAgent } from "./proxy"
import { host } from "./host"
import { renderWithPrecision } from "./precision"
import crossFetch from "cross-fetch"
import prettyBytes from "pretty-bytes"
import { fileTypeFromBuffer } from "file-type"
import { isBinaryMimeType } from "./binary"
import { toBase64 } from "./base64"
import { deleteUndefinedValues } from "./cleaners"

export type FetchType = (
input: string | URL | globalThis.Request,
Expand Down Expand Up @@ -137,7 +140,7 @@ export async function fetchText(
const url = urlOrFile.filename
let ok = false
let status = 404
let text: string
let bytes: Uint8Array
if (/^https?:\/\//i.test(url)) {
const f = await createFetch({
retries,
Expand All @@ -149,25 +152,39 @@ export async function fetchText(
const resp = await f(url, rest)
ok = resp.ok
status = resp.status
if (ok) text = await resp.text()
if (ok) bytes = new Uint8Array(await resp.arrayBuffer())
} else {
try {
text = await readText("workspace://" + url)
ok = true
bytes = await host.readFile(url)
} catch (e) {
logVerbose(e)
ok = false
status = 404
}
}
const file: WorkspaceFile = {
filename: urlOrFile.filename,
content: text,

let content: string
let encoding: "base64"
let type: string
const mime = await fileTypeFromBuffer(bytes)
if (isBinaryMimeType(mime?.mime)) {
encoding = "base64"
content = toBase64(bytes)
} else {
content = host.createUTF8Decoder().decode(bytes)
}
ok = true
const file: WorkspaceFile = deleteUndefinedValues({
filename: urlOrFile.filename,
encoding,
type,
content,
})
return {
ok,
status,
text,
text: content,
bytes,
file,
}
}
Expand Down
12 changes: 12 additions & 0 deletions packages/core/src/filesystem.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { stat } from "fs/promises"
import { JSONLineCache } from "./cache"
import { DOT_ENV_REGEX } from "./constants"
import { CSVTryParse } from "./csv"
Expand Down Expand Up @@ -111,6 +112,17 @@ export function createFileSystem(): Omit<WorkspaceFileSystem, "grep"> {
const res = JSONLineCache.byName<any, any>(name)
return res
},
stat: async (filename: string) => {
try {
const res = await stat(filename)
return {
size: res.size,
mode: res.mode,
}
} catch {
return undefined
}
},
} satisfies Omit<WorkspaceFileSystem, "grep">
;(fs as any).readFile = readText
return Object.freeze(fs)
Expand Down
20 changes: 13 additions & 7 deletions packages/core/src/genaisrc/system.video.genai.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,16 @@ defTool(
properties: {
filename: {
type: "string",
description: "The video filename or URL to probe",
description: "The video filename to probe",
},
},
required: ["filename"],
},
async (args) => {
const { context, filename } = args
if (!filename) return "No filename or url provided"
if (!filename) return "No filename provided"
if (!(await workspace.stat(filename)))
return `File ${filename} does not exist.`
context.log(`probing ${filename}`)
const info = await ffmpeg.probe(filename)
return YAML.stringify(info)
Expand All @@ -26,20 +28,22 @@ defTool(

defTool(
"video_extract_audio",
"Extract audio from a video file into a .wav file. Returns the audio filename.",
"Extract audio from a video file into an audio file. Returns the audio filename.",
{
type: "object",
properties: {
filename: {
type: "string",
description: "The video filename or URL to probe",
description: "The video filename to probe",
},
},
required: ["filename"],
},
async (args) => {
const { context, filename } = args
if (!filename) return "No filename or url provided"
if (!filename) return "No filename provided"
if (!(await workspace.stat(filename)))
return `File ${filename} does not exist.`
context.log(`extracting audio from ${filename}`)
const audioFile = await ffmpeg.extractAudio(filename)
return audioFile
Expand All @@ -54,7 +58,7 @@ defTool(
properties: {
filename: {
type: "string",
description: "The video filename or URL to probe",
description: "The video filename to probe",
},
keyframes: {
type: "boolean",
Expand Down Expand Up @@ -83,7 +87,9 @@ defTool(
},
async (args) => {
const { context, filename, transcription, ...options } = args
if (!filename) return "No filename or url provided"
if (!filename) return "No filename provided"
if (!(await workspace.stat(filename)))
return `File ${filename} does not exist.`
context.log(`extracting frames from ${filename}`)

if (transcription) {
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/promptcontext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ export async function createPromptContext(
})
return res
},
stat: (filename) => runtimeHost.workspace.stat(filename),
grep: async (
query,
grepOptions: string | WorkspaceGrepOptions,
Expand Down
14 changes: 14 additions & 0 deletions packages/core/src/types/prompt_template.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -885,6 +885,14 @@ interface FindFilesOptions {
readText?: boolean
}

interface FileStats {
/**
* Size of the file in bytes
*/
size: number
mode: number
}

interface WorkspaceFileSystem {
/**
* Searches for files using the glob pattern and returns a list of files.
Expand All @@ -911,6 +919,12 @@ interface WorkspaceFileSystem {
options?: Omit<WorkspaceGrepOptions, "path" | "glob">
): Promise<WorkspaceGrepResult>

/**
* Reads metadata information about the file. Returns undefined if the file does not exist.
* @param filename
*/
stat(filename: string): Promise<FileStats>

/**
* Reads the content of a file as text
* @param path
Expand Down

0 comments on commit 36248a5

Please sign in to comment.