3.1.2 • Published 4 months ago

parse-sng v3.1.2

Weekly downloads
-
License
MIT
Repository
-
Last release
4 months ago

.sng is a simple and generic binary container format that groups a list of files and metadata into a single file. The file format specification can be found here: https://github.com/mdsitton/SngFileFormat

Works in a browser context and Node.js v17.0.0 and later.

API

This package provides two ways to parse .sng files:

Parse .sng Buffer

interface SngHeader {
    fileIdentifier: string
    version: number
    xorMask: Uint8Array
    metadata: {
        [key: string]: string
    }
    fileMeta: {
        filename: string
        contentsLen: bigint
        contentsIndex: bigint
    }[]
}

/**
 * @param sngBuffer The .sng file buffer.
 * @throws an exception if the .sng file is incorrectly formatted.
 * @returns A `SngHeader` object containing the .sng file's metadata.
 */
function parseSngHeader(sngBuffer: Uint8Array): SngHeader

/**
 * @param header The `SngHeader` object returned from `parseSngHeader()`.
 * @param sngBuffer The .sng file buffer.
 * @param filename The name of the file to read from the .sng [file] section.
 * @throws an exception if `filename` does not match any filenames found in
 * `header.fileMeta` or if the .sng file is incorrectly formatted.
 * @returns A new `Uint8Array` object containing the unmasked binary contents of the file.
 */
function readSngFile(header: SngHeader, sngBuffer: Uint8Array, filename: string): Uint8Array

Parse .sng Stream

interface SngStreamConfig {
  /**
   * The .sng format doesn't list a `song.ini` file in the `fileMeta`; that information is stored in `metadata`.
   *
   * Set this to true for `SngStream` to generate and emit a `song.ini` file in the `file` or `files` events.
   *
   * Default: `false`.
   */
  generateSongIni: boolean
}

/**
 * A class that reads and parses a .sng `Uint8Array` stream and emits
 * events when the different components of the stream have been parsed.
 */
class SngStream {

    constructor(
        /**
         * @returns a `ReadableStream` for the portion of the file between `byteStart` (inclusive) and `byteEnd` (inclusive).
         * If `byteEnd` is not specified, it should default to `Infinity` or `undefined`.
         * This may be called multiple times to create multiple concurrent streams.
         */
        getSngStream: (byteStart: bigint, byteEnd?: bigint) => ReadableStream<Uint8Array>,
        config?: SngStreamConfig,
    ) { }

    /**
     * Starts processing the provided .sng stream. Event listeners should be attached before calling this.
     */
    start(): Promise<void>

    /**
     * Registers `listener` to be called when the .sng header has been parsed.
     * The `SngHeader` object is passed to `listener`.
     *
     * This event is emitted before any `file` events are emitted.
     */
    on(event: 'header', listener: (header: SngHeader) => void): void

    /**
     * Registers `listener` to be called when each file in .sng has started to parse.
     * The `fileName` is passed to `listener`, along with a `ReadableStream`
     * for the (unmasked) binary contents of the file.
     *
     * `fileStream` will emit its `end` event before `listener` is called again
     * with the next file.
     *
     * Note: using this event will only cause `getSngStream()` to be called once.
     * Handling `byteStart` and `byteEnd` is not necessary.
     *
     * Cancelling `fileStream` will prevent any more `file` events from emitting.
     * The source stream is canceled, and the `end` event fires.
     */
    on(event: 'file', listener: (fileName: string, fileStream: ReadableStream<Uint8Array>) => void): void

    /**
     * Registers `listener` to be called after the .sng header has been parsed.
     * An array of `ReadableStream`s is `listener`, along with each `fileName`.
     * The streams are for the (unmasked) binary contents of the files.
     *
     * If a listener for the `file` event is registered, this event will not fire.
     *
     * Note: using this event will cause `getSngStream()` to be called once for
     * getting the header and once for each individual file.
     *
     * `fileStream`s can be cancelled. This will not affect other `fileStream`s.
     */
    on(event: 'files', listener: (files: { fileName: string, fileStream: ReadableStream<Uint8Array> }[]) => void): void

    /**
     * Registers `listener` to be called when the .sng file has been fully streamed
     * during the `file` or `files` events.
     *
     * This event is emitted after the `end` event of the last `fileStream`.
     */
    on(event: 'end', listener: () => void): void

    /**
     * Registers `listener` to be called if an error occurs during the stream.
     *
     * The `Error` object is passed to `listener`.
     *
     * This can either happen when `sngStream` emits an `error` event, or
     * if the .sng's header failed to parse.
     */
    on(event: 'error', listener: (error: Error) => void): void
}

Parse .sng Stream Node.js Example

import { createReadStream, createWriteStream } from 'fs'
import { SngStream } from 'parse-sng'
import { Readable } from 'stream'
import { mkdir } from 'fs/promises'
import { join, parse } from 'path'

const sngStream = new SngStream(
  (start, end) => Readable.toWeb(
    createReadStream('C:/dev/test.sng', { start: Number(start), end: Number(end) || undefined })
  ) as ReadableStream<Uint8Array>,
  { generateSongIni: true },
)

sngStream.on('header', header => {
    console.log('Header:', header)
})

sngStream.on('files', files => {
  files.forEach(async ({ fileName, fileStream }) => {
    console.log(`Starting to read file ${fileName}`)
    const reader = fileStream.getReader()

    await mkdir(join('C:/dev/output', parse(fileName).dir), { recursive: true })
    const writeStream = createWriteStream(join('C:/dev/output', fileName))
    while(true) {
      const { done, value } = await reader.read()
      if (done) { break }
      writeStream.write(value)
    }
    writeStream.close()

    console.log(`Finished reading file ${fileName}`)
  })
})

sngStream.on('end', () => console.log('test.sng has been fully parsed'))

sngStream.on('error', error => console.log(error))

sngStream.start()
3.1.2

4 months ago

3.1.1

5 months ago

3.0.4

6 months ago

3.0.3

7 months ago

3.0.2

7 months ago

3.1.0

6 months ago

3.0.5

6 months ago

3.0.1

8 months ago

3.0.0

8 months ago

2.0.0

8 months ago

1.0.1

1 year ago

1.0.0

1 year ago