tirtc-devtools-cli 0.1.1 → 0.1.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.
@@ -8,6 +8,7 @@ const fs_1 = __importDefault(require("fs"));
8
8
  const path_1 = __importDefault(require("path"));
9
9
  const embedded_paths_1 = require("./embedded_paths");
10
10
  const media_assets_1 = require("./media_assets");
11
+ const progress_1 = require("./progress");
11
12
  const token_command_1 = require("./token_command");
12
13
  const role_driver_1 = require("./role_driver");
13
14
  function resolveCliVersion() {
@@ -69,12 +70,19 @@ function printError(error, options) {
69
70
  return 1;
70
71
  }
71
72
  async function runAssetsPrepare(commandOptions, options) {
73
+ const progress = new progress_1.ProgressIndicator();
74
+ progress.start('Preparing media assets');
72
75
  try {
73
76
  const result = await (0, media_assets_1.prepareMediaAssets)({
74
77
  source: commandOptions.source ?? 'runtime/assets/source.mp4',
75
78
  outputRoot: commandOptions.outputRoot ?? 'runtime/assets/.workspace',
76
79
  overwrite: false,
80
+ }, {
81
+ progress: (message) => {
82
+ progress.update(message);
83
+ },
77
84
  });
85
+ progress.succeed(result.cache_hit ? 'Media assets cache hit' : 'Prepared media assets');
78
86
  if (options.json) {
79
87
  console.log(JSON.stringify({ code: 0, message: 'OK', data: result }));
80
88
  }
@@ -84,6 +92,7 @@ async function runAssetsPrepare(commandOptions, options) {
84
92
  return 0;
85
93
  }
86
94
  catch (error) {
95
+ progress.fail('Media assets prepare failed');
87
96
  return printError(error, options);
88
97
  }
89
98
  }
@@ -17,9 +17,11 @@ type ExecFileLike = (file: string, args: string[], options: {
17
17
  stdout?: string | Buffer;
18
18
  stderr?: string | Buffer;
19
19
  }>;
20
+ type ProgressCallback = (message: string) => void;
20
21
  type PrepareMediaAssetsOptions = {
21
22
  repoRoot?: string;
22
23
  execFile?: ExecFileLike;
24
+ progress?: ProgressCallback;
23
25
  };
24
26
  export declare function prepareMediaAssets(request: PrepareMediaAssetsRequest, options?: PrepareMediaAssetsOptions): Promise<PrepareMediaAssetsResult>;
25
27
  export {};
@@ -18,6 +18,9 @@ class PrepareMediaAssetsError extends Error {
18
18
  this.reasonCode = reasonCode;
19
19
  }
20
20
  }
21
+ const prepareProgressPrefix = '[prepare_runtime_media_dataset] progress: ';
22
+ const prepareErrorPrefix = '[prepare_runtime_media_dataset] error: ';
23
+ const prepareExecMaxBuffer = 10 * 1024 * 1024;
21
24
  function resolveRepoRoot(fromDir) {
22
25
  const candidates = [
23
26
  path_1.default.resolve(fromDir, '../../..'),
@@ -44,9 +47,43 @@ function resolvePrepareScript(repoRoot) {
44
47
  }
45
48
  return path_1.default.join(repoRoot, 'runtime/script/prepare_runtime_media_dataset.sh');
46
49
  }
50
+ function appendBounded(chunks, text, currentSize, maxSize) {
51
+ const nextSize = currentSize + Buffer.byteLength(text);
52
+ if (nextSize > maxSize) {
53
+ throw new Error('media assets prepare output exceeded buffer limit');
54
+ }
55
+ chunks.push(text);
56
+ return nextSize;
57
+ }
58
+ function emitProgressLine(line, progress) {
59
+ if (!progress || !line.startsWith(prepareProgressPrefix)) {
60
+ return;
61
+ }
62
+ const message = line.slice(prepareProgressPrefix.length).trim();
63
+ if (message.length > 0) {
64
+ progress(message);
65
+ }
66
+ }
67
+ function emitBufferedProgress(stderr, progress) {
68
+ if (!stderr || !progress) {
69
+ return;
70
+ }
71
+ String(stderr)
72
+ .split(/\r?\n/)
73
+ .forEach((line) => {
74
+ emitProgressLine(line, progress);
75
+ });
76
+ }
77
+ function stripProgressLines(message) {
78
+ const lines = message.split(/\r?\n/);
79
+ const nonProgressLines = lines.filter((line) => !line.startsWith(prepareProgressPrefix));
80
+ const cleaned = nonProgressLines.join('\n').trim();
81
+ return cleaned.length > 0 ? cleaned : message.trim();
82
+ }
47
83
  function normalizeExecErrorMessage(message) {
48
- const prefix = '[prepare_runtime_media_dataset] error: ';
49
- const normalized = message.startsWith(prefix) ? message.slice(prefix.length).trim() : message;
84
+ const cleaned = stripProgressLines(message);
85
+ const errorLine = cleaned.split(/\r?\n/).find((line) => line.startsWith(prepareErrorPrefix));
86
+ const normalized = errorLine ? errorLine.slice(prepareErrorPrefix.length).trim() : cleaned;
50
87
  const reasonMatch = normalized.match(/^([a-z][a-z0-9_]*):\s*(.+)$/);
51
88
  if (reasonMatch) {
52
89
  return {
@@ -56,11 +93,14 @@ function normalizeExecErrorMessage(message) {
56
93
  }
57
94
  return { message: normalized };
58
95
  }
59
- function parseExecError(error) {
96
+ function parseExecError(error, progress) {
60
97
  if (!error || typeof error !== 'object') {
61
98
  return normalizeExecErrorMessage(String(error));
62
99
  }
63
100
  const typed = error;
101
+ if (!typed.progressEmitted) {
102
+ emitBufferedProgress(typed.stderr, progress);
103
+ }
64
104
  const stderr = typeof typed.stderr === 'string' ? typed.stderr.trim() : typed.stderr?.toString('utf8').trim();
65
105
  if (stderr && stderr.length > 0) {
66
106
  return normalizeExecErrorMessage(stderr);
@@ -85,6 +125,74 @@ function assertPrepareRequest(request) {
85
125
  throw new Error('media assets prepare requires non-empty --output-dir when provided');
86
126
  }
87
127
  }
128
+ function execPrepareWithProgress(file, args, options) {
129
+ return new Promise((resolve, reject) => {
130
+ const child = (0, child_process_1.spawn)(file, args, {
131
+ cwd: options.cwd,
132
+ env: options.env,
133
+ stdio: ['ignore', 'pipe', 'pipe'],
134
+ });
135
+ const stdoutChunks = [];
136
+ const stderrChunks = [];
137
+ let stdoutSize = 0;
138
+ let stderrSize = 0;
139
+ let stderrLineBuffer = '';
140
+ let settled = false;
141
+ function rejectOnce(error) {
142
+ if (settled) {
143
+ return;
144
+ }
145
+ settled = true;
146
+ child.kill();
147
+ reject(error);
148
+ }
149
+ child.stdout.on('data', (chunk) => {
150
+ try {
151
+ stdoutSize = appendBounded(stdoutChunks, chunk.toString('utf8'), stdoutSize, options.maxBuffer);
152
+ }
153
+ catch (error) {
154
+ rejectOnce(error instanceof Error ? error : new Error(String(error)));
155
+ }
156
+ });
157
+ child.stderr.on('data', (chunk) => {
158
+ const text = chunk.toString('utf8');
159
+ try {
160
+ stderrSize = appendBounded(stderrChunks, text, stderrSize, options.maxBuffer);
161
+ }
162
+ catch (error) {
163
+ rejectOnce(error instanceof Error ? error : new Error(String(error)));
164
+ return;
165
+ }
166
+ stderrLineBuffer += text;
167
+ const lines = stderrLineBuffer.split(/\r?\n/);
168
+ stderrLineBuffer = lines.pop() ?? '';
169
+ lines.forEach((line) => {
170
+ emitProgressLine(line, options.progress);
171
+ });
172
+ });
173
+ child.on('error', (error) => {
174
+ rejectOnce(error);
175
+ });
176
+ child.on('close', (code, signal) => {
177
+ if (settled) {
178
+ return;
179
+ }
180
+ settled = true;
181
+ if (stderrLineBuffer.length > 0) {
182
+ emitProgressLine(stderrLineBuffer, options.progress);
183
+ }
184
+ const stdout = stdoutChunks.join('');
185
+ const stderr = stderrChunks.join('');
186
+ if (code === 0) {
187
+ resolve({ stdout, stderr });
188
+ return;
189
+ }
190
+ const error = new Error(signal ? `process killed by signal ${signal}` : `process exited with code ${code}`);
191
+ Object.assign(error, { stdout, stderr, progressEmitted: true });
192
+ reject(error);
193
+ });
194
+ });
195
+ }
88
196
  async function prepareMediaAssets(request, options = {}) {
89
197
  assertPrepareRequest(request);
90
198
  const repoRoot = options.repoRoot ? path_1.default.resolve(options.repoRoot) : resolveDefaultRepoRoot();
@@ -108,18 +216,28 @@ async function prepareMediaAssets(request, options = {}) {
108
216
  }
109
217
  let stdout = '';
110
218
  try {
111
- const result = await execFile('bash', [scriptPath, ...args], {
219
+ const execOptions = {
112
220
  cwd: repoRoot,
113
221
  env: {
114
222
  ...process.env,
115
223
  TIRTC_ENSURE_FFMPEG_SCRIPT: ensureFfmpegScriptPath,
116
224
  },
117
- maxBuffer: 10 * 1024 * 1024,
118
- });
225
+ maxBuffer: prepareExecMaxBuffer,
226
+ };
227
+ const useStreamingProgress = options.progress && !options.execFile;
228
+ const result = useStreamingProgress
229
+ ? await execPrepareWithProgress('bash', [scriptPath, ...args], {
230
+ ...execOptions,
231
+ progress: options.progress,
232
+ })
233
+ : await execFile('bash', [scriptPath, ...args], execOptions);
234
+ if (!useStreamingProgress) {
235
+ emitBufferedProgress(result.stderr, options.progress);
236
+ }
119
237
  stdout = String(result.stdout ?? '').trim();
120
238
  }
121
239
  catch (error) {
122
- const parsed = parseExecError(error);
240
+ const parsed = parseExecError(error, options.progress);
123
241
  throw new PrepareMediaAssetsError(parsed.message, parsed.reasonCode);
124
242
  }
125
243
  let parsed;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tirtc-devtools-cli",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "private": false,
5
5
  "main": "dist/cli/src/index.js",
6
6
  "types": "dist/cli/src/index.d.ts",
@@ -1,6 +1,6 @@
1
1
  platform=linux-x64
2
2
  profile=full
3
- staged_at_utc=2026-04-30T09:36:57Z
3
+ staged_at_utc=2026-04-30T09:51:52Z
4
4
  source_sdk=/Users/allenfeng/Development/Repositories/tirtc-nexus/tirtc-matrix/.build/sdk/linux-x64
5
5
 
6
6
  bfc096be1484ac2b1c2776ccabfa2a4c6a982e869abf875192ff06fad85e82ca include/tirtc/audio.h
@@ -36,7 +36,7 @@ b1f4135025cb8e8520a14268d831b9998920a4239f84bb6b409d0e4909fd5bee lib/libcrypto.
36
36
  8e3aff3a400fe0971c61511947c0e69d06754002d970e21eaab2339ae38b4a42 lib/libmatrix_runtime_audio.a
37
37
  09696896a45a4fab199f0767979471c947b252fc8741a0e803624cdac6953b59 lib/libmatrix_runtime_facade.a
38
38
  2c8cc881eeeae679129360df5813dec17d0e33935f196d79cf79671dbd0c8581 lib/libmatrix_runtime_foundation_http.a
39
- e5aff9f5d9221b9cf08dd8a9144c6039d9dba7bdee3892af2d0e7f7d69633461 lib/libmatrix_runtime_foundation_logging.a
39
+ 4623c3c72a263f0397e554435e4998551c274b5b40f98f331902a8c0c8a29e3e lib/libmatrix_runtime_foundation_logging.a
40
40
  fe6b5575ce5e80f81e07a7799a05d0a8ee5a338915a28ada86d9009eee952e96 lib/libmatrix_runtime_media.a
41
41
  86d9b41f84c6bcc13f8d23a1f9b46d97fe6769573758722f9f6c8d4a130306be lib/libmatrix_runtime_transport.a
42
42
  6ac178f5cd371e9a5be1989dd1e8a3767291f4c4d9d30dcf12ecdd75a2081afa lib/libmatrix_runtime_video.a
@@ -1,6 +1,6 @@
1
1
  platform=macos-arm64
2
2
  profile=full
3
- staged_at_utc=2026-04-30T09:35:10Z
3
+ staged_at_utc=2026-04-30T09:50:11Z
4
4
  source_sdk=/Users/allenfeng/Development/Repositories/tirtc-nexus/tirtc-matrix/.build/sdk/macos-arm64
5
5
 
6
6
  bfc096be1484ac2b1c2776ccabfa2a4c6a982e869abf875192ff06fad85e82ca include/tirtc/audio.h
@@ -35,13 +35,13 @@ cae0bbeb884e5466a56da15182c78cc22baab6c743f349a58d3595f623333585 include/tirtc/
35
35
  8db86d6714264047e8fd4086ddd7315722d675749719e6175f89eb5a636b48a1 lib/libTiRTC.a
36
36
  b39daee6a3d39bf0ca20c45084601133c4198de8dca848dcff6dd9c70ae99016 lib/libcrypto.a
37
37
  c052857ef315e3d61db9c862cad10709a3a6b2487dc41799cbe4d74a805de875 lib/libcrypto.dylib
38
- 8196040f7450f650ebdfcb15abe5b08c0b7e2dcb93c9c8cb557e2a831fd032ae lib/libmatrix_runtime_audio.a
39
- 24dad84f6d7f29841cd73ef069d44d5bccf9db234bfd3adeda0ee048d1448509 lib/libmatrix_runtime_facade.a
40
- 12ccfbe34a6ed3496c9cbbdc9c58193daac90987b792eae3535743cfa76d0a67 lib/libmatrix_runtime_foundation_http.a
41
- caddced4a0d14c5256b1e53683e7655034ac57bbad0288b5a3d9451633470984 lib/libmatrix_runtime_foundation_logging.a
42
- 6f0ec3bb2586ac965c67692e8e746a030512a4f3622d2a92e5892aa8267a2e05 lib/libmatrix_runtime_media.a
43
- 31797ff96ad7a63a898e22b30c97be6cea5cd9f95bdd6354c6054666b1f96f37 lib/libmatrix_runtime_transport.a
44
- 6de3cc7602dd1da437fcf803f72d81b08a82b061bc743dcae8b72d838dfaf284 lib/libmatrix_runtime_video.a
38
+ d6f03b56a9de8db6b283acbdd9fc2805d23d672f4cb88749f55a627443b2519f lib/libmatrix_runtime_audio.a
39
+ 7524498dba92087ba33853290db0cfaa0ec79b50ffdf22c8e35b0484c24af7cc lib/libmatrix_runtime_facade.a
40
+ 05687add5f3f27adea62568b9d42be52659a494500cce0245d601da566aa45e3 lib/libmatrix_runtime_foundation_http.a
41
+ 89af61f8a48b2c7cfa9c80de8b9a708bca4ac2b1b4b2929d1d932a5c4d422501 lib/libmatrix_runtime_foundation_logging.a
42
+ 6a61357646112e1427db956ba87766838b51bdd7d8aec43ec07c2aba20570ee7 lib/libmatrix_runtime_media.a
43
+ cc39fbeacd2c8cd6b508cd3300bf8fd5b6a93f6d2d19b160b65ed03deca777c4 lib/libmatrix_runtime_transport.a
44
+ 7ab317069e6bdeb3d24d215cd7bb6f298ea47cf1cff43ac36460aaab7effbd8d lib/libmatrix_runtime_video.a
45
45
  c11c65d373a127028350c41fa58cd2d1223f2b5d70a84e13b115d90daaba25ca lib/libssl.a
46
46
  ef1c1104bbdd2528ed7b958fb7252bd6249875f92300b0c9577d6c4bd6c0d88a lib/libssl.dylib
47
47
  e14e846e43d64e240fa0e5745bf4e702b79d0f2442e7f768beb990610735c71b lib/libtgrtc.dylib
@@ -20,6 +20,10 @@ fail() {
20
20
  exit 1
21
21
  }
22
22
 
23
+ progress() {
24
+ echo "[prepare_runtime_media_dataset] progress: $*" >&2
25
+ }
26
+
23
27
  require_cmd() {
24
28
  command -v "$1" >/dev/null 2>&1 || fail "missing required command: $1"
25
29
  }
@@ -145,6 +149,7 @@ readonly k_audio_codec="g711a"
145
149
  readonly k_output_audio_format="pcm_s16"
146
150
  readonly k_output_video_format="rgba8888"
147
151
 
152
+ progress "checking source media streams"
148
153
  AUDIO_STREAM_PRESENT="$(
149
154
  AUDIO_STREAM_JSON="$("$FFPROBE_BIN" -v error -select_streams a:0 \
150
155
  -show_entries stream=index -of json "$SOURCE_ABS")" \
@@ -230,6 +235,7 @@ fi
230
235
  MANIFEST_PATH="$ASSETS_DIR/manifest.json"
231
236
 
232
237
  if [[ -f "$MANIFEST_PATH" && "$OVERWRITE" -ne 1 ]]; then
238
+ progress "using cached prepared media assets"
233
239
  emit_result "$ASSETS_DIR" "$MANIFEST_PATH" true
234
240
  exit 0
235
241
  fi
@@ -274,6 +280,7 @@ else
274
280
  video_encode_args+=(-crf 18)
275
281
  fi
276
282
 
283
+ progress "encoding h264 video track"
277
284
  "$FFMPEG_BIN" -hide_banner -loglevel error -y \
278
285
  -i "$SOURCE_ABS" \
279
286
  -an \
@@ -302,6 +309,7 @@ else
302
309
  video_h265_encode_args+=(-crf 23)
303
310
  fi
304
311
 
312
+ progress "encoding h265 video track"
305
313
  "$FFMPEG_BIN" -hide_banner -loglevel error -y \
306
314
  -i "$SOURCE_ABS" \
307
315
  -an \
@@ -311,6 +319,7 @@ fi
311
319
 
312
320
  mjpeg_frame_dir="$ASSETS_DIR/video/mjpeg_frames"
313
321
  mkdir -p "$mjpeg_frame_dir"
322
+ progress "extracting mjpeg video frames"
314
323
  "$FFMPEG_BIN" -hide_banner -loglevel error -y \
315
324
  -i "$SOURCE_ABS" \
316
325
  -vf "fps=1/${k_mjpeg_extract_interval_seconds},scale=${TARGET_VIDEO_WIDTH}:${TARGET_VIDEO_HEIGHT}:flags=lanczos" \
@@ -326,6 +335,7 @@ if ! find "$mjpeg_frame_dir" -type f -name 'frame_*.jpg' | grep -q .; then
326
335
  "$mjpeg_frame_dir/frame_000001.jpg"
327
336
  fi
328
337
 
338
+ progress "encoding 8k g711a audio track"
329
339
  "$FFMPEG_BIN" -hide_banner -loglevel error -y \
330
340
  -i "$SOURCE_ABS" \
331
341
  -vn \
@@ -335,6 +345,7 @@ fi
335
345
  -f alaw \
336
346
  "$AUDIO_PATH"
337
347
 
348
+ progress "encoding 16k g711a audio track"
338
349
  "$FFMPEG_BIN" -hide_banner -loglevel error -y \
339
350
  -i "$SOURCE_ABS" \
340
351
  -vn \
@@ -344,6 +355,7 @@ fi
344
355
  -f alaw \
345
356
  "$AUDIO_16K_PATH"
346
357
 
358
+ progress "indexing h264 video packets"
347
359
  VIDEO_PACKET_INDEX_PATH="$VIDEO_H264_PACKET_INDEX_PATH" \
348
360
  VIDEO_PATH="$VIDEO_H264_PATH" \
349
361
  VIDEO_FPS="$k_video_fps" \
@@ -441,6 +453,7 @@ for (let index = 0; index < packetOffsets.length; index += 1) {
441
453
  fs.writeFileSync(outputPath, `${lines.join('\n')}\n`, 'utf8');
442
454
  NODE
443
455
 
456
+ progress "indexing h265 video packets"
444
457
  VIDEO_PACKET_INDEX_PATH="$VIDEO_H265_PACKET_INDEX_PATH" \
445
458
  VIDEO_PATH="$VIDEO_H265_PATH" \
446
459
  VIDEO_FPS="$k_video_fps" \
@@ -538,6 +551,7 @@ for (let index = 0; index < packetOffsets.length; index += 1) {
538
551
  fs.writeFileSync(outputPath, `${lines.join('\n')}\n`, 'utf8');
539
552
  NODE
540
553
 
554
+ progress "indexing mjpeg video packets"
541
555
  VIDEO_MJPEG_PATH="$VIDEO_MJPEG_PATH" \
542
556
  VIDEO_MJPEG_PACKET_INDEX_PATH="$VIDEO_MJPEG_PACKET_INDEX_PATH" \
543
557
  MJPEG_FRAME_DIR="$mjpeg_frame_dir" \
@@ -593,6 +607,7 @@ for (let index = 0; index < packetCount; index += 1) {
593
607
  fs.writeFileSync(indexPath, `${lines.join('\n')}\n`, 'utf8');
594
608
  NODE
595
609
 
610
+ progress "indexing 8k g711a audio packets"
596
611
  AUDIO_BYTES="$(wc -c < "$AUDIO_PATH" | tr -d '[:space:]')"
597
612
  AUDIO_PACKET_BYTES=$((k_audio_sample_rate_hz * k_audio_packet_duration_ms / 1000))
598
613
  [[ "$AUDIO_PACKET_BYTES" -gt 0 ]] || fail "invalid audio packet bytes"
@@ -624,6 +639,7 @@ AUDIO_PACKET_COUNT=$((AUDIO_BYTES / AUDIO_PACKET_BYTES))
624
639
  done
625
640
  } > "$AUDIO_PACKET_INDEX_PATH"
626
641
 
642
+ progress "indexing 16k g711a audio packets"
627
643
  AUDIO_16K_BYTES="$(wc -c < "$AUDIO_16K_PATH" | tr -d '[:space:]')"
628
644
  AUDIO_16K_PACKET_BYTES=$((k_audio_sample_rate_16k_hz * k_audio_packet_duration_ms / 1000))
629
645
  [[ "$AUDIO_16K_PACKET_BYTES" -gt 0 ]] || fail "invalid 16k audio packet bytes"
@@ -655,6 +671,7 @@ AUDIO_16K_PACKET_COUNT=$((AUDIO_16K_BYTES / AUDIO_16K_PACKET_BYTES))
655
671
  done
656
672
  } > "$AUDIO_16K_PACKET_INDEX_PATH"
657
673
 
674
+ progress "writing prepared media manifest"
658
675
  cat > "$MANIFEST_PATH" <<JSON
659
676
  {
660
677
  "schema_version": ${k_schema_version},