Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ env.sh
!.yarn/sdks
!.yarn/versions
.aider*
.DS_Store
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"is-stream": "^2.0.1",
"p-map": "^4.0.0",
"tus-js-client": "^4.3.1",
"type-fest": "^4.39.1",
"zod": "^3.24.2"
},
"devDependencies": {
Expand Down
1 change: 1 addition & 0 deletions src/alphalib/types/robots/_index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,7 @@ const robotStepsInstructionsWithHiddenFields = [
/**
* Public robot instructions
*/
export type RobotsSchema = z.infer<typeof robotsSchema>
export const robotsSchema = z.discriminatedUnion('robot', [...robotStepsInstructions])
export const robotsWithHiddenFieldsSchema = z.discriminatedUnion('robot', [
...robotStepsInstructionsWithHiddenFields,
Expand Down
137 changes: 127 additions & 10 deletions src/alphalib/types/robots/_instructions-primitives.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { Replace } from 'type-fest'
import { z } from 'zod'

import { stackVersions } from '../stackVersions.ts'
Expand Down Expand Up @@ -315,11 +316,72 @@ Selects the FFmpeg stack version to use for encoding. These versions reflect rea
`),
})

function preprocessPreset(preset: unknown) {
return typeof preset === 'string' ? preset.replaceAll('_', '-') : preset
/**
* Replace all underscores with hyphens.
*
* @param preset
* The input preset which may contain underscores.
* @returns
* The hyphenated preset.
*/
function transformPreset<T extends string>(preset: T): Replace<T, '_', '-', { all: true }> {
return preset.replaceAll('_', '-') as Replace<T, '_', '-', { all: true }>
}

/**
* Convert a preset with hyphens to any underscore/hyphen combination.
*
* @template T
* The preset to process.
*/
type ReplacePreset<T extends string> = T extends `${infer T0}-${infer Tail}`
? T | `${T0}-${ReplacePreset<Tail>}` | `${T0}_${ReplacePreset<Tail>}`
: T

/**
* Generate all possible underscore/hyphen combinations of a preset.
*
* @param chunks
* A normalized preset split on hyphens.
* @returns
* An iterable that yields all possible combinations.
*/
function* generateCombinations(chunks: string[]): Iterable<string> {
if (chunks.length === 0) {
return
}

if (chunks.length === 1) {
yield chunks[0]
}

const [head, ...remaining] = chunks
for (const result of generateCombinations(remaining)) {
yield `${head}-${result}`
yield `${head}_${result}`
}
}

/**
* Create all possible preset combinations from a list of normalized presets.
*
* @param inputs
* The hyphenated presets.
* @returns
* An array of all possible combinations.
*/
function createPresets<T extends string>(
inputs: T[],
): readonly [ReplacePreset<T>, ...ReplacePreset<T>[]] {
const results: string[] = []
for (const input of inputs) {
results.push(...generateCombinations(input.split('-')))
}

return [...results].sort() as [ReplacePreset<T>, ...ReplacePreset<T>[]]
}

const audioPresets = [
const audioPresets = createPresets([
'aac',
'alac',
'audio/aac',
Expand All @@ -344,15 +406,15 @@ const audioPresets = [
'opus',
'speech',
'wav',
] as const
])

/**
* A robot that uses FFmpeg to **output** audio.
*/
export type FFmpegAudio = z.infer<typeof robotFFmpegAudio>
export const robotFFmpegAudio = robotFFmpeg
.extend({
preset: z.preprocess(preprocessPreset, z.enum(audioPresets)).optional().describe(`
preset: z.enum(audioPresets).transform(transformPreset).optional().describe(`
Performs conversion using pre-configured settings.

If you specify your own FFmpeg parameters using the <dfn>Robot</dfn>'s \`ffmpeg\` parameter and you have not specified a preset, then the default \`mp3\` preset is not applied. This is to prevent you from having to override each of the MP3 preset's values manually.
Expand All @@ -379,9 +441,8 @@ Height of the new video, in pixels.
If the value is not specified and the \`preset\` parameter is available, the \`preset\`'s [supplied height](/docs/transcoding/video-encoding/video-presets/) will be implemented.
`),
preset: z
.preprocess(
preprocessPreset,
z.enum([
.enum([
...createPresets([
'android-high',
'android-low',
'android',
Expand Down Expand Up @@ -461,9 +522,10 @@ If the value is not specified and the \`preset\` parameter is available, the \`p
'webm-1080p',
'webm',
'wmv',
...audioPresets,
]),
)
...audioPresets,
])
.transform(transformPreset)
.optional().describe(`
Converts a video according to [pre-configured settings](/docs/transcoding/video-encoding/video-presets/).

Expand Down Expand Up @@ -834,3 +896,58 @@ While we recommend to use <dfn>Template Credentials</dfn> at all times, some use
secret: z.string().optional(),
})
.strict()

export type FilterExpression = z.infer<typeof filterExpression>
export const filterExpression = z.union([
z.string(),
z.number(),
z.array(z.union([z.string(), z.number()])),
])

export type FilterCondition = z.infer<typeof filterCondition>
export const filterCondition = z
.array(
z.union([
z.tuple([
filterExpression,
z.union([
z.literal('==').describe('Equals without type check'),
z.literal('===').describe('Strict equals with type check'),
z.literal('<').describe('Less than'),
z.literal('>').describe('Greater than'),
z.literal('<=').describe('Less or equal'),
z.literal('>=').describe('Greater or equal'),
z.literal('!=').describe('Simple inequality check without type check'),
z.literal('!==').describe('Strict inequality check with type check'),
z
.literal('regex')
.describe(
'Case-insensitive regular expression based on [RE2](https://github.com/google/re2) `.match()`',
),
z
.literal('!regex')
.describe(
'Case-insensitive regular expression based on [RE2](https://github.com/google/re2) `!.match()`',
),
z
.literal('includes')
.describe(
'Check if the right element is included in the array, which is represented by the left element',
),
z
.literal('empty')
.describe(
'Check if the left element is an empty array, an object without properties, an empty string, the number zero or the boolean false. Leave the third element of the array to be an empty string. It won’t be evaluated.',
),
z
.literal('!empty')
.describe(
'Check if the left element is an array with members, an object with at least one property, a non-empty string, a number that does not equal zero or the boolean true. Leave the third element of the array to be an empty string. It won’t be evaluated.',
),
]),
filterExpression,
]),
z.string(),
]),
)
.default([])
16 changes: 4 additions & 12 deletions src/alphalib/types/robots/file-filter.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { z } from 'zod'

import { robotBase, robotUse } from './_instructions-primitives.ts'
import { filterCondition, robotBase, robotUse } from './_instructions-primitives.ts'
import type { RobotMeta } from './_instructions-primitives.ts'

export const meta: RobotMeta = {
Expand All @@ -13,7 +13,7 @@ export const meta: RobotMeta = {
filtered: {
robot: '/file/filter',
use: ':original',
declines: [['${file.size}', '&gt;', '20971520']],
declines: [['${file.size}', '>', '20971520']],
error_on_decline: true,
error_msg: 'File size must not exceed 20 MB',
},
Expand Down Expand Up @@ -77,22 +77,14 @@ Examples:

As indicated, we charge for this via [🤖/script/run](/docs/transcoding/code-evaluation/script-run/). See also [Dynamic Evaluation](/docs/topics/dynamic-evaluation/) for more details on allowed syntax and behavior.
`),
accepts: z
.array(
z.union([z.string(), z.tuple([z.string(), z.string(), z.union([z.string(), z.number()])])]),
)
.default([]).describe(`
accepts: filterCondition.describe(`
Files that match at least one requirement will be accepted, or declined otherwise. If the array is empty, all files will be accepted. Example:

\`[["\${file.mime}", "==", "image/gif"]]\`.

If the \`condition_type\` parameter is set to \`"and"\`, then all requirements must match for the file to be accepted.
`),
declines: z
.array(
z.union([z.string(), z.tuple([z.string(), z.string(), z.union([z.string(), z.number()])])]),
)
.default([]).describe(`
declines: filterCondition.describe(`
Files that match at least one requirement will be declined, or accepted otherwise. Example:

\`[["\${file.size}",">","1024"]]\`.
Expand Down
2 changes: 1 addition & 1 deletion src/alphalib/types/robots/image-generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const robotImageGenerateInstructionsSchema = robotBase
.optional()
.describe('Format of the generated image.'),
seed: z.number().optional().describe('Seed for the random number generator.'),
aspectRatio: z.string().optional().describe('Aspect ratio of the generated image.'),
aspect_ratio: z.string().optional().describe('Aspect ratio of the generated image.'),
height: z.number().optional().describe('Height of the generated image.'),
width: z.number().optional().describe('Width of the generated image.'),
style: z.string().optional().describe('Style of the generated image.'),
Expand Down
2 changes: 1 addition & 1 deletion tsconfig.build.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"include": ["src"],
"exclude": ["coverage"],
"exclude": ["coverage", "dist"],
"compilerOptions": {
"composite": true,
"declaration": true,
Expand Down
8 changes: 8 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -6786,6 +6786,7 @@ __metadata:
prettier: "npm:^3.3.3"
temp: "npm:^0.9.4"
tus-js-client: "npm:^4.3.1"
type-fest: "npm:^4.39.1"
typescript: "npm:^5.7.2"
vitest: "npm:^2.1.3"
zod: "npm:^3.24.2"
Expand Down Expand Up @@ -6851,6 +6852,13 @@ __metadata:
languageName: node
linkType: hard

"type-fest@npm:^4.39.1":
version: 4.39.1
resolution: "type-fest@npm:4.39.1"
checksum: 10c0/f5bf302eb2e2f70658be1757aa578f4a09da3f65699b0b12b7ae5502ccea76e5124521a6e6b69540f442c3dc924c394202a2ab58718d0582725c7ac23c072594
languageName: node
linkType: hard

"typed-array-buffer@npm:^1.0.2":
version: 1.0.2
resolution: "typed-array-buffer@npm:1.0.2"
Expand Down
Loading