@boruto_vk7/opus-ogg 1.2.0 → 1.2.2

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.
package/dist/cjs/index.js CHANGED
@@ -43,7 +43,8 @@ const http = __importStar(require("node:http"));
43
43
  const node_child_process_1 = require("node:child_process");
44
44
  const node_stream_1 = require("node:stream");
45
45
  const os = __importStar(require("node:os"));
46
- const oggDemuxer_1 = require("./oggDemuxer"); // Import the new demuxer
46
+ const oggDemuxer_js_1 = require("./oggDemuxer.js");
47
+ // Import the new demuxer
47
48
  // Fallback to system ffmpeg if local bin is not found
48
49
  let ffmpegPath = 'ffmpeg';
49
50
  /**
@@ -152,7 +153,7 @@ function EngineOgg(input, options = {}) {
152
153
  * @returns A Readable stream emitting raw Opus packets (Buffers).
153
154
  */
154
155
  function OggToOpusStream(oggInput) {
155
- const demuxer = new oggDemuxer_1.OggDemuxer();
156
+ const demuxer = new oggDemuxer_js_1.OggDemuxer();
156
157
  oggInput.pipe(demuxer);
157
158
  return demuxer;
158
159
  }
package/dist/esm/index.js CHANGED
@@ -5,7 +5,8 @@ import * as http from 'node:http';
5
5
  import { spawn } from 'node:child_process';
6
6
  import { Readable, PassThrough } from 'node:stream';
7
7
  import * as os from 'node:os';
8
- import { OggDemuxer } from './oggDemuxer'; // Import the new demuxer
8
+ import { OggDemuxer } from './oggDemuxer.js';
9
+ // Import the new demuxer
9
10
  // Fallback to system ffmpeg if local bin is not found
10
11
  let ffmpegPath = 'ffmpeg';
11
12
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boruto_vk7/opus-ogg",
3
- "version": "1.2.0",
3
+ "version": "1.2.2",
4
4
  "description": "Ultra-fast Audio to OGG Opus converter for WhatsApp (iPhone) and Discord",
5
5
  "main": "./dist/cjs/index.js",
6
6
  "module": "./dist/esm/index.js",
@@ -1,177 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.EngineOgg = EngineOgg;
37
- exports.OggToOpusStream = OggToOpusStream;
38
- exports.EngineOggFile = EngineOggFile;
39
- const fs = __importStar(require("node:fs"));
40
- const path = __importStar(require("node:path"));
41
- const https = __importStar(require("node:https"));
42
- const http = __importStar(require("node:http"));
43
- const node_child_process_1 = require("node:child_process");
44
- const node_stream_1 = require("node:stream");
45
- const os = __importStar(require("node:os"));
46
- const oggDemuxer_1 = require("./oggDemuxer"); // Import the new demuxer
47
- // Fallback to system ffmpeg if local bin is not found
48
- let ffmpegPath = 'ffmpeg';
49
- /**
50
- * Resolves the path to the FFmpeg binary.
51
- * Priority:
52
- * 1. Local bin/ folder (downloaded via postinstall)
53
- * 2. System PATH
54
- */
55
- function resolveFFmpeg() {
56
- const possiblePaths = [
57
- path.join(process.cwd(), 'bin', os.platform() === 'win32' ? 'ffmpeg.exe' : 'ffmpeg'),
58
- path.join(__dirname, '..', '..', 'bin', os.platform() === 'win32' ? 'ffmpeg.exe' : 'ffmpeg'),
59
- path.join(__dirname, '..', 'bin', os.platform() === 'win32' ? 'ffmpeg.exe' : 'ffmpeg')
60
- ];
61
- for (const p of possiblePaths) {
62
- try {
63
- if (fs.existsSync(p))
64
- return p;
65
- }
66
- catch { /* ignore */ }
67
- }
68
- return 'ffmpeg';
69
- }
70
- try {
71
- ffmpegPath = resolveFFmpeg();
72
- }
73
- catch {
74
- ffmpegPath = 'ffmpeg';
75
- }
76
- /**
77
- * Converts audio to OGG Opus compatible with WhatsApp (iPhone) and Discord.
78
- * Optimized for ultra-low latency and minimal CPU stress using streaming.
79
- */
80
- function EngineOgg(input, options = {}) {
81
- const outputStream = new node_stream_1.PassThrough({ highWaterMark: 128 * 1024 });
82
- const channels = options.forceStereo ? 2 : 1;
83
- const profile = options.profile || 'voip';
84
- const bitrate = options.bitrate || (profile === 'audio' ? '128k' : '64k');
85
- const bin = options.ffmpegPath || ffmpegPath;
86
- const args = [
87
- '-hide_banner',
88
- '-loglevel', 'error',
89
- '-probesize', '32',
90
- '-analyzeduration', '0',
91
- '-i', 'pipe:0',
92
- '-vn',
93
- '-c:a', 'libopus',
94
- '-ac', channels.toString(),
95
- '-ar', '48000',
96
- '-b:a', bitrate,
97
- '-application', profile,
98
- '-vbr', 'on',
99
- '-compression_level', '0',
100
- '-frame_duration', '20',
101
- '-map_metadata', '-1',
102
- '-f', 'opus',
103
- 'pipe:1'
104
- ];
105
- const ffmpeg = (0, node_child_process_1.spawn)(bin, args);
106
- // Handle Input
107
- if (typeof input === 'string') {
108
- if (/^https?:\/\//.test(input)) {
109
- const lib = input.startsWith('https') ? https : http;
110
- lib.get(input, { headers: { 'User-Agent': 'Mozilla/5.0' } }, (res) => {
111
- if (res.statusCode !== 200) {
112
- outputStream.emit('error', new Error(`HTTP ${res.statusCode}`));
113
- return;
114
- }
115
- res.pipe(ffmpeg.stdin);
116
- }).on('error', (err) => {
117
- outputStream.emit('error', err);
118
- });
119
- }
120
- else {
121
- if (!fs.existsSync(input)) {
122
- outputStream.emit('error', new Error(`File not found: ${input}`));
123
- }
124
- else {
125
- fs.createReadStream(input).pipe(ffmpeg.stdin);
126
- }
127
- }
128
- }
129
- else if (input instanceof node_stream_1.Readable) {
130
- input.pipe(ffmpeg.stdin);
131
- }
132
- // Handle Output
133
- ffmpeg.stdout.pipe(outputStream);
134
- // Error Handling
135
- ffmpeg.on('error', (err) => {
136
- if (err.code === 'ENOENT') {
137
- const msg = `FFmpeg not found at "${bin}". Please ensure it is installed.`;
138
- outputStream.emit('error', new Error(msg));
139
- }
140
- else {
141
- outputStream.emit('error', err);
142
- }
143
- });
144
- ffmpeg.stdin.on('error', () => { });
145
- return outputStream;
146
- }
147
- /**
148
- * Converts an Ogg Opus stream into a stream of raw Opus packets,
149
- * suitable for `@discordjs/voice` with `StreamType.Opus`.
150
- *
151
- * @param oggInput A Readable stream containing Ogg Opus data.
152
- * @returns A Readable stream emitting raw Opus packets (Buffers).
153
- */
154
- function OggToOpusStream(oggInput) {
155
- const demuxer = new oggDemuxer_1.OggDemuxer();
156
- oggInput.pipe(demuxer);
157
- return demuxer;
158
- }
159
- /**
160
- * Legacy support for file-based conversion (Async)
161
- */
162
- async function EngineOggFile(id, input, options = {}) {
163
- const TEMP_DIR = path.resolve(process.cwd(), 'temp_audio');
164
- if (!fs.existsSync(TEMP_DIR))
165
- fs.mkdirSync(TEMP_DIR, { recursive: true });
166
- const ts = Date.now();
167
- const outputFile = path.join(TEMP_DIR, `out_${id}_${ts}.ogg`);
168
- const stream = EngineOgg(input, options);
169
- const writeStream = fs.createWriteStream(outputFile);
170
- return new Promise((resolve, reject) => {
171
- stream.pipe(writeStream);
172
- writeStream.on('finish', () => resolve(outputFile));
173
- writeStream.on('error', reject);
174
- stream.on('error', reject);
175
- });
176
- }
177
- exports.default = EngineOgg;
@@ -1,169 +0,0 @@
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;
@@ -1,29 +0,0 @@
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;
@@ -1,19 +0,0 @@
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
- }