eprec 1.13.0 → 1.13.1

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.
@@ -75,8 +75,7 @@ function parseTimestampInput(value: string) {
75
75
  if (parts.length !== 2 && parts.length !== 3) return null
76
76
  const secondsPart = Number.parseFloat(parts[parts.length - 1] ?? '')
77
77
  const minutesPart = Number.parseFloat(parts[parts.length - 2] ?? '')
78
- const hoursPart =
79
- parts.length === 3 ? Number.parseFloat(parts[0] ?? '') : 0
78
+ const hoursPart = parts.length === 3 ? Number.parseFloat(parts[0] ?? '') : 0
80
79
  if (
81
80
  !Number.isFinite(secondsPart) ||
82
81
  !Number.isFinite(minutesPart) ||
@@ -99,7 +98,9 @@ function classNames(...values: Array<string | false | null | undefined>) {
99
98
  export function TrimPoints(handle: Handle) {
100
99
  const initialVideoPath = readInitialVideoPath()
101
100
  let videoPathInput = initialVideoPath
102
- let outputPathInput = initialVideoPath ? buildOutputPath(initialVideoPath) : ''
101
+ let outputPathInput = initialVideoPath
102
+ ? buildOutputPath(initialVideoPath)
103
+ : ''
103
104
  let pathStatus: 'idle' | 'loading' | 'ready' | 'error' = initialVideoPath
104
105
  ? 'loading'
105
106
  : 'idle'
@@ -117,9 +118,11 @@ export function TrimPoints(handle: Handle) {
117
118
  let trimRanges: TrimRangeWithId[] = []
118
119
  let selectedRangeId: string | null = null
119
120
  let rangeCounter = 1
120
- let activeDrag:
121
- | { rangeId: string; edge: 'start' | 'end'; pointerId: number }
122
- | null = null
121
+ let activeDrag: {
122
+ rangeId: string
123
+ edge: 'start' | 'end'
124
+ pointerId: number
125
+ } | null = null
123
126
  let runStatus: 'idle' | 'running' | 'success' | 'error' = 'idle'
124
127
  let runProgress = 0
125
128
  let runError = ''
@@ -155,6 +158,9 @@ export function TrimPoints(handle: Handle) {
155
158
  previewReady = false
156
159
  previewError = ''
157
160
  previewDuration = 0
161
+ trimRanges = []
162
+ selectedRangeId = null
163
+ activeDrag = null
158
164
  }
159
165
 
160
166
  const syncVideoToTime = (
@@ -202,8 +208,7 @@ export function TrimPoints(handle: Handle) {
202
208
  const width = waveformNode.clientWidth
203
209
  const height = waveformNode.clientHeight
204
210
  if (width <= 0 || height <= 0) return
205
- const dpr =
206
- typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1
211
+ const dpr = typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1
207
212
  waveformNode.width = Math.floor(width * dpr)
208
213
  waveformNode.height = Math.floor(height * dpr)
209
214
  ctx.setTransform(dpr, 0, 0, dpr, 0, 0)
@@ -271,17 +276,13 @@ export function TrimPoints(handle: Handle) {
271
276
  audioBuffer.getChannelData(index),
272
277
  )
273
278
  const totalSamples = audioBuffer.length
274
- const sampleCount = Math.max(
275
- 1,
276
- Math.min(WAVEFORM_SAMPLES, totalSamples),
277
- )
279
+ const sampleCount = Math.max(1, Math.min(WAVEFORM_SAMPLES, totalSamples))
278
280
  const blockSize = Math.max(1, Math.floor(totalSamples / sampleCount))
279
281
  const samples = new Array(sampleCount).fill(0)
280
282
  let maxValue = 0
281
283
  for (let i = 0; i < sampleCount; i++) {
282
284
  const start = i * blockSize
283
- const end =
284
- i === sampleCount - 1 ? totalSamples : start + blockSize
285
+ const end = i === sampleCount - 1 ? totalSamples : start + blockSize
285
286
  let peak = 0
286
287
  for (let j = start; j < end; j++) {
287
288
  let sum = 0
@@ -306,9 +307,7 @@ export function TrimPoints(handle: Handle) {
306
307
  if (waveformSource !== fetchedUrl) return
307
308
  waveformStatus = 'error'
308
309
  waveformError =
309
- error instanceof Error
310
- ? error.message
311
- : 'Unable to render waveform.'
310
+ error instanceof Error ? error.message : 'Unable to render waveform.'
312
311
  handle.update()
313
312
  }
314
313
  }
@@ -415,9 +414,7 @@ export function TrimPoints(handle: Handle) {
415
414
  trimRanges = sortRanges(
416
415
  trimRanges.map((range) => {
417
416
  if (range.id !== rangeId) return range
418
- let nextStart = Number.isFinite(patch.start)
419
- ? patch.start
420
- : range.start
417
+ let nextStart = Number.isFinite(patch.start) ? patch.start : range.start
421
418
  let nextEnd = Number.isFinite(patch.end) ? patch.end : range.end
422
419
  if (edge === 'start') {
423
420
  nextStart = clamp(
@@ -425,11 +422,7 @@ export function TrimPoints(handle: Handle) {
425
422
  0,
426
423
  Math.max(previewDuration - MIN_TRIM_LENGTH, 0),
427
424
  )
428
- nextEnd = clamp(
429
- nextEnd,
430
- nextStart + MIN_TRIM_LENGTH,
431
- previewDuration,
432
- )
425
+ nextEnd = clamp(nextEnd, nextStart + MIN_TRIM_LENGTH, previewDuration)
433
426
  } else if (edge === 'end') {
434
427
  nextEnd = clamp(nextEnd, MIN_TRIM_LENGTH, previewDuration)
435
428
  nextStart = clamp(nextStart, 0, nextEnd - MIN_TRIM_LENGTH)
@@ -506,10 +499,8 @@ export function TrimPoints(handle: Handle) {
506
499
  range: TrimRangeWithId,
507
500
  edge: 'start' | 'end',
508
501
  ) => {
509
- const isForward =
510
- event.key === 'ArrowUp' || event.key === 'ArrowRight'
511
- const isBackward =
512
- event.key === 'ArrowDown' || event.key === 'ArrowLeft'
502
+ const isForward = event.key === 'ArrowUp' || event.key === 'ArrowRight'
503
+ const isBackward = event.key === 'ArrowDown' || event.key === 'ArrowLeft'
513
504
  if (!isForward && !isBackward) return
514
505
  event.preventDefault()
515
506
  const step = event.shiftKey ? SHIFT_STEP : KEYBOARD_STEP
@@ -693,7 +684,9 @@ export function TrimPoints(handle: Handle) {
693
684
  MIN_TRIM_LENGTH,
694
685
  )
695
686
  const commandPreview =
696
- videoPathInput.trim() && outputPathInput.trim() && normalizedRanges.length > 0
687
+ videoPathInput.trim() &&
688
+ outputPathInput.trim() &&
689
+ normalizedRanges.length > 0
697
690
  ? buildFfmpegCommandPreview({
698
691
  inputPath: videoPathInput.trim(),
699
692
  outputPath: outputPathInput.trim(),
@@ -731,8 +724,8 @@ export function TrimPoints(handle: Handle) {
731
724
  <div>
732
725
  <h2>Video source</h2>
733
726
  <p class="app-muted">
734
- Load a local video file to calculate the trim timeline and output
735
- command.
727
+ Load a local video file to calculate the trim timeline and
728
+ output command.
736
729
  </p>
737
730
  </div>
738
731
  <span
@@ -938,7 +931,8 @@ export function TrimPoints(handle: Handle) {
938
931
  </button>
939
932
  </div>
940
933
  <p class="app-muted trim-hint" id={hintId}>
941
- Use arrow keys to nudge by {KEYBOARD_STEP}s. Hold Shift for {SHIFT_STEP}
934
+ Use arrow keys to nudge by {KEYBOARD_STEP}s. Hold Shift for{' '}
935
+ {SHIFT_STEP}
942
936
  s.
943
937
  </p>
944
938
  <div
@@ -998,8 +992,7 @@ export function TrimPoints(handle: Handle) {
998
992
  on={{
999
993
  focus: () =>
1000
994
  syncVideoToTime(range.start, { updateInput: true }),
1001
- pointerdown: (event) =>
1002
- startDrag(event, range.id, 'start'),
995
+ pointerdown: (event) => startDrag(event, range.id, 'start'),
1003
996
  pointermove: moveDrag,
1004
997
  pointerup: endDrag,
1005
998
  pointercancel: endDrag,
@@ -1086,9 +1079,7 @@ export function TrimPoints(handle: Handle) {
1086
1079
  <section class="app-card">
1087
1080
  <div class="panel-header">
1088
1081
  <h2>Trim ranges</h2>
1089
- <span class="summary-subtext">
1090
- {sortedRanges.length} total
1091
- </span>
1082
+ <span class="summary-subtext">{sortedRanges.length} total</span>
1092
1083
  </div>
1093
1084
  {sortedRanges.length === 0 ? (
1094
1085
  <p class="app-muted">
@@ -1211,9 +1202,7 @@ export function TrimPoints(handle: Handle) {
1211
1202
  <div class="summary-item">
1212
1203
  <span class="summary-label">Output length</span>
1213
1204
  <span class="summary-value">
1214
- {previewReady
1215
- ? formatTimestamp(outputDuration)
1216
- : '--:--.--'}
1205
+ {previewReady ? formatTimestamp(outputDuration) : '--:--.--'}
1217
1206
  </span>
1218
1207
  <span class="summary-subtext">
1219
1208
  {previewReady && duration > 0
@@ -15,8 +15,8 @@ const trimPointsHandler = {
15
15
  <span class="app-kicker">Eprec Studio</span>
16
16
  <h1 class="app-title">Trim points</h1>
17
17
  <p class="app-subtitle">
18
- Add start and stop points, generate an ffmpeg trim command, and run
19
- it with live progress.
18
+ Add start and stop points, generate an ffmpeg trim command, and
19
+ run it with live progress.
20
20
  </p>
21
21
  <nav class="app-nav">
22
22
  <a class="app-link" href="/">Editing workspace</a>
package/app/trim-api.ts CHANGED
@@ -45,7 +45,10 @@ function parseRanges(ranges: unknown): TrimRange[] {
45
45
  .map((entry) => {
46
46
  if (!entry || typeof entry !== 'object') return null
47
47
  const candidate = entry as TrimRange
48
- if (!Number.isFinite(candidate.start) || !Number.isFinite(candidate.end)) {
48
+ if (
49
+ !Number.isFinite(candidate.start) ||
50
+ !Number.isFinite(candidate.end)
51
+ ) {
49
52
  return null
50
53
  }
51
54
  return { start: candidate.start, end: candidate.end }
@@ -178,9 +181,7 @@ export async function handleTrimRequest(request: Request): Promise<Response> {
178
181
  let outTimeSeconds = 0
179
182
  const send = (payload: Record<string, unknown>) => {
180
183
  try {
181
- controller.enqueue(
182
- encoder.encode(`${JSON.stringify(payload)}\n`),
183
- )
184
+ controller.enqueue(encoder.encode(`${JSON.stringify(payload)}\n`))
184
185
  } catch {
185
186
  // stream closed
186
187
  }
@@ -191,63 +192,63 @@ export async function handleTrimRequest(request: Request): Promise<Response> {
191
192
  stderr: 'pipe',
192
193
  })
193
194
 
194
- request.signal.addEventListener('abort', () => {
195
- try {
196
- process.kill()
197
- } catch {
198
- // ignore
199
- }
200
- })
195
+ request.signal.addEventListener('abort', () => {
196
+ try {
197
+ process.kill()
198
+ } catch {
199
+ // ignore
200
+ }
201
+ })
201
202
 
202
- const stdoutReader = readLines(process.stdout, (line) => {
203
- const [key, rawValue] = line.split('=')
204
- const value = rawValue ?? ''
205
- if (key === 'out_time_ms') {
206
- const next = Number.parseFloat(value)
207
- if (Number.isFinite(next)) outTimeSeconds = next / 1000
208
- }
209
- if (key === 'out_time_us') {
210
- const next = Number.parseFloat(value)
211
- if (Number.isFinite(next)) outTimeSeconds = next / 1000000
212
- }
213
- if (key === 'out_time') {
214
- const parsed = parseOutTimeValue(value)
215
- if (parsed !== null) outTimeSeconds = parsed
216
- }
217
- if (key === 'progress') {
218
- const progress =
219
- outputDurationSeconds > 0
220
- ? clamp(outTimeSeconds / outputDurationSeconds, 0, 1)
221
- : 0
222
- send({ type: 'progress', progress })
223
- if (value === 'end') {
224
- send({ type: 'progress', progress: 1 })
203
+ const stdoutReader = readLines(process.stdout, (line) => {
204
+ const [key, rawValue] = line.split('=')
205
+ const value = rawValue ?? ''
206
+ if (key === 'out_time_ms') {
207
+ const next = Number.parseFloat(value)
208
+ if (Number.isFinite(next)) outTimeSeconds = next / 1000
225
209
  }
226
- }
227
- })
210
+ if (key === 'out_time_us') {
211
+ const next = Number.parseFloat(value)
212
+ if (Number.isFinite(next)) outTimeSeconds = next / 1000000
213
+ }
214
+ if (key === 'out_time') {
215
+ const parsed = parseOutTimeValue(value)
216
+ if (parsed !== null) outTimeSeconds = parsed
217
+ }
218
+ if (key === 'progress') {
219
+ const progress =
220
+ outputDurationSeconds > 0
221
+ ? clamp(outTimeSeconds / outputDurationSeconds, 0, 1)
222
+ : 0
223
+ send({ type: 'progress', progress })
224
+ if (value === 'end') {
225
+ send({ type: 'progress', progress: 1 })
226
+ }
227
+ }
228
+ })
228
229
 
229
- const stderrReader = readLines(process.stderr, (line) => {
230
- send({ type: 'log', message: line })
231
- })
230
+ const stderrReader = readLines(process.stderr, (line) => {
231
+ send({ type: 'log', message: line })
232
+ })
232
233
 
233
- Promise.all([stdoutReader, stderrReader, process.exited])
234
- .then(([, , exitCode]) => {
235
- send({
236
- type: 'done',
237
- success: exitCode === 0,
238
- exitCode,
234
+ Promise.all([stdoutReader, stderrReader, process.exited])
235
+ .then(([, , exitCode]) => {
236
+ send({
237
+ type: 'done',
238
+ success: exitCode === 0,
239
+ exitCode,
240
+ })
239
241
  })
240
- })
241
- .catch((error) => {
242
- send({
243
- type: 'done',
244
- success: false,
245
- error: error instanceof Error ? error.message : String(error),
242
+ .catch((error) => {
243
+ send({
244
+ type: 'done',
245
+ success: false,
246
+ error: error instanceof Error ? error.message : String(error),
247
+ })
248
+ })
249
+ .finally(() => {
250
+ controller.close()
246
251
  })
247
- })
248
- .finally(() => {
249
- controller.close()
250
- })
251
252
  },
252
253
  })
253
254
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "eprec",
3
3
  "type": "module",
4
- "version": "1.13.0",
4
+ "version": "1.13.1",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
@@ -8,6 +8,7 @@ import {
8
8
  createInquirerPrompter,
9
9
  createPathPicker,
10
10
  createStepProgressReporter,
11
+ handlePromptFailure,
11
12
  isInteractive,
12
13
  pauseActiveSpinner,
13
14
  resolveOptionalString,
@@ -373,6 +374,8 @@ export async function runEditsCli(rawArgs = hideBin(process.argv)) {
373
374
  )
374
375
  .demandCommand(1)
375
376
  .strict()
377
+ .fail(handlePromptFailure)
378
+ .exitProcess(false)
376
379
  .help()
377
380
 
378
381
  await parser.parseAsync()
package/src/cli.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env bun
2
2
  import path from 'node:path'
3
- import type { Arguments, CommandBuilder, CommandHandler } from 'yargs'
3
+ import type { Argv, Arguments, CommandBuilder, CommandHandler } from 'yargs'
4
4
  import yargs from 'yargs/yargs'
5
5
  import { hideBin } from 'yargs/helpers'
6
6
  import { startAppServer } from './app-server'
@@ -28,6 +28,7 @@ import {
28
28
  createInquirerPrompter,
29
29
  createPathPicker,
30
30
  createStepProgressReporter,
31
+ handlePromptFailure,
31
32
  isInteractive,
32
33
  pauseActiveSpinner,
33
34
  resumeActiveSpinner,
@@ -249,6 +250,8 @@ async function main(rawArgs = hideBin(process.argv)) {
249
250
  )
250
251
  .demandCommand(1)
251
252
  .strict()
253
+ .fail(handlePromptFailure)
254
+ .exitProcess(false)
252
255
  .help()
253
256
 
254
257
  await parser.parseAsync()