UNPKG

portkey-ai

Version:
381 lines 16.7 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const fs_1 = __importDefault(require("fs")); const util_1 = require("util"); const open = (0, util_1.promisify)(fs_1.default.open); const read = (0, util_1.promisify)(fs_1.default.read); const stat = (0, util_1.promisify)(fs_1.default.stat); const close = (0, util_1.promisify)(fs_1.default.close); /** * Get audio file duration in milliseconds * Uses optimized file reading to avoid loading entire files into memory */ function getAudioFileDuration(filePath) { var _a; return __awaiter(this, void 0, void 0, function* () { const extension = (_a = filePath.split('.').pop()) === null || _a === void 0 ? void 0 : _a.toLowerCase(); const fileStats = yield stat(filePath); const fileSize = fileStats.size; switch (extension) { case 'wav': return calculateWavDuration(filePath); case 'mp3': return calculateMp3Duration(filePath, fileSize); case 'mpga': return calculateMpgaDuration(filePath, fileSize); case 'flac': return calculateFlacDuration(filePath); case 'ogg': return calculateOggDuration(filePath, fileSize); case 'mp4': case 'm4a': return calculateMp4Duration(filePath); default: return null; } }); } /** * Helper function to read a portion of a file */ function readFileChunk(filePath, position, length) { return __awaiter(this, void 0, void 0, function* () { const fd = yield open(filePath, 'r'); const buffer = Buffer.alloc(length); try { const { bytesRead } = yield read(fd, buffer, 0, length, position); if (bytesRead < length) { return buffer.slice(0, bytesRead); } return buffer; } finally { yield close(fd); } }); } function calculateWavDuration(filePath) { return __awaiter(this, void 0, void 0, function* () { try { // For WAV files, we only need to read the header (44 bytes is sufficient) const buffer = yield readFileChunk(filePath, 0, 44); // Check if buffer has enough bytes for WAV header if (buffer.length < 44) { return null; } const sampleRate = buffer.readUInt32LE(24); const dataSize = buffer.readUInt32LE(40); const bitsPerSample = buffer.readUInt16LE(34); const channels = buffer.readUInt16LE(22); const durationSec = dataSize / (sampleRate * channels * (bitsPerSample / 8)); const durationMs = Math.round(durationSec * 1000); return durationMs.toString(); } catch (error) { return null; } }); } function calculateMp3Duration(filePath, fileSize) { return __awaiter(this, void 0, void 0, function* () { try { // Read the first 10KB to find the header const headerBuffer = (yield readFileChunk(filePath, 0, Math.min(10240, fileSize))); if (headerBuffer.length < 128) { return null; } for (let i = 0; i < headerBuffer.length - 4; i++) { if (headerBuffer[i] === 0xff && (headerBuffer[i + 1] & 0xe0) === 0xe0) { const bitrateIndex = (headerBuffer[i + 2] >> 4) & 0x0f; const sampleRateIndex = (headerBuffer[i + 2] >> 2) & 0x03; const bitrates = [ 0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, ]; const sampleRates = [44100, 48000, 32000]; // Check for valid indices if (bitrateIndex >= bitrates.length || sampleRateIndex >= sampleRates.length) { continue; } // Now we know these indices are valid const bitrate = Number(bitrates[bitrateIndex]); const sampleRate = Number(sampleRates[sampleRateIndex]); if (!bitrate || !sampleRate) { continue; } const fileSizeInBits = fileSize * 8; const duration = (fileSizeInBits / (bitrate * 1000)) * 1000; return duration.toFixed(0); } } } catch (error) { // If any calculation fails, return null } return null; }); } function calculateMpgaDuration(filePath, fileSize) { var _a, _b; return __awaiter(this, void 0, void 0, function* () { try { // Read the first 10KB to find the header const headerBuffer = (yield readFileChunk(filePath, 0, Math.min(10240, fileSize))); if (headerBuffer.length < 128) { return null; } for (let i = 0; i < headerBuffer.length - 4; i++) { if (headerBuffer[i] === 0xff && (headerBuffer[i + 1] & 0xf0) === 0xf0) { const bitrateIndex = (headerBuffer[i + 2] >> 4) & 0x0f; const sampleRateIndex = (headerBuffer[i + 2] >> 2) & 0x03; const bitrates = [ 0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0, ]; const sampleRates = [ [44100, 48000, 32000, 0], [22050, 24000, 16000, 0], [11025, 12000, 8000, 0], ]; const versionBits = (headerBuffer[i + 1] >> 3) & 0x03; const versionIndex = versionBits === 3 ? 0 : versionBits === 2 ? 1 : 2; // Check all indices are valid if (bitrateIndex === 0 || bitrateIndex >= bitrates.length || sampleRateIndex >= 3 || versionIndex >= sampleRates.length) { continue; } // Check for valid sampleRate index if (sampleRateIndex >= ((_a = sampleRates[versionIndex]) === null || _a === void 0 ? void 0 : _a.length)) { continue; } // Now we know these indices are valid const bitrate = Number(bitrates[bitrateIndex]); const sampleRate = Number((_b = sampleRates[versionIndex]) === null || _b === void 0 ? void 0 : _b[sampleRateIndex]); if (!bitrate || !sampleRate) { continue; } const fileSizeInBits = fileSize * 8; const duration = (fileSizeInBits / (bitrate * 1000)) * 1000; return duration.toFixed(0); } } } catch (error) { // If any calculation fails, return null } return null; }); } function calculateFlacDuration(filePath) { return __awaiter(this, void 0, void 0, function* () { try { // Read the first portion of the file to find FLAC metadata const headerBuffer = yield readFileChunk(filePath, 0, 4); if (headerBuffer.toString() !== 'fLaC') { return null; } // Read up to 100KB to find the stream info block const metadataBuffer = (yield readFileChunk(filePath, 4, 102400)); let offset = 0; let isLastBlock = false; let foundStreamInfo = false; let totalSamples = 0; let sampleRate = 0; while (!isLastBlock && offset < metadataBuffer.length) { if (offset + 4 > metadataBuffer.length) { break; } isLastBlock = (metadataBuffer[offset] & 0x80) === 0x80; const blockType = metadataBuffer[offset] & 0x7f; const blockLength = metadataBuffer.readUInt32BE(offset) & 0x00ffffff; offset += 4; if (blockType === 0 && offset + 18 <= metadataBuffer.length) { sampleRate = (metadataBuffer[offset + 10] << 12) | (metadataBuffer[offset + 11] << 4) | ((metadataBuffer[offset + 12] & 0xf0) >> 4); totalSamples = ((metadataBuffer[offset + 13] & 0x0f) << 32) | (metadataBuffer[offset + 14] << 24) | (metadataBuffer[offset + 15] << 16) | (metadataBuffer[offset + 16] << 8) | metadataBuffer[offset + 17]; foundStreamInfo = true; break; } offset += blockLength; } if (!foundStreamInfo || sampleRate === 0) { return null; } const durationSec = totalSamples / sampleRate; const durationMs = Math.round(durationSec * 1000); return durationMs.toString(); } catch (error) { return null; } }); } function calculateOggDuration(filePath, fileSize) { return __awaiter(this, void 0, void 0, function* () { try { // Read the beginning of the file to check signature const headerBuffer = yield readFileChunk(filePath, 0, 4); if (headerBuffer.toString() !== 'OggS') { return null; } // For Ogg, we need to read the beginning for the sample rate and end for granule position const headPart = yield readFileChunk(filePath, 0, Math.min(10000, fileSize)); // Read the last 10KB of the file to find the last page const tailSize = Math.min(10240, fileSize); const tailPart = yield readFileChunk(filePath, Math.max(0, fileSize - tailSize), tailSize); let sampleRate = 0; let offset = 0; // Find sample rate in the header part while (offset < headPart.length - 7) { if (offset + 7 <= headPart.length && headPart.slice(offset, offset + 7).toString() === '\x01vorbis') { if (offset + 16 < headPart.length) { sampleRate = headPart.readUInt32LE(offset + 12); break; } } offset++; } if (sampleRate === 0) { return null; } // Find the last granule position in the tail part let lastGranulePos = 0; offset = 0; while (offset < tailPart.length - 27) { if (offset + 4 <= tailPart.length && tailPart.slice(offset, offset + 4).toString() === 'OggS') { if (offset + 14 < tailPart.length) { const granulePos = Number(tailPart.readBigInt64LE(offset + 6)); if (granulePos > lastGranulePos) { lastGranulePos = granulePos; } if (offset + 27 >= tailPart.length) { break; } const pageSegments = tailPart[offset + 26]; const headerSize = 27 + pageSegments; let pageSize = headerSize; for (let i = 0; i < pageSegments && offset + 27 + i < tailPart.length; i++) { pageSize += tailPart[offset + 27 + i]; } offset += pageSize; } else { break; } } else { offset++; } } if (lastGranulePos === 0) { return null; } const durationSec = lastGranulePos / sampleRate; const durationMs = Math.round(durationSec * 1000); return durationMs.toString(); } catch (error) { return null; } }); } function calculateMp4Duration(filePath) { return __awaiter(this, void 0, void 0, function* () { try { const fd = yield open(filePath, 'r'); try { return yield findMp4Duration(fd, filePath, 0); } finally { yield close(fd); } } catch (error) { return null; } }); } function findMp4Duration(fd, filePath, position) { return __awaiter(this, void 0, void 0, function* () { try { const headerSize = 8; const headerBuffer = Buffer.alloc(headerSize); const { bytesRead } = yield read(fd, headerBuffer, 0, headerSize, position); if (bytesRead < headerSize) return null; const atomSize = headerBuffer.readUInt32BE(0); if (atomSize < 8) return null; const atomType = headerBuffer.slice(4, 8).toString(); if (atomType === 'moov') { // Read the entire moov atom, which contains duration info const moovBuffer = yield readFileChunk(filePath, position, atomSize); // Search for mvhd atom inside moov for (let offset = 8; offset < moovBuffer.length - 8; offset++) { if (offset + 4 <= moovBuffer.length && moovBuffer.slice(offset, offset + 4).toString() === 'mvhd') { if (offset + 5 >= moovBuffer.length) break; const version = moovBuffer[offset + 4]; let timescale, duration; if (version === 0) { if (offset + 24 >= moovBuffer.length) break; timescale = moovBuffer.readUInt32BE(offset + 16); duration = moovBuffer.readUInt32BE(offset + 20); } else { if (offset + 36 >= moovBuffer.length) break; timescale = moovBuffer.readUInt32BE(offset + 24); const durationHigh = moovBuffer.readUInt32BE(offset + 28); const durationLow = moovBuffer.readUInt32BE(offset + 32); duration = durationHigh * Math.pow(2, 32) + durationLow; } if (timescale > 0) { const durationSec = duration / timescale; const durationMs = Math.round(durationSec * 1000); return durationMs.toString(); } } } } else if (['trak', 'mdia', 'minf', 'stbl'].includes(atomType)) { // Read and recursively search these container atoms return findMp4Duration(fd, filePath, position + 8); } // If we didn't find duration in this atom, try the next one if (atomSize > 0) { return findMp4Duration(fd, filePath, position + atomSize); } } catch (e) { return null; } return null; }); } exports.default = getAudioFileDuration; //# sourceMappingURL=getAudioDuration.js.map