Task 785: .VPM File Format
Task 785: .VPM File Format
1. List of Properties of the .VPM File Format Intrinsic to Its File System
The .VPM file format, primarily used by Garmin for voice data in GPS navigation devices, is a binary container for audio prompts. The properties intrinsic to the file format, based on its structure and specifications, include the following:
- File Signature (Magic Number): Bytes 0-5 contain the ASCII string 'AUDIMG', identifying the file as a non-TTS Garmin voice file.
- Format Version: Bytes 6-8 contain the ASCII string '230', indicating the version of the voice file format.
- Timestamp: Bytes 9-15 store the release timestamp in binary format:
- Byte 9: Seconds (0-59).
- Byte 10: Minutes (0-59).
- Byte 11: Hours (0-23).
- Byte 12: Day (1-31).
- Byte 13: Month (1-12).
- Byte 14: Year (e.g., low byte of the year such as 6 for 2006).
- Byte 15: Redundant or confirmatory year byte.
- Pointer to First Audio Sample (Header Size): Bytes 16-17 (2-byte unsigned integer, likely little-endian) indicate the offset to the start of the audio data, effectively defining the header length.
- Language ID: Byte 18 contains a single-byte identifier for the language (e.g., used to display the voice in the device menu).
- Phrase Structures: Starting from byte 19 until the header end (as defined by bytes 16-17), a repeating sequence of 4-byte entries for each phrase:
- Bytes n to n+1: Number of voice samples in the phrase (2-byte unsigned integer).
- Bytes n+2 to n+3: Pointer (index) to the first voice sample in the phrase (2-byte unsigned integer).
This continues for all phrases, with pointers serving as indices to embedded audio samples rather than byte offsets. - Audio Samples: Embedded WAV audio data starting at the offset specified in bytes 16-17. Each sample is a full WAV file (RIFF format) with specifications including:
- Channels: 1 (mono).
- Sample Rate: 22050 Hz.
- Bit Depth: 16-bit PCM or 4-bit IMA ADPCM compression.
- Checksum: The last byte of the file is adjusted such that the sum of all bytes in the file equals 0 modulo 256 (optional in some implementations but ensures file integrity).
- File Size Range: Typically 700 KB to 1.4 MB for non-TTS voices.
- Encryption Indicator: For encrypted voices (e.g., proprietary ones like DrNightmare.vpm), the header is larger (e.g., 288 bytes), and audio sections are encrypted, differing from the standard structure.
- Overall File Type: Binary, proprietary container format, often accompanied by a .SUM checksum file in Garmin systems.
These properties apply primarily to non-TTS (text-to-speech) voices; TTS voices may have variations but share the .VPM extension.
2. Two Direct Download Links for .VPM Files
- https://raw.githubusercontent.com/yuryleb/garmin-russian-tts-voices/master/dist/Pycckuu__Tatiana 2.30/Pycckuu__Tatiana.vpm
- https://raw.githubusercontent.com/yuryleb/garmin-russian-tts-voices/master/dist/Pycckuu__Katerina 1.30/Pycckuu__Katerina.vpm
3. Ghost Blog Embedded HTML JavaScript for Drag-and-Drop .VPM File Dump
The following is a complete HTML page with embedded JavaScript that can be embedded in a Ghost blog (or any HTML-supporting platform). It allows users to drag and drop a .VPM file, parses it, and displays all the properties listed in section 1 on the screen.
4. Python Class for .VPM File Handling
The following Python class can open, decode, read, write, and print all properties from a .VPM file to the console.
import struct
import sys
class VPMFile:
def __init__(self, filename):
self.filename = filename
self.signature = None
self.version = None
self.timestamp = None
self.first_audio = None
self.lang_id = None
self.phrases = []
self.audio_data = None
self.checksum = None
self.data = None
def read(self):
with open(self.filename, 'rb') as f:
self.data = f.read()
# Decode header
self.signature = self.data[0:6].decode('ascii')
self.version = self.data[6:9].decode('ascii')
sec, min_, hr, day, mon, year1, year2 = struct.unpack('<BBBBBBB', self.data[9:16])
self.timestamp = (hr, min_, sec, day, mon, year1, year2)
self.first_audio = struct.unpack('<H', self.data[16:18])[0]
self.lang_id = self.data[18]
# Phrases
offset = 19
while offset + 3 < self.first_audio:
num_samples = struct.unpack('<H', self.data[offset:offset+2])[0]
sample_ptr = struct.unpack('<H', self.data[offset+2:offset+4])[0]
self.phrases.append((num_samples, sample_ptr))
offset += 4
# Audio data
self.audio_data = self.data[self.first_audio:-1]
# Checksum
self.checksum = self.data[-1]
def print_properties(self):
print(f"Signature: {self.signature}")
print(f"Version: {self.version}")
print(f"Timestamp: {self.timestamp[0]}:{self.timestamp[1]}:{self.timestamp[2]} {self.timestamp[3]}/{self.timestamp[4]}/{2000 + self.timestamp[5]} (redundant: {self.timestamp[6]})")
print(f"First Audio Pointer: {self.first_audio}")
print(f"Language ID: {self.lang_id}")
print("Phrases:")
for i, (num, ptr) in enumerate(self.phrases, 1):
print(f" Phrase {i}: Num Samples = {num}, First Sample Index = {ptr}")
print(f"Audio Data Length: {len(self.audio_data)} bytes")
sum_check = sum(self.data) % 256
print(f"Checksum Valid: {sum_check == 0}")
print(f"File Size: {len(self.data)} bytes")
def write(self, new_filename):
if not self.data:
raise ValueError("No data to write.")
with open(new_filename, 'wb') as f:
f.write(self.data)
# Example usage
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python vpm_handler.py <filename.vpm>")
sys.exit(1)
vpm = VPMFile(sys.argv[1])
vpm.read()
vpm.print_properties()
# To write: vpm.write('output.vpm')
5. Java Class for .VPM File Handling
The following Java class can open, decode, read, write, and print all properties from a .VPM file to the console.
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.List;
public class VPMFile {
private String filename;
private String signature;
private String version;
private int[] timestamp; // hr, min, sec, day, mon, year1, year2
private int firstAudio;
private int langId;
private List<int[]> phrases; // Each: [numSamples, samplePtr]
private byte[] audioData;
private byte checksum;
private byte[] data;
public VPMFile(String filename) {
this.filename = filename;
this.phrases = new ArrayList<>();
}
public void read() throws IOException {
try (FileInputStream fis = new FileInputStream(filename);
BufferedInputStream bis = new BufferedInputStream(fis)) {
data = bis.readAllBytes();
}
ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN);
// Signature
byte[] sigBytes = new byte[6];
bb.get(sigBytes);
signature = new String(sigBytes);
// Version
byte[] verBytes = new byte[3];
bb.get(verBytes);
version = new String(verBytes);
// Timestamp
timestamp = new int[7];
for (int i = 0; i < 7; i++) {
timestamp[i] = bb.get() & 0xFF;
}
// First audio
firstAudio = bb.getShort() & 0xFFFF;
// Lang ID
langId = bb.get() & 0xFF;
// Phrases
int offset = 19;
while (offset + 3 < firstAudio) {
int numSamples = bb.getShort(offset) & 0xFFFF;
int samplePtr = bb.getShort(offset + 2) & 0xFFFF;
phrases.add(new int[]{numSamples, samplePtr});
offset += 4;
}
// Audio data
audioData = new byte[data.length - firstAudio - 1];
System.arraycopy(data, firstAudio, audioData, 0, audioData.length);
// Checksum
checksum = data[data.length - 1];
}
public void printProperties() {
System.out.println("Signature: " + signature);
System.out.println("Version: " + version);
System.out.printf("Timestamp: %d:%d:%d %d/%d/%d (redundant: %d)\n",
timestamp[2], timestamp[1], timestamp[0], timestamp[3], timestamp[4], 2000 + timestamp[5], timestamp[6]);
System.out.println("First Audio Pointer: " + firstAudio);
System.out.println("Language ID: " + langId);
System.out.println("Phrases:");
for (int i = 0; i < phrases.size(); i++) {
int[] p = phrases.get(i);
System.out.printf(" Phrase %d: Num Samples = %d, First Sample Index = %d\n", i + 1, p[0], p[1]);
}
System.out.println("Audio Data Length: " + audioData.length + " bytes");
int sum = 0;
for (byte b : data) sum = (sum + (b & 0xFF)) % 256;
System.out.println("Checksum Valid: " + (sum == 0));
System.out.println("File Size: " + data.length + " bytes");
}
public void write(String newFilename) throws IOException {
if (data == null) throw new IllegalStateException("No data to write.");
try (FileOutputStream fos = new FileOutputStream(newFilename)) {
fos.write(data);
}
}
public static void main(String[] args) throws IOException {
if (args.length < 1) {
System.out.println("Usage: java VPMFile <filename.vpm>");
System.exit(1);
}
VPMFile vpm = new VPMFile(args[0]);
vpm.read();
vpm.printProperties();
// To write: vpm.write("output.vpm");
}
}
6. JavaScript Class for .VPM File Handling
The following JavaScript class can open (via FileReader), decode, read, write (via Blob download), and print all properties from a .VPM file to the console. It is designed for browser use.
class VPMFile {
constructor(file) {
this.file = file;
this.signature = null;
this.version = null;
this.timestamp = null;
this.firstAudio = null;
this.langId = null;
this.phrases = [];
this.audioData = null;
this.checksum = null;
this.data = null;
}
async read() {
this.data = await this.file.arrayBuffer();
const dv = new DataView(this.data);
// Signature
this.signature = String.fromCharCode(...Array.from({length: 6}, (_, i) => dv.getUint8(i)));
// Version
this.version = String.fromCharCode(...Array.from({length: 3}, (_, i) => dv.getUint8(6 + i)));
// Timestamp
this.timestamp = Array.from({length: 7}, (_, i) => dv.getUint8(9 + i));
// First audio
this.firstAudio = dv.getUint16(16, true);
// Lang ID
this.langId = dv.getUint8(18);
// Phrases
let offset = 19;
while (offset + 3 < this.firstAudio) {
const numSamples = dv.getUint16(offset, true);
const samplePtr = dv.getUint16(offset + 2, true);
this.phrases.push([numSamples, samplePtr]);
offset += 4;
}
// Audio data
this.audioData = this.data.slice(this.firstAudio, -1);
// Checksum
this.checksum = dv.getUint8(this.data.byteLength - 1);
}
printProperties() {
console.log(`Signature: ${this.signature}`);
console.log(`Version: ${this.version}`);
console.log(`Timestamp: ${this.timestamp[2]}:${this.timestamp[1]}:${this.timestamp[0]} ${this.timestamp[3]}/${this.timestamp[4]}/${2000 + this.timestamp[5]} (redundant: ${this.timestamp[6]})`);
console.log(`First Audio Pointer: ${this.firstAudio}`);
console.log(`Language ID: ${this.langId}`);
console.log('Phrases:');
this.phrases.forEach((p, i) => {
console.log(` Phrase ${i + 1}: Num Samples = ${p[0]}, First Sample Index = ${p[1]}`);
});
console.log(`Audio Data Length: ${this.audioData.byteLength} bytes`);
let sum = 0;
for (let i = 0; i < this.data.byteLength; i++) {
sum = (sum + dv.getUint8(i)) % 256;
}
console.log(`Checksum Valid: ${sum === 0}`);
console.log(`File Size: ${this.data.byteLength} bytes`);
}
write() {
if (!this.data) throw new Error('No data to write.');
const blob = new Blob([this.data], {type: 'application/octet-stream'});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'output.vpm';
a.click();
URL.revokeObjectURL(url);
}
}
// Example usage (in browser console or script)
// const input = document.getElementById('fileInput'); // Assume <input type="file" id="fileInput">
// input.addEventListener('change', async (e) => {
// const file = e.target.files[0];
// if (file.name.endsWith('.vpm')) {
// const vpm = new VPMFile(file);
// await vpm.read();
// vpm.printProperties();
// // To write: vpm.write();
// }
// });
7. C++ Class for .VPM File Handling
The following C++ class (using standard libraries) can open, decode, read, write, and print all properties from a .VPM file to the console.
#include <iostream>
#include <fstream>
#include <vector>
#include <cstdint>
#include <cstring>
class VPMFile {
private:
std::string filename;
std::string signature;
std::string version;
uint8_t timestamp[7]; // sec, min, hr, day, mon, year1, year2
uint16_t firstAudio;
uint8_t langId;
std::vector<std::pair<uint16_t, uint16_t>> phrases; // numSamples, samplePtr
std::vector<uint8_t> audioData;
uint8_t checksum;
std::vector<uint8_t> data;
public:
VPMFile(const std::string& fn) : filename(fn) {}
bool read() {
std::ifstream file(filename, std::ios::binary | std::ios::ate);
if (!file) return false;
size_t size = file.tellg();
file.seekg(0);
data.resize(size);
file.read(reinterpret_cast<char*>(data.data()), size);
file.close();
// Signature
signature.assign(data.begin(), data.begin() + 6);
// Version
version.assign(data.begin() + 6, data.begin() + 9);
// Timestamp
memcpy(timestamp, data.data() + 9, 7);
// First audio (little-endian)
firstAudio = data[16] | (data[17] << 8);
// Lang ID
langId = data[18];
// Phrases
size_t offset = 19;
while (offset + 3 < firstAudio) {
uint16_t numSamples = data[offset] | (data[offset + 1] << 8);
uint16_t samplePtr = data[offset + 2] | (data[offset + 3] << 8);
phrases.emplace_back(numSamples, samplePtr);
offset += 4;
}
// Audio data
audioData.assign(data.begin() + firstAudio, data.end() - 1);
// Checksum
checksum = data.back();
return true;
}
void printProperties() const {
std::cout << "Signature: " << signature << std::endl;
std::cout << "Version: " << version << std::endl;
std::cout << "Timestamp: " << static_cast<int>(timestamp[2]) << ":"
<< static_cast<int>(timestamp[1]) << ":" << static_cast<int>(timestamp[0]) << " "
<< static_cast<int>(timestamp[3]) << "/" << static_cast<int>(timestamp[4]) << "/"
<< (2000 + static_cast<int>(timestamp[5])) << " (redundant: "
<< static_cast<int>(timestamp[6]) << ")" << std::endl;
std::cout << "First Audio Pointer: " << firstAudio << std::endl;
std::cout << "Language ID: " << static_cast<int>(langId) << std::endl;
std::cout << "Phrases:" << std::endl;
for (size_t i = 0; i < phrases.size(); ++i) {
auto [num, ptr] = phrases[i];
std::cout << " Phrase " << (i + 1) << ": Num Samples = " << num
<< ", First Sample Index = " << ptr << std::endl;
}
std::cout << "Audio Data Length: " << audioData.size() << " bytes" << std::endl;
uint8_t sum = 0;
for (auto b : data) sum = (sum + b) % 256;
std::cout << "Checksum Valid: " << (sum == 0) << std::endl;
std::cout << "File Size: " << data.size() << " bytes" << std::endl;
}
bool write(const std::string& newFilename) const {
if (data.empty()) return false;
std::ofstream file(newFilename, std::ios::binary);
if (!file) return false;
file.write(reinterpret_cast<const char*>(data.data()), data.size());
file.close();
return true;
}
};
int main(int argc, char* argv[]) {
if (argc < 2) {
std::cerr << "Usage: " << argv[0] << " <filename.vpm>" << std::endl;
return 1;
}
VPMFile vpm(argv[1]);
if (!vpm.read()) {
std::cerr << "Failed to read file." << std::endl;
return 1;
}
vpm.printProperties();
// To write: vpm.write("output.vpm");
return 0;
}