A comprehensive TypeScript library for parsing and analyzing VBO (Vehicle Bus Observer) telemetry files from motorsport data acquisition systems.
- 🏎️ Complete VBO file parsing - Parse motorsport telemetry data from VBO files
- 🔄 Automatic lap detection - Smart lap detection using GPS data or existing lap markers
- 📊 Sector analysis - Automatically generate sector times and analysis
- 🔁 Session comparison - Synchronize and compare multiple racing sessions
- 🎯 Type-safe - Full TypeScript support with Zod validation
- 🌐 Browser & Node.js - Works in both browser and server environments
- 📱 File System Access API - Modern browser file picking support
- ⚡ Performance optimized - Efficient parsing for large telemetry files
- 🧪 Well tested - Comprehensive test suite
npm install '@vbo-parser/core@git+https://github.com/tobi/vbo-parser'
# or
yarn add '@vbo-parser/core@git+https://github.com/tobi/vbo-parser'
# or
bun add '@vbo-parser/core@git+https://github.com/tobi/vbo-parser'
import { VBOParser, detectLaps, findFastestLap } from '@vbo-parser/core';
// Parse a VBO file
const parser = new VBOParser();
const session = await parser.parseVBOFile(file);
// Detect laps automatically
const laps = detectLaps(session.dataPoints);
// Find the fastest lap
const fastestLap = findFastestLap(laps);
console.log(`Session: ${session.filePath}`);
console.log(`Total time: ${session.totalTime}s`);
console.log(`Laps: ${laps.length}`);
console.log(`Fastest lap: ${fastestLap?.lapTime}s`);
- 📖 Complete Documentation - Comprehensive documentation and advanced usage
- 🔧 API Reference - Detailed API documentation
- 📋 VBO Format Specification - Complete VBO file format documentation
- 🧪 Testing Guide - Testing and debugging information
Main parser class for VBO files.
const parser = new VBOParser({
calculateLaps: true, // Auto-detect laps
validateDataPoints: false, // Validate against schema (slower)
maxDataPoints: undefined, // Limit data points (for large files)
customColumnMappings: {} // Custom column name mappings
});
// Parse from different sources
const session = await parser.parseVBOFile(file); // File object
const session = await parser.parseVBOFile(content); // String content
const session = await parser.parseVBOFile(buffer); // Uint8Array
// Parse multiple files
const sessions = await parser.parseVBOFromInput(fileList);
interface VBOSession {
filePath: string;
videos: VBOVideoFile[]; // Associated video files
header: VBOHeader;
dataPoints: VBODataPoint[];
laps: VBOLap[];
totalTime: number;
trackLength?: number;
}
interface VBODataPoint {
satellites: number;
time: number;
latitude: number;
longitude: number;
velocity: number;
heading: number;
height: number;
engineSpeed: number;
steeringAngle: number;
brakePressureFront: number;
throttlePedal: number;
vehicleSpeed: number;
gear: number;
// ... and many more telemetry channels
}
interface VBOLap {
lapNumber: number;
startTime: number;
endTime: number;
lapTime: number;
sectors: VBOSector[];
dataPoints: VBODataPoint[];
isValid: boolean;
}
Synchronize and compare multiple racing sessions for detailed analysis.
import { SessionComparison } from '@vbo-parser/core';
// Create a comparison between multiple sessions
const comparison = new SessionComparison(
mainSession, // Primary session to control navigation
[session2, session3], // Sessions to compare against
{
progressTolerance: 0.01, // Progress matching tolerance (1% of lap)
allowDifferentTracks: false // Only compare same track sessions
}
);
// Navigate through sessions in sync
comparison.jumpToLap(2); // Jump to specific lap
comparison.advanceMain(50); // Move forward by data points
comparison.rewindMain(10); // Move backward
comparison.setMainProgress(0.5); // Jump to 50% through session
// Get synchronized data from all sessions
const syncedData = comparison.getSynchronizedDataPoints();
console.log('Main speed:', syncedData.main.velocity);
console.log('Comparator speeds:', syncedData.comparators.map(d => d?.velocity));
// Get comparison summary with time deltas
const summary = comparison.getSummary();
summary.comparators.forEach(comp => {
console.log(`Delta: ${comp.delta}s, Speed: ${comp.speed} km/h`);
});
// Find closest point in another session
const closest = comparison.findClosestToSession(targetSession, sourceSession, dataPointIndex);
import { LapDetection } from '@vbo-parser/core';
// Detect laps with custom options
const laps = LapDetection.detectLaps(dataPoints, {
minLapTime: 30, // Minimum lap time (seconds)
maxLapTime: 600, // Maximum lap time (seconds)
minDistance: 1000, // Minimum distance (meters)
speedThreshold: 20, // Speed threshold for line crossing
sectorCount: 3 // Number of sectors per lap
});
// Find fastest lap
const fastest = LapDetection.findFastestLap(laps);
// Calculate average lap time
const avgTime = LapDetection.calculateAverageLapTime(laps);
// Find best sector times
const bestSectors = LapDetection.findBestSectorTimes(laps);
// Open file picker (modern browsers only)
const fileHandles = await VBOParser.openFilePicker();
const sessions = await parser.parseVBOFromInput(fileHandles);
// List files from directory (if supported)
const directoryHandle = await window.showDirectoryPicker();
const vboFiles = await VBOParser.listVBOFiles(directoryHandle);
// Get video file and timestamp for a data point
const videoInfo = VBOParser.getVideoForDataPoint(session, dataPoint);
// Returns: { file: '/videos/filename_0001.mp4', timestamp: 15.25 } or null
// Calculate GPS distance
const distance = VBOParser.calculateDistance(lat1, lng1, lat2, lng2);
// List known VBO files
const files = await VBOParser.listVBOFiles('/public/videos');
const parser = new VBOParser({
customColumnMappings: {
'custom_speed': 'velocity',
'brake_front': 'brakePressureFront',
'engine_rpm': 'engineSpeed'
}
});
import { VBOParseError, VBOValidationError } from '@vbo-parser/core';
try {
const session = await parser.parseVBOFile(file);
} catch (error) {
if (error instanceof VBOValidationError) {
console.error('Validation failed:', error.validationErrors);
} else if (error instanceof VBOParseError) {
console.error('Parse error:', error.message, error.cause);
}
}
// For large files, limit data points
const parser = new VBOParser({
maxDataPoints: 10000,
validateDataPoints: false // Skip validation for better performance
});
// Process data in chunks
const session = await parser.parseVBOFile(file);
const chunkSize = 1000;
for (let i = 0; i < session.dataPoints.length; i += chunkSize) {
const chunk = session.dataPoints.slice(i, i + chunkSize);
// Process chunk...
}
- ✅ Chrome 86+ (File System Access API)
- ✅ Firefox 90+ (File API)
- ✅ Safari 14+ (File API)
- ✅ Edge 86+ (File System Access API)
- ✅ Node.js 16+
- ✅ Works with fs.readFile
- ✅ Stream processing support
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature
) - Commit your changes (
git commit -m 'Add some amazing feature'
) - Push to the branch (
git push origin feature/amazing-feature
) - Open a Pull Request
MIT License - see LICENSE file for details.
See CHANGELOG.md for version history.
- 📖 Documentation
- 🐛 Issues
- 💬 Discussions
- 📧 Email: [email protected]