@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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 eduh_dev021 (Borutovk7)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,92 @@
1
+ # @boruto_vk7/opus-ogg
2
+
3
+ > **High-performance audio conversion engine for Node.js.**
4
+ > Seamless OGG Opus encoding for WhatsApp (iOS/Android) and Discord.js.
5
+
6
+ ---
7
+
8
+ ### Core Capabilities
9
+
10
+ * **WhatsApp iOS Compatibility**: Automatic mono-downmixing for native iPhone support.
11
+ * **Native Ogg/Opus Demuxer**: Built-in frame extraction for `@discordjs/voice` (no `prism-media` required).
12
+ * **Smart Provisioning**: Automatic FFmpeg binary resolution for Windows, Linux, and macOS.
13
+ * **Zero-Latency Streaming**: Optimized piping architecture for minimal CPU and memory footprint.
14
+ * **Termux Optimized**: Fully compatible with Android environments via Termux.
15
+
16
+ ---
17
+
18
+ ### Installation
19
+
20
+ ```bash
21
+ npm install @boruto_vk7/opus-ogg
22
+ ```
23
+
24
+ *Note: In Termux environments, manual installation of FFmpeg is required via `pkg install ffmpeg`.*
25
+
26
+ ---
27
+
28
+ ### Implementation Examples
29
+
30
+ #### WhatsApp Voice Messaging
31
+ Ensures full compatibility with iOS devices by enforcing mono-channel OGG Opus.
32
+
33
+ ```javascript
34
+ const { EngineOgg } = require('@boruto_vk7/opus-ogg');
35
+ const fs = require('fs');
36
+
37
+ const stream = EngineOgg('./audio.mp3', {
38
+ profile: 'voip',
39
+ forceStereo: false
40
+ });
41
+
42
+ stream.pipe(fs.createWriteStream('./output.ogg'));
43
+ ```
44
+
45
+ #### Discord.js Audio Resource
46
+ Direct extraction of raw Opus packets for high-efficiency voice streaming.
47
+
48
+ ```javascript
49
+ const { EngineOgg, OggToOpusStream } = require('@boruto_vk7/opus-ogg');
50
+ const { createAudioResource, StreamType } = require('@discordjs/voice');
51
+
52
+ const oggStream = EngineOgg('https://example.com/audio.mp3', {
53
+ profile: 'audio',
54
+ forceStereo: true
55
+ });
56
+
57
+ const opusStream = OggToOpusStream(oggStream);
58
+
59
+ const resource = createAudioResource(opusStream, {
60
+ inputType: StreamType.Opus
61
+ });
62
+ ```
63
+
64
+ ---
65
+
66
+ ### API Specification
67
+
68
+ #### `EngineOgg(input, options)`
69
+ Returns a `Readable` stream of OGG Opus data.
70
+
71
+ | Parameter | Type | Description |
72
+ | :--- | :--- | :--- |
73
+ | `input` | `string \| Readable` | Local path, URL, or readable stream. |
74
+ | `options.profile` | `'voip' \| 'audio'` | Voice vs. Music optimization. |
75
+ | `options.forceStereo` | `boolean` | Enforce 2-channel output (not for WhatsApp iOS). |
76
+ | `options.bitrate` | `string` | Target bitrate (e.g., `64k`, `128k`). |
77
+ | `options.ffmpegPath` | `string` | Optional path to a custom FFmpeg binary. |
78
+
79
+ #### `OggToOpusStream(oggStream)`
80
+ A native demuxer that transforms OGG Opus data into raw Opus packets (Buffers).
81
+
82
+ #### `EngineOggFile(id, input, options)`
83
+ Async utility to convert and store audio in a temporary file. Returns `Promise<string>`.
84
+
85
+ ---
86
+
87
+ ### License
88
+ MIT
89
+
90
+ ---
91
+
92
+ **Developed by eduh_dev021 ([Borutovk7](https://github.com/Borutovk7))**
@@ -0,0 +1,177 @@
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;
@@ -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 @@
1
+ {"type":"commonjs"}
@@ -0,0 +1,177 @@
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;