@boruto_vk7/opus-ogg 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,169 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OggDemuxer = void 0;
4
+ const node_stream_1 = require("node:stream");
5
+ // Constants for Ogg parsing
6
+ const OGG_CAPTURE_PATTERN = 0x4f676753; // 'OggS'
7
+ const OGG_HEADER_SIZE = 27; // Fixed size of Ogg page header
8
+ // Opus header types
9
+ const OPUS_HEAD_SIGNATURE = Buffer.from('OpusHead');
10
+ const OPUS_TAGS_SIGNATURE = Buffer.from('OpusTags');
11
+ /**
12
+ * A Transform stream that demuxes Ogg Opus streams, extracting raw Opus packets.
13
+ * It parses Ogg pages and emits individual Opus packets as Buffers.
14
+ */
15
+ class OggDemuxer extends node_stream_1.Transform {
16
+ buffer;
17
+ currentGranulePosition;
18
+ bitstreamSerialNumber;
19
+ pageSequenceNumber;
20
+ opusHeadParsed;
21
+ opusTagsParsed;
22
+ constructor() {
23
+ super({ readableObjectMode: true }); // Emits Opus packets as objects (Buffers)
24
+ this.buffer = Buffer.alloc(0);
25
+ this.currentGranulePosition = 0n;
26
+ this.bitstreamSerialNumber = null;
27
+ this.pageSequenceNumber = 0;
28
+ this.opusHeadParsed = false;
29
+ this.opusTagsParsed = false;
30
+ }
31
+ _transform(chunk, encoding, callback) {
32
+ this.buffer = Buffer.concat([this.buffer, chunk]);
33
+ this.parseBuffer();
34
+ callback();
35
+ }
36
+ _flush(callback) {
37
+ // Attempt to parse any remaining data in the buffer
38
+ this.parseBuffer();
39
+ if (this.buffer.length > 0) {
40
+ this.emit('warn', `OggDemuxer: Remaining unparsed data in buffer: ${this.buffer.length} bytes`);
41
+ }
42
+ callback();
43
+ }
44
+ parseBuffer() {
45
+ while (this.buffer.length >= OGG_HEADER_SIZE) {
46
+ const header = this.parsePageHeader(this.buffer);
47
+ if (!header) {
48
+ // No valid OggS pattern found at current position, try to resync
49
+ const nextOggS = this.buffer.indexOf(Buffer.from("OggS"), 1); // Search from next byte
50
+ if (nextOggS === -1) {
51
+ // No more OggS patterns, clear buffer and wait for more data
52
+ this.buffer = Buffer.alloc(0);
53
+ break;
54
+ }
55
+ else {
56
+ // Resync: discard data until next OggS
57
+ this.emit("debug", `OggDemuxer: Resyncing, discarding ${nextOggS} bytes.`);
58
+ this.buffer = this.buffer.subarray(nextOggS);
59
+ continue;
60
+ }
61
+ }
62
+ const pageLength = OGG_HEADER_SIZE + header.pageSegments + header.segmentTable.reduce((a, b) => a + b, 0);
63
+ if (this.buffer.length < pageLength) {
64
+ // Not enough data for the full page, wait for more
65
+ break;
66
+ }
67
+ // Validate bitstream serial number for logical stream continuity
68
+ if (this.bitstreamSerialNumber === null) {
69
+ this.bitstreamSerialNumber = header.bitstreamSerialNumber;
70
+ }
71
+ else if (this.bitstreamSerialNumber !== header.bitstreamSerialNumber) {
72
+ // This indicates a new logical bitstream, which is fine for concatenated Ogg files
73
+ // For simplicity, we'll just reset state for the new stream
74
+ this.bitstreamSerialNumber = header.bitstreamSerialNumber;
75
+ this.opusHeadParsed = false;
76
+ this.opusTagsParsed = false;
77
+ this.pageSequenceNumber = 0;
78
+ this.currentGranulePosition = 0n;
79
+ this.emit('warn', `OggDemuxer: Detected new logical bitstream (serial: ${header.bitstreamSerialNumber})`);
80
+ }
81
+ // Check page sequence number for continuity (optional, but good for error detection)
82
+ if (!(header.headerTypeFlag & 0x02) && header.pageSequenceNumber !== this.pageSequenceNumber) {
83
+ this.emit("warn", `OggDemuxer: Page sequence discontinuity detected. Expected ${this.pageSequenceNumber}, got ${header.pageSequenceNumber}.`);
84
+ // We can still try to process, but this might indicate corruption
85
+ }
86
+ this.pageSequenceNumber = header.pageSequenceNumber + 1;
87
+ this.currentGranulePosition = header.granulePosition;
88
+ // Extract packet data
89
+ let packetOffset = OGG_HEADER_SIZE + header.pageSegments;
90
+ let currentPacketBuffer = Buffer.alloc(0);
91
+ for (let i = 0; i < header.segmentTable.length; i++) {
92
+ const segmentLength = header.segmentTable[i];
93
+ const segmentData = this.buffer.subarray(packetOffset, packetOffset + segmentLength);
94
+ currentPacketBuffer = Buffer.concat([currentPacketBuffer, segmentData]);
95
+ packetOffset += segmentLength;
96
+ // If segment is less than 255, it's the end of a packet
97
+ if (segmentLength < 255 || i === header.segmentTable.length - 1) {
98
+ this.processPacket(currentPacketBuffer);
99
+ currentPacketBuffer = Buffer.alloc(0);
100
+ }
101
+ }
102
+ // Remove processed page from buffer
103
+ this.buffer = this.buffer.subarray(pageLength);
104
+ }
105
+ }
106
+ parsePageHeader(buffer) {
107
+ // Check capture pattern 'OggS'
108
+ // OGG_CAPTURE_PATTERN is 0x4f676753. buffer.readUInt32LE(0) reads 0x5367674f on little-endian systems.
109
+ // So we need to compare with Buffer.from("OggS").readUInt32LE(0)
110
+ if (buffer.readUInt32LE(0) !== Buffer.from("OggS").readUInt32LE(0)) {
111
+ return null;
112
+ }
113
+ const header = {
114
+ capturePattern: buffer.readUInt32LE(0),
115
+ streamStructureVersion: buffer.readUInt8(4),
116
+ headerTypeFlag: buffer.readUInt8(5),
117
+ granulePosition: buffer.readBigInt64LE(6),
118
+ bitstreamSerialNumber: buffer.readUInt32LE(14),
119
+ pageSequenceNumber: buffer.readUInt32LE(18),
120
+ pageChecksum: buffer.readUInt32LE(22),
121
+ pageSegments: buffer.readUInt8(26),
122
+ segmentTable: [],
123
+ };
124
+ // Read segment table
125
+ const segmentTableOffset = OGG_HEADER_SIZE;
126
+ for (let i = 0; i < header.pageSegments; i++) {
127
+ header.segmentTable.push(buffer.readUInt8(segmentTableOffset + i));
128
+ }
129
+ return header;
130
+ }
131
+ processPacket(packet) {
132
+ // The first two packets in an Ogg Opus stream are OpusHead and OpusTags
133
+ if (!this.opusHeadParsed) {
134
+ if (packet.subarray(0, 8).equals(OPUS_HEAD_SIGNATURE)) {
135
+ // This is the OpusHead packet (identification header)
136
+ // We don't need to emit it as a raw Opus frame for @discordjs/voice
137
+ this.opusHeadParsed = true;
138
+ this.emit("debug", `OggDemuxer: Parsed OpusHead packet. Length: ${packet.length}`);
139
+ }
140
+ else {
141
+ this.emit("warn", `OggDemuxer: Expected OpusHead packet but got something else. Packet length: ${packet.length}, first 8 bytes: ${packet.subarray(0, 8).toString()}`);
142
+ // Potentially a malformed stream or not an Opus stream
143
+ }
144
+ return;
145
+ }
146
+ if (!this.opusTagsParsed) {
147
+ if (packet.subarray(0, 8).equals(OPUS_TAGS_SIGNATURE)) {
148
+ // This is the OpusTags packet (comment header)
149
+ // We don't need to emit it as a raw Opus frame for @discordjs/voice
150
+ this.opusTagsParsed = true;
151
+ this.emit("debug", `OggDemuxer: Parsed OpusTags packet. Length: ${packet.length}`);
152
+ }
153
+ else {
154
+ this.emit("warn", `OggDemuxer: Expected OpusTags packet but got something else. Packet length: ${packet.length}, first 8 bytes: ${packet.subarray(0, 8).toString()}`);
155
+ // Potentially a malformed stream or not an Opus stream
156
+ }
157
+ return;
158
+ }
159
+ // After OpusHead and OpusTags, all subsequent packets are raw Opus frames
160
+ if (this.opusHeadParsed && this.opusTagsParsed) {
161
+ this.emit("debug", `OggDemuxer: Emitting Opus packet. Length: ${packet.length}`);
162
+ this.push(packet);
163
+ }
164
+ else {
165
+ this.emit("debug", `OggDemuxer: Skipping non-Opus data before headers. Length: ${packet.length}`);
166
+ }
167
+ }
168
+ }
169
+ exports.OggDemuxer = OggDemuxer;
@@ -0,0 +1,139 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ import * as https from 'node:https';
4
+ import * as http from 'node:http';
5
+ import { spawn } from 'node:child_process';
6
+ import { Readable, PassThrough } from 'node:stream';
7
+ import * as os from 'node:os';
8
+ import { OggDemuxer } from './oggDemuxer'; // Import the new demuxer
9
+ // Fallback to system ffmpeg if local bin is not found
10
+ let ffmpegPath = 'ffmpeg';
11
+ /**
12
+ * Resolves the path to the FFmpeg binary.
13
+ * Priority:
14
+ * 1. Local bin/ folder (downloaded via postinstall)
15
+ * 2. System PATH
16
+ */
17
+ function resolveFFmpeg() {
18
+ const possiblePaths = [
19
+ path.join(process.cwd(), 'bin', os.platform() === 'win32' ? 'ffmpeg.exe' : 'ffmpeg'),
20
+ path.join(__dirname, '..', '..', 'bin', os.platform() === 'win32' ? 'ffmpeg.exe' : 'ffmpeg'),
21
+ path.join(__dirname, '..', 'bin', os.platform() === 'win32' ? 'ffmpeg.exe' : 'ffmpeg')
22
+ ];
23
+ for (const p of possiblePaths) {
24
+ try {
25
+ if (fs.existsSync(p))
26
+ return p;
27
+ }
28
+ catch { /* ignore */ }
29
+ }
30
+ return 'ffmpeg';
31
+ }
32
+ try {
33
+ ffmpegPath = resolveFFmpeg();
34
+ }
35
+ catch {
36
+ ffmpegPath = 'ffmpeg';
37
+ }
38
+ /**
39
+ * Converts audio to OGG Opus compatible with WhatsApp (iPhone) and Discord.
40
+ * Optimized for ultra-low latency and minimal CPU stress using streaming.
41
+ */
42
+ export function EngineOgg(input, options = {}) {
43
+ const outputStream = new PassThrough({ highWaterMark: 128 * 1024 });
44
+ const channels = options.forceStereo ? 2 : 1;
45
+ const profile = options.profile || 'voip';
46
+ const bitrate = options.bitrate || (profile === 'audio' ? '128k' : '64k');
47
+ const bin = options.ffmpegPath || ffmpegPath;
48
+ const args = [
49
+ '-hide_banner',
50
+ '-loglevel', 'error',
51
+ '-probesize', '32',
52
+ '-analyzeduration', '0',
53
+ '-i', 'pipe:0',
54
+ '-vn',
55
+ '-c:a', 'libopus',
56
+ '-ac', channels.toString(),
57
+ '-ar', '48000',
58
+ '-b:a', bitrate,
59
+ '-application', profile,
60
+ '-vbr', 'on',
61
+ '-compression_level', '0',
62
+ '-frame_duration', '20',
63
+ '-map_metadata', '-1',
64
+ '-f', 'opus',
65
+ 'pipe:1'
66
+ ];
67
+ const ffmpeg = spawn(bin, args);
68
+ // Handle Input
69
+ if (typeof input === 'string') {
70
+ if (/^https?:\/\//.test(input)) {
71
+ const lib = input.startsWith('https') ? https : http;
72
+ lib.get(input, { headers: { 'User-Agent': 'Mozilla/5.0' } }, (res) => {
73
+ if (res.statusCode !== 200) {
74
+ outputStream.emit('error', new Error(`HTTP ${res.statusCode}`));
75
+ return;
76
+ }
77
+ res.pipe(ffmpeg.stdin);
78
+ }).on('error', (err) => {
79
+ outputStream.emit('error', err);
80
+ });
81
+ }
82
+ else {
83
+ if (!fs.existsSync(input)) {
84
+ outputStream.emit('error', new Error(`File not found: ${input}`));
85
+ }
86
+ else {
87
+ fs.createReadStream(input).pipe(ffmpeg.stdin);
88
+ }
89
+ }
90
+ }
91
+ else if (input instanceof Readable) {
92
+ input.pipe(ffmpeg.stdin);
93
+ }
94
+ // Handle Output
95
+ ffmpeg.stdout.pipe(outputStream);
96
+ // Error Handling
97
+ ffmpeg.on('error', (err) => {
98
+ if (err.code === 'ENOENT') {
99
+ const msg = `FFmpeg not found at "${bin}". Please ensure it is installed.`;
100
+ outputStream.emit('error', new Error(msg));
101
+ }
102
+ else {
103
+ outputStream.emit('error', err);
104
+ }
105
+ });
106
+ ffmpeg.stdin.on('error', () => { });
107
+ return outputStream;
108
+ }
109
+ /**
110
+ * Converts an Ogg Opus stream into a stream of raw Opus packets,
111
+ * suitable for `@discordjs/voice` with `StreamType.Opus`.
112
+ *
113
+ * @param oggInput A Readable stream containing Ogg Opus data.
114
+ * @returns A Readable stream emitting raw Opus packets (Buffers).
115
+ */
116
+ export function OggToOpusStream(oggInput) {
117
+ const demuxer = new OggDemuxer();
118
+ oggInput.pipe(demuxer);
119
+ return demuxer;
120
+ }
121
+ /**
122
+ * Legacy support for file-based conversion (Async)
123
+ */
124
+ export async function EngineOggFile(id, input, options = {}) {
125
+ const TEMP_DIR = path.resolve(process.cwd(), 'temp_audio');
126
+ if (!fs.existsSync(TEMP_DIR))
127
+ fs.mkdirSync(TEMP_DIR, { recursive: true });
128
+ const ts = Date.now();
129
+ const outputFile = path.join(TEMP_DIR, `out_${id}_${ts}.ogg`);
130
+ const stream = EngineOgg(input, options);
131
+ const writeStream = fs.createWriteStream(outputFile);
132
+ return new Promise((resolve, reject) => {
133
+ stream.pipe(writeStream);
134
+ writeStream.on('finish', () => resolve(outputFile));
135
+ writeStream.on('error', reject);
136
+ stream.on('error', reject);
137
+ });
138
+ }
139
+ export default EngineOgg;
@@ -0,0 +1,165 @@
1
+ import { Transform } from 'node:stream';
2
+ // Constants for Ogg parsing
3
+ const OGG_CAPTURE_PATTERN = 0x4f676753; // 'OggS'
4
+ const OGG_HEADER_SIZE = 27; // Fixed size of Ogg page header
5
+ // Opus header types
6
+ const OPUS_HEAD_SIGNATURE = Buffer.from('OpusHead');
7
+ const OPUS_TAGS_SIGNATURE = Buffer.from('OpusTags');
8
+ /**
9
+ * A Transform stream that demuxes Ogg Opus streams, extracting raw Opus packets.
10
+ * It parses Ogg pages and emits individual Opus packets as Buffers.
11
+ */
12
+ export class OggDemuxer extends Transform {
13
+ buffer;
14
+ currentGranulePosition;
15
+ bitstreamSerialNumber;
16
+ pageSequenceNumber;
17
+ opusHeadParsed;
18
+ opusTagsParsed;
19
+ constructor() {
20
+ super({ readableObjectMode: true }); // Emits Opus packets as objects (Buffers)
21
+ this.buffer = Buffer.alloc(0);
22
+ this.currentGranulePosition = 0n;
23
+ this.bitstreamSerialNumber = null;
24
+ this.pageSequenceNumber = 0;
25
+ this.opusHeadParsed = false;
26
+ this.opusTagsParsed = false;
27
+ }
28
+ _transform(chunk, encoding, callback) {
29
+ this.buffer = Buffer.concat([this.buffer, chunk]);
30
+ this.parseBuffer();
31
+ callback();
32
+ }
33
+ _flush(callback) {
34
+ // Attempt to parse any remaining data in the buffer
35
+ this.parseBuffer();
36
+ if (this.buffer.length > 0) {
37
+ this.emit('warn', `OggDemuxer: Remaining unparsed data in buffer: ${this.buffer.length} bytes`);
38
+ }
39
+ callback();
40
+ }
41
+ parseBuffer() {
42
+ while (this.buffer.length >= OGG_HEADER_SIZE) {
43
+ const header = this.parsePageHeader(this.buffer);
44
+ if (!header) {
45
+ // No valid OggS pattern found at current position, try to resync
46
+ const nextOggS = this.buffer.indexOf(Buffer.from("OggS"), 1); // Search from next byte
47
+ if (nextOggS === -1) {
48
+ // No more OggS patterns, clear buffer and wait for more data
49
+ this.buffer = Buffer.alloc(0);
50
+ break;
51
+ }
52
+ else {
53
+ // Resync: discard data until next OggS
54
+ this.emit("debug", `OggDemuxer: Resyncing, discarding ${nextOggS} bytes.`);
55
+ this.buffer = this.buffer.subarray(nextOggS);
56
+ continue;
57
+ }
58
+ }
59
+ const pageLength = OGG_HEADER_SIZE + header.pageSegments + header.segmentTable.reduce((a, b) => a + b, 0);
60
+ if (this.buffer.length < pageLength) {
61
+ // Not enough data for the full page, wait for more
62
+ break;
63
+ }
64
+ // Validate bitstream serial number for logical stream continuity
65
+ if (this.bitstreamSerialNumber === null) {
66
+ this.bitstreamSerialNumber = header.bitstreamSerialNumber;
67
+ }
68
+ else if (this.bitstreamSerialNumber !== header.bitstreamSerialNumber) {
69
+ // This indicates a new logical bitstream, which is fine for concatenated Ogg files
70
+ // For simplicity, we'll just reset state for the new stream
71
+ this.bitstreamSerialNumber = header.bitstreamSerialNumber;
72
+ this.opusHeadParsed = false;
73
+ this.opusTagsParsed = false;
74
+ this.pageSequenceNumber = 0;
75
+ this.currentGranulePosition = 0n;
76
+ this.emit('warn', `OggDemuxer: Detected new logical bitstream (serial: ${header.bitstreamSerialNumber})`);
77
+ }
78
+ // Check page sequence number for continuity (optional, but good for error detection)
79
+ if (!(header.headerTypeFlag & 0x02) && header.pageSequenceNumber !== this.pageSequenceNumber) {
80
+ this.emit("warn", `OggDemuxer: Page sequence discontinuity detected. Expected ${this.pageSequenceNumber}, got ${header.pageSequenceNumber}.`);
81
+ // We can still try to process, but this might indicate corruption
82
+ }
83
+ this.pageSequenceNumber = header.pageSequenceNumber + 1;
84
+ this.currentGranulePosition = header.granulePosition;
85
+ // Extract packet data
86
+ let packetOffset = OGG_HEADER_SIZE + header.pageSegments;
87
+ let currentPacketBuffer = Buffer.alloc(0);
88
+ for (let i = 0; i < header.segmentTable.length; i++) {
89
+ const segmentLength = header.segmentTable[i];
90
+ const segmentData = this.buffer.subarray(packetOffset, packetOffset + segmentLength);
91
+ currentPacketBuffer = Buffer.concat([currentPacketBuffer, segmentData]);
92
+ packetOffset += segmentLength;
93
+ // If segment is less than 255, it's the end of a packet
94
+ if (segmentLength < 255 || i === header.segmentTable.length - 1) {
95
+ this.processPacket(currentPacketBuffer);
96
+ currentPacketBuffer = Buffer.alloc(0);
97
+ }
98
+ }
99
+ // Remove processed page from buffer
100
+ this.buffer = this.buffer.subarray(pageLength);
101
+ }
102
+ }
103
+ parsePageHeader(buffer) {
104
+ // Check capture pattern 'OggS'
105
+ // OGG_CAPTURE_PATTERN is 0x4f676753. buffer.readUInt32LE(0) reads 0x5367674f on little-endian systems.
106
+ // So we need to compare with Buffer.from("OggS").readUInt32LE(0)
107
+ if (buffer.readUInt32LE(0) !== Buffer.from("OggS").readUInt32LE(0)) {
108
+ return null;
109
+ }
110
+ const header = {
111
+ capturePattern: buffer.readUInt32LE(0),
112
+ streamStructureVersion: buffer.readUInt8(4),
113
+ headerTypeFlag: buffer.readUInt8(5),
114
+ granulePosition: buffer.readBigInt64LE(6),
115
+ bitstreamSerialNumber: buffer.readUInt32LE(14),
116
+ pageSequenceNumber: buffer.readUInt32LE(18),
117
+ pageChecksum: buffer.readUInt32LE(22),
118
+ pageSegments: buffer.readUInt8(26),
119
+ segmentTable: [],
120
+ };
121
+ // Read segment table
122
+ const segmentTableOffset = OGG_HEADER_SIZE;
123
+ for (let i = 0; i < header.pageSegments; i++) {
124
+ header.segmentTable.push(buffer.readUInt8(segmentTableOffset + i));
125
+ }
126
+ return header;
127
+ }
128
+ processPacket(packet) {
129
+ // The first two packets in an Ogg Opus stream are OpusHead and OpusTags
130
+ if (!this.opusHeadParsed) {
131
+ if (packet.subarray(0, 8).equals(OPUS_HEAD_SIGNATURE)) {
132
+ // This is the OpusHead packet (identification header)
133
+ // We don't need to emit it as a raw Opus frame for @discordjs/voice
134
+ this.opusHeadParsed = true;
135
+ this.emit("debug", `OggDemuxer: Parsed OpusHead packet. Length: ${packet.length}`);
136
+ }
137
+ else {
138
+ this.emit("warn", `OggDemuxer: Expected OpusHead packet but got something else. Packet length: ${packet.length}, first 8 bytes: ${packet.subarray(0, 8).toString()}`);
139
+ // Potentially a malformed stream or not an Opus stream
140
+ }
141
+ return;
142
+ }
143
+ if (!this.opusTagsParsed) {
144
+ if (packet.subarray(0, 8).equals(OPUS_TAGS_SIGNATURE)) {
145
+ // This is the OpusTags packet (comment header)
146
+ // We don't need to emit it as a raw Opus frame for @discordjs/voice
147
+ this.opusTagsParsed = true;
148
+ this.emit("debug", `OggDemuxer: Parsed OpusTags packet. Length: ${packet.length}`);
149
+ }
150
+ else {
151
+ this.emit("warn", `OggDemuxer: Expected OpusTags packet but got something else. Packet length: ${packet.length}, first 8 bytes: ${packet.subarray(0, 8).toString()}`);
152
+ // Potentially a malformed stream or not an Opus stream
153
+ }
154
+ return;
155
+ }
156
+ // After OpusHead and OpusTags, all subsequent packets are raw Opus frames
157
+ if (this.opusHeadParsed && this.opusTagsParsed) {
158
+ this.emit("debug", `OggDemuxer: Emitting Opus packet. Length: ${packet.length}`);
159
+ this.push(packet);
160
+ }
161
+ else {
162
+ this.emit("debug", `OggDemuxer: Skipping non-Opus data before headers. Length: ${packet.length}`);
163
+ }
164
+ }
165
+ }
@@ -0,0 +1 @@
1
+ {"type":"module"}
@@ -0,0 +1,29 @@
1
+ import { Readable } from 'node:stream';
2
+ /**
3
+ * EngineOgg Configuration Options
4
+ */
5
+ export interface EngineOggOptions {
6
+ profile?: 'voip' | 'audio';
7
+ forceStereo?: boolean;
8
+ bitrate?: string;
9
+ preset?: string;
10
+ ffmpegPath?: string;
11
+ }
12
+ /**
13
+ * Converts audio to OGG Opus compatible with WhatsApp (iPhone) and Discord.
14
+ * Optimized for ultra-low latency and minimal CPU stress using streaming.
15
+ */
16
+ export declare function EngineOgg(input: string | Readable, options?: EngineOggOptions): Readable;
17
+ /**
18
+ * Converts an Ogg Opus stream into a stream of raw Opus packets,
19
+ * suitable for `@discordjs/voice` with `StreamType.Opus`.
20
+ *
21
+ * @param oggInput A Readable stream containing Ogg Opus data.
22
+ * @returns A Readable stream emitting raw Opus packets (Buffers).
23
+ */
24
+ export declare function OggToOpusStream(oggInput: Readable): Readable;
25
+ /**
26
+ * Legacy support for file-based conversion (Async)
27
+ */
28
+ export declare function EngineOggFile(id: string | number, input: string | Readable, options?: EngineOggOptions): Promise<string>;
29
+ export default EngineOgg;
@@ -0,0 +1,19 @@
1
+ import { Transform, TransformCallback } from 'node:stream';
2
+ /**
3
+ * A Transform stream that demuxes Ogg Opus streams, extracting raw Opus packets.
4
+ * It parses Ogg pages and emits individual Opus packets as Buffers.
5
+ */
6
+ export declare class OggDemuxer extends Transform {
7
+ private buffer;
8
+ private currentGranulePosition;
9
+ private bitstreamSerialNumber;
10
+ private pageSequenceNumber;
11
+ private opusHeadParsed;
12
+ private opusTagsParsed;
13
+ constructor();
14
+ _transform(chunk: Buffer, encoding: BufferEncoding, callback: TransformCallback): void;
15
+ _flush(callback: TransformCallback): void;
16
+ private parseBuffer;
17
+ private parsePageHeader;
18
+ private processPacket;
19
+ }
@@ -0,0 +1,29 @@
1
+ import { Readable } from 'node:stream';
2
+ /**
3
+ * EngineOgg Configuration Options
4
+ */
5
+ export interface EngineOggOptions {
6
+ profile?: 'voip' | 'audio';
7
+ forceStereo?: boolean;
8
+ bitrate?: string;
9
+ preset?: string;
10
+ ffmpegPath?: string;
11
+ }
12
+ /**
13
+ * Converts audio to OGG Opus compatible with WhatsApp (iPhone) and Discord.
14
+ * Optimized for ultra-low latency and minimal CPU stress using streaming.
15
+ */
16
+ export declare function EngineOgg(input: string | Readable, options?: EngineOggOptions): Readable;
17
+ /**
18
+ * Converts an Ogg Opus stream into a stream of raw Opus packets,
19
+ * suitable for `@discordjs/voice` with `StreamType.Opus`.
20
+ *
21
+ * @param oggInput A Readable stream containing Ogg Opus data.
22
+ * @returns A Readable stream emitting raw Opus packets (Buffers).
23
+ */
24
+ export declare function OggToOpusStream(oggInput: Readable): Readable;
25
+ /**
26
+ * Legacy support for file-based conversion (Async)
27
+ */
28
+ export declare function EngineOggFile(id: string | number, input: string | Readable, options?: EngineOggOptions): Promise<string>;
29
+ export default EngineOgg;
@@ -0,0 +1,19 @@
1
+ import { Transform, TransformCallback } from 'node:stream';
2
+ /**
3
+ * A Transform stream that demuxes Ogg Opus streams, extracting raw Opus packets.
4
+ * It parses Ogg pages and emits individual Opus packets as Buffers.
5
+ */
6
+ export declare class OggDemuxer extends Transform {
7
+ private buffer;
8
+ private currentGranulePosition;
9
+ private bitstreamSerialNumber;
10
+ private pageSequenceNumber;
11
+ private opusHeadParsed;
12
+ private opusTagsParsed;
13
+ constructor();
14
+ _transform(chunk: Buffer, encoding: BufferEncoding, callback: TransformCallback): void;
15
+ _flush(callback: TransformCallback): void;
16
+ private parseBuffer;
17
+ private parsePageHeader;
18
+ private processPacket;
19
+ }
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@boruto_vk7/opus-ogg",
3
+ "version": "1.2.0",
4
+ "description": "Ultra-fast Audio to OGG Opus converter for WhatsApp (iPhone) and Discord",
5
+ "main": "./dist/cjs/index.js",
6
+ "module": "./dist/esm/index.js",
7
+ "types": "./dist/types/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "require": "./dist/cjs/index.js",
11
+ "import": "./dist/esm/index.js",
12
+ "types": "./dist/types/index.d.ts"
13
+ }
14
+ },
15
+ "author": "eduh_dev021 (Borutovk7)",
16
+ "license": "MIT",
17
+ "files": [
18
+ "dist",
19
+ "bin",
20
+ "scripts",
21
+ "README.md",
22
+ "LICENSE"
23
+ ],
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "git+https://github.com/Borutovk7/opus-ogg.git"
27
+ },
28
+ "scripts": {
29
+ "build": "tsc -p tsconfig.cjs.json && tsc -p tsconfig.esm.json && node -e \"const fs = require('fs'); if (!fs.existsSync('dist/cjs')) fs.mkdirSync('dist/cjs', {recursive:true}); if (!fs.existsSync('dist/esm')) fs.mkdirSync('dist/esm', {recursive:true}); fs.writeFileSync('dist/cjs/package.json', JSON.stringify({type:'commonjs'})); fs.writeFileSync('dist/esm/package.json', JSON.stringify({type:'module'}));\"",
30
+ "test": "node test.mjs",
31
+ "prepublishOnly": "npm run build",
32
+ "postinstall": "node scripts/install-ffmpeg.js"
33
+ },
34
+ "dependencies": {},
35
+ "devDependencies": {
36
+ "@types/node": "^20.0.0",
37
+ "typescript": "^5.0.0"
38
+ }
39
+ }