chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:44:08 +08:00
commit 983960e2dd
1244 changed files with 281996 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
from .src.python.ffmpeg_tool import FfmpegTool
__all__ = ["FfmpegTool"]
@@ -0,0 +1 @@
{}
@@ -0,0 +1,508 @@
import { Tool } from '@sdk/base-tool'
import { ToolkitConfig } from '@sdk/toolkit-config'
const DEFAULT_SETTINGS: Record<string, unknown> = {}
const REQUIRED_SETTINGS: string[] = []
export default class FfmpegTool extends Tool {
private static readonly TOOLKIT = 'video_streaming'
private readonly config: ReturnType<typeof ToolkitConfig.load>
constructor() {
super()
// Load configuration from central toolkits directory
this.config = ToolkitConfig.load(FfmpegTool.TOOLKIT, this.toolName)
const toolSettings = ToolkitConfig.loadToolSettings(
FfmpegTool.TOOLKIT,
this.toolName,
DEFAULT_SETTINGS
)
this.settings = toolSettings
this.requiredSettings = REQUIRED_SETTINGS
this.checkRequiredSettings(this.toolName)
}
get toolName(): string {
return 'ffmpeg'
}
get toolkit(): string {
return FfmpegTool.TOOLKIT
}
get description(): string {
return this.config['description']
}
/**
* Get global FFmpeg arguments to hide banner and set log level to error
*/
private getGlobalArgs(): string[] {
return ['-hide_banner', '-loglevel', 'error']
}
/**
* Converts a video file to a different format.
* @param inputPath The file path of the video to be converted.
* @param outputPath The desired file path for the converted video.
* @returns A promise that resolves with the path to the converted video file.
*/
async convertVideoFormat(
inputPath: string,
outputPath: string
): Promise<string> {
try {
await this.executeCommand({
binaryName: 'ffmpeg',
args: [...this.getGlobalArgs(), '-i', inputPath, outputPath],
options: { sync: true }
})
return outputPath
} catch (error: unknown) {
throw new Error(`Video conversion failed: ${(error as Error).message}`)
}
}
/**
* Extracts the audio track from a video file and saves it as a separate audio file.
* @param videoPath The file path of the video from which to extract audio.
* @param audioPath The desired file path for the extracted audio.
* @returns A promise that resolves with the path to the extracted audio file.
*/
async extractAudio(videoPath: string, audioPath: string): Promise<string> {
try {
// Keep it simple: do not force codec/bitrate. Let ffmpeg choose defaults based on extension.
// Add -progress to emit periodic key=value lines we can log as progress.
const args = [
...this.getGlobalArgs(),
'-y',
'-i',
videoPath,
'-vn',
// Progress to stderr so we can parse without interfering with stdout JSON
'-progress',
'pipe:2',
audioPath
]
await this.executeCommand({
binaryName: 'ffmpeg',
args,
options: { sync: false },
onOutput: (data: string, isError?: boolean) => {
// Parse ffmpeg -progress key=value lines from stderr
if (!isError) return
const lines = data.split('\n')
for (const line of lines) {
const trimmed = line.trim()
if (!trimmed || !trimmed.includes('=')) continue
const [key, value] = trimmed.split('=')
if (!key || value === undefined) continue
// Log some useful progress keys
if (key === 'progress') {
this.log(`ffmpeg progress: ${value}`)
} else if (key === 'out_time_ms') {
const ms = parseInt(value, 10)
if (!Number.isNaN(ms)) {
const seconds = Math.floor(ms / 1_000_000)
this.log(`processed_time_seconds=${seconds}`)
}
} else if (key === 'speed') {
this.log(`speed=${value}`)
}
}
}
})
return audioPath
} catch (error: unknown) {
throw new Error(`Audio extraction failed: ${(error as Error).message}`)
}
}
/**
* Trims a media (video or audio) file to a specified duration.
* @param inputPath The file path of the media to be trimmed.
* @param outputPath The desired file path for the trimmed media.
* @param startTime The start time for the trim, formatted as HH:MM:SS.
* @param endTime The end time for the trim, formatted as HH:MM:SS.
* @returns A promise that resolves with the path to the trimmed media file.
*/
async trimMedia(
inputPath: string,
outputPath: string,
startTime: string,
endTime: string
): Promise<string> {
try {
await this.executeCommand({
binaryName: 'ffmpeg',
args: [
...this.getGlobalArgs(),
'-i',
inputPath,
'-ss',
startTime,
'-to',
endTime,
'-c',
'copy',
outputPath
],
options: { sync: true }
})
return outputPath
} catch (error: unknown) {
throw new Error(`Video trimming failed: ${(error as Error).message}`)
}
}
/**
* Resizes a video to the specified dimensions.
* @param inputPath The file path of the video to be resized.
* @param outputPath The desired file path for the resized video.
* @param width The target width of the video in pixels.
* @param height The target height of the video in pixels.
* @returns A promise that resolves with the path to the resized video file.
*/
async resizeVideo(
inputPath: string,
outputPath: string,
width: number,
height: number
): Promise<string> {
try {
await this.executeCommand({
binaryName: 'ffmpeg',
args: [
...this.getGlobalArgs(),
'-i',
inputPath,
'-vf',
`scale=${width}:${height}`,
outputPath
],
options: { sync: true }
})
return outputPath
} catch (error: unknown) {
throw new Error(`Video resizing failed: ${(error as Error).message}`)
}
}
/**
* Merges a video file with a separate audio file.
* @param videoPath The file path of the video file.
* @param audioPath The file path of the audio file.
* @param outputPath The desired file path for the combined video and audio.
* @returns A promise that resolves with the path to the merged video file.
*/
async combineVideoAndAudio(
videoPath: string,
audioPath: string,
outputPath: string
): Promise<string> {
try {
await this.executeCommand({
binaryName: 'ffmpeg',
args: [
...this.getGlobalArgs(),
'-i',
videoPath,
'-i',
audioPath,
'-c:v',
'copy',
'-c:a',
'aac',
'-strict',
'experimental',
outputPath
],
options: { sync: true }
})
return outputPath
} catch (error: unknown) {
throw new Error(
`Video and audio combination failed: ${(error as Error).message}`
)
}
}
/**
* Replaces the audio track of a video with a new audio file.
* Removes/mutes the original audio and merges the new audio with the video.
* @param videoPath The file path of the video file.
* @param newAudioPath The file path of the new audio file to replace the original audio.
* @param outputPath The desired file path for the video with replaced audio.
* @returns A promise that resolves with the path to the video file with new audio.
*/
async replaceVideoAudio(
videoPath: string,
newAudioPath: string,
outputPath: string
): Promise<string> {
try {
// Use -map to explicitly map video from first input and audio from second input
// This effectively removes the original audio and replaces it with the new audio
await this.executeCommand({
binaryName: 'ffmpeg',
args: [
...this.getGlobalArgs(),
'-y', // Overwrite output file if it exists
'-i',
videoPath,
'-i',
newAudioPath,
'-map',
'0:v:0', // Map video from first input
'-map',
'1:a:0', // Map audio from second input
'-c:v',
'copy', // Copy video codec (no re-encoding)
'-c:a',
'aac', // Encode audio to AAC
'-b:a',
'192k', // Audio bitrate
'-shortest', // Finish encoding when the shortest input stream ends
outputPath
],
options: { sync: true }
})
return outputPath
} catch (error: unknown) {
throw new Error(
`Video audio replacement failed: ${(error as Error).message}`
)
}
}
/**
* Compresses a video to reduce its file size.
* @param inputPath The file path of the video to be compressed.
* @param outputPath The desired file path for the compressed video.
* @param bitrate The target bitrate for the video (e.g., "1000k").
* @returns A promise that resolves with the path to the compressed video file.
*/
async compressVideo(
inputPath: string,
outputPath: string,
bitrate: string
): Promise<string> {
try {
await this.executeCommand({
binaryName: 'ffmpeg',
args: [
...this.getGlobalArgs(),
'-i',
inputPath,
'-b:v',
bitrate,
outputPath
],
options: { sync: true }
})
return outputPath
} catch (error: unknown) {
throw new Error(`Video compression failed: ${(error as Error).message}`)
}
}
/**
* Adjusts the tempo (speed) of an audio file using the atempo filter.
* If the speed factor is greater than 2.0, multiple atempo filters are chained.
* @param inputPath The file path of the audio to be speed-adjusted.
* @param outputPath The desired file path for the speed-adjusted audio.
* @param speedFactor The speed multiplier (e.g., 1.3 for 30% faster, 0.8 for 20% slower). Must be between 0.5 and 100.0.
* @param sampleRate Optional sample rate for the output audio (defaults to the input's sample rate).
* @returns A promise that resolves with the path to the speed-adjusted audio file.
*/
async adjustTempo(
inputPath: string,
outputPath: string,
speedFactor: number,
sampleRate?: number
): Promise<string> {
try {
if (speedFactor < 0.5 || speedFactor > 100.0) {
throw new Error('Speed factor must be between 0.5 and 100.0')
}
// FFmpeg's atempo filter only supports values between 0.5 and 2.0
// For larger speed factors, we need to chain multiple atempo filters
const atempoFilters: string[] = []
let remainingSpeed = speedFactor
while (remainingSpeed > 2.0) {
atempoFilters.push('atempo=2.0')
remainingSpeed /= 2.0
}
if (remainingSpeed < 1.0 && remainingSpeed < 0.5) {
while (remainingSpeed < 0.5) {
atempoFilters.push('atempo=0.5')
remainingSpeed /= 0.5
}
}
atempoFilters.push(`atempo=${remainingSpeed.toFixed(6)}`)
const filterComplex = atempoFilters.join(',')
const args = [
...this.getGlobalArgs(),
'-y',
'-i',
inputPath,
'-filter:a',
filterComplex
]
if (sampleRate) {
args.push('-ar', sampleRate.toString())
}
args.push(outputPath)
await this.executeCommand({
binaryName: 'ffmpeg',
args,
options: { sync: true }
})
return outputPath
} catch (error: unknown) {
throw new Error(
`Audio tempo adjustment failed: ${(error as Error).message}`
)
}
}
/**
* Merges two audio files into one.
* @param firstAudioPath The path to the first audio file.
* @param secondAudioPath The path to the second audio file.
* @param outputPath The desired file path for the merged audio.
* @returns A promise that resolves with the path to the merged audio file.
*/
async mergeAudio(
firstAudioPath: string,
secondAudioPath: string,
outputPath: string
): Promise<string> {
try {
await this.executeCommand({
binaryName: 'ffmpeg',
args: [
...this.getGlobalArgs(),
'-y',
'-i',
firstAudioPath,
'-i',
secondAudioPath,
'-filter_complex',
'amix=inputs=2:duration=longest:dropout_transition=0',
outputPath
],
options: { sync: true }
})
return outputPath
} catch (error: unknown) {
throw new Error(`Audio merging failed: ${(error as Error).message}`)
}
}
/**
* Assembles multiple audio segments into a single audio file with precise timing.
* Each segment is placed at its exact timestamp with silence padding where needed.
* Similar to pydub's overlay functionality but using FFmpeg.
* @param segments Array of {path: string, startMs: number} objects representing audio segments and their start times in milliseconds
* @param outputPath The desired file path for the assembled audio
* @param totalDurationMs The total duration of the output audio in milliseconds
* @param sampleRate Optional sample rate for the output audio (default: 22050)
* @returns A promise that resolves with the path to the assembled audio file
*/
async assembleAudioSegments(
segments: Array<{ path: string, startMs: number }>,
outputPath: string,
totalDurationMs: number,
sampleRate: number = 22_050
): Promise<string> {
try {
if (segments.length === 0) {
throw new Error('No segments provided for assembly')
}
// Build FFmpeg filter_complex for assembling segments at precise timestamps
// We'll use the adelay filter to position each segment at its start time
const inputs: string[] = []
const filterParts: string[] = []
// Add all segment files as inputs
for (const segment of segments) {
inputs.push('-i', segment.path)
}
// Build filter chain: adelay each segment, then amix them all together
for (let i = 0; i < segments.length; i += 1) {
const delayMs = segments[i]?.startMs ?? 0
// adelay takes delay in milliseconds
filterParts.push(`[${i}:a]adelay=${delayMs}|${delayMs}[a${i}]`)
}
// Mix all delayed streams together with normalization
// Use amix with normalize=0 and weights=1 to prevent volume reduction
const mixInputs = segments.map((_, i) => `[a${i}]`).join('')
filterParts.push(
`${mixInputs}amix=inputs=${segments.length}:duration=longest:dropout_transition=0:normalize=0[mixed]`
)
// Apply dynamic normalization and compression to maintain consistent volume
filterParts.push('[mixed]dynaudnorm=f=150:g=15:p=0.9:s=5[normalized]')
// Apply a slight compression to even out volume levels
filterParts.push(
'[normalized]acompressor=threshold=0.089:ratio=4:attack=20:release=250[aout]'
)
const filterComplex = filterParts.join(';')
// Calculate total duration in seconds for ffmpeg
const totalDurationS = totalDurationMs / 1000
const args = [
...this.getGlobalArgs(),
'-y',
...inputs,
'-filter_complex',
filterComplex,
'-map',
'[aout]',
'-ar',
sampleRate.toString(),
'-t',
totalDurationS.toFixed(3),
'-c:a',
'pcm_s16le',
outputPath
]
await this.executeCommand({
binaryName: 'ffmpeg',
args,
options: { sync: true }
})
return outputPath
} catch (error: unknown) {
throw new Error(`Audio assembly failed: ${(error as Error).message}`)
}
}
}
@@ -0,0 +1 @@
export { default } from './ffmpeg-tool'
@@ -0,0 +1,401 @@
from typing import List, Dict, Optional
from bridges.python.src.sdk.base_tool import BaseTool, ExecuteCommandOptions
from bridges.python.src.sdk.toolkit_config import ToolkitConfig
DEFAULT_SETTINGS = {}
REQUIRED_SETTINGS = []
class FfmpegTool(BaseTool):
TOOLKIT = "video_streaming"
def __init__(self):
super().__init__()
self.config = ToolkitConfig.load(self.TOOLKIT, self.tool_name)
self.settings = ToolkitConfig.load_tool_settings(
self.TOOLKIT, self.tool_name, DEFAULT_SETTINGS
)
self.required_settings = REQUIRED_SETTINGS
self._check_required_settings(self.tool_name)
@property
def tool_name(self) -> str:
return "ffmpeg"
@property
def toolkit(self) -> str:
return self.TOOLKIT
@property
def description(self) -> str:
return self.config["description"]
def _get_global_args(self) -> List[str]:
"""Get global FFmpeg arguments to hide banner and set log level to error"""
return ["-hide_banner", "-loglevel", "error"]
def convert_video_format(self, input_path: str, output_path: str) -> str:
"""
Converts a video file to a different format.
Args:
input_path: The file path of the video to be converted.
output_path: The desired file path for the converted video.
Returns:
The path to the converted video file.
"""
try:
self.execute_command(
ExecuteCommandOptions(
binary_name="ffmpeg",
args=self._get_global_args() + ["-i", input_path, output_path],
options={"sync": True},
)
)
return output_path
except Exception as e:
raise Exception(f"Video conversion failed: {str(e)}")
def extract_audio(self, video_path: str, audio_path: str) -> str:
"""
Extracts the audio track from a video file and saves it as a separate audio file.
Args:
video_path: The file path of the video from which to extract audio.
audio_path: The desired file path for the extracted audio.
Returns:
The path to the extracted audio file.
"""
try:
# Keep it simple: don't force codec/bitrate, let ffmpeg decide from extension.
# Use -progress pipe:2 to stream progress to stderr and log it.
args = self._get_global_args() + [
"-y",
"-i",
video_path,
"-vn",
"-progress",
"pipe:2",
audio_path,
]
def on_output(data: str, is_error: bool = False) -> None:
if not is_error:
return
for line in data.split("\n"):
line = line.strip()
if not line or "=" not in line:
continue
key, value = line.split("=", 1)
if key == "progress":
self.log(f"ffmpeg progress: {value}")
elif key == "out_time_ms":
try:
ms = int(value)
seconds = ms // 1_000_000
self.log(f"processed_time_seconds={seconds}")
except Exception:
pass
elif key == "speed":
self.log(f"speed={value}")
self.execute_command(
ExecuteCommandOptions(
binary_name="ffmpeg",
args=args,
options={"sync": False},
on_output=on_output,
)
)
return audio_path
except Exception as e:
raise Exception(f"Audio extraction failed: {str(e)}")
def trim_media(
self, input_path: str, output_path: str, start_time: str, end_time: str
) -> str:
"""
Trims a media (video or audio) file to a specified duration.
Args:
input_path: The file path of the media to be trimmed.
output_path: The desired file path for the trimmed media.
start_time: The start time for the trim, formatted as HH:MM:SS.
end_time: The end time for the trim, formatted as HH:MM:SS.
Returns:
The path to the trimmed media file.
"""
try:
self.execute_command(
ExecuteCommandOptions(
binary_name="ffmpeg",
args=self._get_global_args()
+ [
"-i",
input_path,
"-ss",
start_time,
"-to",
end_time,
"-c",
"copy",
output_path,
],
options={"sync": True},
)
)
return output_path
except Exception as e:
raise Exception(f"Video trimming failed: {str(e)}")
def resize_video(
self, input_path: str, output_path: str, width: int, height: int
) -> str:
"""
Resizes a video to the specified dimensions.
Args:
input_path: The file path of the video to be resized.
output_path: The desired file path for the resized video.
width: The target width of the video in pixels.
height: The target height of the video in pixels.
Returns:
The path to the resized video file.
"""
try:
self.execute_command(
ExecuteCommandOptions(
binary_name="ffmpeg",
args=self._get_global_args()
+ ["-i", input_path, "-vf", f"scale={width}:{height}", output_path],
options={"sync": True},
)
)
return output_path
except Exception as e:
raise Exception(f"Video resizing failed: {str(e)}")
def combine_video_and_audio(
self, video_path: str, audio_path: str, output_path: str
) -> str:
"""
Merges a video file with a separate audio file.
Args:
video_path: The file path of the video file.
audio_path: The file path of the audio file.
output_path: The desired file path for the combined video and audio.
Returns:
The path to the merged video file.
"""
try:
self.execute_command(
ExecuteCommandOptions(
binary_name="ffmpeg",
args=self._get_global_args()
+ [
"-i",
video_path,
"-i",
audio_path,
"-c:v",
"copy",
"-c:a",
"aac",
"-strict",
"experimental",
output_path,
],
options={"sync": True},
)
)
return output_path
except Exception as e:
raise Exception(f"Video and audio combination failed: {str(e)}")
def compress_video(self, input_path: str, output_path: str, bitrate: str) -> str:
"""
Compresses a video to reduce its file size.
Args:
input_path: The file path of the video to be compressed.
output_path: The desired file path for the compressed video.
bitrate: The target bitrate for the video (e.g., "1000k").
Returns:
The path to the compressed video file.
"""
try:
self.execute_command(
ExecuteCommandOptions(
binary_name="ffmpeg",
args=self._get_global_args()
+ ["-i", input_path, "-b:v", bitrate, output_path],
options={"sync": True},
)
)
return output_path
except Exception as e:
raise Exception(f"Video compression failed: {str(e)}")
def adjust_tempo(
self,
input_path: str,
output_path: str,
speed_factor: float,
sample_rate: int = None,
) -> str:
"""
Adjusts the tempo (speed) of an audio file using the atempo filter.
If the speed factor is greater than 2.0, multiple atempo filters are chained.
Args:
input_path: The file path of the audio to be speed-adjusted.
output_path: The desired file path for the speed-adjusted audio.
speed_factor: The speed multiplier (e.g., 1.3 for 30% faster, 0.8 for 20% slower). Must be between 0.5 and 100.0.
sample_rate: Optional sample rate for the output audio (defaults to the input's sample rate).
Returns:
The path to the speed-adjusted audio file.
"""
try:
if speed_factor < 0.5 or speed_factor > 100.0:
raise ValueError("Speed factor must be between 0.5 and 100.0")
# FFmpeg's atempo filter only supports values between 0.5 and 2.0
# For larger speed factors, we need to chain multiple atempo filters
atempo_filters = []
remaining_speed = speed_factor
while remaining_speed > 2.0:
atempo_filters.append("atempo=2.0")
remaining_speed /= 2.0
if remaining_speed < 1.0 and remaining_speed < 0.5:
while remaining_speed < 0.5:
atempo_filters.append("atempo=0.5")
remaining_speed /= 0.5
atempo_filters.append(f"atempo={remaining_speed:.6f}")
filter_complex = ",".join(atempo_filters)
args = self._get_global_args() + [
"-y",
"-i",
input_path,
"-filter:a",
filter_complex,
]
if sample_rate:
args.extend(["-ar", str(sample_rate)])
args.append(output_path)
self.execute_command(
ExecuteCommandOptions(
binary_name="ffmpeg", args=args, options={"sync": True}
)
)
return output_path
except Exception as e:
raise Exception(f"Audio tempo adjustment failed: {str(e)}")
def assemble_audio_segments(
self,
segments: List[Dict[str, any]],
output_path: str,
total_duration_ms: int,
sample_rate: int = 22_050,
) -> str:
"""
Assembles multiple audio segments into a single audio file with precise timing.
Each segment is placed at its exact timestamp with silence padding where needed.
Similar to pydub's overlay functionality but using FFmpeg.
Args:
segments: List of dictionaries with 'path' (str) and 'start_ms' (int) keys
representing audio segments and their start times in milliseconds
output_path: The desired file path for the assembled audio
total_duration_ms: The total duration of the output audio in milliseconds
sample_rate: Optional sample rate for the output audio (default: 22050)
Returns:
The path to the assembled audio file
"""
try:
if not segments:
raise ValueError("No segments provided for assembly")
# Build FFmpeg filter_complex for assembling segments at precise timestamps
# We'll use the adelay filter to position each segment at its start time
inputs = []
filter_parts = []
# Add all segment files as inputs
for segment in segments:
inputs.extend(["-i", segment["path"]])
# Build filter chain: adelay each segment, then amix them all together
for i, segment in enumerate(segments):
delay_ms = segment.get("start_ms", 0)
# adelay takes delay in milliseconds
filter_parts.append(f"[{i}:a]adelay={delay_ms}|{delay_ms}[a{i}]")
# Mix all delayed streams together with normalization
# Use amix with normalize=0 and weights=1 to prevent volume reduction
mix_inputs = "".join([f"[a{i}]" for i in range(len(segments))])
filter_parts.append(
f"{mix_inputs}amix=inputs={len(segments)}:duration=longest:dropout_transition=0:normalize=0[mixed]"
)
# Apply dynamic normalization and compression to maintain consistent volume
filter_parts.append("[mixed]dynaudnorm=f=150:g=15:p=0.9:s=5[normalized]")
# Apply a slight compression to even out volume levels
filter_parts.append(
"[normalized]acompressor=threshold=0.089:ratio=4:attack=20:release=250[aout]"
)
filter_complex = ";".join(filter_parts)
# Calculate total duration in seconds for ffmpeg
total_duration_s = total_duration_ms / 1000
args = self._get_global_args() + [
"-y",
*inputs,
"-filter_complex",
filter_complex,
"-map",
"[aout]",
"-ar",
str(sample_rate),
"-t",
f"{total_duration_s:.3f}",
"-c:a",
"pcm_s16le",
output_path,
]
self.execute_command(
ExecuteCommandOptions(
binary_name="ffmpeg", args=args, options={"sync": True}
)
)
return output_path
except Exception as e:
raise Exception(f"Audio assembly failed: {str(e)}")
+264
View File
@@ -0,0 +1,264 @@
{
"$schema": "../../../schemas/tool-schemas/tool.json",
"tool_id": "ffmpeg",
"toolkit_id": "video_streaming",
"name": "FFmpeg",
"description": "A tool for video/audio processing, conversion, and manipulation. Use this for encoding, decoding, transcoding, muxing, demuxing, streaming, filtering, and playing media files. For analyzing or extracting metadata without modifying files, use ffprobe instead.",
"icon_name": "film-line",
"author": {
"name": "Louis Grenard",
"email": "louis@getleon.ai",
"url": "https://twitter.com/grenlouis"
},
"binaries": {
"linux-x86_64": "https://github.com/leon-ai/leon-binaries/releases/download/ffmpeg_7.0.2/ffmpeg_7.0.2-linux-x86_64",
"linux-aarch64": "https://github.com/leon-ai/leon-binaries/releases/download/ffmpeg_7.0.2/ffmpeg_7.0.2-linux-aarch64",
"macosx-x86_64": "https://github.com/leon-ai/leon-binaries/releases/download/ffmpeg_7.0.2/ffmpeg_7.0.2-macosx-x86_64",
"macosx-arm64": "https://github.com/leon-ai/leon-binaries/releases/download/ffmpeg_7.0.2/ffmpeg_7.0.2-macosx-arm64",
"win-amd64": "https://github.com/leon-ai/leon-binaries/releases/download/ffmpeg_7.0.2/ffmpeg_7.0.2-win-amd64.exe"
},
"functions": {
"convertVideoFormat": {
"description": "Convert a video file to a different format.",
"parameters": {
"type": "object",
"properties": {
"inputPath": {
"type": "string"
},
"outputPath": {
"type": "string"
}
},
"required": [
"inputPath",
"outputPath"
]
}
},
"extractAudio": {
"description": "Extract an audio track from a video file.",
"parameters": {
"type": "object",
"properties": {
"videoPath": {
"type": "string"
},
"audioPath": {
"type": "string"
}
},
"required": [
"videoPath",
"audioPath"
]
}
},
"trimMedia": {
"description": "Trim a media file to a specific time range.",
"parameters": {
"type": "object",
"properties": {
"inputPath": {
"type": "string"
},
"outputPath": {
"type": "string"
},
"startTime": {
"type": "string"
},
"endTime": {
"type": "string"
}
},
"required": [
"inputPath",
"outputPath",
"startTime",
"endTime"
]
}
},
"resizeVideo": {
"description": "Resize a video to a given width and height.",
"parameters": {
"type": "object",
"properties": {
"inputPath": {
"type": "string"
},
"outputPath": {
"type": "string"
},
"width": {
"type": "number"
},
"height": {
"type": "number"
}
},
"required": [
"inputPath",
"outputPath",
"width",
"height"
]
}
},
"combineVideoAndAudio": {
"description": "Combine a video file with an audio file.",
"parameters": {
"type": "object",
"properties": {
"videoPath": {
"type": "string"
},
"audioPath": {
"type": "string"
},
"outputPath": {
"type": "string"
}
},
"required": [
"videoPath",
"audioPath",
"outputPath"
]
}
},
"replaceVideoAudio": {
"description": "Replace the audio track in a video file.",
"parameters": {
"type": "object",
"properties": {
"videoPath": {
"type": "string"
},
"audioPath": {
"type": "string"
},
"outputPath": {
"type": "string"
}
},
"required": [
"videoPath",
"audioPath",
"outputPath"
]
}
},
"compressVideo": {
"description": "Compress a video by specifying a target bitrate.",
"parameters": {
"type": "object",
"properties": {
"inputPath": {
"type": "string"
},
"outputPath": {
"type": "string"
},
"bitrate": {
"type": "string"
}
},
"required": [
"inputPath",
"outputPath",
"bitrate"
]
}
},
"adjustTempo": {
"description": "Adjust the tempo (speed) of an audio file.",
"parameters": {
"type": "object",
"properties": {
"inputPath": {
"type": "string"
},
"outputPath": {
"type": "string"
},
"speedFactor": {
"type": "number"
},
"sampleRate": {
"type": "number"
}
},
"required": [
"inputPath",
"outputPath",
"speedFactor"
]
}
},
"mergeAudio": {
"description": "Merge two audio files into one.",
"parameters": {
"type": "object",
"properties": {
"firstAudioPath": {
"type": "string"
},
"secondAudioPath": {
"type": "string"
},
"outputPath": {
"type": "string"
}
},
"required": [
"firstAudioPath",
"secondAudioPath",
"outputPath"
]
}
},
"assembleAudioSegments": {
"description": "Assemble audio segments into a single audio file with precise timing.",
"parameters": {
"type": "object",
"properties": {
"segments": {
"type": "array",
"items": {
"type": "object",
"properties": {
"path": {
"type": "string"
},
"startMs": {
"type": "number"
}
},
"required": [
"path",
"startMs"
],
"additionalProperties": false
}
},
"outputPath": {
"type": "string"
},
"totalDurationMs": {
"type": "number"
},
"sampleRate": {
"type": "number"
}
},
"required": [
"segments",
"outputPath",
"totalDurationMs"
]
}
}
}
}