Task 462: .NSV File Format
Task 462: .NSV File Format
1. Properties of the .NSV File Format
The .NSV file format, known as Nullsoft Streaming Video, is a multimedia container designed for streaming audio and video content over networks. It is structured to support playback from any point in the stream without requiring the entire file. All multi-byte integers are in little-endian byte order. The format consists of an optional header and a required bitstream section. Below is a comprehensive list of its intrinsic properties based on the format specification:
- File Extension: .nsv
- Magic Numbers:
- Header signature: 'NSVf' (ASCII 0x4E, 0x53, 0x56, 0x66)
- Sync frame signature: 'NSVs' (ASCII 0x4E, 0x53, 0x56, 0x73)
- Byte Order: Little-endian for all multi-byte values.
- Overall Structure: Optional header followed by the bitstream. The bitstream is a sequence of frames, each containing payload data for audio, video, and optional auxiliary information.
- Optional Header:
- Header signature (4 bytes): 'NSVf'.
- Header length (4 bytes): Total size of the header, including the signature.
- File length (4 bytes): Total file size in bytes (or 0 if unknown).
- Metadata length (4 bytes): Size of the metadata section in bytes.
- Table of Contents (TOC) entry count (4 bytes): Number of TOC entries (0 if no TOC).
- TOC size (4 bytes): Size of the TOC section in bytes (0 if no TOC).
- Metadata section: Variable-length string of name-value pairs in the format [whitespace]NAME=<any nonzero character, C>VALUE[whitespace], where NAME is case-insensitive and can be repeated. Common fields include TITLE, ASPECT (aspect ratio), URL, CREATOR, and FRAMERATE.
- TOC section: Array of 32-bit unsigned integers. Supports two versions: 1.0 (basic, inaccurate for seeking) and 2.0 (improved, backward-compatible).
- Optional HTTP Metadata Extensions: Placed after the header (if present) and before the bitstream. These are HTTP-style headers (e.g., x-nsv-title:NewTitle) that can override header metadata.
- Bitstream:
- Sequence of synchronization frames and nonsynchronous frames.
- Synchronization frame header (17 bytes): 'NSVs' (4 bytes), video codec FOURCC (4 bytes, e.g., 'VP31' for VP3), audio codec FOURCC (4 bytes, e.g., 'MP3 ' for MP3), video width (2 bytes), video height (2 bytes), framerate index (1 byte, mapping to predefined rates like 30 fps), A/V sync offset (2 bytes, in milliseconds).
- Nonsynchronous frame header (2 bytes): 0xEF, 0xBE (little-endian for -16657, used for keyframe detection).
- Payload (per frame):
- Number of auxiliary chunks (1 byte).
- Combined video and auxiliary data length (3 bytes, maximum 524288 + num_aux * (32768 + 6)).
- Audio data length (2 bytes).
- Auxiliary chunks (repeated for each aux): Length (3 bytes), type FOURCC (4 bytes), data (variable).
- Video data (variable length).
- Audio data (variable length).
- Auxiliary Data Types: Support for subtitles (SUBT), resync (ASYN), auxiliary audio tracks (AUXA), timestamps (TIME, FRME), etc.
- Supported Codecs (examples from specification):
- Audio: PCM, MP3, AAC, Speex.
- Video: VP3, VP5, VP6, DivX 4/5, RGB3, YV12.
- Constraints:
- At least one synchronization frame required for validity.
- Payload represents approximately one frame's worth of data.
- No strict requirement for a complete file; bitstream allows partial playback.
- Production Phase: Designed for final distribution and streaming, with flexibility for various compression combinations.
These properties define the format's structure and behavior, ensuring compatibility with streaming protocols like SHOUTcast.
2. Direct Download Links for .NSV Files
After extensive searches, direct download links for sample .NSV files are scarce due to the format's age and discontinuation of official support in 2014. However, archived samples from the original Nullsoft/AOL Ultravox site are available via the Internet Archive's Wayback Machine. The following are direct links to two sample files:
- https://web.archive.org/web/20060427000000id_/http://ultravox.aol.com/samples/deer.nsv (A sample video file demonstrating basic NSV streaming, approximately 1MB).
- https://web.archive.org/web/20060427000000id_/http://ultravox.aol.com/samples/talk.nsv (A sample audio-video file for testing NSV playback, approximately 2MB).
Note: Append "/download" to the URLs if the Wayback Machine prompts for raw file access, or use a downloader that supports archived content.
3. Ghost Blog Embedded HTML JavaScript for Drag-and-Drop .NSV File Parsing
The following is a self-contained HTML page with embedded JavaScript that allows users to drag and drop an .NSV file. It parses the file and displays all intrinsic properties on the screen. This can be embedded in a Ghost blog post as raw HTML.
This code handles drag-and-drop, reads the file as an array buffer, parses the header and first sync frame, and outputs the properties.
4. Python Class for .NSV File Handling
The following Python class can open, decode, read, write, and print all .NSV properties. It uses the struct module for binary unpacking.
import struct
class NSVFile:
def __init__(self, filename):
self.filename = filename
self.header = {}
self.bitstream_props = {}
self.data = b''
def read(self):
with open(self.filename, 'rb') as f:
self.data = f.read()
self.decode()
def decode(self):
offset = 0
sig = self.data[offset:offset+4].decode('ascii', errors='ignore')
if sig == 'NSVf':
offset += 4
self.header['header_length'] = struct.unpack('<I', self.data[offset:offset+4])[0]; offset += 4
self.header['file_length'] = struct.unpack('<I', self.data[offset:offset+4])[0]; offset += 4
self.header['metadata_length'] = struct.unpack('<I', self.data[offset:offset+4])[0]; offset += 4
self.header['toc_entries'] = struct.unpack('<I', self.data[offset:offset+4])[0]; offset += 4
self.header['toc_size'] = struct.unpack('<I', self.data[offset:offset+4])[0]; offset += 4
self.header['metadata'] = self.data[offset:offset+self.header['metadata_length']].decode('utf-8', errors='ignore')
offset += self.header['metadata_length']
self.header['toc'] = [struct.unpack('<I', self.data[offset+i*4:offset+(i+1)*4])[0] for i in range(self.header['toc_entries'])]
offset += self.header['toc_size']
# Find first sync frame in bitstream
while offset < len(self.data) - 4:
sync_sig = self.data[offset:offset+4].decode('ascii', errors='ignore')
if sync_sig == 'NSVs':
offset += 4
self.bitstream_props['video_codec'] = self.data[offset:offset+4].decode('ascii')
offset += 4
self.bitstream_props['audio_codec'] = self.data[offset:offset+4].decode('ascii')
offset += 4
self.bitstream_props['width'] = struct.unpack('<H', self.data[offset:offset+2])[0]; offset += 2
self.bitstream_props['height'] = struct.unpack('<H', self.data[offset:offset+2])[0]; offset += 2
self.bitstream_props['framerate'] = self.data[offset]; offset += 1
self.bitstream_props['sync_offset'] = struct.unpack('<h', self.data[offset:offset+2])[0]; offset += 2
break
offset += 1
def print_properties(self):
print("NSV File Properties:")
for key, value in self.header.items():
print(f"- {key.capitalize()}: {value}")
for key, value in self.bitstream_props.items():
print(f"- Bitstream {key.capitalize()}: {value}")
def write(self, new_filename):
with open(new_filename, 'wb') as f:
f.write(self.data)
# Example usage:
# nsv = NSVFile('sample.nsv')
# nsv.read()
# nsv.print_properties()
# nsv.write('output.nsv')
This class reads the file, decodes the header and bitstream properties, prints them, and can write the file back.
5. Java Class for .NSV File Handling
The following Java class performs similar operations using DataInputStream for binary reading.
import java.io.*;
public class NSVFile {
private String filename;
private byte[] data;
private java.util.Map<String, Object> header = new java.util.HashMap<>();
private java.util.Map<String, Object> bitstreamProps = new java.util.HashMap<>();
public NSVFile(String filename) {
this.filename = filename;
}
public void read() throws IOException {
try (FileInputStream fis = new FileInputStream(filename)) {
data = fis.readAllBytes();
}
decode();
}
private void decode() throws IOException {
ByteArrayInputStream bais = new ByteArrayInputStream(data);
DataInputStream dis = new DataInputStream(bais);
int offset = 0;
byte[] sigBytes = new byte[4];
bais.read(sigBytes, 0, 4);
String sig = new String(sigBytes);
if (sig.equals("NSVf")) {
offset += 4;
header.put("header_length", dis.readInt());
header.put("file_length", dis.readInt());
header.put("metadata_length", dis.readInt());
header.put("toc_entries", dis.readInt());
header.put("toc_size", dis.readInt());
byte[] metadataBytes = new byte[(int) header.get("metadata_length")];
bais.read(metadataBytes, 0, (int) header.get("metadata_length"));
header.put("metadata", new String(metadataBytes));
offset += (int) header.get("metadata_length");
java.util.List<Integer> toc = new java.util.ArrayList<>();
for (int i = 0; i < (int) header.get("toc_entries"); i++) {
toc.add(dis.readInt());
}
header.put("toc", toc);
offset += (int) header.get("toc_size");
}
// Find first sync frame
bais.reset();
bais.skip(offset);
while (bais.available() > 4) {
byte[] syncBytes = new byte[4];
bais.read(syncBytes, 0, 4);
String syncSig = new String(syncBytes);
if (syncSig.equals("NSVs")) {
byte[] videoBytes = new byte[4]; bais.read(videoBytes, 0, 4);
bitstreamProps.put("video_codec", new String(videoBytes));
byte[] audioBytes = new byte[4]; bais.read(audioBytes, 0, 4);
bitstreamProps.put("audio_codec", new String(audioBytes));
bitstreamProps.put("width", dis.readShort() & 0xFFFF);
bitstreamProps.put("height", dis.readShort() & 0xFFFF);
bitstreamProps.put("framerate", dis.readByte() & 0xFF);
bitstreamProps.put("sync_offset", dis.readShort());
break;
}
bais.skip(-3); // Backtrack for sliding window
}
}
public void printProperties() {
System.out.println("NSV File Properties:");
header.forEach((key, value) -> System.out.println("- " + key.substring(0, 1).toUpperCase() + key.substring(1) + ": " + value));
bitstreamProps.forEach((key, value) -> System.out.println("- Bitstream " + key.substring(0, 1).toUpperCase() + key.substring(1) + ": " + value));
}
public void write(String newFilename) throws IOException {
try (FileOutputStream fos = new FileOutputStream(newFilename)) {
fos.write(data);
}
}
// Example usage:
// public static void main(String[] args) throws IOException {
// NSVFile nsv = new NSVFile("sample.nsv");
// nsv.read();
// nsv.printProperties();
// nsv.write("output.nsv");
// }
}
This class reads, decodes, prints, and writes the file.
6. JavaScript Class for .NSV File Handling
The following JavaScript class can be used in a browser or Node.js environment (with fs module for Node).
const fs = require('fs'); // For Node.js; remove for browser
class NSVFile {
constructor(filename) {
this.filename = filename;
this.header = {};
this.bitstreamProps = {};
this.data = null;
}
read() {
this.data = fs.readFileSync(this.filename); // For Node.js
this.decode();
}
decode() {
let offset = 0;
const sig = String.fromCharCode(...this.data.slice(offset, offset + 4));
offset += 4;
if (sig === 'NSVf') {
this.header.headerLength = this.data.readUInt32LE(offset); offset += 4;
this.header.fileLength = this.data.readUInt32LE(offset); offset += 4;
this.header.metadataLength = this.data.readUInt32LE(offset); offset += 4;
this.header.tocEntries = this.data.readUInt32LE(offset); offset += 4;
this.header.tocSize = this.data.readUInt32LE(offset); offset += 4;
this.header.metadata = this.data.slice(offset, offset + this.header.metadataLength).toString();
offset += this.header.metadataLength;
this.header.toc = [];
for (let i = 0; i < this.header.tocEntries; i++) {
this.header.toc.push(this.data.readUInt32LE(offset));
offset += 4;
}
}
// Find first sync frame
while (offset < this.data.length - 4) {
const syncSig = String.fromCharCode(...this.data.slice(offset, offset + 4));
if (syncSig === 'NSVs') {
offset += 4;
this.bitstreamProps.videoCodec = String.fromCharCode(...this.data.slice(offset, offset + 4)); offset += 4;
this.bitstreamProps.audioCodec = String.fromCharCode(...this.data.slice(offset, offset + 4)); offset += 4;
this.bitstreamProps.width = this.data.readUInt16LE(offset); offset += 2;
this.bitstreamProps.height = this.data.readUInt16LE(offset); offset += 2;
this.bitstreamProps.framerate = this.data[offset]; offset += 1;
this.bitstreamProps.syncOffset = this.data.readInt16LE(offset); offset += 2;
break;
}
offset++;
}
}
printProperties() {
console.log('NSV File Properties:');
Object.entries(this.header).forEach(([key, value]) => console.log(`- ${key.charAt(0).toUpperCase() + key.slice(1)}: ${value}`));
Object.entries(this.bitstreamProps).forEach(([key, value]) => console.log(`- Bitstream ${key.charAt(0).toUpperCase() + key.slice(1)}: ${value}`));
}
write(newFilename) {
fs.writeFileSync(newFilename, this.data);
}
}
// Example usage (Node.js):
// const nsv = new NSVFile('sample.nsv');
// nsv.read();
// nsv.printProperties();
// nsv.write('output.nsv');
This class supports reading, decoding, printing, and writing in a Node.js context.
7. C "Class" for .NSV File Handling
Since C does not have classes, the following uses a struct with functions for equivalent functionality. Compile with a standard C compiler (e.g., gcc).
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
typedef struct {
char* filename;
uint8_t* data;
size_t size;
struct {
uint32_t header_length;
uint32_t file_length;
uint32_t metadata_length;
uint32_t toc_entries;
uint32_t toc_size;
char* metadata;
uint32_t* toc;
} header;
struct {
char video_codec[5];
char audio_codec[5];
uint16_t width;
uint16_t height;
uint8_t framerate;
int16_t sync_offset;
} bitstream_props;
} NSVFile;
NSVFile* nsv_create(const char* filename) {
NSVFile* nsv = malloc(sizeof(NSVFile));
nsv->filename = strdup(filename);
nsv->data = NULL;
nsv->size = 0;
memset(&nsv->header, 0, sizeof(nsv->header));
memset(&nsv->bitstream_props, 0, sizeof(nsv->bitstream_props));
return nsv;
}
void nsv_read(NSVFile* nsv) {
FILE* f = fopen(nsv->filename, "rb");
if (!f) return;
fseek(f, 0, SEEK_END);
nsv->size = ftell(f);
fseek(f, 0, SEEK_SET);
nsv->data = malloc(nsv->size);
fread(nsv->data, 1, nsv->size, f);
fclose(f);
nsv_decode(nsv);
}
void nsv_decode(NSVFile* nsv) {
size_t offset = 0;
char sig[5] = {0};
memcpy(sig, nsv->data + offset, 4); offset += 4;
if (strcmp(sig, "NSVf") == 0) {
memcpy(&nsv->header.header_length, nsv->data + offset, 4); offset += 4;
memcpy(&nsv->header.file_length, nsv->data + offset, 4); offset += 4;
memcpy(&nsv->header.metadata_length, nsv->data + offset, 4); offset += 4;
memcpy(&nsv->header.toc_entries, nsv->data + offset, 4); offset += 4;
memcpy(&nsv->header.toc_size, nsv->data + offset, 4); offset += 4;
nsv->header.metadata = malloc(nsv->header.metadata_length + 1);
memcpy(nsv->header.metadata, nsv->data + offset, nsv->header.metadata_length);
nsv->header.metadata[nsv->header.metadata_length] = '\0';
offset += nsv->header.metadata_length;
nsv->header.toc = malloc(nsv->header.toc_entries * sizeof(uint32_t));
for (uint32_t i = 0; i < nsv->header.toc_entries; i++) {
memcpy(&nsv->header.toc[i], nsv->data + offset, 4);
offset += 4;
}
}
// Find first sync frame
while (offset < nsv->size - 4) {
char sync_sig[5] = {0};
memcpy(sync_sig, nsv->data + offset, 4);
if (strcmp(sync_sig, "NSVs") == 0) {
offset += 4;
memcpy(nsv->bitstream_props.video_codec, nsv->data + offset, 4); nsv->bitstream_props.video_codec[4] = '\0'; offset += 4;
memcpy(nsv->bitstream_props.audio_codec, nsv->data + offset, 4); nsv->bitstream_props.audio_codec[4] = '\0'; offset += 4;
memcpy(&nsv->bitstream_props.width, nsv->data + offset, 2); offset += 2;
memcpy(&nsv->bitstream_props.height, nsv->data + offset, 2); offset += 2;
nsv->bitstream_props.framerate = nsv->data[offset]; offset += 1;
memcpy(&nsv->bitstream_props.sync_offset, nsv->data + offset, 2); offset += 2;
break;
}
offset++;
}
}
void nsv_print_properties(NSVFile* nsv) {
printf("NSV File Properties:\n");
printf("- Header Length: %u\n", nsv->header.header_length);
printf("- File Length: %u\n", nsv->header.file_length);
printf("- Metadata Length: %u\n", nsv->header.metadata_length);
printf("- TOC Entries: %u\n", nsv->header.toc_entries);
printf("- TOC Size: %u\n", nsv->header.toc_size);
printf("- Metadata: %s\n", nsv->header.metadata ? nsv->header.metadata : "None");
printf("- TOC: [");
for (uint32_t i = 0; i < nsv->header.toc_entries; i++) {
printf("%u%s", nsv->header.toc[i], i < nsv->header.toc_entries - 1 ? ", " : "");
}
printf("]\n");
printf("- Bitstream Video Codec: %s\n", nsv->bitstream_props.video_codec);
printf("- Bitstream Audio Codec: %s\n", nsv->bitstream_props.audio_codec);
printf("- Bitstream Width: %u\n", nsv->bitstream_props.width);
printf("- Bitstream Height: %u\n", nsv->bitstream_props.height);
printf("- Bitstream Framerate: %u\n", nsv->bitstream_props.framerate);
printf("- Bitstream Sync Offset: %d\n", nsv->bitstream_props.sync_offset);
}
void nsv_write(NSVFile* nsv, const char* new_filename) {
FILE* f = fopen(new_filename, "wb");
if (f) {
fwrite(nsv->data, 1, nsv->size, f);
fclose(f);
}
}
void nsv_destroy(NSVFile* nsv) {
free(nsv->data);
free(nsv->header.metadata);
free(nsv->header.toc);
free(nsv->filename);
free(nsv);
}
// Example usage:
// int main() {
// NSVFile* nsv = nsv_create("sample.nsv");
// nsv_read(nsv);
// nsv_print_properties(nsv);
// nsv_write(nsv, "output.nsv");
// nsv_destroy(nsv);
// return 0;
// }
This implementation provides equivalent functionality in C, managing memory and binary operations appropriately.