unbundle
A clean, ergonomic Rust library for extracting video frames, audio tracks, and subtitles from media files using FFmpeg.
New: unbundle-cli MVP
Install:
Commands:
# Probe metadata
# Extract frames
# Extract full audio
# Extract subtitle file
# Validate media structure
Quick Comparison
| Tool | Best for | Level | Notes |
|---|---|---|---|
unbundle |
Rust-native media extraction APIs | High-level | Strong typing, iterators, options, errors |
unbundle-cli |
Simple extraction workflows | High-level | Friendly commands for common tasks |
ffmpeg-next |
Building custom low-level media flows | Mid/Low-level | Flexible, but more boilerplate |
ffmpeg CLI |
One-off shell pipelines | CLI-level | Powerful, but less ergonomic in Rust code |
CI is passing on Linux, macOS, and Windows (see the CI badge above). Contributing guide: CONTRIBUTING.md.
Project metadata
- Homepage: https://docs.rs/unbundle
- Repository: https://github.com/skanderjeddi/unbundle
- Changelog: https://github.com/skanderjeddi/unbundle/releases
- Topics: rust, ffmpeg, video, audio, subtitles, media-processing
use MediaFile;
let mut unbundler = open?;
// Extract a frame at 30 seconds
let frame = unbundler.video.frame_at?;
frame.save?;
// Extract complete audio track
unbundler.audio.save?;
Why unbundle?
- Type-safe API — frames as
image::DynamicImage, audio as bytes or files, subtitles as structured events - Flexible extraction — by frame number, timestamp, range, interval, or custom frame lists
- Streaming support — lazy iterators and async streams avoid buffering entire frame sets
- Rich metadata — dimensions, frame rates, codecs, chapters, per-frame decode info
- Production-ready — progress callbacks, cancellation tokens, hardware acceleration, parallel processing
Use Cases
- Video thumbnails — contact sheets, smart frame selection, chapter previews
- Media processing — format conversion, audio extraction, subtitle manipulation
- Analysis tools — scene detection, keyframe analysis, Group of Pictures structure inspection
- Content indexing — frame extraction for search, waveform visualization
- Transcoding pipelines — lossless remuxing, audio re-encoding
Contact-sheet mini-guide
use ;
let mut unbundler = open?;
let options = new.with_thumbnail_width;
let sheet = grid?;
sheet.save?;
This creates a 4×3 contact sheet from evenly spaced frames across the video.
Installation
Add to your Cargo.toml:
[]
= "5.1.0"
Or with additional features:
[]
= { = "5.1.0", = ["async", "rayon", "hardware"] }
System Requirements
unbundle requires FFmpeg libraries (4.0+) installed on your system.
Linux (Debian/Ubuntu):
macOS:
Windows:
Use vcpkg (recommended for headers + libs):
# Install vcpkg if needed
git clone https://github.com/microsoft/vcpkg.git C:\vcpkg
C:\vcpkg\bootstrap-vcpkg.bat
# Install FFmpeg for MSVC x64
vcpkg install ffmpeg:x64-windows
# Configure environment for build scripts
setx VCPKG_ROOT "C:\vcpkg"
setx VCPKGRS_DYNAMIC "1"
setx FFMPEG_DIR "C:\vcpkg\installed\x64-windows"
setx PATH "%PATH%;C:\vcpkg\installed\x64-windows\bin"
Then restart your terminal and run cargo build.
For the current shell only (without setx):
$env:VCPKG_ROOT = "C:\vcpkg"
$env:VCPKGRS_DYNAMIC = "1"
$env:FFMPEG_DIR = "C:\vcpkg\installed\x64-windows"
$env:PATH = "C:\vcpkg\installed\x64-windows\bin;" + $env:PATH
The crate includes a Windows build helper (build.rs) that emits guidance when
FFMPEG_DIR is missing and a vcpkg install is detected.
Quick Start
Extract Video Frames
use Duration;
use MediaFile;
let mut unbundler = open?;
// Extract the first frame
let frame = unbundler.video.frame?;
frame.save?;
// Extract a frame at 30 seconds
let frame = unbundler.video.frame_at?;
frame.save?;
Extract Multiple Frames
use Duration;
use ;
let mut unbundler = open?;
// Every 30th frame
let frames = unbundler.video.frames?;
// Frames between two timestamps
let frames = unbundler.video.frames?;
// Specific frame numbers
let frames = unbundler.video.frames?;
Open from URLs or Streams
use MediaFile;
// Works with paths, file:// URLs, and network URLs supported by your FFmpeg build.
let mut unbundler = open_url?;
let metadata = unbundler.metadata;
println!;
open_url() accepts any FFmpeg input string, including http://, https://, rtsp://, and local path-like sources.
Apply FFmpeg Filters to Frames
use MediaFile;
let mut unbundler = open?;
// Chain multiple filters incrementally.
let frame = unbundler
.video
.filter
.filter
.frame?;
frame.save?;
Streaming Frame Iteration
use ;
let mut unbundler = open?;
// Push-based: process each frame without buffering
unbundler.video.for_each_frame?;
// Pull-based: lazy iterator with early exit
let iter = unbundler.video.frame_iter?;
for result in iter
Extract Audio
use Duration;
use ;
let mut unbundler = open?;
// Save complete audio track to WAV
unbundler.audio.save?;
// Extract a 30-second segment as MP3
unbundler.audio.save_range?;
// Extract audio to memory
let audio_bytes = unbundler.audio.extract?;
// Multi-track: extract the second audio track
let audio_bytes = unbundler.audio_track?.extract?;
Extract Subtitles
use ;
let mut unbundler = open?;
// Extract subtitle events with timing
let events = unbundler.subtitle.extract?;
for event in &events
// Save as SRT file
unbundler.subtitle.save?;
// Multi-track: extract the second subtitle track
unbundler.subtitle_track?.save?;
Raw Stream Copy (No Re-encode)
use Duration;
use MediaFile;
let mut unbundler = open?;
// Copy video packets exactly as-is (codec preserved)
unbundler.video.stream_copy?;
// Copy a time segment without decoding/re-encoding
unbundler.video.stream_copy_range?;
// In-memory stream copy
let mkv_bytes = unbundler.video.stream_copy_to_memory?;
// Audio stream copy (codec preserved)
unbundler.audio.stream_copy?;
let adts_bytes = unbundler.audio.stream_copy_to_memory?;
// Subtitle stream copy (codec preserved)
unbundler.subtitle.stream_copy?;
let subtitle_bytes = unbundler.subtitle.stream_copy_to_memory?;
Advanced Filter Graph Recipes
use MediaFile;
let mut unbundler = open?;
// Complex chain: resize -> crop -> mirror -> color adjust
let frame = unbundler.video.frame_with_filter?;
frame.save?;
// Another chain: downscale + grayscale + mirror
let frame = unbundler
.video
.frame_with_filter?;
frame.save?;
Progress & Cancellation
use Arc;
use ;
;
let token = new;
let config = new
.with_progress
.with_cancellation;
let mut unbundler = open?;
let frames = unbundler.video.frames_with_options?;
Features
Core Capabilities
- Frame extraction — by frame number, timestamp, range, interval, or specific frame list
- Audio extraction — to WAV, MP3, FLAC, or AAC (file or in-memory)
- Subtitle extraction — decode text-based subtitles to SRT, WebVTT, or raw text
- Container remuxing — lossless format conversion (e.g. MKV → MP4) without re-encoding
- Rich metadata — video dimensions, frame rate, frame count, audio sample rate, channels, codec info, multi-track audio/subtitle metadata
- Configurable output — pixel format (RGB8, RGBA8, GRAY8), target resolution with aspect ratio preservation
- Custom FFmpeg filters — apply filter graphs during frame extraction (e.g. scale, crop, eq, hflip)
- Progress & cancellation — cooperative progress callbacks and
CancellationTokenfor long-running operations - Streaming iteration — lazy
FrameIterator(pull-based) andfor_each_frame(push-based) without buffering entire frame sets - Audio sample iteration — lazy
AudioIteratoryields mono f32 chunks for incremental audio processing - Validation — inspect media files for structural issues before extraction
- Chapter support — extract chapter metadata (titles, timestamps) from containers
- Frame metadata — per-frame decode info (PTS, keyframe flag, picture type) via
frame_and_metadata/frames_and_metadata - Segmented extraction — extract frames from multiple disjoint time ranges in a single call with
FrameRange::Segments - Stream probing — lightweight
MediaProbefor quick metadata inspection without keeping the demuxer open - Thumbnail helpers — single-frame thumbnails, contact-sheet grids, and variance-based "smart" thumbnail selection
- Keyframe & Group of Pictures analysis — scan video packets for keyframe positions and Group of Pictures structure without decoding
- VFR detection — detect variable frame rate streams and analyze PTS distributions
- Packet iteration — raw packet-level demuxer iteration for advanced inspection
- Raw stream copy — copy video/audio/subtitle packets directly to file or memory without re-encoding
- Efficient seeking — seeks to the nearest keyframe, then decodes forward
- Zero-copy in-memory audio — uses FFmpeg's dynamic buffer I/O
Optional Features
Enable additional functionality through Cargo features:
| Feature | Description |
|---|---|
async |
FrameStream (async frame iteration) and AudioFuture via Tokio |
rayon |
frames_parallel() distributes decoding across rayon threads |
hardware |
Hardware-accelerated decoding (CUDA, VAAPI, DXVA2, D3D11VA, VideoToolbox, QSV) |
scene |
Scene change detection via FFmpeg's scdet filter |
gif |
Animated GIF export from video frames |
waveform |
Audio waveform visualization data (min/max/RMS per bin) |
loudness |
Peak/RMS loudness analysis with dBFS conversion |
transcode |
Audio re-encoding between formats (e.g. AAC → MP3) |
encode |
Encode DynamicImage sequences into video files (H.264, H.265, MPEG-4) |
full |
Enables all of the above |
[]
= { = "5.1.0", = ["full"] }
Feature Usage Guide
- Use
asyncwhen integrating with async web servers or when processing multiple videos concurrently - Use
rayonfor CPU-intensive batch frame extraction (e.g., generating thousands of thumbnails) - Use
hardwarewhen processing high-resolution video (4K+) or when CPU is a bottleneck - Use
scenefor video analysis, automatic chapter detection, or intelligent thumbnail selection - Use
giffor creating preview animations or social media content - Use
waveformandloudnessfor audio visualization or normalization workflows - Use
transcodefor audio format conversion in media pipelines - Use
encodefor creating time-lapses, slideshows, or re-encoding frame sequences
Examples
The examples/ directory contains complete, runnable examples:
| Example | Description |
|---|---|
extract_frames |
Extract frames by number, timestamp, range, interval |
extract_audio |
Extract the complete audio track |
extract_audio_segment |
Extract a specific time range as MP3 |
open_url |
Open from URL/path-like source strings |
thumbnail |
Create a thumbnail grid from evenly-spaced frames |
metadata |
Display all media metadata |
video_iterator |
Lazy frame iteration with early exit |
pixel_formats |
Demonstrate RGB8/RGBA8/GRAY8 output |
progress |
Progress callbacks and cancellation |
subtitle |
Extract subtitles as SRT/WebVTT/raw text |
remux |
Lossless container format conversion |
validate |
Media file validation report |
async_extraction |
Async frame streaming and audio extraction (async) |
rayon |
Parallel frame extraction across threads (rayon) |
scene |
Scene change detection (scene) |
hardware_acceleration |
Hardware-accelerated decoding (hardware) |
gif_export |
Export video frames as animated GIF (gif) |
waveform |
Generate audio waveform data (waveform) |
loudness |
Analyze audio loudness levels (loudness) |
audio_iterator |
Lazy audio sample iteration |
video_encoder |
Encode image sequences into video files (encode) |
transcode |
Re-encode audio between formats (transcode) |
keyframe |
Group of Pictures/keyframe structure analysis |
variable_framerate |
Variable frame rate detection |
packet_iterator |
Raw packet-level demuxer inspection |
subtitle_search |
Search subtitle text content |
Run an example:
Changelog
Release notes are tracked in CHANGELOG.md.
Advanced Usage
Container Remuxing
use Remuxer;
// Convert MKV to MP4 without re-encoding
new?.run?;
// Exclude subtitles during remux
new?
.exclude_subtitles
.run?;
Custom Output Format
use ;
let config = new
.with_pixel_format
.with_resolution;
let mut unbundler = open?;
let frames = unbundler.video.frames_with_options?;
Read Metadata
use MediaFile;
let unbundler = open?;
let metadata = unbundler.metadata;
println!;
println!;
if let Some = &metadata.video
if let Some = &metadata.audio
// List all audio and subtitle tracks
if let Some = &metadata.audio_tracks
if let Some = &metadata.subtitle_tracks
Validate Media Files
use MediaFile;
let unbundler = open?;
let report = unbundler.validate;
if report.is_valid else
Probe Media Files
use MediaProbe;
// Quick metadata inspection without keeping the file open
let metadata = probe?;
println!;
// Probe multiple files at once
let results = probe_many;
Chapter Metadata
use MediaFile;
let unbundler = open?;
let metadata = unbundler.metadata;
if let Some = &metadata.chapters
Frame Metadata
use MediaFile;
let mut unbundler = open?;
// Get a frame with its decode metadata
let = unbundler.video.frame_and_metadata?;
println!;
Thumbnail Generation
use Duration;
use ;
let mut unbundler = open?;
// Single thumbnail at a timestamp
let thumb = at_timestamp?;
// Contact-sheet grid
let config = new; // 4 columns × 3 rows
let grid = grid?;
grid.save?;
// Smart thumbnail (picks frame with highest visual variance)
let smart = smart?;
GIF Export
use Duration;
use ;
let mut unbundler = open?;
let config = new.width.frame_delay;
unbundler.video.export_gif?;
// Or export to memory
let bytes = unbundler.video.export_gif_to_memory?;
Audio Waveform
use ;
let mut unbundler = open?;
let waveform = unbundler.audio.generate_waveform?;
for bin in &waveform.bins
Loudness Analysis
use MediaFile;
let mut unbundler = open?;
let loudness = unbundler.audio.analyze_loudness?;
println!;
Audio Sample Iteration
use MediaFile;
let mut unbundler = open?;
let iter = unbundler.audio.sample_iter?;
let mut total_samples = 0u64;
for chunk in iter
println!;
Audio Transcoding
use ;
let mut unbundler = open?;
// Re-encode audio from the source format to MP3
new
.format
.run?;
Video Writing
use ;
let mut unbundler = open?;
let frames = unbundler.video.frames?;
let config = default
.resolution
.frames_per_second
.codec;
new.write?;
Keyframe & Group of Pictures Analysis
use MediaFile;
let mut unbundler = open?;
let group_of_pictures = unbundler.video.analyze_group_of_pictures?;
println!;
VFR Detection
use MediaFile;
let mut unbundler = open?;
let analysis = unbundler.video.analyze_variable_framerate?;
println!;
Packet Inspection
use MediaFile;
let mut unbundler = open?;
for packet in unbundler.packet_iter?
API Documentation
Complete API documentation is available at docs.rs/unbundle.
Essential Types
| Type | Description |
|---|---|
MediaFile |
Main entry point — opens a media file and provides access to media handles |
VideoHandle |
Extracts video frames as DynamicImage |
AudioHandle |
Extracts audio tracks as bytes or files |
SubtitleHandle |
Extracts text-based subtitle tracks |
FrameRange |
Specifies which frames to extract (range, interval, timestamps, etc.) |
ExtractOptions |
Configure threading, progress callbacks, cancellation, pixel format, resolution, hardware acceleration |
Stream & Iteration Types
| Type | Description |
|---|---|
FrameIterator |
Lazy, pull-based frame iterator |
AudioIterator |
Lazy pull-based audio sample iterator (mono f32) |
AudioChunk |
A chunk of decoded audio samples with timing |
PacketIterator |
Lazy raw-packet-level demuxer iterator |
FrameStream |
Async stream of decoded frames via Tokio (feature: async) |
AudioFuture |
Async audio extraction future (feature: async) |
Configuration Types
| Type | Description |
|---|---|
FrameOutputOptions |
Pixel format and resolution settings for frame output |
PixelFormat |
Output pixel format (RGB8, RGBA8, GRAY8) |
AudioFormat |
Output audio format (WAV, MP3, FLAC, AAC) |
SubtitleFormat |
Output subtitle format (SRT, WebVTT, Raw) |
ThumbnailOptions |
Grid thumbnail options (columns, rows, width) |
GifOptions |
Animated GIF export configuration (width, delay, repeat) (feature: gif) |
WaveformOptions |
Waveform generation settings (bin count, time range) (feature: waveform) |
SceneDetectionOptions |
Scene detection threshold configuration (feature: scene) |
VideoEncoderOptions |
Video encoder settings (FPS, resolution, codec, CRF) (feature: encode) |
HardwareAccelerationMode |
Hardware acceleration mode selection (feature: hardware) |
Metadata Types
| Type | Description |
|---|---|
MediaMetadata |
Container-level metadata (duration, format) |
VideoMetadata |
Video stream metadata (dimensions, frame rate, codec) |
AudioMetadata |
Audio stream metadata (sample rate, channels, codec) |
SubtitleMetadata |
Subtitle stream metadata (codec, language) |
ChapterMetadata |
Chapter information (title, start/end times) |
FrameMetadata |
Per-frame decode metadata (PTS, keyframe flag, picture type) |
FrameType |
Picture type enum (I, P, B, etc.) |
KeyFrameMetadata |
Keyframe position metadata (packet number, PTS, timestamp) |
GroupOfPicturesInfo |
Group of Pictures structure analysis result (keyframes, sizes, statistics) |
VariableFrameRateAnalysis |
Variable frame rate detection result (min/max/mean FPS) |
PacketInfo |
Per-packet metadata (stream index, PTS, DTS, size, keyframe) |
Utility Types
| Type | Description |
|---|---|
MediaProbe |
Lightweight stateless media file probing |
ThumbnailHandle |
Thumbnail generation helpers (single, grid, smart) |
Remuxer |
Lossless container format conversion |
ValidationReport |
Result of media file validation |
ProgressCallback |
Trait for receiving progress updates |
ProgressInfo |
Progress event data (current, total, percentage, ETA) |
CancellationToken |
Cooperative cancellation via Arc<AtomicBool> |
OperationType |
Identifies the operation being tracked |
UnbundleError |
Error type with rich context |
SubtitleEvent |
A single decoded subtitle event (text, start/end time) |
BitmapSubtitleEvent |
A bitmap subtitle event with image and timing |
Feature-Specific Types
| Type | Feature | Description |
|---|---|---|
Transcoder |
transcode |
Audio re-encoding builder (format, range, bitrate) |
VideoEncoder |
encode |
Encodes image sequences into video files |
VideoCodec |
encode |
Supported video codecs (H.264, H.265, MPEG-4) |
WaveformData |
waveform |
Generated waveform result with per-bin statistics |
WaveformBin |
waveform |
Single waveform bin (min, max, RMS amplitude) |
LoudnessInfo |
loudness |
Peak/RMS loudness with dBFS equivalents |
SceneChange |
scene |
Detected scene change with timestamp and score |
HardwareDeviceType |
hardware |
Supported hardware device types (CUDA, VAAPI, etc.) |
Performance
unbundle is designed for efficiency in both single-file and batch processing scenarios:
- Smart seeking — Uses FFmpeg's keyframe-based seeking. For sequential access (ranges, intervals), frames are decoded without redundant seeks.
- Lightweight decoders — Each extraction call creates a fresh decoder. FFmpeg decoder creation is fast relative to actual decoding work.
- Batch optimization —
FrameRange::Specificsorts requested frame numbers and processes them in order to minimize seeks. - Memory-efficient streaming —
for_each_frameandFrameIteratorprocess frames one at a time without buffering entire frame sets. - Parallel extraction —
frames_parallel()(featurerayon) splits frames across rayon threads, each with its own demuxer for true parallelism. - Hardware acceleration — When enabled (feature
hardware), attempts GPU-accelerated decoding with automatic fallback to software. - Correct stride handling — Properly handles FFmpeg's row padding when converting frames to
imagebuffers. - Zero-copy audio — Uses
avio_open_dyn_buffor in-memory audio encoding without temporary files.
Testing
Generate test fixtures first:
# Linux / macOS
# Windows
Then run tests:
Test Coverage
The test suite includes comprehensive coverage:
| Test Module | Coverage |
|---|---|
video |
Single frames, ranges, intervals, timestamps, specific lists, pixel formats, resolution scaling |
audio |
WAV/MP3/FLAC/AAC extraction, ranges, file output, multi-track |
subtitle |
Subtitle decoding, SRT/WebVTT export, multi-track |
metadata |
Container metadata, video/audio/subtitle stream properties |
configuration |
ExtractOptions builder, pixel formats, resolution, cancellation |
progress |
ProgressCallback, ProgressInfo fields, CancellationToken |
error_handling |
Error variants, context, invalid inputs, missing streams |
video_iterator |
FrameIterator, lazy iteration, early exit |
conversion |
Remuxer, stream exclusion, lossless format conversion |
validation |
ValidationReport, warnings, errors, valid files |
chapters |
Chapter metadata extraction, titles, timestamps, ordering |
frame_metadata |
FrameMetadata, FrameType, keyframe detection, PTS values |
segmented_extraction |
FrameRange::Segments, multiple disjoint time ranges |
probing |
MediaProbe, probe/probe_many, error handling |
thumbnail |
ThumbnailHandle, grid, smart selection, aspect ratio |
audio_iterator |
AudioIterator, chunk iteration, sample rates |
keyframe |
GroupOfPicturesInfo, KeyFrameMetadata, Group of Pictures statistics |
variable_framerate |
VariableFrameRateAnalysis, constant vs variable frame rate |
packet_iterator |
PacketIterator, PacketInfo, stream filtering |
subtitle_search |
Subtitle search, case-insensitive matching |
metadata_extended |
Extended metadata: video tracks, colorspace, HDR |
Feature-specific tests (require corresponding features enabled):
| Test Module | Feature Required | Coverage |
|---|---|---|
scene |
scene |
Scene change detection, threshold configuration |
async_extraction |
async |
FrameStream, AudioFuture, async streaming |
rayon |
rayon |
frames_parallel, sequential parity, interval mode |
hardware_acceleration |
hardware |
Hardware device enumeration, Auto/Software modes |
gif_export |
gif |
GIF encoding, file and in-memory output |
waveform |
waveform |
WaveformOptions, bin statistics, time ranges |
loudness |
loudness |
Peak/RMS loudness, dBFS values |
video_encoder |
encode |
VideoEncoder, codec selection, frame encoding |
transcode |
transcode |
Transcoder, format conversion, time ranges |
Benchmarks
Run performance benchmarks:
Criterion benchmarks are located in benches/ and measure:
- Frame extraction throughput (single vs parallel)
- Seek performance (sequential vs random access)
- Audio extraction speed across formats
- Iterator overhead vs batch extraction
Troubleshooting
FFmpeg Linking Errors
Problem: error: linking with 'cc' failed or cannot find -lavcodec
Solution:
- Ensure FFmpeg development libraries are installed (see Installation)
- Set
PKG_CONFIG_PATHto point to FFmpeg's.pcfiles - On Windows, set
FFMPEG_DIRenvironment variable - Verify with:
pkg-config --libs --cflags libavcodec
Codec Not Supported
Problem: UnbundleError::UnsupportedAudioFormat
Solution:
- Check that your FFmpeg build includes the required codec
- Run
ffmpeg -codecsto list available codecs - Some codecs require FFmpeg to be built with specific flags (e.g.,
--enable-libx264)
Hardware Acceleration Fails
Problem: Hardware decoding falls back to software or fails entirely
Solution:
- Verify GPU drivers are up to date
- Check available hardware devices:
unbundle::hardware_acceleration::available_hardware_devices() - Use
ExtractOptions::with_hardware_acceleration(HardwareAccelerationMode::Auto)for automatic fallback - Not all codecs/formats support hardware acceleration
- Try
ffmpeg -hwaccelsto list available hardware acceleration methods
Out of Memory
Problem: High memory usage when extracting many frames
Solution:
- Use streaming iteration instead of batch extraction:
frame_iter()orfor_each_frame() - Process frames in smaller batches
- Use
AudioIteratorfor large audio files instead of loading entire tracks
Slow Frame Extraction
Problem: Frame extraction is slower than expected
Solution:
- Use
frames_parallel()(featurerayon) for CPU-bound workloads - Enable hardware acceleration (feature
hardware) for high-resolution video - Avoid extracting specific frames in random order — sorted access is much faster
- Consider using
FrameRange::Intervalinstead of many individual frame numbers
Permission Denied / File Not Found
Problem: Cannot open media file
Solution:
- Verify file path is correct and file exists
- Check file permissions (readable by current user)
- Ensure file is not locked by another process
- On Windows, use raw string literals for paths:
r"C:\path\to\video.mp4"
Contributing
Contributions are welcome! Please see the GitHub repository for:
- Bug reports and feature requests
- Pull requests
- Discussions and questions
License
MIT — see LICENSE file for details.