Instructions to use autotools/ai_video_studio with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- llama.cpp
How to use autotools/ai_video_studio with llama.cpp:
Install (macOS, Linux)
curl -LsSf https://llama.app/install.sh | sh # Start a local OpenAI-compatible server with a web UI: llama serve -hf autotools/ai_video_studio:Q4_K_M # Run inference directly in the terminal: llama cli -hf autotools/ai_video_studio:Q4_K_M
Install from WinGet (Windows)
winget install llama.cpp # Start a local OpenAI-compatible server with a web UI: llama serve -hf autotools/ai_video_studio:Q4_K_M # Run inference directly in the terminal: llama cli -hf autotools/ai_video_studio:Q4_K_M
Use pre-built binary
# Download pre-built binary from: # https://github.com/ggerganov/llama.cpp/releases # Start a local OpenAI-compatible server with a web UI: ./llama-server -hf autotools/ai_video_studio:Q4_K_M # Run inference directly in the terminal: ./llama-cli -hf autotools/ai_video_studio:Q4_K_M
Build from source code
git clone https://github.com/ggerganov/llama.cpp.git cd llama.cpp cmake -B build cmake --build build -j --target llama-server llama-cli # Start a local OpenAI-compatible server with a web UI: ./build/bin/llama-server -hf autotools/ai_video_studio:Q4_K_M # Run inference directly in the terminal: ./build/bin/llama-cli -hf autotools/ai_video_studio:Q4_K_M
Use Docker
docker model run hf.co/autotools/ai_video_studio:Q4_K_M
- LM Studio
- Jan
- Ollama
How to use autotools/ai_video_studio with Ollama:
ollama run hf.co/autotools/ai_video_studio:Q4_K_M
- Unsloth Studio
How to use autotools/ai_video_studio with Unsloth Studio:
Install Unsloth Studio (macOS, Linux, WSL)
curl -fsSL https://unsloth.ai/install.sh | sh # Run unsloth studio unsloth studio -H 0.0.0.0 -p 8888 # Then open http://localhost:8888 in your browser # Search for autotools/ai_video_studio to start chatting
Install Unsloth Studio (Windows)
irm https://unsloth.ai/install.ps1 | iex # Run unsloth studio unsloth studio -H 0.0.0.0 -p 8888 # Then open http://localhost:8888 in your browser # Search for autotools/ai_video_studio to start chatting
Using HuggingFace Spaces for Unsloth
# No setup required # Open https://huggingface.co/spaces/unsloth/studio in your browser # Search for autotools/ai_video_studio to start chatting
- Atomic Chat new
- Docker Model Runner
How to use autotools/ai_video_studio with Docker Model Runner:
docker model run hf.co/autotools/ai_video_studio:Q4_K_M
- Lemonade
How to use autotools/ai_video_studio with Lemonade:
Pull the model
# Download Lemonade from https://lemonade-server.ai/ lemonade pull autotools/ai_video_studio:Q4_K_M
Run and chat with the model
lemonade run user.ai_video_studio-Q4_K_M
List all available models
lemonade list
| #!/usr/bin/env python3 | |
| # Copyright 2026 Xiaomi Corp. (authors: Han Zhu) | |
| # | |
| # See ../../LICENSE for clarification regarding multiple authors | |
| # | |
| # Licensed under the Apache License, Version 2.0 (the "License"); | |
| # you may not use this file except in compliance with the License. | |
| # You may obtain a copy of the License at | |
| # | |
| # http://www.apache.org/licenses/LICENSE-2.0 | |
| # | |
| # Unless required by applicable law or agreed to in writing, software | |
| # distributed under the License is distributed on an "AS IS" BASIS, | |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| # See the License for the specific language governing permissions and | |
| # limitations under the License. | |
| """Audio I/O and processing utilities. | |
| Provides functions for loading, resampling, silence removal, | |
| chunking, cross-fading, and format conversion. | |
| All public functions in this module operate on **numpy float32 arrays** | |
| with shape ``(C, T)`` (channels-first). | |
| """ | |
| import io | |
| import logging | |
| import numpy as np | |
| import soundfile as sf | |
| import torch | |
| import torchaudio | |
| from pydub import AudioSegment | |
| from pydub.silence import detect_leading_silence, detect_nonsilent, split_on_silence | |
| logger = logging.getLogger(__name__) | |
| # --------------------------------------------------------------------------- | |
| # Loading | |
| # --------------------------------------------------------------------------- | |
| def load_waveform(audio_path: str): | |
| """Load audio from a file path, returning (data, sample_rate). | |
| Tries two backends in order: | |
| 1. soundfile — covers WAV/FLAC/OGG etc., no ffmpeg needed. | |
| 2. librosa — covers MP3/M4A etc. via audioread + ffmpeg. | |
| Returns: | |
| (data, sample_rate) where data is a numpy float32 array of | |
| shape (C, T). | |
| """ | |
| try: | |
| data, sr = sf.read(audio_path, dtype="float32", always_2d=True) | |
| return data.T, sr # (T, C) → (C, T) | |
| except Exception: | |
| # soundfile cannot handle MP3/M4A etc., fall back to librosa. | |
| import librosa | |
| data, sr = librosa.load(audio_path, sr=None, mono=False) | |
| if data.ndim == 1: | |
| data = data[np.newaxis, :] | |
| return data, sr | |
| def load_audio(audio_path: str, sampling_rate: int) -> np.ndarray: | |
| """Load a waveform from file and resample to the target rate. | |
| Parameters: | |
| audio_path: path of the audio. | |
| sampling_rate: target sampling rate. | |
| Returns: | |
| Numpy float32 array of shape (1, T). | |
| """ | |
| data, sr = load_waveform(audio_path) | |
| if data.shape[0] > 1: | |
| data = np.mean(data, axis=0, keepdims=True) | |
| if sr != sampling_rate: | |
| data = torchaudio.functional.resample( | |
| torch.from_numpy(data), orig_freq=sr, new_freq=sampling_rate | |
| ).numpy() | |
| return data | |
| def load_audio_bytes(raw: bytes, sampling_rate: int) -> np.ndarray: | |
| """Load audio from in-memory bytes and resample. | |
| Parameters: | |
| raw: raw audio file bytes (e.g. from WebDataset). | |
| sampling_rate: target sampling rate. | |
| Returns: | |
| Numpy float32 array of shape (1, T). | |
| """ | |
| buf = io.BytesIO(raw) | |
| try: | |
| data, sr = sf.read(buf, dtype="float32", always_2d=True) | |
| data = data.T # (T, C) → (C, T) | |
| except Exception: | |
| import librosa | |
| buf.seek(0) | |
| data, sr = librosa.load(buf, sr=None, mono=False) | |
| if data.ndim == 1: | |
| data = data[np.newaxis, :] | |
| if data.shape[0] > 1: | |
| data = np.mean(data, axis=0, keepdims=True) | |
| if sr != sampling_rate: | |
| data = torchaudio.functional.resample( | |
| torch.from_numpy(data), orig_freq=sr, new_freq=sampling_rate | |
| ).numpy() | |
| return data | |
| # --------------------------------------------------------------------------- | |
| # Audio processing (all numpy in / numpy out) | |
| # --------------------------------------------------------------------------- | |
| def numpy_to_audiosegment(audio: np.ndarray, sample_rate: int) -> AudioSegment: | |
| """Convert a numpy float32 array of shape (C, T) to a pydub AudioSegment.""" | |
| audio_int = (audio * 32768.0).clip(-32768, 32767).astype(np.int16) | |
| if audio_int.shape[0] > 1: | |
| audio_int = audio_int.T.flatten() # interleave channels | |
| return AudioSegment( | |
| data=audio_int.tobytes(), | |
| sample_width=2, | |
| frame_rate=sample_rate, | |
| channels=audio.shape[0], | |
| ) | |
| def audiosegment_to_numpy(aseg: AudioSegment) -> np.ndarray: | |
| """Convert a pydub AudioSegment to a numpy float32 array of shape (C, T).""" | |
| data = np.array(aseg.get_array_of_samples()).astype(np.float32) / 32768.0 | |
| if aseg.channels == 1: | |
| return data[np.newaxis, :] | |
| return data.reshape(-1, aseg.channels).T | |
| def remove_silence( | |
| audio: np.ndarray, | |
| sampling_rate: int, | |
| mid_sil: int = 300, | |
| lead_sil: int = 100, | |
| trail_sil: int = 300, | |
| ) -> np.ndarray: | |
| """Remove middle silences longer than *mid_sil* ms and trim edge silences. | |
| Parameters: | |
| audio: numpy array with shape (C, T). | |
| sampling_rate: sampling rate of the audio. | |
| mid_sil: middle-silence threshold in ms (0 to skip). | |
| lead_sil: kept leading silence in ms. | |
| trail_sil: kept trailing silence in ms. | |
| Returns: | |
| Numpy array with shape (C, T'). | |
| """ | |
| wave = numpy_to_audiosegment(audio, sampling_rate) | |
| if mid_sil > 0: | |
| non_silent_segs = split_on_silence( | |
| wave, | |
| min_silence_len=mid_sil, | |
| silence_thresh=-50, | |
| keep_silence=mid_sil, | |
| seek_step=10, | |
| ) | |
| wave = AudioSegment.silent(duration=0) | |
| for seg in non_silent_segs: | |
| wave += seg | |
| wave = remove_silence_edges(wave, lead_sil, trail_sil, -50) | |
| return audiosegment_to_numpy(wave) | |
| def remove_silence_edges( | |
| audio: AudioSegment, | |
| lead_sil: int = 100, | |
| trail_sil: int = 300, | |
| silence_threshold: float = -50, | |
| ) -> AudioSegment: | |
| """Remove edge silences, keeping *lead_sil* / *trail_sil* ms.""" | |
| start_idx = detect_leading_silence(audio, silence_threshold=silence_threshold) | |
| start_idx = max(0, start_idx - lead_sil) | |
| audio = audio[start_idx:] | |
| audio = audio.reverse() | |
| start_idx = detect_leading_silence(audio, silence_threshold=silence_threshold) | |
| start_idx = max(0, start_idx - trail_sil) | |
| audio = audio[start_idx:] | |
| audio = audio.reverse() | |
| return audio | |
| def fade_and_pad_audio( | |
| audio: np.ndarray, | |
| pad_duration: float = 0.1, | |
| fade_duration: float = 0.1, | |
| sample_rate: int = 24000, | |
| ) -> np.ndarray: | |
| """Apply fade-in/out and pad with silence to prevent clicks. | |
| Args: | |
| audio: numpy array of shape (C, T). | |
| pad_duration: silence padding duration per side (seconds). | |
| fade_duration: fade curve duration (seconds). | |
| sample_rate: audio sampling rate. | |
| Returns: | |
| Processed numpy array of shape (C, T_new). | |
| """ | |
| if audio.shape[-1] == 0: | |
| return audio | |
| fade_samples = int(fade_duration * sample_rate) | |
| pad_samples = int(pad_duration * sample_rate) | |
| processed = audio.copy() | |
| if fade_samples > 0: | |
| k = min(fade_samples, processed.shape[-1] // 2) | |
| if k > 0: | |
| fade_in = np.linspace(0, 1, k, dtype=np.float32)[np.newaxis, :] | |
| processed[..., :k] *= fade_in | |
| fade_out = np.linspace(1, 0, k, dtype=np.float32)[np.newaxis, :] | |
| processed[..., -k:] *= fade_out | |
| if pad_samples > 0: | |
| silence = np.zeros( | |
| (processed.shape[0], pad_samples), | |
| dtype=processed.dtype, | |
| ) | |
| processed = np.concatenate([silence, processed, silence], axis=-1) | |
| return processed | |
| def trim_long_audio( | |
| audio: np.ndarray, | |
| sampling_rate: int, | |
| max_duration: float = 15.0, | |
| min_duration: float = 3.0, | |
| trim_threshold: float = 20.0, | |
| ) -> np.ndarray: | |
| """Trim audio to <= *max_duration* by splitting at the largest silence gap. | |
| Only trims when the audio exceeds *trim_threshold* seconds. | |
| Args: | |
| audio: numpy array of shape (C, T). | |
| sampling_rate: audio sampling rate. | |
| max_duration: maximum duration in seconds. | |
| min_duration: minimum duration in seconds. | |
| trim_threshold: only trim if audio is longer than this (seconds). | |
| Returns: | |
| Trimmed numpy array. | |
| """ | |
| duration = audio.shape[-1] / sampling_rate | |
| if duration <= trim_threshold: | |
| return audio | |
| seg = numpy_to_audiosegment(audio, sampling_rate) | |
| nonsilent = detect_nonsilent( | |
| seg, min_silence_len=100, silence_thresh=-40, seek_step=10 | |
| ) | |
| if not nonsilent: | |
| return audio | |
| max_ms = int(max_duration * 1000) | |
| min_ms = int(min_duration * 1000) | |
| best_split = 0 | |
| for start, end in nonsilent: | |
| if start > best_split and start <= max_ms: | |
| best_split = start | |
| if end > max_ms: | |
| break | |
| if best_split < min_ms: | |
| best_split = min(max_ms, len(seg)) | |
| trimmed = seg[:best_split] | |
| return audiosegment_to_numpy(trimmed) | |
| def cross_fade_chunks( | |
| chunks: list[np.ndarray], | |
| sample_rate: int, | |
| silence_duration: float = 0.3, | |
| ) -> np.ndarray: | |
| """Concatenate audio chunks with silence gaps and cross-fade at boundaries. | |
| Args: | |
| chunks: list of numpy arrays, each (C, T). | |
| sample_rate: audio sample rate. | |
| silence_duration: total silence gap duration in seconds. | |
| Returns: | |
| Merged numpy array (C, T_total). | |
| """ | |
| if len(chunks) == 1: | |
| return chunks[0] | |
| total_n = int(silence_duration * sample_rate) | |
| fade_n = total_n // 3 | |
| silence_n = fade_n | |
| merged = chunks[0].copy() | |
| for chunk in chunks[1:]: | |
| parts = [merged] | |
| fout_n = min(fade_n, merged.shape[-1]) | |
| if fout_n > 0: | |
| w_out = np.linspace(1, 0, fout_n, dtype=np.float32)[np.newaxis, :] | |
| parts[-1][..., -fout_n:] *= w_out | |
| parts.append(np.zeros((chunks[0].shape[0], silence_n), dtype=np.float32)) | |
| fade_in = chunk.copy() | |
| fin_n = min(fade_n, fade_in.shape[-1]) | |
| if fin_n > 0: | |
| w_in = np.linspace(0, 1, fin_n, dtype=np.float32)[np.newaxis, :] | |
| fade_in[..., :fin_n] *= w_in | |
| parts.append(fade_in) | |
| merged = np.concatenate(parts, axis=-1) | |
| return merged | |