Repository: VoxExtract-Labs/vox-whisper
Pull request #2: Feat/whisper class
Author: login: angelxmoreno
Created: 2025-03-29T06:14:06Z | Merged: 2025-03-29T07:04:48Z



<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Introduced a streamlined transcription process that now accepts expanded configuration options (e.g., model, language, output format) and directly returns transcription results.
  - Enhanced error reporting to facilitate improved troubleshooting during transcription operations.
  - Added a new custom error class for improved error handling specific to transcription tasks.

- **Documentation**
  - Updated installation instructions to support multiple package managers.
  - Revised usage examples and configuration details for clearer guidance on how to use the transcription tool.

- **Tests**
  - Added integration and unit tests for the transcription function to ensure robust functionality and error handling.

- **Chores**
  - Updated GitHub Actions workflow to ensure the `tmp` directory is writable during the pull request check.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml
index 1f4f77d..059ec98 100644
--- a/.github/workflows/pr-check.yml
+++ b/.github/workflows/pr-check.yml
@@ -26,6 +26,9 @@ jobs:
       - name: Lint project files
         run: bun run lint
 
+      - name: Ensure tmp directory is writable
+        run: chmod -R 777 tmp/
+
       - name: Run tests with coverage
         run: bun run coverage
 
@@ -36,8 +39,8 @@ jobs:
           files: coverage/lcov.info
           flags: unittests
 
-      - name: Upload coverage to Codacy
-        uses: codacy/codacy-coverage-reporter-action@89d6c85cfafaec52c72b6c5e8b2878d33104c699
-        with:
-          project-token: ${{ secrets.CODACY_PROJECT_TOKEN }}
-          coverage-reports: ./coverage/lcov.info
+#      - name: Upload coverage to Codacy
+#        uses: codacy/codacy-coverage-reporter-action@89d6c85cfafaec52c72b6c5e8b2878d33104c699
+#        with:
+#          project-token: ${{ secrets.CODACY_PROJECT_TOKEN }}
+#          coverage-reports: ./coverage/lcov.info
diff --git a/.gitignore b/.gitignore
index 1170717..2ffb142 100644
--- a/.gitignore
+++ b/.gitignore
@@ -134,3 +134,9 @@ dist
 .yarn/build-state.yml
 .yarn/install-state.gz
 .pnp.*
+
+tmp/*
+!tmp/test/
+!tmp/test/.gitkeep
+tmp/test/*
+!tmp/test/.gitkeep
diff --git a/README.md b/README.md
index 1b071cb..c3ee908 100644
--- a/README.md
+++ b/README.md
@@ -16,41 +16,52 @@
 
 ## Installation
 
-Install the package via npm:
+Install the package via bun or your preferred package manager:
 
 ```sh
-npm install vox-whisper
+bun add @voxextractlabs/vox-whisper
+yarn add @voxextractlabs/vox-whisper
+npm install @voxextractlabs/vox-whisper
 ```
 
 ## Usage Example
 
 ```ts
-import { Whisper } from 'vox-whisper';
+import { whisperTranscribe } from '@voxextractlabs/vox-whisper';
 
-const whisper = new Whisper({
-    input: 'path/to/audio.wav',       // Path to your input audio file
-    out: 'output-folder',             // Output directory for transcriptions
-    device: 'cpu',                    // Inference device: 'cpu' or 'cuda'
-    dockerImage: 'voxextractlabs/vox-whisper:latest' // Docker image (can be customized)
+const result = await whisperTranscribe({
+    input: 'path/to/audio.wav',
+    output: 'tmp/output/result.txt',
+    device: 'cpu',
+    dockerImage: 'voxextractlabs/vox-whisper:cpu-v1.0.0'
 });
 
-whisper.run().then(result => {
-    console.log('Transcription result:', result);
-});
+console.log('Transcription result:', result);
 ```
 
 ## Configuration Details
 
-- **input:** Path to the audio file.
-- **out:** Output directory.
-- **device:** 'cpu' or 'cuda' for inference.
-- **dockerImage:** Custom Docker image tag if needed.
+The function accepts a `WhisperConfig` object with the following fields:
+
+| Property         | Type               | Description                                  |
+|------------------|--------------------|----------------------------------------------|
+| `input`          | `string`           | Path to the input audio file                 |
+| `output`         | `string`           | Path where the transcription will be written |
+| `device`         | `'cpu' \| 'cuda'`  | Inference device                             |
+| `dockerImage`    | `string`           | Docker image to use                          |
+| `model`          | `string?`          | Model size: `tiny`, `base`, `small`, etc.    |
+| `language`       | `string?`          | Language code (e.g. `en`)                    |
+| `output_format`  | `string?`          | Format: `txt`, `json`, `srt`                 |
+| `timestamps`     | `boolean?`         | Include word-level timestamps (JSON only)    |
+| `threads`        | `number?`          | Number of CPU threads                        |
+| `verbose`        | `boolean?`         | Enable verbose/debug logging                 |
 
 ## Contribution Guidelines
 
-Contributions are welcome! Please follow the [Conventional Commits](https://www.conventionalcommits.org/) guidelines and run pre-commit hooks via Lefthook. For more details, see the CONTRIBUTING guide.
+Contributions are welcome! Please follow the [Conventional Commits](https://www.conventionalcommits.org/) specification and run pre-commit hooks using Lefthook. Ensure that your code passes all lint checks, tests, and includes docs where appropriate.
 
 ## License and Acknowledgments
 
-Licensed under the MIT License.  
+Licensed under the [MIT License](./LICENSE).  
 Special thanks to the contributors and the open-source community for their invaluable support.
+
diff --git a/bun.lockb b/bun.lockb
index 5ca9d75..bbf3aab 100755
Binary files a/bun.lockb and b/bun.lockb differ
diff --git a/package.json b/package.json
index 2d771ab..55e14ac 100644
--- a/package.json
+++ b/package.json
@@ -28,7 +28,7 @@
         "tree": "tree -I node_modules .",
         "lint": "bun biome check",
         "lint:fix": "bun biome check --write",
-        "coverage": "bun test --coverage --coverage-reporter=lcov",
+        "coverage": "bun test --coverage --coverage-reporter=lcov --coverage-reporter=text",
         "build": "bun run build:dist && bun run build:types",
         "build:dist": "bun run build.ts",
         "build:types": "tsc -p tsconfig.types.json",
@@ -49,6 +49,6 @@
         "bun": ">=1.0"
     },
     "dependencies": {
-        "execa": "^9.5.2"
+        "execa": "^8.0.1"
     }
 }
diff --git a/src/WhisperConfig.ts b/src/WhisperConfig.ts
new file mode 100644
index 0000000..838904a
--- /dev/null
+++ b/src/WhisperConfig.ts
@@ -0,0 +1,61 @@
+// src/WhisperConfig.ts
+
+/**
+ * Base configuration interface for the CLI options of docker-vox-whisper.
+ */
+interface WhisperConfigBase {
+    /**
+     * Path to the input audio file (required).
+     */
+    input: string;
+    /**
+     * Output file path for the transcription result (required).
+     */
+    output: string;
+    /**
+     * Model size to use. Although this is typed as a string, recommended values include:
+     * - 'tiny'
+     * - 'base'
+     * - 'small'
+     * - 'medium'
+     * - 'large-*' (e.g., 'large-v1', 'large-v2')
+     *
+     * Additionally, any valid model identifier (including HuggingFace models) is allowed.
+     */
+    model?: string;
+    /**
+     * Inference device: 'cpu' or 'cuda' (required).
+     */
+    device: 'cpu' | 'cuda';
+    /**
+     * Language code (default is 'en').
+     */
+    language?: string;
+    /**
+     * Output format: 'txt', 'json', or 'srt'.
+     */
+    output_format?: 'txt' | 'json' | 'srt';
+    /**
+     * Include word-level timestamps (only applicable for JSON output).
+     */
+    timestamps?: boolean;
+    /**
+     * Number of CPU threads to use.
+     */
+    threads?: number;
+    /**
+     * Enable debug logging.
+     */
+    verbose?: boolean;
+}
+
+/**
+ * Extended configuration interface that includes the Docker image.
+ */
+export interface WhisperConfig extends WhisperConfigBase {
+    /**
+     * Docker image to use for running the CLI.
+     * Example: 'voxextractlabs/vox-whisper:cpu-v1.0.0' or 'voxextractlabs/vox-whisper:cuda-v1.0.0'
+     */
+    dockerImage: string;
+}
diff --git a/src/WhisperError.ts b/src/WhisperError.ts
new file mode 100644
index 0000000..9d82ea9
--- /dev/null
+++ b/src/WhisperError.ts
@@ -0,0 +1,19 @@
+// src/WhisperError.ts
+
+/**
+ * Custom error class for Whisper-related errors.
+ */
+export class WhisperError extends Error {
+    public originalError?: Error;
+
+    /**
+     * Creates an instance of WhisperError.
+     * @param message - The error message.
+     * @param originalError - The original error that was caught.
+     */
+    constructor(message: string, originalError?: Error) {
+        super(message);
+        this.name = 'WhisperError';
+        this.originalError = originalError;
+    }
+}
diff --git a/src/whisperTranscribe.ts b/src/whisperTranscribe.ts
new file mode 100644
index 0000000..905bbb4
--- /dev/null
+++ b/src/whisperTranscribe.ts
@@ -0,0 +1,110 @@
+// src/whisperTranscribe.ts
+
+import fs from 'node:fs/promises';
+import path from 'node:path';
+import type { WhisperConfig } from '@/WhisperConfig.js';
+import { WhisperError } from '@/WhisperError.ts';
+import { execa } from 'execa';
+
+/**
+ * Executes the docker-vox-whisper CLI with the provided configuration options.
+ * Automatically checks if the Docker image exists locally and pulls it if necessary.
+ *
+ * @param config - Configuration options for transcription.
+ * @returns A promise that resolves to the transcription result (stdout).
+ * @throws {WhisperError} If required configuration options are missing, if the input file is inaccessible, or if the Docker command fails.
+ */
+export async function whisperTranscribe(config: WhisperConfig): Promise<string> {
+    if (!config.input) {
+        throw new WhisperError('The "input" field is required.');
+    }
+    if (!config.output) {
+        throw new WhisperError('The "output" field is required.');
+    }
+    if (!config.device) {
+        throw new WhisperError('The "device" field is required.');
+    }
+    if (!config.dockerImage) {
+        throw new WhisperError('The "dockerImage" field is required.');
+    }
+
+    // Resolve absolute paths for input and output
+    const resolvedInputPath = path.resolve(config.input);
+    const resolvedOutputPath = path.resolve(config.output);
+
+    // Asynchronously check if the input file exists.
+    try {
+        await fs.access(resolvedInputPath);
+    } catch (err: unknown) {
+        throw new WhisperError(`Input file not found or inaccessible: ${resolvedInputPath}`, err as Error);
+    }
+
+    // Ensure the output directory exists.
+    const outputDir = path.dirname(resolvedOutputPath);
+    try {
+        await fs.access(outputDir);
+    } catch {
+        // If the directory doesn't exist, create it with default permissions.
+        await fs.mkdir(path.resolve(`${outputDir}`), { recursive: true });
+    }
+
+    // Ensure the Docker image exists locally; if not, pull it.
+    try {
+        await execa('docker', ['image', 'inspect', config.dockerImage], { stdio: 'pipe' });
+    } catch (error: unknown) {
+        console.log(`Docker image ${config.dockerImage} not found locally. Pulling image...`);
+        try {
+            await execa('docker', ['pull', config.dockerImage], { stdio: 'inherit' });
+        } catch (pullError: unknown) {
+            throw new WhisperError(`Failed to pull Docker image ${config.dockerImage}`, pullError as Error);
+        }
+    }
+
+    const inputDir = path.dirname(resolvedInputPath);
+    const outputFileName = path.basename(resolvedOutputPath);
+    const inputFileName = path.basename(resolvedInputPath);
+
+    // Construct Docker command arguments.
+    const dockerArgs: string[] = ['run', '--rm'];
+    if (config.device === 'cuda') {
+        dockerArgs.push('--gpus', 'all');
+    }
+    dockerArgs.push('-v', `${inputDir}:/app/audio`);
+    dockerArgs.push('-v', `${outputDir}:/app/output`);
+    dockerArgs.push(config.dockerImage);
+
+    // Construct the CLI arguments for the container.
+    const cliArgs: string[] = [];
+    cliArgs.push('--input', `/app/audio/${inputFileName}`);
+    cliArgs.push('--output', `/app/output/${outputFileName}`);
+    if (config.model) {
+        cliArgs.push('--model', config.model);
+    }
+    if (config.device === 'cuda') {
+        cliArgs.push('--device', 'cuda');
+    }
+    if (config.language) {
+        cliArgs.push('--language', config.language);
+    }
+    if (config.output_format) {
+        cliArgs.push('--output_format', config.output_format);
+    }
+    if (config.timestamps) {
+        cliArgs.push('--timestamps');
+    }
+    if (config.threads !== undefined) {
+        cliArgs.push('--threads', config.threads.toString());
+    }
+    if (config.verbose) {
+        cliArgs.push('--verbose');
+    }
+
+    const fullArgs = dockerArgs.concat(cliArgs);
+
+    try {
+        const result = await execa('docker', fullArgs, { stdio: 'pipe' });
+        return result.stdout;
+    } catch (error: unknown) {
+        throw new WhisperError('Error executing Docker command', error as Error);
+    }
+}
diff --git a/tests/sample.mp3 b/tests/sample.mp3
new file mode 100644
index 0000000..8693913
Binary files /dev/null and b/tests/sample.mp3 differ
diff --git a/tests/whisperTranscribe.integration.test.ts b/tests/whisperTranscribe.integration.test.ts
new file mode 100644
index 0000000..5918fcb
--- /dev/null
+++ b/tests/whisperTranscribe.integration.test.ts
@@ -0,0 +1,57 @@
+// tests/whisperTranscribe.integration.test.ts
+
+import { afterAll, beforeAll, describe, expect, it } from 'bun:test';
+import fs from 'node:fs/promises';
+import path from 'node:path';
+import type { WhisperConfig } from '@/WhisperConfig.js';
+import { whisperTranscribe } from '@/whisperTranscribe.ts';
+
+const TIMEOUT = 90000;
+
+describe('whisperTranscribe Integration Tests', () => {
+    const sampleFile = path.resolve('tests/sample.mp3');
+    const outputDir = path.resolve('tmp/test');
+
+    const outputFileName = `output.${Date.now()}.txt`;
+    const outputFile = path.join(outputDir, outputFileName);
+
+    beforeAll(async () => {
+        if (await fs.exists(outputFile)) {
+            await fs.unlink(outputFile);
+        }
+    });
+    afterAll(async () => {
+        if (await fs.exists(outputFile)) {
+            await fs.unlink(outputFile);
+        }
+    });
+
+    it(
+        'should transcribe the sample audio file and create an output file in tmp/tests',
+        async () => {
+            // Ensure the temporary output directory exists
+            if (!(await fs.exists(outputDir))) {
+                await fs.mkdir(outputDir, { recursive: true });
+            }
+
+            const config: WhisperConfig = {
+                input: sampleFile,
+                output: outputFile,
+                device: 'cpu',
+                dockerImage: 'voxextractlabs/vox-whisper:cpu-v1.0.0',
+                language: 'en',
+                output_format: 'txt',
+            };
+
+            // Run transcription
+            const stdout = await whisperTranscribe(config);
+            console.log('Transcription stdout:', stdout);
+
+            // Verify that the output file was created and is non-empty
+            expect(fs.exists(outputFile)).resolves.toBe(true);
+            const transcription = await fs.readFile(outputFile, 'utf8');
+            expect(transcription.length).toBeGreaterThan(0);
+        },
+        TIMEOUT,
+    );
+});
diff --git a/tests/whisperTranscribe.unit.test.ts b/tests/whisperTranscribe.unit.test.ts
new file mode 100644
index 0000000..14b021a
--- /dev/null
+++ b/tests/whisperTranscribe.unit.test.ts
@@ -0,0 +1,173 @@
+// tests/whisperTranscribe.unit.test.ts
+
+import { afterAll, describe, expect, it, mock, spyOn } from 'bun:test';
+import fs from 'node:fs/promises';
+import type { WhisperConfig } from '@/WhisperConfig.ts';
+import { WhisperError } from '@/WhisperError.ts';
+import { whisperTranscribe } from '@/whisperTranscribe.ts';
+import { execa } from 'execa';
+
+const originalExeca = execa;
+
+const mockedExeca = mock(async (...args: unknown[]) => {
+    const cmd = args[0] as string;
+    const cmdArgs = args[1] as string[];
+    return {
+        stdout: [cmd, ...cmdArgs].join('||'),
+    };
+});
+
+// Override the execa module with our default mock implementation.
+mock.module('execa', () => ({
+    execa: mockedExeca,
+}));
+
+describe('whisperTranscribe Unit Tests', () => {
+    afterAll(() => {
+        mock.restore();
+        mock.module('execa', () => ({
+            execa: originalExeca,
+        }));
+    });
+
+    describe('Configuration validation', () => {
+        it('should throw an error if input is missing', () => {
+            expect(
+                whisperTranscribe({
+                    input: '',
+                    output: 'temp/test/result.txt',
+                    device: 'cpu',
+                    dockerImage: 'voxextractlabs/vox-whisper:cpu-v1.0.0',
+                }),
+            ).rejects.toThrow(WhisperError);
+        });
+
+        it('should throw an error if output is missing', () => {
+            expect(
+                whisperTranscribe({
+                    input: 'tests/sample.mp3',
+                    output: '',
+                    device: 'cpu',
+                    dockerImage: 'voxextractlabs/vox-whisper:cpu-v1.0.0',
+                }),
+            ).rejects.toThrow(WhisperError);
+        });
+
+        it('should throw an error if device is missing', () => {
+            const config = {
+                input: 'tests/sample.mp3',
+                output: 'temp/test/result.txt',
+                dockerImage: 'voxextractlabs/vox-whisper:cpu-v1.0.0',
+            } as unknown as WhisperConfig;
+            expect(whisperTranscribe(config)).rejects.toThrow(WhisperError);
+        });
+
+        it('should throw an error if dockerImage is missing', () => {
+            const config = {
+                input: 'tests/sample.mp3',
+                output: 'temp/test/result.txt',
+                device: 'cpu',
+            } as unknown as WhisperConfig;
+            expect(whisperTranscribe(config)).rejects.toThrow(WhisperError);
+        });
+    });
+
+    describe('File existence check', () => {
+        it('should throw a clear error if the input file does not exist', async () => {
+            const nonExistentInput = '/path/to/nonexistent/input.mp3';
+            const config: WhisperConfig = {
+                input: nonExistentInput,
+                output: 'temp/test/result.txt',
+                device: 'cpu',
+                dockerImage: 'voxextractlabs/vox-whisper:cpu-v1.0.0',
+                language: 'en',
+            };
+
+            // Simulate that the file is inaccessible by having fs.access reject.
+            spyOn(fs, 'access').mockRejectedValueOnce(new Error('File not found'));
+            await expect(whisperTranscribe(config)).rejects.toThrowError(
+                new RegExp(`Input file not found or inaccessible: ${nonExistentInput}`),
+            );
+        });
+    });
+
+    describe('Docker command construction', () => {
+        it('should build the correct docker command for CPU', async () => {
+            const config: WhisperConfig = {
+                input: 'tests/sample.mp3',
+                output: 'temp/test/result.txt',
+                device: 'cpu',
+                dockerImage: 'voxextractlabs/vox-whisper:cpu-v1.0.0',
+                language: 'en',
+            };
+
+            const output = await whisperTranscribe(config);
+            // The output should include the Docker image.
+            expect(output).toContain('voxextractlabs/vox-whisper:cpu-v1.0.0');
+            // CPU configuration should NOT include the GPU flag.
+            expect(output).not.toContain('--gpus||all');
+            // Must include the --input and --output parameters mapped to container paths.
+            expect(output).toContain('--input||/app/audio/sample.mp3');
+            expect(output).toContain('--output||/app/output/result.txt');
+        });
+
+        it('should build the correct docker command for CUDA', async () => {
+            const config: WhisperConfig = {
+                input: 'tests/sample.mp3',
+                output: 'temp/test/result.txt',
+                device: 'cuda',
+                dockerImage: 'voxextractlabs/vox-whisper:cuda-v1.0.0',
+                language: 'en',
+            };
+
+            const output = await whisperTranscribe(config);
+            // CUDA configuration should include the GPU flag.
+            expect(output).toContain('--gpus||all');
+            // And the CLI arguments should include "--device||cuda".
+            expect(output).toContain('--device||cuda');
+        });
+    });
+
+    describe('Docker image check and pull', () => {
+        it('should pull the docker image if inspect fails', async () => {
+            // Create a local mock that simulates a failure on the inspect command.
+            let callCount = 0;
+            const localMockedExeca = mock(async (...args: unknown[]) => {
+                const cmd = args[0] as string;
+                const cmdArgs = args[1] as string[];
+                callCount++;
+                // Simulate failure when inspecting the image.
+                if (cmd === 'docker' && cmdArgs[0] === 'image' && cmdArgs[1] === 'inspect') {
+                    throw new Error('Image not found');
+                }
+                return {
+                    stdout: [cmd, ...cmdArgs].join('||'),
+                };
+            });
+            // Override execa with the local mock for this test.
+            mock.module('execa', () => ({
+                execa: localMockedExeca,
+            }));
+
+            const config: WhisperConfig = {
+                input: 'tests/sample.mp3',
+                output: 'temp/test/result.txt',
+                device: 'cpu',
+                dockerImage: 'voxextractlabs/vox-whisper:cpu-v1.0.0',
+                language: 'en',
+            };
+
+            const output = await whisperTranscribe(config);
+            // Expect that the inspect call failed and then a pull was attempted.
+            // The call count should be at least 3 (one for inspect, one for pull, and one for docker run).
+            expect(callCount).toBeGreaterThanOrEqual(3);
+            // The output should still include the docker image as part of the docker run command.
+            expect(output).toContain('voxextractlabs/vox-whisper:cpu-v1.0.0');
+
+            // Reset the module override back to the default for subsequent tests.
+            mock.module('execa', () => ({
+                execa: mockedExeca,
+            }));
+        });
+    });
+});
diff --git a/tmp/test/.gitkeep b/tmp/test/.gitkeep
new file mode 100644
index 0000000..e69de29


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>🧹 Nitpick comments (8)</summary><blockquote>

<details>
<summary>.gitignore (1)</summary><blockquote>

`138-142`: **Remove duplicate gitignore rule**

There's a duplicate rule for `!tmp/test/.gitkeep` at lines 140 and 142. This redundancy should be removed for clarity.

```diff
 tmp/*
 !tmp/test/
 !tmp/test/.gitkeep
 tmp/test/*
-!tmp/test/.gitkeep
```

</blockquote></details>
<details>
<summary>src/WhisperConfig.ts (1)</summary><blockquote>

`18-18`: **Improve type safety for model property**

The model property uses a union type that includes specific string literals and a general string type, which makes the literals redundant since any string would be accepted.

For better type safety and IDE auto-completion, consider one of these approaches:

```diff
-    model?: 'tiny' | 'base' | 'small' | 'medium' | string;
+    model?: 'tiny' | 'base' | 'small' | 'medium' | `large-${string}`;
```

Or:

```diff
-    model?: 'tiny' | 'base' | 'small' | 'medium' | string;
+    model?: string;
```

With JSDoc updated to:
```
 /**
  * Model size: tiny, base, small, medium, large-*
  * @example 'tiny' | 'base' | 'small' | 'medium' | 'large-v2'
  */
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 GitHub Check: Codacy Static Code Analysis</summary>

[warning] 18-18: src/WhisperConfig.ts#L18
"tiny" is overridden by string in this union type.

</details>

</details>

</blockquote></details>
<details>
<summary>tests/whisperTranscribe.integration.test.ts (2)</summary><blockquote>

`9-10`: **Consider reducing or parameterizing the default timeout.**  
A 90-second test timeout may be too large or may mask slowdowns. If this extended duration isn’t explicitly required, consider lowering it or making it configurable for different environments.

---

`51-53`: **Use file access checks instead of `fs.exists()`.**  
Calling `fs.exists` can be less reliable than using `fs.access` or `fs.stat` in a try/catch block to confirm file presence. Some environments also mark `fs.exists()` as deprecated or non-standard.

</blockquote></details>
<details>
<summary>src/whisperTranscribe.ts (2)</summary><blockquote>

`36-38`: **Prefer asynchronous file-check methods over `fs.existsSync`.**  
Using a synchronous check can block the event loop, especially if file I/O is slow or on network drives. Consider switching to `fs.promises.access` with a try/catch to handle missing files more gracefully.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 GitHub Check: Codacy Static Code Analysis</summary>

[warning] 36-36: src/whisperTranscribe.ts#L36
Found existsSync from package "node:fs" with non literal argument at index 0

</details>

</details>

---

`100-106`: **Include detailed Docker error output for troubleshooting.**  
Capturing Docker’s stderr or logs in the error path can simplify debugging when transcription fails. Consider adding Docker logs to the thrown `WhisperError` message or adopting an execa option to preserve them.

</blockquote></details>
<details>
<summary>README.md (2)</summary><blockquote>

`30-40`: **Updated Usage Example with `whisperTranscribe` Function**  
The usage example now correctly imports and uses the `whisperTranscribe` function instead of the previous `Whisper` class. This update is consistent with the new API design. As a minor nitpick, you might consider adding a brief comment or note that this function should be invoked within an asynchronous context if the reader is not already familiar with async/await patterns.

---

`61-61`: **Contribution Guidelines Tone**  
The contribution guidelines are comprehensive and welcoming. The phrase "Contributions are welcome!" sets an inviting tone. If a more formal style is preferred, consider toning down the exclamation; however, as it stands, it supports an enthusiastic community spirit.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 LanguageTool</summary>

[style] ~61-~61: Using many exclamation marks might seem excessive (in this case: 6 exclamation marks for a text that’s 2875 characters long)
Context: ...on Guidelines  Contributions are welcome! Please follow the [Conventional Commits...

(EN_EXCESSIVE_EXCLAMATION)

</details>

</details>

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between fd351adb83bad046bb761aa0fdc378f56d146ecd and 086a226f62479ec0323b329bf99ee04b6e0e03cf.

</details>

<details>
<summary>⛔ Files ignored due to path filters (2)</summary>

* `bun.lockb` is excluded by `!**/bun.lockb`
* `tests/sample.mp3` is excluded by `!**/*.mp3`

</details>

<details>
<summary>📒 Files selected for processing (8)</summary>

* `.gitignore` (1 hunks)
* `README.md` (1 hunks)
* `package.json` (2 hunks)
* `src/WhisperConfig.ts` (1 hunks)
* `src/WhisperError.ts` (1 hunks)
* `src/whisperTranscribe.ts` (1 hunks)
* `tests/whisperTranscribe.integration.test.ts` (1 hunks)
* `tests/whisperTranscribe.unit.test.ts` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧠 Learnings (1)</summary>

<details>
<summary>README.md (1)</summary>

```
Learnt from: angelxmoreno
PR: VoxExtract-Labs/vox-whisper#1
File: README.md:25-40
Timestamp: 2025-03-28T16:39:40.740Z
Learning: The import statement in the README.md example should be corrected from `import { Whisper } from 'vox-whisper';` to `import { Whisper } from '@voxextractlabs/vox-whisper';` to properly reflect the namespaced package name. This change is planned for a future PR.
```

</details>

</details><details>
<summary>🧬 Code Definitions (1)</summary>

<details>
<summary>src/whisperTranscribe.ts (2)</summary><blockquote>

<details>
<summary>src/WhisperConfig.ts (1)</summary>

* `WhisperConfig` (48-54)

</details>
<details>
<summary>src/WhisperError.ts (1)</summary>

* `WhisperError` (6-19)

</details>

</blockquote></details>

</details><details>
<summary>🪛 GitHub Check: docker-check</summary>

<details>
<summary>src/WhisperError.ts</summary>

[failure] 15-15: WhisperError: Error executing Docker command

      at new WhisperError (/home/runner/work/vox-whisper/vox-whisper/src/WhisperError.ts:15:9)
      at <anonymous> (/home/runner/work/vox-whisper/vox-whisper/src/whisperTranscribe.ts:104:15)

</details>

</details>
<details>
<summary>🪛 GitHub Check: Codacy Static Code Analysis</summary>

<details>
<summary>src/WhisperConfig.ts</summary>

[warning] 18-18: src/WhisperConfig.ts#L18
"tiny" is overridden by string in this union type.

</details>
<details>
<summary>src/whisperTranscribe.ts</summary>

[warning] 36-36: src/whisperTranscribe.ts#L36
Found existsSync from package "node:fs" with non literal argument at index 0

</details>

</details>
<details>
<summary>🪛 LanguageTool</summary>

<details>
<summary>README.md</summary>

[style] ~61-~61: Using many exclamation marks might seem excessive (in this case: 6 exclamation marks for a text that’s 2875 characters long)
Context: ...on Guidelines  Contributions are welcome! Please follow the [Conventional Commits...

(EN_EXCESSIVE_EXCLAMATION)

</details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (1)</summary>

* GitHub Check: Codacy Static Code Analysis

</details>

<details>
<summary>🔇 Additional comments (9)</summary><blockquote>

<details>
<summary>src/WhisperError.ts (1)</summary>

`3-19`: **Well-structured error class with proper error chaining!**

The custom `WhisperError` class follows best practices by extending the native `Error` class, setting a custom name, and supporting error chaining through the `originalError` property. The JSDoc documentation is clear and comprehensive.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 GitHub Check: docker-check</summary>

[failure] 15-15: WhisperError: Error executing Docker command

      at new WhisperError (/home/runner/work/vox-whisper/vox-whisper/src/WhisperError.ts:15:9)
      at <anonymous> (/home/runner/work/vox-whisper/vox-whisper/src/whisperTranscribe.ts:104:15)

</details>

</details>

</details>
<details>
<summary>package.json (2)</summary>

`31-31`: **Great addition of text reporter for coverage!**

Adding the text reporter alongside lcov provides better visibility of coverage metrics directly in the terminal output.

---

`52-52`: <details>
<summary>❓ Verification inconclusive</summary>

**Investigate the execa version downgrade**

The execa dependency has been downgraded from "^9.5.2" to a fixed version "8.0.1". This seems like an intentional downgrade rather than an update.

Was this downgrade intentional? If so, please document the reason (compatibility issues, etc.) in a comment or commit message.

---

🏁 Script executed:

```shell
#!/bin/bash
# Check execa version availability and changes between versions
npm view execa versions --json | grep -E '"8.0.1"|"9.5.2"'
echo "Checking for breaking changes between versions..."
curl -s https://raw.githubusercontent.com/sindresorhus/execa/HEAD/readme.md | grep -A 20 "Breaking changes"
```

Length of output: 300

---



</details>

**Clarify Execa Dependency Downgrade**

Our verification confirms that both "8.0.1" and "9.5.2" are published on npm. The check for "Breaking changes" in the latest README did not reveal any explicit notes, leaving the impact of the version change inconclusive.

Please confirm if downgrading from "^9.5.2" to the fixed "8.0.1" version was intentional. If so, document the rationale (e.g., compatibility issues, bug fixes, etc.) in a commit message or comment.

</details>
<details>
<summary>src/WhisperConfig.ts (2)</summary>

`3-43`: **Well-structured configuration interface**

The `WhisperConfigBase` interface is well-documented with comprehensive JSDoc comments and includes all necessary parameters for the Whisper CLI with appropriate optionality.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 GitHub Check: Codacy Static Code Analysis</summary>

[warning] 18-18: src/WhisperConfig.ts#L18
"tiny" is overridden by string in this union type.

</details>

</details>

---

`45-54`: **Good separation of Docker-specific configuration**

Extending the base configuration interface with Docker-specific properties is a clean design approach that separates concerns appropriately.

</details>
<details>
<summary>tests/whisperTranscribe.unit.test.ts (1)</summary>

`25-173`: **Excellent test coverage!**  
These unit tests thoroughly cover required configurations, the Docker logic, and file existence checks. No concerns here—great work!

</details>
<details>
<summary>README.md (3)</summary>

`19-25`: **Installation Instructions Are Clear and Updated**  
The updated installation instructions now include commands for Bun, Yarn, and npm. This change effectively communicates the available package managers and aligns well with the project's cross-platform objectives.

---

`44-57`: **Improved Configuration Details Format**  
The configuration details have been reformatted into a clear Markdown table, which significantly enhances readability and maintenance. This structured presentation makes it easier for consumers of the package to understand the expected fields for the `WhisperConfig` object.

---

`65-67`: **License and Acknowledgments Section is Clear**  
The license section clearly states that the project is released under the MIT License and appropriately credits the contributors. This meets standard documentation practices and enhances clarity.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-29T06:18:55Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 1**

<details>
<summary>🧹 Nitpick comments (7)</summary><blockquote>

<details>
<summary>.github/workflows/pr-check.yml (1)</summary><blockquote>

`29-31`: **Ensure the temporary directory is writable safely**  
The new step ensures that the `tmp/` directory has the necessary write permissions for the tests by running `chmod -R 777 tmp/`. While this is effective in a CI environment where we control the context, note that the `777` permission is very permissive. Please ensure that this directory is strictly used for temporary/test outputs and not deployed in production. Additionally, consider checking that the `tmp/` directory exists before applying the permissions to avoid potential command failures.

</blockquote></details>
<details>
<summary>src/whisperTranscribe.ts (6)</summary><blockquote>

`6-6`: **Fix import path extension.**

The import statement includes a `.ts` extension, which is unusual in TypeScript projects as TypeScript typically uses the `.js` extension in import paths (even for TypeScript files) to align with the output JavaScript.

```diff
-import { WhisperError } from '@/WhisperError.ts';
+import { WhisperError } from '@/WhisperError.js';
```

---

`48-51`: **Improve error handling for directory operations.**

The catch block silently ignores any error that's not related to directory non-existence. This could hide legitimate errors like permission issues or other filesystem problems.

```diff
-    } catch {
+    } catch (error) {
+        // Check if the error is specifically about the directory not existing
+        if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
             // Create the directory with appropriate permissions.
-            await fs.mkdir(outputDir, { recursive: true, mode: 0o777 });
+            await fs.mkdir(outputDir, { recursive: true, mode: 0o755 });
+        } else {
+            // Rethrow other errors
+            throw new WhisperError(`Error accessing output directory: ${outputDir}`, error as Error);
+        }
     }
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 GitHub Check: Codacy Static Code Analysis</summary>

[warning] 50-50: src/whisperTranscribe.ts#L50
Found mkdir from package "node:fs/promises" with non literal argument at index 0

</details>

</details>

---

`57-57`: **Consider using a proper logging solution.**

Using `console.log` directly for operational logging is inconsistent with professional error handling. Consider implementing a more structured logging approach.

```diff
-        console.log(`Docker image ${config.dockerImage} not found locally. Pulling image...`);
+        // Replace with a proper logging solution or consistent approach
+        // For example, if you want to stay with console methods:
+        console.info(`Docker image ${config.dockerImage} not found locally. Pulling image...`);
```

---

`78-102`: **Consider validating conditional configuration values.**

While the code correctly checks for the presence of configuration fields, it doesn't validate their values. For example, `device` should be either 'cuda' or 'cpu'.

```diff
     if (!config.device) {
         throw new WhisperError('The "device" field is required.');
     }
+    if (config.device !== 'cuda' && config.device !== 'cpu') {
+        throw new WhisperError('The "device" field must be either "cuda" or "cpu".');
+    }
```

You might also want to validate other configuration values like `output_format` or `threads` to ensure they meet expected criteria.

---

`106-111`: **Consider adding timeout for Docker command execution.**

Docker operations can hang indefinitely in certain scenarios. Adding a timeout would prevent the function from blocking indefinitely.

```diff
     try {
-        const result = await execa('docker', fullArgs, { stdio: 'pipe' });
+        const result = await execa('docker', fullArgs, { 
+            stdio: 'pipe',
+            timeout: 300000 // 5 minutes timeout, adjust based on expected transcription times
+        });
         return result.stdout;
     } catch (error: unknown) {
-        throw new WhisperError('Error executing Docker command', error as Error);
+        // Provide more specific error message based on error type
+        if (error instanceof Error && error.name === 'TimedOutError') {
+            throw new WhisperError('Docker command timed out after 5 minutes', error as Error);
+        } else {
+            throw new WhisperError('Error executing Docker command', error as Error);
+        }
     }
```

---

`17-112`: **Consider breaking down the function into smaller, focused functions.**

The `whisperTranscribe` function is quite long and handles multiple concerns (validation, file system operations, Docker operations). Consider refactoring it into smaller, focused functions to improve maintainability and testability.


For example:
```typescript
async function validateConfig(config: WhisperConfig): Promise<void> {
    // Configuration validation logic
}

async function prepareFilesystem(inputPath: string, outputPath: string): Promise<void> {
    // File system preparation logic
}

async function ensureDockerImage(imageName: string): Promise<void> {
    // Docker image check and pull logic
}

async function buildDockerCommand(config: WhisperConfig, resolvedInputPath: string, resolvedOutputPath: string): Promise<string[]> {
    // Command building logic
}

async function executeDockerCommand(args: string[]): Promise<string> {
    // Command execution logic
}

export async function whisperTranscribe(config: WhisperConfig): Promise<string> {
    await validateConfig(config);
    
    const resolvedInputPath = path.resolve(config.input);
    const resolvedOutputPath = path.resolve(config.output);
    
    await prepareFilesystem(resolvedInputPath, resolvedOutputPath);
    await ensureDockerImage(config.dockerImage);
    
    const commandArgs = await buildDockerCommand(config, resolvedInputPath, resolvedOutputPath);
    return executeDockerCommand(commandArgs);
}
```

This approach would make the code more maintainable, easier to test, and would allow for better separation of concerns.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 GitHub Check: Codacy Static Code Analysis</summary>

[warning] 47-47: src/whisperTranscribe.ts#L47
Found chmod from package "node:fs/promises" with non literal argument at index 0

---

[warning] 50-50: src/whisperTranscribe.ts#L50
Found mkdir from package "node:fs/promises" with non literal argument at index 0

</details>

</details>

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 086a226f62479ec0323b329bf99ee04b6e0e03cf and 677f7e333aae3d60d3647ae22eba67ad9e3688e7.

</details>

<details>
<summary>⛔ Files ignored due to path filters (1)</summary>

* `bun.lockb` is excluded by `!**/bun.lockb`

</details>

<details>
<summary>📒 Files selected for processing (5)</summary>

* `.github/workflows/pr-check.yml` (1 hunks)
* `package.json` (2 hunks)
* `src/WhisperConfig.ts` (1 hunks)
* `src/whisperTranscribe.ts` (1 hunks)
* `tests/whisperTranscribe.unit.test.ts` (1 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (3)</summary>

* package.json
* src/WhisperConfig.ts
* tests/whisperTranscribe.unit.test.ts

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Definitions (1)</summary>

<details>
<summary>src/whisperTranscribe.ts (2)</summary><blockquote>

<details>
<summary>src/WhisperConfig.ts (1)</summary>

* `WhisperConfig` (55-61)

</details>
<details>
<summary>src/WhisperError.ts (1)</summary>

* `WhisperError` (6-19)

</details>

</blockquote></details>

</details><details>
<summary>🪛 GitHub Check: Codacy Static Code Analysis</summary>

<details>
<summary>src/whisperTranscribe.ts</summary>

[warning] 47-47: src/whisperTranscribe.ts#L47
Found chmod from package "node:fs/promises" with non literal argument at index 0

---

[warning] 50-50: src/whisperTranscribe.ts#L50
Found mkdir from package "node:fs/promises" with non literal argument at index 0

</details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (1)</summary>

* GitHub Check: Codacy Static Code Analysis

</details>

<details>
<summary>🔇 Additional comments (3)</summary><blockquote>

<details>
<summary>src/whisperTranscribe.ts (3)</summary>

`18-29`: **LGTM: Proper validation of required configuration.**

The function correctly validates the required configuration fields and throws appropriate error messages.

---

`35-40`: **Improved file access check.**

Good use of async `fs.access()` to check file existence, which addresses the security concern mentioned in a previous review comment about using `existsSync` with non-literal arguments.

---

`69-76`: **LGTM: Appropriate handling of Docker run arguments.**

The construction of Docker arguments is well-implemented, especially the conditional handling of GPU support.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-29T06:52:08Z
user: login: codacy-production[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-29T07:02:38Z
user: login: angelxmoreno
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-29T07:03:36Z

Review comments:
diff_hunk: @@ -0,0 +1,112 @@
+// src/whisperTranscribe.ts
+
+import fs from 'node:fs/promises';
+import path from 'node:path';
+import type { WhisperConfig } from '@/WhisperConfig.js';
+import { WhisperError } from '@/WhisperError.ts';
+import { execa } from 'execa';
+
+/**
+ * Executes the docker-vox-whisper CLI with the provided configuration options.
+ * Automatically checks if the Docker image exists locally and pulls it if necessary.
+ *
+ * @param config - Configuration options for transcription.
+ * @returns A promise that resolves to the transcription result (stdout).
+ * @throws {WhisperError} If required configuration options are missing, if the input file is inaccessible, or if the Docker command fails.
+ */
+export async function whisperTranscribe(config: WhisperConfig): Promise<string> {
+    if (!config.input) {
+        throw new WhisperError('The "input" field is required.');
+    }
+    if (!config.output) {
+        throw new WhisperError('The "output" field is required.');
+    }
+    if (!config.device) {
+        throw new WhisperError('The "device" field is required.');
+    }
+    if (!config.dockerImage) {
+        throw new WhisperError('The "dockerImage" field is required.');
+    }
+
+    // Resolve absolute paths for input and output
+    const resolvedInputPath = path.resolve(config.input);
+    const resolvedOutputPath = path.resolve(config.output);
+
+    // Asynchronously check if the input file exists.
+    try {
+        await fs.access(resolvedInputPath);
+    } catch (err: unknown) {
+        throw new WhisperError(`Input file not found or inaccessible: ${resolvedInputPath}`, err as Error);
+    }
+
+    // Ensure the output directory exists and update its permissions.
+    const outputDir = path.dirname(resolvedOutputPath);
+    try {
+        await fs.access(outputDir);
+        // Adjust permissions if needed.
+        await fs.chmod(outputDir, 0o777);
+    } catch {
+        // Create the directory with broad permissions.
+        await fs.mkdir(outputDir, { recursive: true, mode: 0o777 });
+    }
path: src/whisperTranscribe.ts
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Security concern: Overly permissive file permissions.**

Setting directory permissions to `0o777` (read, write, execute for all users) is extremely permissive and creates a security vulnerability. Consider using more restrictive permissions that still allow the necessary operations.

```diff
-        await fs.chmod(outputDir, 0o777);
+        await fs.chmod(outputDir, 0o755); // Owner: rwx, Group: r-x, Others: r-x
```

```diff
-        await fs.mkdir(outputDir, { recursive: true, mode: 0o777 });
+        await fs.mkdir(outputDir, { recursive: true, mode: 0o755 });
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    // Ensure the output directory exists and update its permissions.
    const outputDir = path.dirname(resolvedOutputPath);
    try {
        await fs.access(outputDir);
        // Adjust permissions if needed.
        await fs.chmod(outputDir, 0o755); // Owner: rwx, Group: r-x, Others: r-x
    } catch {
        // Create the directory with broad permissions.
        await fs.mkdir(outputDir, { recursive: true, mode: 0o755 });
    }
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 GitHub Check: Codacy Static Code Analysis</summary>

[warning] 47-47: src/whisperTranscribe.ts#L47
Found chmod from package "node:fs/promises" with non literal argument at index 0

---

[warning] 50-50: src/whisperTranscribe.ts#L50
Found mkdir from package "node:fs/promises" with non literal argument at index 0

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-29T06:52:07Z
author_association: NONE
diff_hunk: @@ -0,0 +1,110 @@
+// src/whisperTranscribe.ts
+
+import fs from 'node:fs/promises';
+import path from 'node:path';
+import type { WhisperConfig } from '@/WhisperConfig.js';
+import { WhisperError } from '@/WhisperError.ts';
+import { execa } from 'execa';
+
+/**
+ * Executes the docker-vox-whisper CLI with the provided configuration options.
+ * Automatically checks if the Docker image exists locally and pulls it if necessary.
+ *
+ * @param config - Configuration options for transcription.
+ * @returns A promise that resolves to the transcription result (stdout).
+ * @throws {WhisperError} If required configuration options are missing, if the input file is inaccessible, or if the Docker command fails.
+ */
+export async function whisperTranscribe(config: WhisperConfig): Promise<string> {
+    if (!config.input) {
+        throw new WhisperError('The "input" field is required.');
+    }
+    if (!config.output) {
+        throw new WhisperError('The "output" field is required.');
+    }
+    if (!config.device) {
+        throw new WhisperError('The "device" field is required.');
+    }
+    if (!config.dockerImage) {
+        throw new WhisperError('The "dockerImage" field is required.');
+    }
+
+    // Resolve absolute paths for input and output
+    const resolvedInputPath = path.resolve(config.input);
+    const resolvedOutputPath = path.resolve(config.output);
+
+    // Asynchronously check if the input file exists.
+    try {
+        await fs.access(resolvedInputPath);
+    } catch (err: unknown) {
+        throw new WhisperError(`Input file not found or inaccessible: ${resolvedInputPath}`, err as Error);
+    }
+
+    // Ensure the output directory exists.
+    const outputDir = path.dirname(resolvedOutputPath);
+    try {
+        await fs.access(outputDir);
+    } catch {
+        // If the directory doesn't exist, create it with default permissions.
+        await fs.mkdir(path.resolve(`${outputDir}`), { recursive: true });
path: src/whisperTranscribe.ts
user: login: codacy-production[bot]
body: :warning: **Codacy** found a **medium Security** issue: [Found mkdir from package "node:fs/promises" with non literal argument at index 0](https://app.codacy.com/gh/VoxExtract-Labs/vox-whisper/pull-requests/2)

The issue identified by the ESLint linter is related to the use of `fs.mkdir` with a non-literal argument. This can potentially lead to security vulnerabilities, such as directory traversal attacks, where an attacker could manipulate the path to create directories in unintended locations.

To resolve this issue, we should ensure that the argument passed to `fs.mkdir` is a sanitized and controlled value, ideally a literal string or a variable that has been properly validated.

A simple fix for this would be to directly use the `outputDir` variable without resolving it again, as it is already a valid directory path. Here’s the suggested change:

```suggestion
        await fs.mkdir(outputDir, { recursive: true });
```

---

 *This comment was generated by an experimental AI tool.*
created_at: 2025-03-29T07:02:38Z
author_association: NONE
diff_hunk: @@ -0,0 +1,110 @@
+// src/whisperTranscribe.ts
+
+import fs from 'node:fs/promises';
+import path from 'node:path';
+import type { WhisperConfig } from '@/WhisperConfig.js';
+import { WhisperError } from '@/WhisperError.ts';
+import { execa } from 'execa';
+
+/**
+ * Executes the docker-vox-whisper CLI with the provided configuration options.
+ * Automatically checks if the Docker image exists locally and pulls it if necessary.
+ *
+ * @param config - Configuration options for transcription.
+ * @returns A promise that resolves to the transcription result (stdout).
+ * @throws {WhisperError} If required configuration options are missing, if the input file is inaccessible, or if the Docker command fails.
+ */
+export async function whisperTranscribe(config: WhisperConfig): Promise<string> {
+    if (!config.input) {
+        throw new WhisperError('The "input" field is required.');
+    }
+    if (!config.output) {
+        throw new WhisperError('The "output" field is required.');
+    }
+    if (!config.device) {
+        throw new WhisperError('The "device" field is required.');
+    }
+    if (!config.dockerImage) {
+        throw new WhisperError('The "dockerImage" field is required.');
+    }
+
+    // Resolve absolute paths for input and output
+    const resolvedInputPath = path.resolve(config.input);
+    const resolvedOutputPath = path.resolve(config.output);
+
+    // Asynchronously check if the input file exists.
+    try {
+        await fs.access(resolvedInputPath);
+    } catch (err: unknown) {
+        throw new WhisperError(`Input file not found or inaccessible: ${resolvedInputPath}`, err as Error);
+    }
+
+    // Ensure the output directory exists.
+    const outputDir = path.dirname(resolvedOutputPath);
+    try {
+        await fs.access(outputDir);
+    } catch {
+        // If the directory doesn't exist, create it with default permissions.
+        await fs.mkdir(path.resolve(`${outputDir}`), { recursive: true });
path: src/whisperTranscribe.ts
user: login: angelxmoreno
body: are you serious? YOU suggested the change in the first place! ignoring this
created_at: 2025-03-29T07:03:36Z
author_association: CONTRIBUTOR

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-03-29T06:14:13Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- This is an auto-generated comment: failure by coderabbit.ai -->

> [!CAUTION]
> ## Review failed
> 
> The pull request is closed.

<!-- end of auto-generated comment: failure by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough
This pull request introduces new functionality for a Docker-based transcription process along with associated configuration and error handling. It updates documentation and package settings, refines ignore rules for temporary files, and adds both integration and unit tests using the Bun framework. New TypeScript files define configuration interfaces, a custom error class, and an asynchronous transcription function that validates input, checks for Docker image availability, constructs command arguments, executes the Docker command, and returns the transcription result.

## Changes

| File(s)                                                                                      | Summary of Changes                                                                                                                                                                                                                                                                                           |
|----------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `.gitignore`                                                                                 | Added rules to ignore all files in the `tmp` directory while excluding the `tmp/test` directory and its `.gitkeep` file.                                                                                                                                                                                  |
| `README.md`                                                                                  | Updated installation instructions to include support for Bun and Yarn, revised usage examples with new import and function calls, and reformatted configuration details.                                                                                                                               |
| `package.json`                                                                               | Modified the `coverage` script to include a text reporter and downgraded the `execa` dependency from version `^9.5.2` to `8.0.1`.                                                                                                                                                                       |
| `src/WhisperConfig.ts`, `src/WhisperError.ts`, `src/whisperTranscribe.ts`                     | Added new TypeScript files: a configuration interface (`WhisperConfig` and `WhisperConfigBase`), a custom error class (`WhisperError`), and an asynchronous function (`whisperTranscribe`) that validates inputs, checks file and Docker image existence, constructs Docker commands, and executes them. |
| `tests/whisperTranscribe.integration.test.ts`, `tests/whisperTranscribe.unit.test.ts`          | Introduced integration and unit tests for the `whisperTranscribe` function using the Bun testing framework, covering configuration validation, file checks, Docker command construction, and error handling scenarios.                                                                                   |
| `.github/workflows/pr-check.yml`                                                            | Added a step to ensure the `tmp` directory is writable by changing its permissions to allow all users.                                                                                                                                                                                                     | 

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant C as Client
    participant W as whisperTranscribe()
    participant FS as File System
    participant D as Docker CLI

    C->>W: Call whisperTranscribe(config)
    W->>FS: Check if input file exists
    FS-->>W: File exists
    W->>W: Validate config fields
    W->>D: Inspect Docker image existence
    alt Image not available
        D-->>W: Inspection fails
        W->>D: Pull Docker image
        D-->>W: Image pulled successfully
    else Image available
        D-->>W: Image confirmed
    end
    W->>D: Execute Docker command with constructed arguments
    D-->>W: Return transcription output
    W->>C: Return transcription result
```

## Possibly related PRs
- VoxExtract-Labs/vox-whisper#2: The changes in the main PR and the retrieved PR are related as both involve modifications to the `.gitignore` file, specifically adding rules for ignoring files and directories within the `tmp` directory.

<!-- walkthrough_end -->
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->

---

<details>
<summary>📜 Recent review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 677f7e333aae3d60d3647ae22eba67ad9e3688e7 and 80a88342efbbfb791b45fcc442632076b6ecd4cd.

</details>

<details>
<summary>📒 Files selected for processing (2)</summary>

* `.github/workflows/pr-check.yml` (2 hunks)
* `src/whisperTranscribe.ts` (1 hunks)

</details>

</details>
<!-- finishing_touch_checkbox_start -->

<details>
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---



<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=VoxExtract-Labs/vox-whisper&utm_content=2):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai plan` to trigger planning for file edits and PR creation.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: NONE
user: login: codacy-production[bot]
created_at: 2025-03-29T06:47:20Z
body: ## Coverage summary from Codacy
### [See diff coverage on Codacy](https://app.codacy.com/gh/VoxExtract-Labs/vox-whisper/pull-requests/2/coverage?utm_source=github.com&utm_medium=coverageSummary&utm_campaign=coverageSummaryPullRequest)

 | Coverage variation | Diff coverage | 
 | ------------- | ------------- |
 | Report missing for fd351adb83bad046bb761aa0fdc378f56d146ecd[^1] | :white_check_mark: **91.46%** |

<details>
<summary>Coverage variation details</summary>

| | Coverable lines | Covered lines | Coverage |
| ------------- | ------------- | ------------- | ------------- |
| Common ancestor commit (fd351adb83bad046bb761aa0fdc378f56d146ecd) | Report Missing | Report Missing | Report Missing | |
| Head commit (90a20379be2df38a6cc382d6862ab16c5346117c) | 83 | 76 | 91.57% |

**Coverage variation** is the difference between the coverage for the head and common ancestor commits of the pull request branch: `<coverage of head commit> - <coverage of common ancestor commit>`
</details>

<details>
<summary>Diff coverage details</summary>

| | Coverable lines | Covered lines | Diff coverage |
| ------------- | ------------- | ------------- | ------------- |
| Pull request (#2) | 82 | 75 | **91.46%** |

**Diff coverage** is the percentage of lines that are covered by tests out of the coverable lines that the pull request added or modified: `<covered lines added or modified>/<coverable lines added or modified> * 100%`
</details>

#### [See your quality gate settings](https://app.codacy.com/gh/VoxExtract-Labs/vox-whisper/settings/quality?utm_source=github.com&utm_medium=coverageSummary&utm_campaign=coverageSummaryGates)&nbsp;&nbsp;&nbsp;&nbsp;[Change summary preferences](https://app.codacy.com/gh/VoxExtract-Labs/vox-whisper/settings/integrations?utm_source=github.com&utm_medium=coverageSummary&utm_campaign=coverageSummarySettings)
[^1]: Codacy didn't receive coverage data for the commit, or there was an error processing the received data. [Check your integration for errors](https://app.codacy.com/gh/VoxExtract-Labs/vox-whisper/settings/coverage?utm_source=github.com&utm_medium=coverageSummary&utm_campaign=coverageSummarySetup) and [validate that your coverage setup is correct](https://docs.codacy.com/coverage-reporter/#validating-coverage?utm_source=github.com&utm_medium=coverageSummary&utm_campaign=coverageSummaryTroubleshoot).
<!-- 108f945d-e553-4918-8ccf-027c6d4acc2c -->
author_association: NONE