feat: HLS streaming, transcripts, agent course generation & landing-page course previews (#663)
New features: - HLS video encoding/streaming (hls routes, hls-encoder) and a media transcripts pipeline (transcripts route + media-transcript queries) - AI assistant chat vs. run model: agent-course-generation worker/queue, run-monitor pane, chat-changes card, content ask-ai bar - Per-theme course-page previews across org landing-page themes (bold, classic, corporate, editorial, etc.) with a shared course token/component set - View-as-student mode and login-link / link-invite invite flows Refactor: - Extract packages/core consolidating config (env, storage, dashboard-url), services and utils previously duplicated under apps/api/src/config - Remove legacy agent service modules (chat-tools, chat-context, context-window, document, fetch-url, lesson-content) - Relax Cross-Origin-Resource-Policy to allow cross-origin media embedding - Add format script to tenant-router so turbo run format covers it
This commit is contained in:
@@ -1,214 +0,0 @@
|
||||
'use strict';
|
||||
var __extends =
|
||||
(this && this.__extends) ||
|
||||
(function () {
|
||||
var extendStatics = function (d, b) {
|
||||
extendStatics =
|
||||
Object.setPrototypeOf ||
|
||||
({ __proto__: [] } instanceof Array &&
|
||||
function (d, b) {
|
||||
d.__proto__ = b;
|
||||
}) ||
|
||||
function (d, b) {
|
||||
for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p];
|
||||
};
|
||||
return extendStatics(d, b);
|
||||
};
|
||||
return function (d, b) {
|
||||
if (typeof b !== 'function' && b !== null)
|
||||
throw new TypeError('Class extends value ' + String(b) + ' is not a constructor or null');
|
||||
extendStatics(d, b);
|
||||
function __() {
|
||||
this.constructor = d;
|
||||
}
|
||||
d.prototype = b === null ? Object.create(b) : ((__.prototype = b.prototype), new __());
|
||||
};
|
||||
})();
|
||||
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 __generator =
|
||||
(this && this.__generator) ||
|
||||
function (thisArg, body) {
|
||||
var _ = {
|
||||
label: 0,
|
||||
sent: function () {
|
||||
if (t[0] & 1) throw t[1];
|
||||
return t[1];
|
||||
},
|
||||
trys: [],
|
||||
ops: []
|
||||
},
|
||||
f,
|
||||
y,
|
||||
t,
|
||||
g = Object.create((typeof Iterator === 'function' ? Iterator : Object).prototype);
|
||||
return (
|
||||
(g.next = verb(0)),
|
||||
(g['throw'] = verb(1)),
|
||||
(g['return'] = verb(2)),
|
||||
typeof Symbol === 'function' &&
|
||||
(g[Symbol.iterator] = function () {
|
||||
return this;
|
||||
}),
|
||||
g
|
||||
);
|
||||
function verb(n) {
|
||||
return function (v) {
|
||||
return step([n, v]);
|
||||
};
|
||||
}
|
||||
function step(op) {
|
||||
if (f) throw new TypeError('Generator is already executing.');
|
||||
while ((g && ((g = 0), op[0] && (_ = 0)), _))
|
||||
try {
|
||||
if (
|
||||
((f = 1),
|
||||
y &&
|
||||
(t = op[0] & 2 ? y['return'] : op[0] ? y['throw'] || ((t = y['return']) && t.call(y), 0) : y.next) &&
|
||||
!(t = t.call(y, op[1])).done)
|
||||
)
|
||||
return t;
|
||||
if (((y = 0), t)) op = [op[0] & 2, t.value];
|
||||
switch (op[0]) {
|
||||
case 0:
|
||||
case 1:
|
||||
t = op;
|
||||
break;
|
||||
case 4:
|
||||
_.label++;
|
||||
return { value: op[1], done: false };
|
||||
case 5:
|
||||
_.label++;
|
||||
y = op[1];
|
||||
op = [0];
|
||||
continue;
|
||||
case 7:
|
||||
op = _.ops.pop();
|
||||
_.trys.pop();
|
||||
continue;
|
||||
default:
|
||||
if (!((t = _.trys), (t = t.length > 0 && t[t.length - 1])) && (op[0] === 6 || op[0] === 2)) {
|
||||
_ = 0;
|
||||
continue;
|
||||
}
|
||||
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) {
|
||||
_.label = op[1];
|
||||
break;
|
||||
}
|
||||
if (op[0] === 6 && _.label < t[1]) {
|
||||
_.label = t[1];
|
||||
t = op;
|
||||
break;
|
||||
}
|
||||
if (t && _.label < t[2]) {
|
||||
_.label = t[2];
|
||||
_.ops.push(op);
|
||||
break;
|
||||
}
|
||||
if (t[2]) _.ops.pop();
|
||||
_.trys.pop();
|
||||
continue;
|
||||
}
|
||||
op = body.call(thisArg, _);
|
||||
} catch (e) {
|
||||
op = [6, e];
|
||||
y = 0;
|
||||
} finally {
|
||||
f = t = 0;
|
||||
}
|
||||
if (op[0] & 5) throw op[1];
|
||||
return { value: op[0] ? op[1] : void 0, done: true };
|
||||
}
|
||||
};
|
||||
Object.defineProperty(exports, '__esModule', { value: true });
|
||||
exports.JobCanceledError = void 0;
|
||||
exports.isMediaJobCancelRequested = isMediaJobCancelRequested;
|
||||
exports.throwIfCancelRequested = throwIfCancelRequested;
|
||||
exports.errorMessage = errorMessage;
|
||||
var jobs_1 = require('@cio/db/queries/jobs');
|
||||
var logger_1 = require('./logger');
|
||||
/**
|
||||
* Lightweight cooperative cancellation. Workers check `isCancelRequested` at
|
||||
* step boundaries and inside long ffmpeg/fetch loops. The plan calls for
|
||||
* `cancelRequestedAt` on each run table, with the worker abandoning further
|
||||
* work the moment it observes a request.
|
||||
*/
|
||||
var JobCanceledError = /** @class */ (function (_super) {
|
||||
__extends(JobCanceledError, _super);
|
||||
function JobCanceledError(mediaJobId) {
|
||||
var _this = _super.call(this, 'Media job '.concat(mediaJobId, ' canceled by user request')) || this;
|
||||
_this.mediaJobId = mediaJobId;
|
||||
_this.name = 'JobCanceledError';
|
||||
return _this;
|
||||
}
|
||||
return JobCanceledError;
|
||||
})(Error);
|
||||
exports.JobCanceledError = JobCanceledError;
|
||||
function isMediaJobCancelRequested(mediaJobId) {
|
||||
return __awaiter(this, void 0, void 0, function () {
|
||||
var job, error_1;
|
||||
return __generator(this, function (_a) {
|
||||
switch (_a.label) {
|
||||
case 0:
|
||||
_a.trys.push([0, 2, , 3]);
|
||||
return [4 /*yield*/, (0, jobs_1.getMediaJobById)(mediaJobId)];
|
||||
case 1:
|
||||
job = _a.sent();
|
||||
return [2 /*return*/, Boolean(job === null || job === void 0 ? void 0 : job.cancelRequestedAt)];
|
||||
case 2:
|
||||
error_1 = _a.sent();
|
||||
logger_1.log.warn('cancel-check-failed', { mediaJobId: mediaJobId, error: errorMessage(error_1) });
|
||||
return [2 /*return*/, false];
|
||||
case 3:
|
||||
return [2 /*return*/];
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
function throwIfCancelRequested(mediaJobId) {
|
||||
return __awaiter(this, void 0, void 0, function () {
|
||||
return __generator(this, function (_a) {
|
||||
switch (_a.label) {
|
||||
case 0:
|
||||
return [4 /*yield*/, isMediaJobCancelRequested(mediaJobId)];
|
||||
case 1:
|
||||
if (_a.sent()) {
|
||||
throw new JobCanceledError(mediaJobId);
|
||||
}
|
||||
return [2 /*return*/];
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
function errorMessage(error) {
|
||||
return error instanceof Error ? error.message : String(error);
|
||||
}
|
||||
@@ -1,250 +0,0 @@
|
||||
'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 __generator =
|
||||
(this && this.__generator) ||
|
||||
function (thisArg, body) {
|
||||
var _ = {
|
||||
label: 0,
|
||||
sent: function () {
|
||||
if (t[0] & 1) throw t[1];
|
||||
return t[1];
|
||||
},
|
||||
trys: [],
|
||||
ops: []
|
||||
},
|
||||
f,
|
||||
y,
|
||||
t,
|
||||
g = Object.create((typeof Iterator === 'function' ? Iterator : Object).prototype);
|
||||
return (
|
||||
(g.next = verb(0)),
|
||||
(g['throw'] = verb(1)),
|
||||
(g['return'] = verb(2)),
|
||||
typeof Symbol === 'function' &&
|
||||
(g[Symbol.iterator] = function () {
|
||||
return this;
|
||||
}),
|
||||
g
|
||||
);
|
||||
function verb(n) {
|
||||
return function (v) {
|
||||
return step([n, v]);
|
||||
};
|
||||
}
|
||||
function step(op) {
|
||||
if (f) throw new TypeError('Generator is already executing.');
|
||||
while ((g && ((g = 0), op[0] && (_ = 0)), _))
|
||||
try {
|
||||
if (
|
||||
((f = 1),
|
||||
y &&
|
||||
(t = op[0] & 2 ? y['return'] : op[0] ? y['throw'] || ((t = y['return']) && t.call(y), 0) : y.next) &&
|
||||
!(t = t.call(y, op[1])).done)
|
||||
)
|
||||
return t;
|
||||
if (((y = 0), t)) op = [op[0] & 2, t.value];
|
||||
switch (op[0]) {
|
||||
case 0:
|
||||
case 1:
|
||||
t = op;
|
||||
break;
|
||||
case 4:
|
||||
_.label++;
|
||||
return { value: op[1], done: false };
|
||||
case 5:
|
||||
_.label++;
|
||||
y = op[1];
|
||||
op = [0];
|
||||
continue;
|
||||
case 7:
|
||||
op = _.ops.pop();
|
||||
_.trys.pop();
|
||||
continue;
|
||||
default:
|
||||
if (!((t = _.trys), (t = t.length > 0 && t[t.length - 1])) && (op[0] === 6 || op[0] === 2)) {
|
||||
_ = 0;
|
||||
continue;
|
||||
}
|
||||
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) {
|
||||
_.label = op[1];
|
||||
break;
|
||||
}
|
||||
if (op[0] === 6 && _.label < t[1]) {
|
||||
_.label = t[1];
|
||||
t = op;
|
||||
break;
|
||||
}
|
||||
if (t && _.label < t[2]) {
|
||||
_.label = t[2];
|
||||
_.ops.push(op);
|
||||
break;
|
||||
}
|
||||
if (t[2]) _.ops.pop();
|
||||
_.trys.pop();
|
||||
continue;
|
||||
}
|
||||
op = body.call(thisArg, _);
|
||||
} catch (e) {
|
||||
op = [6, e];
|
||||
y = 0;
|
||||
} finally {
|
||||
f = t = 0;
|
||||
}
|
||||
if (op[0] & 5) throw op[1];
|
||||
return { value: op[0] ? op[1] : void 0, done: true };
|
||||
}
|
||||
};
|
||||
var _a, _b;
|
||||
Object.defineProperty(exports, '__esModule', { value: true });
|
||||
exports.FFPROBE_BIN = exports.FFMPEG_BIN = void 0;
|
||||
exports.ffprobeJson = ffprobeJson;
|
||||
exports.ffmpegRun = ffmpegRun;
|
||||
exports.warnIfFfmpegMissing = warnIfFfmpegMissing;
|
||||
var node_child_process_1 = require('node:child_process');
|
||||
var node_util_1 = require('node:util');
|
||||
var logger_1 = require('./logger');
|
||||
var execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
|
||||
/**
|
||||
* Resolved at module load. Set `FFMPEG_PATH` / `FFPROBE_PATH` to point at
|
||||
* non-default binary locations (e.g. when ffmpeg lives outside PATH for the
|
||||
* worker process). When unset we fall through to `ffmpeg` / `ffprobe` on
|
||||
* PATH, which is what most installs provide.
|
||||
*/
|
||||
exports.FFMPEG_BIN = (_a = process.env.FFMPEG_PATH) !== null && _a !== void 0 ? _a : 'ffmpeg';
|
||||
exports.FFPROBE_BIN = (_b = process.env.FFPROBE_PATH) !== null && _b !== void 0 ? _b : 'ffprobe';
|
||||
/** Cap subprocess stdout/stderr so a runaway log can't blow up the worker. */
|
||||
var MAX_OUTPUT_BYTES = 16 * 1024 * 1024;
|
||||
/**
|
||||
* Run `ffprobe -print_format json -show_format -show_streams <file>` and
|
||||
* return the parsed JSON. Mirrors the shape we used to get from
|
||||
* `fluent-ffmpeg.ffprobe()` but without the deprecated wrapper.
|
||||
*/
|
||||
function ffprobeJson(localPath) {
|
||||
return __awaiter(this, void 0, void 0, function () {
|
||||
var args, stdout;
|
||||
return __generator(this, function (_a) {
|
||||
switch (_a.label) {
|
||||
case 0:
|
||||
args = ['-v', 'error', '-print_format', 'json', '-show_format', '-show_streams', localPath];
|
||||
return [4 /*yield*/, execFileAsync(exports.FFPROBE_BIN, args, { maxBuffer: MAX_OUTPUT_BYTES })];
|
||||
case 1:
|
||||
stdout = _a.sent().stdout;
|
||||
return [2 /*return*/, JSON.parse(stdout)];
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Spawn ffmpeg with the supplied argv. Captures stderr and surfaces a useful
|
||||
* tail in the error message when ffmpeg exits non-zero, since ffmpeg writes
|
||||
* progress and diagnostics to stderr by default.
|
||||
*/
|
||||
function ffmpegRun(args) {
|
||||
return __awaiter(this, void 0, void 0, function () {
|
||||
var error_1, stderr, tail;
|
||||
var _a, _b;
|
||||
return __generator(this, function (_c) {
|
||||
switch (_c.label) {
|
||||
case 0:
|
||||
_c.trys.push([0, 2, , 3]);
|
||||
return [4 /*yield*/, execFileAsync(exports.FFMPEG_BIN, args, { maxBuffer: MAX_OUTPUT_BYTES })];
|
||||
case 1:
|
||||
_c.sent();
|
||||
return [3 /*break*/, 3];
|
||||
case 2:
|
||||
error_1 = _c.sent();
|
||||
stderr =
|
||||
(_b =
|
||||
(_a = error_1 === null || error_1 === void 0 ? void 0 : error_1.stderr) === null || _a === void 0
|
||||
? void 0
|
||||
: _a.toString('utf8')) !== null && _b !== void 0
|
||||
? _b
|
||||
: '';
|
||||
tail = stderr.split('\n').filter(Boolean).slice(-5).join(' | ');
|
||||
throw new Error('ffmpeg exited with error'.concat(tail ? ': '.concat(tail) : ''), { cause: error_1 });
|
||||
case 3:
|
||||
return [2 /*return*/];
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Probe for the binaries at worker startup. Logs a warning when missing so
|
||||
* media jobs surface a clearer failure than a raw ENOENT from the first
|
||||
* spawn. Non-media workers (emails, maintenance) keep booting either way.
|
||||
*/
|
||||
function warnIfFfmpegMissing() {
|
||||
return __awaiter(this, void 0, void 0, function () {
|
||||
var checks, missing;
|
||||
return __generator(this, function (_a) {
|
||||
switch (_a.label) {
|
||||
case 0:
|
||||
return [4 /*yield*/, Promise.all([isExecutable(exports.FFMPEG_BIN), isExecutable(exports.FFPROBE_BIN)])];
|
||||
case 1:
|
||||
checks = _a.sent();
|
||||
missing = [];
|
||||
if (!checks[0]) missing.push(exports.FFMPEG_BIN);
|
||||
if (!checks[1]) missing.push(exports.FFPROBE_BIN);
|
||||
if (missing.length === 0) {
|
||||
logger_1.log.info('ffmpeg-binaries-resolved', { ffmpeg: exports.FFMPEG_BIN, ffprobe: exports.FFPROBE_BIN });
|
||||
return [2 /*return*/];
|
||||
}
|
||||
logger_1.log.warn('ffmpeg-binaries-missing', {
|
||||
missing: missing,
|
||||
hint: 'Install ffmpeg (macOS: brew install ffmpeg, Debian/Ubuntu: apt install ffmpeg) or set FFMPEG_PATH / FFPROBE_PATH. Media jobs will fail until this is resolved; other workers are unaffected.'
|
||||
});
|
||||
return [2 /*return*/];
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
function isExecutable(bin) {
|
||||
return __awaiter(this, void 0, void 0, function () {
|
||||
var _a;
|
||||
return __generator(this, function (_b) {
|
||||
switch (_b.label) {
|
||||
case 0:
|
||||
_b.trys.push([0, 2, , 3]);
|
||||
return [4 /*yield*/, execFileAsync(bin, ['-version'], { maxBuffer: MAX_OUTPUT_BYTES })];
|
||||
case 1:
|
||||
_b.sent();
|
||||
return [2 /*return*/, true];
|
||||
case 2:
|
||||
_a = _b.sent();
|
||||
return [2 /*return*/, false];
|
||||
case 3:
|
||||
return [2 /*return*/];
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -62,6 +62,35 @@ export async function ffmpegRun(args: string[]): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the mean luminance (`YAVG`) of an image or single video frame using
|
||||
* ffmpeg's `signalstats` filter. Returned on the original 0-255 scale; a fully
|
||||
* black frame is ~0, fully white ~255. Throws if ffmpeg fails or the value is
|
||||
* not present in stderr.
|
||||
*/
|
||||
export async function ffmpegProbeLuma(filePath: string): Promise<number> {
|
||||
const args = ['-hide_banner', '-nostats', '-i', filePath, '-vf', 'signalstats', '-f', 'null', '-'];
|
||||
let stderr = '';
|
||||
try {
|
||||
const result = await execFileAsync(FFMPEG_BIN, args, { maxBuffer: MAX_OUTPUT_BYTES });
|
||||
const raw = result.stderr as string | Buffer | undefined;
|
||||
stderr = typeof raw === 'string' ? raw : (raw?.toString('utf8') ?? '');
|
||||
} catch (error) {
|
||||
const raw = (error as { stderr?: string | Buffer })?.stderr;
|
||||
stderr = typeof raw === 'string' ? raw : (raw?.toString('utf8') ?? '');
|
||||
if (!stderr) {
|
||||
throw error as Error;
|
||||
}
|
||||
}
|
||||
|
||||
const match = stderr.match(/YAVG:([0-9]+(?:\.[0-9]+)?)/);
|
||||
if (!match) {
|
||||
throw new Error('ffmpegProbeLuma: signalstats YAVG not found in ffmpeg output');
|
||||
}
|
||||
|
||||
return Number.parseFloat(match[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Probe for the binaries at worker startup. Logs a warning when missing so
|
||||
* media jobs surface a clearer failure than a raw ENOENT from the first
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
'use strict';
|
||||
var __assign =
|
||||
(this && this.__assign) ||
|
||||
function () {
|
||||
__assign =
|
||||
Object.assign ||
|
||||
function (t) {
|
||||
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
||||
s = arguments[i];
|
||||
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
|
||||
}
|
||||
return t;
|
||||
};
|
||||
return __assign.apply(this, arguments);
|
||||
};
|
||||
Object.defineProperty(exports, '__esModule', { value: true });
|
||||
exports.log = void 0;
|
||||
function emit(level, message, fields) {
|
||||
if (fields === void 0) {
|
||||
fields = {};
|
||||
}
|
||||
var payload = __assign({ ts: new Date().toISOString(), level: level, msg: message }, fields);
|
||||
var stream = level === 'error' || level === 'warn' ? console.error : console.log;
|
||||
stream(JSON.stringify(payload));
|
||||
}
|
||||
exports.log = {
|
||||
debug: function (msg, fields) {
|
||||
return emit('debug', msg, fields);
|
||||
},
|
||||
info: function (msg, fields) {
|
||||
return emit('info', msg, fields);
|
||||
},
|
||||
warn: function (msg, fields) {
|
||||
return emit('warn', msg, fields);
|
||||
},
|
||||
error: function (msg, fields) {
|
||||
return emit('error', msg, fields);
|
||||
}
|
||||
};
|
||||
@@ -1,266 +0,0 @@
|
||||
'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 __generator =
|
||||
(this && this.__generator) ||
|
||||
function (thisArg, body) {
|
||||
var _ = {
|
||||
label: 0,
|
||||
sent: function () {
|
||||
if (t[0] & 1) throw t[1];
|
||||
return t[1];
|
||||
},
|
||||
trys: [],
|
||||
ops: []
|
||||
},
|
||||
f,
|
||||
y,
|
||||
t,
|
||||
g = Object.create((typeof Iterator === 'function' ? Iterator : Object).prototype);
|
||||
return (
|
||||
(g.next = verb(0)),
|
||||
(g['throw'] = verb(1)),
|
||||
(g['return'] = verb(2)),
|
||||
typeof Symbol === 'function' &&
|
||||
(g[Symbol.iterator] = function () {
|
||||
return this;
|
||||
}),
|
||||
g
|
||||
);
|
||||
function verb(n) {
|
||||
return function (v) {
|
||||
return step([n, v]);
|
||||
};
|
||||
}
|
||||
function step(op) {
|
||||
if (f) throw new TypeError('Generator is already executing.');
|
||||
while ((g && ((g = 0), op[0] && (_ = 0)), _))
|
||||
try {
|
||||
if (
|
||||
((f = 1),
|
||||
y &&
|
||||
(t = op[0] & 2 ? y['return'] : op[0] ? y['throw'] || ((t = y['return']) && t.call(y), 0) : y.next) &&
|
||||
!(t = t.call(y, op[1])).done)
|
||||
)
|
||||
return t;
|
||||
if (((y = 0), t)) op = [op[0] & 2, t.value];
|
||||
switch (op[0]) {
|
||||
case 0:
|
||||
case 1:
|
||||
t = op;
|
||||
break;
|
||||
case 4:
|
||||
_.label++;
|
||||
return { value: op[1], done: false };
|
||||
case 5:
|
||||
_.label++;
|
||||
y = op[1];
|
||||
op = [0];
|
||||
continue;
|
||||
case 7:
|
||||
op = _.ops.pop();
|
||||
_.trys.pop();
|
||||
continue;
|
||||
default:
|
||||
if (!((t = _.trys), (t = t.length > 0 && t[t.length - 1])) && (op[0] === 6 || op[0] === 2)) {
|
||||
_ = 0;
|
||||
continue;
|
||||
}
|
||||
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) {
|
||||
_.label = op[1];
|
||||
break;
|
||||
}
|
||||
if (op[0] === 6 && _.label < t[1]) {
|
||||
_.label = t[1];
|
||||
t = op;
|
||||
break;
|
||||
}
|
||||
if (t && _.label < t[2]) {
|
||||
_.label = t[2];
|
||||
_.ops.push(op);
|
||||
break;
|
||||
}
|
||||
if (t[2]) _.ops.pop();
|
||||
_.trys.pop();
|
||||
continue;
|
||||
}
|
||||
op = body.call(thisArg, _);
|
||||
} catch (e) {
|
||||
op = [6, e];
|
||||
y = 0;
|
||||
} finally {
|
||||
f = t = 0;
|
||||
}
|
||||
if (op[0] & 5) throw op[1];
|
||||
return { value: op[0] ? op[1] : void 0, done: true };
|
||||
}
|
||||
};
|
||||
Object.defineProperty(exports, '__esModule', { value: true });
|
||||
exports.downloadObjectToTempFile = downloadObjectToTempFile;
|
||||
exports.uploadFileToBucket = uploadFileToBucket;
|
||||
exports.uploadBufferToBucket = uploadBufferToBucket;
|
||||
exports.safeUnlink = safeUnlink;
|
||||
exports.videosBucket = videosBucket;
|
||||
exports.mediaBucket = mediaBucket;
|
||||
var client_s3_1 = require('@aws-sdk/client-s3');
|
||||
var node_fs_1 = require('node:fs');
|
||||
var promises_1 = require('node:fs/promises');
|
||||
var node_os_1 = require('node:os');
|
||||
var node_path_1 = require('node:path');
|
||||
var promises_2 = require('node:stream/promises');
|
||||
var storage_1 = require('../config/storage');
|
||||
/**
|
||||
* Streams an S3 object to a temp file on disk and returns the absolute path.
|
||||
* Workers prefer disk over buffers so ffmpeg can seek without holding the
|
||||
* whole video in RAM.
|
||||
*/
|
||||
function downloadObjectToTempFile(bucket, key, fileNameHint) {
|
||||
return __awaiter(this, void 0, void 0, function () {
|
||||
var dir, safeName, localPath, response, body;
|
||||
var _a;
|
||||
return __generator(this, function (_b) {
|
||||
switch (_b.label) {
|
||||
case 0:
|
||||
dir = node_path_1.default.join((0, node_os_1.tmpdir)(), 'cio-jobs');
|
||||
return [4 /*yield*/, (0, promises_1.mkdir)(dir, { recursive: true })];
|
||||
case 1:
|
||||
_b.sent();
|
||||
safeName =
|
||||
(_a =
|
||||
fileNameHint === null || fileNameHint === void 0
|
||||
? void 0
|
||||
: fileNameHint.replace(/[^a-zA-Z0-9._-]/g, '_')) !== null && _a !== void 0
|
||||
? _a
|
||||
: 'media';
|
||||
localPath = node_path_1.default.join(
|
||||
dir,
|
||||
''.concat(Date.now(), '_').concat(process.pid, '_').concat(safeName)
|
||||
);
|
||||
return [
|
||||
4 /*yield*/,
|
||||
(0, storage_1.getS3Client)().send(new client_s3_1.GetObjectCommand({ Bucket: bucket, Key: key }))
|
||||
];
|
||||
case 2:
|
||||
response = _b.sent();
|
||||
if (!response.Body) {
|
||||
throw new Error('S3 object '.concat(bucket, '/').concat(key, ' returned no body'));
|
||||
}
|
||||
body = response.Body;
|
||||
return [4 /*yield*/, (0, promises_2.pipeline)(body, (0, node_fs_1.createWriteStream)(localPath))];
|
||||
case 3:
|
||||
_b.sent();
|
||||
return [2 /*return*/, localPath];
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
function uploadFileToBucket(bucket, key, localPath, contentType, cacheControl) {
|
||||
return __awaiter(this, void 0, void 0, function () {
|
||||
var buffer;
|
||||
return __generator(this, function (_a) {
|
||||
switch (_a.label) {
|
||||
case 0:
|
||||
return [4 /*yield*/, (0, promises_1.readFile)(localPath)];
|
||||
case 1:
|
||||
buffer = _a.sent();
|
||||
return [
|
||||
4 /*yield*/,
|
||||
(0, storage_1.getS3Client)().send(
|
||||
new client_s3_1.PutObjectCommand({
|
||||
Bucket: bucket,
|
||||
Key: key,
|
||||
Body: buffer,
|
||||
ContentType: contentType,
|
||||
CacheControl: cacheControl
|
||||
})
|
||||
)
|
||||
];
|
||||
case 2:
|
||||
_a.sent();
|
||||
return [2 /*return*/];
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
function uploadBufferToBucket(bucket, key, buffer, contentType, cacheControl) {
|
||||
return __awaiter(this, void 0, void 0, function () {
|
||||
return __generator(this, function (_a) {
|
||||
switch (_a.label) {
|
||||
case 0:
|
||||
return [
|
||||
4 /*yield*/,
|
||||
(0, storage_1.getS3Client)().send(
|
||||
new client_s3_1.PutObjectCommand({
|
||||
Bucket: bucket,
|
||||
Key: key,
|
||||
Body: buffer,
|
||||
ContentType: contentType,
|
||||
CacheControl: cacheControl
|
||||
})
|
||||
)
|
||||
];
|
||||
case 1:
|
||||
_a.sent();
|
||||
return [2 /*return*/];
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
function safeUnlink(filePath) {
|
||||
return __awaiter(this, void 0, void 0, function () {
|
||||
var _a;
|
||||
return __generator(this, function (_b) {
|
||||
switch (_b.label) {
|
||||
case 0:
|
||||
if (!filePath) return [2 /*return*/];
|
||||
_b.label = 1;
|
||||
case 1:
|
||||
_b.trys.push([1, 3, , 4]);
|
||||
return [4 /*yield*/, (0, promises_1.unlink)(filePath)];
|
||||
case 2:
|
||||
_b.sent();
|
||||
return [3 /*break*/, 4];
|
||||
case 3:
|
||||
_a = _b.sent();
|
||||
return [3 /*break*/, 4];
|
||||
case 4:
|
||||
return [2 /*return*/];
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
function videosBucket() {
|
||||
return (0, storage_1.getStorageConfig)().bucketVideos;
|
||||
}
|
||||
function mediaBucket() {
|
||||
return (0, storage_1.getStorageConfig)().bucketMedia;
|
||||
}
|
||||
Reference in New Issue
Block a user