dashcam 1.3.30 โ†’ 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)')
@@ -474,8 +474,6 @@ program
474
474
  logger.debug('Reconstructing recording data from status file');
475
475
 
476
476
  const { fixVideoContainer } = await import('../lib/recorder.js');
477
- const { applicationTracker } = await import('../lib/applicationTracker.js');
478
- const { logsTrackerManager } = await import('../lib/logs/index.js');
479
477
 
480
478
  // Get temp file path
481
479
  const tempFileInfoPath = path.join(os.tmpdir(), 'dashcam', 'temp-file.json');
@@ -486,9 +484,36 @@ program
486
484
  logger.error('Failed to read temp file info', { error });
487
485
  }
488
486
 
489
- 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
+
490
504
  const outputPath = result.outputPath;
491
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
+
492
517
  // Fix the video with FFmpeg (handle incomplete recordings)
493
518
  logger.info('Fixing video with FFmpeg', { tempFile, outputPath });
494
519
  if (fs.existsSync(tempFile)) {
@@ -504,14 +529,129 @@ program
504
529
  logger.warn('Temp file not found, using output path directly', { tempFile });
505
530
  }
506
531
 
507
- // We killed the process, so we don't have apps/logs data
508
- // 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
509
645
  const recordingResult = {
510
646
  outputPath,
647
+ gifPath,
648
+ snapshotPath,
511
649
  duration: result.duration,
650
+ fileSize: fs.statSync(outputPath).size,
512
651
  clientStartDate: activeStatus.startTime,
513
- apps: [],
514
- logs: [],
652
+ apps: appTrackingResults.apps,
653
+ icons: appTrackingResults.icons,
654
+ logs: logTrackingResults,
515
655
  title: activeStatus?.options?.title,
516
656
  description: activeStatus?.options?.description,
517
657
  project: activeStatus?.options?.project
@@ -534,6 +674,7 @@ program
534
674
  duration: recordingResult.duration,
535
675
  clientStartDate: recordingResult.clientStartDate,
536
676
  apps: recordingResult.apps,
677
+ icons: recordingResult.icons,
537
678
  logs: recordingResult.logs,
538
679
  gifPath: recordingResult.gifPath,
539
680
  snapshotPath: recordingResult.snapshotPath
@@ -751,7 +892,7 @@ program
751
892
  if (!targetFile) {
752
893
  console.error('Please provide a file path or use --recover option');
753
894
  console.log('Examples:');
754
- console.log(' dashcam upload /path/to/recording.webm');
895
+ console.log(' dashcam upload /path/to/recording.mp4');
755
896
  console.log(' dashcam upload --recover');
756
897
  process.exit(1);
757
898
  }
@@ -14,10 +14,10 @@ import { WebTrackerManager } from '../lib/extension-logs/manager.js';
14
14
  import { logger } from '../lib/logger.js';
15
15
 
16
16
  async function main() {
17
- console.log('โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—');
18
- console.log('โ•‘ Chrome Extension Script Execution Example โ•‘');
19
- console.log('โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•\n');
20
- console.log('๐Ÿ“‹ Prerequisites:');
17
+ console.log('================================================================');
18
+ console.log(' Chrome Extension Script Execution Example');
19
+ console.log('================================================================\n');
20
+ console.log('Prerequisites:');
21
21
  console.log(' 1. Chrome extension must be installed and loaded');
22
22
  console.log(' 2. Have a regular webpage open (NOT chrome:// or chrome-extension://)');
23
23
  console.log(' 3. For best results, navigate to a simple page like example.com\n');
@@ -26,21 +26,19 @@ async function main() {
26
26
 
27
27
  // Start the WebSocket server
28
28
  await server.start();
29
- console.log('โœ“ WebSocket server started on port:', server.port);
30
-
31
- // Create the WebTrackerManager
29
+ console.log('WebSocket server started on port:', server.port); // Create the WebTrackerManager
32
30
  const manager = new WebTrackerManager(server);
33
31
 
34
32
  // Wait for the Chrome extension to connect
35
33
  console.log('\nWaiting for Chrome extension to connect...');
36
34
  const connected = await new Promise((resolve) => {
37
35
  const timeout = setTimeout(() => {
38
- console.log('โฑ๏ธ Timeout waiting for connection');
36
+ console.log('Timeout waiting for connection');
39
37
  resolve(false);
40
38
  }, 10000);
41
39
 
42
40
  const cleanup = server.on('connection', (client) => {
43
- console.log('โœ“ Chrome extension connected!');
41
+ console.log('Chrome extension connected!');
44
42
  clearTimeout(timeout);
45
43
  cleanup();
46
44
  resolve(true);
@@ -48,7 +46,7 @@ async function main() {
48
46
  });
49
47
 
50
48
  if (!connected) {
51
- console.error('\nโŒ Chrome extension did not connect.');
49
+ console.error('\nChrome extension did not connect.');
52
50
  console.error(' Make sure the extension is installed and running.');
53
51
  console.error(' Check chrome://extensions to verify it\'s loaded.');
54
52
  process.exit(1);
@@ -62,9 +60,9 @@ async function main() {
62
60
  const result = await manager.executeScript({
63
61
  code: 'return document.title;'
64
62
  });
65
- console.log('โœ“ Page title:', result);
63
+ console.log('Page title:', result);
66
64
  } catch (error) {
67
- console.error('โŒ Error:', error.message);
65
+ console.error('Error:', error.message);
68
66
  }
69
67
 
70
68
  console.log('\n--- Example 2: Get current URL ---');
@@ -96,7 +94,7 @@ async function main() {
96
94
  code: `
97
95
  const div = document.createElement('div');
98
96
  div.style.cssText = 'position: fixed; top: 20px; right: 20px; background: #4CAF50; color: white; padding: 20px; border-radius: 8px; font-family: Arial; z-index: 10000; box-shadow: 0 4px 6px rgba(0,0,0,0.1);';
99
- div.textContent = 'Hello from CLI! ๐Ÿ‘‹';
97
+ div.textContent = 'Hello from CLI!';
100
98
  document.body.appendChild(div);
101
99
 
102
100
  // Remove after 5 seconds
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/logger.js CHANGED
@@ -109,9 +109,9 @@ logger.trace = (message, meta) => logger.silly(`[TRACE] ${message}`, meta);
109
109
  // Function to log function entry/exit for debugging
110
110
  export function logFunctionCall(functionName, args = {}) {
111
111
  if (isVerbose) {
112
- logger.debug(`โ†’ Entering ${functionName}`, { args });
112
+ logger.debug(`Entering ${functionName}`, { args });
113
113
  return (result) => {
114
- logger.debug(`โ† Exiting ${functionName}`, { result: typeof result });
114
+ logger.debug(`Exiting ${functionName}`, { result: typeof result });
115
115
  };
116
116
  }
117
117
  return () => {}; // No-op if not verbose
@@ -50,7 +50,7 @@ function showPermissionInstructions() {
50
50
  return;
51
51
  }
52
52
 
53
- console.log('\nโš ๏ธ Screen Recording Permission Required\n');
53
+ console.log('\nScreen Recording Permission Required\n');
54
54
  console.log('Dashcam needs screen recording permission to capture your screen.');
55
55
  console.log('\nTo grant permission:');
56
56
  console.log('1. Open System Settings (or System Preferences)');
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.30",
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": {
@@ -30,17 +30,17 @@ import os from 'os';
30
30
  async function analyzeVideo(videoPath) {
31
31
  const ffprobePath = await getFfprobePath();
32
32
 
33
- console.log(`\n๐Ÿ“Š Analyzing video: ${videoPath}`);
34
- console.log('โ”€'.repeat(80));
33
+ console.log(`\nAnalyzing video: ${videoPath}`);
34
+ console.log('='.repeat(80));
35
35
 
36
36
  // Check if file exists
37
37
  if (!fs.existsSync(videoPath)) {
38
- console.error(`โŒ Video file does not exist: ${videoPath}`);
38
+ console.error(`Video file does not exist: ${videoPath}`);
39
39
  return null;
40
40
  }
41
41
 
42
42
  const stats = fs.statSync(videoPath);
43
- console.log(`๐Ÿ“ File size: ${(stats.size / 1024).toFixed(2)} KB`);
43
+ console.log(`File size: ${(stats.size / 1024).toFixed(2)} KB`);
44
44
 
45
45
  try {
46
46
  // Get basic format info
@@ -52,8 +52,8 @@ async function analyzeVideo(videoPath) {
52
52
  ]);
53
53
 
54
54
  const formatData = JSON.parse(formatResult.stdout);
55
- console.log(`โฑ๏ธ Duration: ${formatData.format.duration || 'unknown'}s`);
56
- console.log(`๐Ÿ“Š Bit rate: ${formatData.format.bit_rate || 'unknown'} bits/s`);
55
+ console.log(`Duration: ${formatData.format.duration || 'unknown'}s`);
56
+ console.log(`Bit rate: ${formatData.format.bit_rate || 'unknown'} bits/s`);
57
57
 
58
58
  // Get stream info
59
59
  const streamResult = await execa(ffprobePath, [
@@ -67,9 +67,9 @@ async function analyzeVideo(videoPath) {
67
67
  const videoStream = streamData.streams.find(s => s.codec_name);
68
68
 
69
69
  if (videoStream) {
70
- console.log(`๐ŸŽฅ Codec: ${videoStream.codec_name}`);
71
- console.log(`๐Ÿ“ Resolution: ${videoStream.width}x${videoStream.height}`);
72
- console.log(`๐ŸŽž๏ธ Frame rate: ${videoStream.r_frame_rate}`);
70
+ console.log(`Codec: ${videoStream.codec_name}`);
71
+ console.log(`Resolution: ${videoStream.width}x${videoStream.height}`);
72
+ console.log(`Frame rate: ${videoStream.r_frame_rate}`);
73
73
  }
74
74
 
75
75
  // Count actual frames
@@ -83,10 +83,10 @@ async function analyzeVideo(videoPath) {
83
83
  ], { reject: false });
84
84
 
85
85
  const frameCount = parseInt(frameResult.stdout.trim());
86
- console.log(`๐Ÿ–ผ๏ธ Frame count: ${frameCount || 'unknown'}`);
86
+ console.log(`Frame count: ${frameCount || 'unknown'}`);
87
87
 
88
88
  if (frameResult.stderr) {
89
- console.log(`โš ๏ธ FFprobe warnings: ${frameResult.stderr.trim()}`);
89
+ console.log(`FFprobe warnings: ${frameResult.stderr.trim()}`);
90
90
  }
91
91
 
92
92
  // Check if duration is available in container
@@ -97,10 +97,10 @@ async function analyzeVideo(videoPath) {
97
97
  const hasEncodingIssues = frameResult.stderr.includes('File ended prematurely');
98
98
  const hasMissingMetadata = !hasDuration;
99
99
 
100
- console.log('\n๐Ÿ“‹ Analysis Result:');
101
- console.log(` Single frame: ${isSingleFrame ? 'โŒ YES (BUG!)' : 'โœ… NO'}`);
102
- console.log(` Encoding issues: ${hasEncodingIssues ? 'โš ๏ธ YES' : 'โœ… NO'}`);
103
- console.log(` Missing metadata: ${hasMissingMetadata ? 'โš ๏ธ YES (container incomplete)' : 'โœ… NO'}`);
100
+ console.log('\nAnalysis Result:');
101
+ console.log(` Single frame: ${isSingleFrame ? 'YES (BUG!)' : 'NO'}`);
102
+ console.log(` Encoding issues: ${hasEncodingIssues ? 'YES' : 'NO'}`);
103
+ console.log(` Missing metadata: ${hasMissingMetadata ? 'YES (container incomplete)' : 'NO'}`);
104
104
  console.log(` Platform: ${os.platform()}`);
105
105
 
106
106
  return {
@@ -117,34 +117,34 @@ async function analyzeVideo(videoPath) {
117
117
  };
118
118
 
119
119
  } catch (error) {
120
- console.error(`โŒ Error analyzing video: ${error.message}`);
120
+ console.error(`Error analyzing video: ${error.message}`);
121
121
  return null;
122
122
  }
123
123
  }
124
124
 
125
125
  async function testShortRecording(duration = 3000) {
126
- console.log(`\n๐ŸŽฌ Testing ${duration}ms recording...`);
127
- console.log('โ•'.repeat(80));
126
+ console.log(`\nTesting ${duration}ms recording...`);
127
+ console.log('='.repeat(80));
128
128
 
129
129
  try {
130
130
  // Start recording
131
- console.log('โ–ถ๏ธ Starting recording...');
131
+ console.log('Starting recording...');
132
132
  const { outputPath, startTime } = await startRecording({
133
133
  fps: 30,
134
134
  includeAudio: false
135
135
  });
136
136
 
137
- console.log(`โœ… Recording started at: ${outputPath}`);
137
+ console.log(`Recording started at: ${outputPath}`);
138
138
 
139
139
  // Wait for specified duration
140
- console.log(`โณ Recording for ${duration}ms...`);
140
+ console.log(`Recording for ${duration}ms...`);
141
141
  await new Promise(resolve => setTimeout(resolve, duration));
142
142
 
143
143
  // Stop recording
144
- console.log('โน๏ธ Stopping recording...');
144
+ console.log('Stopping recording...');
145
145
  const result = await stopRecording();
146
146
 
147
- console.log(`โœ… Recording stopped`);
147
+ console.log(`Recording stopped`);
148
148
  console.log(` Duration: ${result.duration}ms`);
149
149
  console.log(` File: ${result.outputPath}`);
150
150
 
@@ -154,15 +154,15 @@ async function testShortRecording(duration = 3000) {
154
154
  return result;
155
155
 
156
156
  } catch (error) {
157
- console.error(`โŒ Test failed: ${error.message}`);
157
+ console.error(`Test failed: ${error.message}`);
158
158
  console.error(error.stack);
159
159
  throw error;
160
160
  }
161
161
  }
162
162
 
163
163
  async function testExistingVideo(videoPath) {
164
- console.log('\n๐Ÿ” Testing existing video...');
165
- console.log('โ•'.repeat(80));
164
+ console.log('\nTesting existing video...');
165
+ console.log('='.repeat(80));
166
166
 
167
167
  return await analyzeVideo(videoPath);
168
168
  }
@@ -171,8 +171,8 @@ async function testExistingVideo(videoPath) {
171
171
  async function main() {
172
172
  const args = process.argv.slice(2);
173
173
 
174
- console.log('\n๐Ÿงช Short Recording Test Suite');
175
- console.log('โ•'.repeat(80));
174
+ console.log('\nShort Recording Test Suite');
175
+ console.log('='.repeat(80));
176
176
  console.log(`Platform: ${os.platform()}`);
177
177
  console.log(`Architecture: ${os.arch()}`);
178
178
  console.log(`Node version: ${process.version}`);
@@ -183,13 +183,13 @@ async function main() {
183
183
  const result = await testExistingVideo(videoPath);
184
184
 
185
185
  if (result?.isSingleFrame) {
186
- console.log('\nโŒ SINGLE-FRAME VIDEO DETECTED!');
186
+ console.log('\nSINGLE-FRAME VIDEO DETECTED!');
187
187
  process.exit(1);
188
188
  } else if (result?.hasMissingMetadata) {
189
- console.log('\nโš ๏ธ WARNING: Video container metadata is incomplete!');
189
+ console.log('\nWARNING: Video container metadata is incomplete!');
190
190
  console.log(' This can cause playback issues in some players.');
191
191
  console.log(' The video has frames but duration is not in the container.');
192
- console.log('\n๐Ÿ’ก Try fixing it with:');
192
+ console.log('\nTry fixing it with:');
193
193
  console.log(` node test-short-recording.js fix ${args[1]} ${args[1].replace(/\.(webm|mp4)$/, '-fixed.$1')}`);
194
194
  process.exit(1);
195
195
  }
@@ -198,38 +198,38 @@ async function main() {
198
198
  const inputPath = path.resolve(args[1]);
199
199
  const outputPath = path.resolve(args[2]);
200
200
 
201
- console.log('\n๐Ÿ”ง Fixing video container...');
202
- console.log('โ•'.repeat(80));
201
+ console.log('\nFixing video container...');
202
+ console.log('='.repeat(80));
203
203
  console.log(`Input: ${inputPath}`);
204
204
  console.log(`Output: ${outputPath}`);
205
205
 
206
206
  if (!fs.existsSync(inputPath)) {
207
- console.error(`โŒ Input file does not exist: ${inputPath}`);
207
+ console.error(`Input file does not exist: ${inputPath}`);
208
208
  process.exit(1);
209
209
  }
210
210
 
211
211
  // Analyze before
212
- console.log('\n๐Ÿ“Š BEFORE:');
212
+ console.log('\nBEFORE:');
213
213
  const beforeResult = await analyzeVideo(inputPath);
214
214
 
215
215
  // Fix the video
216
216
  const fixSuccess = await fixVideoContainer(inputPath, outputPath);
217
217
 
218
218
  if (!fixSuccess) {
219
- console.error('\nโŒ Failed to fix video!');
219
+ console.error('\nFailed to fix video!');
220
220
  process.exit(1);
221
221
  }
222
222
 
223
223
  // Analyze after
224
- console.log('\n๐Ÿ“Š AFTER:');
224
+ console.log('\nAFTER:');
225
225
  const afterResult = await analyzeVideo(outputPath);
226
226
 
227
- console.log('\nโœ… Video fixed successfully!');
228
- console.log(` Before: ${beforeResult?.hasMissingMetadata ? 'Missing metadata โš ๏ธ' : 'Has metadata โœ…'}`);
229
- console.log(` After: ${afterResult?.hasMissingMetadata ? 'Missing metadata โš ๏ธ' : 'Has metadata โœ…'}`);
227
+ console.log('\nVideo fixed successfully!');
228
+ console.log(` Before: ${beforeResult?.hasMissingMetadata ? 'Missing metadata' : 'Has metadata'}`);
229
+ console.log(` After: ${afterResult?.hasMissingMetadata ? 'Missing metadata' : 'Has metadata'}`);
230
230
 
231
231
  if (afterResult?.hasMissingMetadata) {
232
- console.log('\nโš ๏ธ Warning: Metadata still missing after fix. The source file may be corrupted.');
232
+ console.log('\nWarning: Metadata still missing after fix. The source file may be corrupted.');
233
233
  process.exit(1);
234
234
  }
235
235
  } else {
@@ -252,7 +252,7 @@ async function main() {
252
252
  fs.unlinkSync(result.snapshotPath);
253
253
  }
254
254
  } catch (cleanupError) {
255
- console.warn(`โš ๏ธ Cleanup warning: ${cleanupError.message}`);
255
+ console.warn(`Cleanup warning: ${cleanupError.message}`);
256
256
  }
257
257
  } catch (error) {
258
258
  results.push({ duration, success: false, error: error.message });
@@ -263,20 +263,20 @@ async function main() {
263
263
  }
264
264
 
265
265
  // Summary
266
- console.log('\n\n๐Ÿ“Š TEST SUMMARY');
267
- console.log('โ•'.repeat(80));
266
+ console.log('\n\nTEST SUMMARY');
267
+ console.log('='.repeat(80));
268
268
 
269
269
  for (const result of results) {
270
- const status = result.success ? 'โœ…' : 'โŒ';
270
+ const status = result.success ? 'PASS' : 'FAIL';
271
271
  console.log(`${status} ${result.duration}ms recording: ${result.success ? 'PASSED' : result.error}`);
272
272
  }
273
273
 
274
274
  const allPassed = results.every(r => r.success);
275
275
  if (!allPassed) {
276
- console.log('\nโŒ Some tests failed!');
276
+ console.log('\nSome tests failed!');
277
277
  process.exit(1);
278
278
  } else {
279
- console.log('\nโœ… All tests passed!');
279
+ console.log('\nAll tests passed!');
280
280
  }
281
281
  }
282
282
  }
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