Skip to content

Commit

Permalink
Merge pull request #90 from ajcwebdev/image
Browse files Browse the repository at this point in the history
Dockerfile Optimizations and Production Fixes
  • Loading branch information
ajcwebdev authored Jan 2, 2025
2 parents be37cac + a2db523 commit b162afb
Show file tree
Hide file tree
Showing 11 changed files with 80 additions and 144 deletions.
31 changes: 21 additions & 10 deletions .github/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
# .github/Dockerfile

# ---------------------------------------------------
# 1) Node base image
# 1) Node base image - Using Debian slim for smaller footprint
# ---------------------------------------------------

FROM node:22 AS base
FROM node:22-slim AS base

RUN apt-get update && apt-get install -y \
ffmpeg git make curl docker.io ca-certificates cmake \
libopenblas-dev && rm -rf /var/lib/apt/lists/*
# Install only required system dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
ffmpeg git make curl ca-certificates cmake python3 python3-pip \
libopenblas-dev g++ build-essential && rm -rf /var/lib/apt/lists/* \
&& apt-get clean

RUN update-ca-certificates

Expand All @@ -23,15 +25,18 @@ RUN curl -L https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp \
RUN npm install -g tsx

# Install whisper.cpp and download models
RUN git clone https://github.com/ggerganov/whisper.cpp.git && \
RUN git clone --depth=1 https://github.com/ggerganov/whisper.cpp.git && \
cd whisper.cpp && \
make && \
cmake -B build && \
cmake --build build -j --config Release && \
./models/download-ggml-model.sh large-v3-turbo && \
./models/download-ggml-model.sh base && \
./models/download-ggml-model.sh tiny
./models/download-ggml-model.sh tiny && \
rm -rf .git

# Copy package files and install deps
COPY package*.json ./
RUN npm ci
RUN npm ci --production && npm cache clean --force

# Copy source code
COPY src ./src
Expand All @@ -48,7 +53,7 @@ WORKDIR /root/.ollama
# Start Ollama server and pull models
RUN ollama serve & \
sleep 10 && \
ollama pull llama2 && \
ollama pull llama3.2:1b && \
pkill ollama

# ---------------------------------------------------
Expand All @@ -64,6 +69,12 @@ COPY --from=ollama /root/.ollama /root/.ollama
# Create content directory
RUN mkdir -p content

# Set proper permissions
# RUN chown -R node:node /usr/src/app

# Use non-root user
# USER node

EXPOSE 3000
ENTRYPOINT ["/usr/src/app/docker-entrypoint.sh"]
CMD ["--help"]
1 change: 0 additions & 1 deletion src/cli/commander.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ program
.option('--info', 'Skip processing and write metadata to JSON objects (supports --urls, --rss, --playlist, --channel)')
// Transcription service options
.option('--whisper [model]', 'Use Whisper.cpp for transcription with optional model specification')
.option('--whisperDocker [model]', 'Use Whisper.cpp in Docker for transcription with optional model specification')
.option('--deepgram', 'Use Deepgram for transcription')
.option('--assembly', 'Use AssemblyAI for transcription')
.option('--speakerLabels', 'Use speaker labels for AssemblyAI transcription')
Expand Down
2 changes: 1 addition & 1 deletion src/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ async function start() {
// Start the server
await fastify.listen({
port, // Use configured port
host: '0.0.0.0', // Listen on all network interfaces
host: '::', // Listen on all network interfaces
})

// Log successful server start
Expand Down
98 changes: 16 additions & 82 deletions src/transcription/whisper.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
// src/transcription/whisper.ts

/**
* This file manages transcription using various Whisper-based methods:
* - `whisper`: Local whisper.cpp (recommended for single-container approach)
* - `whisperDocker`: Whisper.cpp inside Docker (legacy / fallback)
*
* In a single-container setup, you'll typically ONLY use `whisper`.
* The `whisperDocker` runner here is kept for backward compatibility.
* This file manages transcription using local Whisper.cpp.
* It provides a streamlined, single-container approach for audio transcription.
*/

import { readFile, writeFile } from 'node:fs/promises'
Expand All @@ -15,68 +11,50 @@ import { lrcToTxt } from '../utils/format-transcript'
import { WHISPER_MODELS, execPromise } from '../utils/globals'
import { l, err } from '../utils/logging'
import type { ProcessingOptions } from '../types/process'
import type { WhisperModelType, WhisperTranscriptServices, WhisperRunner } from '../types/transcription'
import type { WhisperModelType, WhisperRunner } from '../types/transcription'

/**
* Main function to handle transcription using various Whisper-based methods.
* @param {ProcessingOptions} options - Additional processing options that determine how transcription is run.
* Main function to handle transcription using local Whisper.cpp.
* @param {ProcessingOptions} options - Processing options that determine how transcription is run.
* @param {string} finalPath - The base filename (without extension) for input and output files.
* @param {WhisperTranscriptServices} transcriptServices - The chosen Whisper-based transcription method to use.
* @returns {Promise<string>} - The formatted transcript content as a string.
*/
export async function callWhisper(
options: ProcessingOptions,
finalPath: string,
transcriptServices: WhisperTranscriptServices
finalPath: string
): Promise<string> {
l.wait(`\n Using ${transcriptServices} for transcription...`)
l.wait('\n Using local whisper.cpp for transcription...')

try {
// Config object: each property points to a different runner
const serviceConfig = {
whisper: {
option: options.whisper, // e.g. '--whisper base'
modelList: WHISPER_MODELS,
runner: runWhisperCpp, // Local runner
},
whisperDocker: {
option: options.whisperDocker, // e.g. '--whisperDocker base'
modelList: WHISPER_MODELS,
runner: runWhisperDocker, // Docker runner (NOT recommended in single-container approach)
},
} as const

const config = serviceConfig[transcriptServices]

// Determine which model was requested (default to "base" if `--whisper` is passed with no model)
const whisperModel = typeof config.option === 'string'
? config.option
: config.option === true
const whisperModel = typeof options.whisper === 'string'
? options.whisper
: options.whisper === true
? 'base'
: (() => { throw new Error(`Invalid ${transcriptServices} option`) })()
: (() => { throw new Error('Invalid whisper option') })()

// Validate that the requested model is in our known model list
if (!(whisperModel in config.modelList)) {
if (!(whisperModel in WHISPER_MODELS)) {
throw new Error(`Unknown model type: ${whisperModel}`)
}

l.wait(`\n - whisperModel: ${whisperModel}`)

// Execute the appropriate runner function (local or Docker)
await config.runner(finalPath, whisperModel)
// Execute the local whisper.cpp runner
await runWhisperCpp(finalPath, whisperModel)

// Read the newly created .txt file
const txtContent = await readFile(`${finalPath}.txt`, 'utf8')
return txtContent

} catch (error) {
err(`Error in callWhisper with ${transcriptServices}:`, (error as Error).message)
err('Error in callWhisper:', (error as Error).message)
process.exit(1)
}
}

/**
* Runs transcription using the **local** whisper.cpp build inside this container.
* Runs transcription using the local whisper.cpp build inside this container.
*
* Steps:
* 1. If whisper.cpp is not cloned/built locally, do so.
Expand Down Expand Up @@ -117,48 +95,4 @@ const runWhisperCpp: WhisperRunner = async (finalPath, whisperModel) => {
const txtContent = lrcToTxt(lrcContent)
await writeFile(`${finalPath}.txt`, txtContent)
l.success(` Transcript transformation successfully completed:\n - ${finalPath}.txt\n`)
}

/**
* Runs transcription by calling Whisper.cpp in a separate Docker container.
*
* In a single-container approach, this is typically unused. If you keep this around
* for legacy reasons, you’ll see that it tries to `docker exec autoshow-whisper-1 ...`.
*
* If you’re no longer spinning up the separate `whisper` container, do NOT use `--whisperDocker`.
*/
const runWhisperDocker: WhisperRunner = async (finalPath, whisperModel) => {
// *** This is mostly for reference/legacy ***
const modelGGMLName = WHISPER_MODELS[whisperModel as WhisperModelType]
const CONTAINER_NAME = 'autoshow-whisper-1'
const modelPathContainer = `/app/models/${modelGGMLName}`

l.wait(` - modelGGMLName: ${modelGGMLName}`)
l.wait(` - CONTAINER_NAME: ${CONTAINER_NAME}`)
l.wait(` - modelPathContainer: ${modelPathContainer}`)

// Check if container is running; if not, we try to do `docker-compose up -d whisper`
await execPromise(`docker ps | grep ${CONTAINER_NAME}`)
.catch(() => execPromise('docker-compose up -d whisper'))

// Check if model is present inside container; if not, download it
await execPromise(`docker exec ${CONTAINER_NAME} test -f ${modelPathContainer}`)
.catch(() => execPromise(`docker exec ${CONTAINER_NAME} /app/models/download-ggml-model.sh ${whisperModel}`))

// Run the CLI inside the whisper container
await execPromise(
`docker exec ${CONTAINER_NAME} ` +
`/app/build/bin/whisper-cli ` +
`-m ${modelPathContainer} ` +
`-f "/app/content/${finalPath.split('/').pop()}.wav" ` +
`-of "/app/content/${finalPath.split('/').pop()}" ` +
`--output-lrc`
)
l.success(`\n Transcript LRC file successfully created:\n - ${finalPath}.lrc`)

// Back on the host side, read .lrc and convert to .txt
const lrcContent = await readFile(`${finalPath}.lrc`, 'utf8')
const txtContent = lrcToTxt(lrcContent)
await writeFile(`${finalPath}.txt`, txtContent)
l.success(` Transcript transformation successfully completed:\n - ${finalPath}.txt\n`)
}
Loading

0 comments on commit b162afb

Please sign in to comment.