dashcam 1.3.31 → 1.4.0-beta

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.
@@ -4,7 +4,7 @@
4
4
  * This script runs detached from the parent process to handle long-running recordings
5
5
  */
6
6
 
7
- import { startRecording, stopRecording } from '../lib/recorder.js';
7
+ import { startRecording } from '../lib/recorder.js';
8
8
  import { logger, setVerbose } from '../lib/logger.js';
9
9
  import fs from 'fs';
10
10
  import path from 'path';
@@ -161,56 +161,13 @@ async function runBackgroundRecording() {
161
161
  }
162
162
  isShuttingDown = true;
163
163
 
164
- logger.info(`Received ${signal}, stopping background recording...`);
165
- console.log('[Background] Received stop signal, stopping recording...');
164
+ logger.info(`Received ${signal}, background process will be killed`);
165
+ console.log('[Background] Received stop signal, process will be terminated...');
166
166
 
167
- try {
168
- // Stop the recording
169
- const stopResult = await stopRecording();
170
-
171
- if (stopResult) {
172
- logger.info('Recording stopped successfully', {
173
- outputPath: stopResult.outputPath,
174
- duration: stopResult.duration
175
- });
176
- console.log('[Background] Recording stopped successfully:', {
177
- outputPath: stopResult.outputPath,
178
- duration: stopResult.duration
179
- });
180
-
181
- // Write recording result for stop command to upload
182
- console.log('[Background] Writing recording result for stop command...');
183
- writeRecordingResult({
184
- outputPath: stopResult.outputPath,
185
- duration: stopResult.duration,
186
- clientStartDate: stopResult.clientStartDate,
187
- apps: stopResult.apps,
188
- logs: stopResult.logs,
189
- gifPath: stopResult.gifPath,
190
- snapshotPath: stopResult.snapshotPath,
191
- // Include options so stop command can use them for upload
192
- title: options.title,
193
- description: options.description,
194
- project: options.project || options.k
195
- });
196
- console.log('[Background] Recording result written successfully');
197
- }
198
-
199
- // Update status to indicate recording stopped
200
- writeStatus({
201
- isRecording: false,
202
- completedTime: Date.now(),
203
- pid: process.pid
204
- });
205
-
206
- console.log('[Background] Background process exiting successfully');
207
- logger.info('Background process exiting successfully');
208
- process.exit(0);
209
- } catch (error) {
210
- console.error('[Background] Error during shutdown:', error.message);
211
- logger.error('Error during shutdown:', error);
212
- process.exit(1);
213
- }
167
+ // Don't try to stop recording here - the main process will handle cleanup
168
+ // after killing this process. Just exit.
169
+ logger.info('Background process exiting');
170
+ process.exit(0);
214
171
  };
215
172
 
216
173
  // Register signal handlers
package/bin/dashcam.js CHANGED
@@ -137,7 +137,7 @@ async function recordingAction(options, command) {
137
137
 
138
138
  // Add timeout to prevent hanging
139
139
  const startRecordingPromise = processManager.startRecording({
140
- fps: parseInt(options.fps) || 30,
140
+ fps: parseInt(options.fps) || 10,
141
141
  audio: options.audio,
142
142
  output: options.output,
143
143
  title: options.title,
@@ -241,7 +241,7 @@ program
241
241
  .command('record')
242
242
  .description('Start a recording terminal to be included in your dashcam video recording')
243
243
  .option('-a, --audio', 'Include audio in the recording')
244
- .option('-f, --fps <fps>', 'Frames per second (default: 30)', '30')
244
+ .option('-f, --fps <fps>', 'Frames per second (default: 10)', '10')
245
245
  .option('-o, --output <path>', 'Custom output path')
246
246
  .option('-t, --title <title>', 'Title for the recording')
247
247
  .option('-d, --description <description>', 'Description for the recording (supports markdown)')
@@ -484,9 +484,36 @@ program
484
484
  logger.error('Failed to read temp file info', { error });
485
485
  }
486
486
 
487
- const tempFile = tempFileInfo?.tempFile || result.outputPath.replace('.webm', '-temp.webm');
487
+ // Fallback: look for temp files in the same directory
488
+ let tempFile = tempFileInfo?.tempFile;
489
+ if (!tempFile || !fs.existsSync(tempFile)) {
490
+ // Try to find any temp-*.webm file in the output directory
491
+ const outputDir = path.dirname(result.outputPath);
492
+ if (fs.existsSync(outputDir)) {
493
+ const files = fs.readdirSync(outputDir);
494
+ const tempFiles = files.filter(f => f.startsWith('temp-') && f.endsWith('.webm'));
495
+ if (tempFiles.length > 0) {
496
+ // Use the most recent temp file
497
+ tempFiles.sort().reverse();
498
+ tempFile = path.join(outputDir, tempFiles[0]);
499
+ logger.info('Found temp file in output directory', { tempFile });
500
+ }
501
+ }
502
+ }
503
+
488
504
  const outputPath = result.outputPath;
489
505
 
506
+ // Verify we have a temp file to work with
507
+ if (!tempFile) {
508
+ console.error('No temp file found for recording');
509
+ logger.error('Temp file missing', {
510
+ tempFileInfo,
511
+ outputDir: path.dirname(result.outputPath),
512
+ expectedPattern: 'temp-*.webm'
513
+ });
514
+ process.exit(1);
515
+ }
516
+
490
517
  // Fix the video with FFmpeg (handle incomplete recordings)
491
518
  logger.info('Fixing video with FFmpeg', { tempFile, outputPath });
492
519
  if (fs.existsSync(tempFile)) {
@@ -502,14 +529,129 @@ program
502
529
  logger.warn('Temp file not found, using output path directly', { tempFile });
503
530
  }
504
531
 
505
- // We killed the process, so we don't have apps/logs data
506
- // The recording is still on disk and can be uploaded without metadata
532
+ // Verify the output file exists before proceeding
533
+ if (!fs.existsSync(outputPath)) {
534
+ console.error('Recording file not found after processing');
535
+ logger.error('Output file missing', { outputPath, tempFile, tempFileExists: fs.existsSync(tempFile) });
536
+ process.exit(1);
537
+ }
538
+
539
+ logger.info('Output file ready for upload', {
540
+ outputPath,
541
+ size: fs.statSync(outputPath).size
542
+ });
543
+
544
+ // Generate paths for additional assets
545
+ const basePath = outputPath.substring(0, outputPath.lastIndexOf('.'));
546
+ const gifPath = `${basePath}.gif`;
547
+ const snapshotPath = `${basePath}.png`;
548
+
549
+ // Create GIF and snapshot (non-blocking, don't fail if these fail)
550
+ console.log('Creating preview assets...');
551
+ const { createGif, createSnapshot } = await import('../lib/ffmpeg.js');
552
+
553
+ // Add timeout wrapper to prevent hanging on large files
554
+ const withTimeout = (promise, timeoutMs, name) => {
555
+ return Promise.race([
556
+ promise,
557
+ new Promise((_, reject) =>
558
+ setTimeout(() => reject(new Error(`${name} timed out after ${timeoutMs}ms`)), timeoutMs)
559
+ )
560
+ ]);
561
+ };
562
+
563
+ try {
564
+ await Promise.all([
565
+ withTimeout(
566
+ createGif(outputPath, gifPath),
567
+ 30000, // 30 second timeout
568
+ 'GIF creation'
569
+ ).catch(err => {
570
+ logger.warn('Failed to create GIF', { error: err.message });
571
+ }),
572
+ withTimeout(
573
+ createSnapshot(outputPath, snapshotPath, 0),
574
+ 10000, // 10 second timeout
575
+ 'Snapshot creation'
576
+ ).catch(err => {
577
+ logger.warn('Failed to create snapshot', { error: err.message });
578
+ })
579
+ ]);
580
+ logger.debug('Preview assets created successfully');
581
+ } catch (error) {
582
+ logger.warn('Failed to create preview assets', { error: error.message });
583
+ }
584
+
585
+ // Collect logs and app tracking data
586
+ console.log('Collecting tracking data...');
587
+
588
+ // Get app tracking data
589
+ const { applicationTracker } = await import('../lib/applicationTracker.js');
590
+ const appTrackingResults = applicationTracker.stop();
591
+ logger.info('Collected app tracking results', {
592
+ apps: appTrackingResults.apps?.length || 0,
593
+ icons: appTrackingResults.icons?.length || 0,
594
+ events: appTrackingResults.events?.length || 0
595
+ });
596
+
597
+ // Get log tracking data - read directly from files since background process was killed
598
+ const recordingDir = path.dirname(outputPath);
599
+ let logTrackingResults = [];
600
+
601
+ try {
602
+ // Check for CLI logs file
603
+ const cliLogsFile = path.join(recordingDir, 'dashcam_logs_cli.jsonl');
604
+ if (fs.existsSync(cliLogsFile)) {
605
+ const { jsonl } = await import('../lib/utilities/jsonl.js');
606
+ const cliLogs = jsonl.read(cliLogsFile);
607
+ if (cliLogs && cliLogs.length > 0) {
608
+ logTrackingResults.push({
609
+ type: 'cli',
610
+ name: 'CLI Logs',
611
+ fileLocation: cliLogsFile,
612
+ count: cliLogs.length,
613
+ trimmedFileLocation: cliLogsFile
614
+ });
615
+ logger.info('Found CLI logs', { count: cliLogs.length, file: cliLogsFile });
616
+ }
617
+ }
618
+
619
+ // Check for web logs file
620
+ const webLogsFile = path.join(recordingDir, 'dashcam_logs_web_events.jsonl');
621
+ if (fs.existsSync(webLogsFile)) {
622
+ const { jsonl } = await import('../lib/utilities/jsonl.js');
623
+ const webLogs = jsonl.read(webLogsFile);
624
+ if (webLogs && webLogs.length > 0) {
625
+ logTrackingResults.push({
626
+ type: 'web',
627
+ name: 'Web Logs',
628
+ fileLocation: webLogsFile,
629
+ count: webLogs.length,
630
+ trimmedFileLocation: webLogsFile
631
+ });
632
+ logger.info('Found web logs', { count: webLogs.length, file: webLogsFile });
633
+ }
634
+ }
635
+
636
+ logger.info('Collected log tracking results', {
637
+ trackers: logTrackingResults.length,
638
+ totalEvents: logTrackingResults.reduce((sum, result) => sum + result.count, 0)
639
+ });
640
+ } catch (error) {
641
+ logger.warn('Failed to collect log tracking results', { error: error.message });
642
+ }
643
+
644
+ // The recording is on disk and can be uploaded with full metadata
507
645
  const recordingResult = {
508
646
  outputPath,
647
+ gifPath,
648
+ snapshotPath,
509
649
  duration: result.duration,
650
+ fileSize: fs.statSync(outputPath).size,
510
651
  clientStartDate: activeStatus.startTime,
511
- apps: [],
512
- logs: [],
652
+ apps: appTrackingResults.apps,
653
+ icons: appTrackingResults.icons,
654
+ logs: logTrackingResults,
513
655
  title: activeStatus?.options?.title,
514
656
  description: activeStatus?.options?.description,
515
657
  project: activeStatus?.options?.project
@@ -532,6 +674,7 @@ program
532
674
  duration: recordingResult.duration,
533
675
  clientStartDate: recordingResult.clientStartDate,
534
676
  apps: recordingResult.apps,
677
+ icons: recordingResult.icons,
535
678
  logs: recordingResult.logs,
536
679
  gifPath: recordingResult.gifPath,
537
680
  snapshotPath: recordingResult.snapshotPath
@@ -749,7 +892,7 @@ program
749
892
  if (!targetFile) {
750
893
  console.error('Please provide a file path or use --recover option');
751
894
  console.log('Examples:');
752
- console.log(' dashcam upload /path/to/recording.webm');
895
+ console.log(' dashcam upload /path/to/recording.mp4');
753
896
  console.log(' dashcam upload --recover');
754
897
  process.exit(1);
755
898
  }
package/lib/ffmpeg.js CHANGED
@@ -72,8 +72,8 @@ export async function createGif(inputVideoPath, outputGifPath) {
72
72
  throw new Error('Video file is not ready or is corrupted');
73
73
  }
74
74
 
75
- const gifFps = 4;
76
- const gifDuration = 10;
75
+ const gifFps = 2; // Reduced from 4 to 2 fps for faster generation
76
+ const gifDuration = 5; // Reduced from 10 to 5 seconds for faster generation
77
77
  const gifFrames = Math.ceil(gifDuration * gifFps);
78
78
 
79
79
  // Get video duration in seconds
@@ -94,8 +94,8 @@ export async function createGif(inputVideoPath, outputGifPath) {
94
94
  stdout
95
95
  });
96
96
 
97
- // Fallback: Single-pass GIF creation at fixed rate
98
- const filters = `fps=1/3,scale=640:-1:flags=lanczos,split[s0][s1];[s0]palettegen=max_colors=128[p];[s1][p]paletteuse=dither=bayer:bayer_scale=5`;
97
+ // Fallback: Fast GIF creation with reduced quality
98
+ const filters = `fps=2,scale=480:-1:flags=fast_bilinear,split[s0][s1];[s0]palettegen=max_colors=64[p];[s1][p]paletteuse=dither=none`;
99
99
 
100
100
  await execa(ffmpegPath, [
101
101
  '-i', inputVideoPath,
@@ -111,8 +111,12 @@ export async function createGif(inputVideoPath, outputGifPath) {
111
111
 
112
112
  const extractedFramesInterval = videoDuration / gifFrames;
113
113
 
114
- // Single-pass GIF creation with palette for better quality and performance
115
- const filters = `fps=1/${extractedFramesInterval},scale=640:-1:flags=lanczos,split[s0][s1];[s0]palettegen=max_colors=128[p];[s1][p]paletteuse=dither=bayer:bayer_scale=5`;
114
+ // Fast GIF creation with reduced quality for speed
115
+ // - Smaller scale (480 vs 640)
116
+ // - Fewer colors (64 vs 128)
117
+ // - Faster scaling algorithm (fast_bilinear vs lanczos)
118
+ // - No dithering for faster processing
119
+ const filters = `fps=1/${extractedFramesInterval},scale=480:-1:flags=fast_bilinear,split[s0][s1];[s0]palettegen=max_colors=64[p];[s1][p]paletteuse=dither=none`;
116
120
 
117
121
  await execa(ffmpegPath, [
118
122
  '-i', inputVideoPath,
package/lib/recorder.js CHANGED
@@ -27,7 +27,7 @@ async function fixVideoContainer(inputVideoPath, outputVideoPath) {
27
27
  '-i', inputVideoPath,
28
28
  '-vcodec', 'copy', // Copy video stream without re-encoding
29
29
  '-acodec', 'copy', // Copy audio stream without re-encoding
30
- '-movflags', 'faststart', // Enable fast start for web playback
30
+ '-f', 'webm', // Force WebM format
31
31
  outputVideoPath,
32
32
  '-y', // Overwrite output file
33
33
  '-hide_banner'
@@ -320,8 +320,9 @@ export async function startRecording({
320
320
 
321
321
  // Construct FFmpeg command arguments
322
322
  const platformArgs = await getPlatformArgs({ fps, includeAudio });
323
+
323
324
  const outputArgs = [
324
- '-c:v', 'libvpx-vp9', // Use VP9 codec for better quality and compression
325
+ '-c:v', 'libvpx', // Use VP9 codec for better quality and compression
325
326
  '-quality', 'realtime', // Use realtime quality preset for faster encoding
326
327
  '-cpu-used', '8', // Maximum speed (0-8, higher = faster but lower quality)
327
328
  '-deadline', 'realtime',// Realtime encoding mode for lowest latency
@@ -340,7 +341,7 @@ export async function startRecording({
340
341
 
341
342
  if (includeAudio) {
342
343
  outputArgs.push(
343
- '-c:a', 'libopus', // Opus audio codec for WebM
344
+ '-c:a', 'libvorbis', // Vorbis audio codec for WebM
344
345
  '-b:a', '128k'
345
346
  );
346
347
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dashcam",
3
- "version": "1.3.31",
3
+ "version": "1.4.0-beta",
4
4
  "description": "Minimal CLI version of Dashcam desktop app",
5
5
  "main": "bin/dashcam.js",
6
6
  "bin": {
package/test_workflow.sh CHANGED
@@ -34,12 +34,11 @@ echo "✅ File tracking configured"
34
34
  echo ""
35
35
  echo "4. Starting dashcam recording in background..."
36
36
  # Start recording and redirect output to a log file so we can still monitor it
37
- ./bin/dashcam.js record --title "Sync Test Recording" --description "Testing video/log synchronization with timestamped events" > /tmp/dashcam-recording.log 2>&1 &
38
- RECORD_PID=$!
37
+ ./bin/dashcam.js record --title "Sync Test Recording" --description "Testing video/log synchronization with timestamped events" > /tmp/dashcam-recording.log 2>&1
39
38
 
40
39
  # Wait for recording to initialize and log tracker to start
41
- echo "Waiting for recording to initialize (PID: $RECORD_PID)..."
42
- sleep 1
40
+ echo "Waiting for recording to initialize..."
41
+ sleep 2
43
42
 
44
43
  # Write first event after log tracker is fully ready
45
44
  RECORDING_START=$(date +%s)
@@ -48,11 +47,12 @@ echo "🔴 EVENT 1: Recording START at $(date '+%H:%M:%S')"
48
47
  echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
49
48
  echo "[EVENT 1] 🔴 Recording started with emoji at $(date '+%H:%M:%S') - TIMESTAMP: $RECORDING_START" >> "$TEMP_FILE"
50
49
 
51
- # Verify recording is actually running
52
- if ps -p $RECORD_PID > /dev/null; then
50
+ # Verify background recording process is actually running
51
+ if pgrep -f "dashcam-background.js" > /dev/null; then
53
52
  echo "✅ Recording started successfully"
54
53
  else
55
54
  echo "❌ Recording process died, check /tmp/dashcam-recording.log"
55
+ cat /tmp/dashcam-recording.log
56
56
  exit 1
57
57
  fi
58
58