_binary diff --git a/src/configs/Config.php b/src/configs/Config.php
index 55cb7ffb8..60aa378c8 100755
--- a/src/configs/Config.php
+++ b/src/configs/Config.php
@@ -356,9 +356,7 @@ nsdefine('ONE_MINUTE', 60);
 nsdefine('ONE_SECOND', 1);
 /** Maximum number of messages to retrieve for summarization */
 nsdefine('MAX_SUMMARIZE_MESSAGES', 10000);
-/** URL endpoint for LLM API service used for AI summarization
- * For example, LM studio api url
- */
+/** URL endpoint for LLM API service used for AI summarization */
 nsdefine('LLM_API_URL', "http://localhost:1234/v1/chat/completions");
 /** Model name to use for LLM API requests */
 nsdefine('LLM_MODEL', "aya-23-8b");
@@ -1062,6 +1060,32 @@ nsconddefine("MAX_VIDEO_CONVERT_SIZE", 2000000000);
  * loop to avoid conversion of same file multiple times.
  */
 nsconddefine('MAX_FILE_TIMESTAMP_LIMIT', 600);
+/**
+ * Path to Whisper binary for audio transcription
+ * Set this to enable automatic transcription of uploaded audio files.
+ * If you install whisper with brew on macOS the path is likely
+ * /opt/homebrew/bin/whisper
+ */
+nsconddefine('WHISPER', '');
+/**
+ * Path to FFMPEG binary for audio/video processing
+ * Used for audio format conversion and video processing
+ * If you install ffmpeg with brew on macOS the path is likely
+ * /opt/homebrew/bin/ffmpeg
+ */
+nsconddefine('FFMPEG', '');
+/**
+ * Maximum size in bytes for audio files that can be transcribed
+ */
+nsconddefine('MAX_AUDIO_TRANSCRIBE_SIZE', 100000000);
+/**
+ * Timeout in seconds for audio transcription processing
+ */
+nsconddefine('TRANSCRIPTION_TIMEOUT', 1800);
+/**
+ * Maximum length of transcript content to display in browser
+ */
+nsconddefine('MAX_TRANSCRIPT_DISPLAY_LENGTH', 50000);
 /**
  * Mail scheduled for delivery by yioop is aggregated in a text file until
  * MAIL_AGGREGATION_TIME seconds has passed. At this point all the mail
diff --git a/src/controllers/ApiController.php b/src/controllers/ApiController.php
index 8d3ab6fd7..6a96afabc 100644
--- a/src/controllers/ApiController.php
+++ b/src/controllers/ApiController.php
@@ -55,7 +55,7 @@ class ApiController extends Controller implements CrawlConstants
      * These are the activities supported by this controller
      * @var array
      */
-    public $activities = ["summarize"];
+    public $activities = ["summarize", "transcribe", "transcriptionStatus"];
     /**
      * Used to process requests related to user group activities outside of
      * the admin panel setting. This either could be because the admin panel
@@ -271,4 +271,168 @@ class ApiController extends Controller implements CrawlConstants
         }
         return $response;
     }
+    /**
+     * Handles request to transcribe an audio file
+     * Creates a transcription task for background processing by MediaUpdater
+     *
+     * @return array Response with success status and any error messages
+     */
+    public function transcribe()
+    {
+        $data = ['success' => false, 'errors' => []];
+        if (!$this->checkCSRFToken()) {
+            $data['errors'][] = tl('api_invalid_csrf');
+            $this->returnJsonResponse($data);
+            return $data;
+        }
+        if (!C\nsdefined('WHISPER')) {
+            $data['errors'][] = tl('api_transcription_not_configured');
+            $this->returnJsonResponse($data);
+            return $data;
+        }
+        $json = file_get_contents('php://input');
+        $task_data = json_decode($json, true);
+        if (!$task_data) {
+            $data['errors'][] = tl('api_invalid_request_data');
+            $this->returnJsonResponse($data);
+            return $data;
+        }
+        $required_fields = ['audio_path', 'filename', 'task_id'];
+        foreach ($required_fields as $field) {
+            if (!isset($task_data[$field]) || empty($task_data[$field])) {
+                $data['errors'][] = tl('api_missing_required_field', $field);
+                $this->returnJsonResponse($data);
+                return $data;
+            }
+        }
+        $task_data['audio_path'] = $this->clean($task_data['audio_path'],
+            'string');
+        $task_data['filename'] = $this->clean($task_data['filename'],
+            'string');
+        $task_data['task_id'] = $this->clean($task_data['task_id'], 'string');
+        if (!file_exists($task_data['audio_path']) ||
+            !is_readable($task_data['audio_path'])) {
+            $data['errors'][] = tl('api_audio_file_not_accessible');
+            $this->returnJsonResponse($data);
+            return $data;
+        }
+        $file_size = filesize($task_data['audio_path']);
+        if ($file_size > C\MAX_AUDIO_TRANSCRIBE_SIZE) {
+            $data['errors'][] = tl('api_audio_file_too_large');
+            $this->returnJsonResponse($data);
+            return $data;
+        }
+        $task_dir = C\SCHEDULES_DIR . "/audio_transcribe";
+        if (!file_exists($task_dir)) {
+            if (!mkdir($task_dir, 0777, true)) {
+                $data['errors'][] = tl('api_failed_create_directory');
+                $this->returnJsonResponse($data);
+                return $data;
+            }
+        }
+        $task_file = $task_dir . "/" . $task_data['task_id'] . ".txt";
+        $status_file = $task_dir . "/" . $task_data['task_id'] . ".status";
+        if (file_exists($task_file)) {
+            $data['errors'][] = tl('api_transcription_task_exists');
+            $this->returnJsonResponse($data);
+            return $data;
+        }
+        $task_data['created_at'] = time();
+        $task_data['status'] = 'pending';
+        $task_data['retry_count'] = 0;
+        if (file_put_contents($task_file, serialize($task_data)) === false) {
+            $data['errors'][] = tl('api_failed_create_task_file');
+            $this->returnJsonResponse($data);
+            return $data;
+        }
+        if (file_put_contents($status_file, 'pending') === false) {
+            $data['errors'][] = tl('api_failed_create_status_file');
+            unlink($task_file);
+            $this->returnJsonResponse($data);
+            return $data;
+        }
+        $data['success'] = true;
+        $data['task_id'] = $task_data['task_id'];
+        $data['message'] = tl('api_transcription_task_created');
+        L\crawlLog("AudioTranscription: Task created for " .
+            $task_data['filename'] . " (ID: " . $task_data['task_id'] . ")");
+        $this->returnJsonResponse($data);
+        return $data;
+    }
+    /**
+     * Checks the status of a transcription task
+     * Returns current status and transcript content if available
+     *
+     * @return array Response with task status and transcript if available
+     */
+    public function transcriptionStatus()
+    {
+        $data = ['success' => false, 'errors' => []];
+        if (!$this->checkCSRFToken()) {
+            $data['errors'][] = tl('api_invalid_csrf');
+            $this->returnJsonResponse($data);
+            return $data;
+        }
+        $task_id = $this->clean($_REQUEST['task_id'] ?? '', 'string');
+        if (!$task_id) {
+            $data['errors'][] = tl('api_invalid_task_id');
+            $this->returnJsonResponse($data);
+            return $data;
+        }
+        $task_dir = C\SCHEDULES_DIR . "/audio_transcribe";
+        $status_file = $task_dir . "/" . $task_id . ".status";
+        $task_file = $task_dir . "/" . $task_id . ".txt";
+        if (!file_exists($status_file)) {
+            $data['errors'][] = tl('api_task_not_found');
+            $this->returnJsonResponse($data);
+            return $data;
+        }
+        $status = file_get_contents($status_file);
+        $data['status'] = $status;
+        $data['success'] = true;
+        if ($status === 'completed') {
+            if (file_exists($task_file)) {
+                $task_data = unserialize(file_get_contents($task_file));
+                if ($task_data && isset($task_data['audio_path'])) {
+                    $transcript_path = dirname($task_data['audio_path']) . "/" .
+                        pathinfo($task_data['filename'],
+                            PATHINFO_FILENAME) . ".txt";
+                    if (file_exists($transcript_path)) {
+                        $transcript_content =
+                            file_get_contents($transcript_path);
+                        if ($transcript_content !== false) {
+                            $data['transcript'] = trim($transcript_content);
+                        } else {
+                            $data['status'] = 'failed';
+                            $data['errors'][] =
+                                tl('api_failed_read_transcript');
+                        }
+                    } else {
+                        $data['status'] = 'failed';
+                        $data['errors'][] = tl('api_transcript_not_found');
+                    }
+                }
+            }
+        } elseif ($status === 'failed') {
+            $data['errors'][] = tl('api_transcription_failed');
+        } elseif ($status === 'processing') {
+            $data['message'] = tl('api_transcription_processing');
+        } elseif ($status === 'pending') {
+            $data['message'] = tl('api_transcription_queued');
+        }
+        $this->returnJsonResponse($data);
+        return $data;
+    }
+    /**
+     * Helper method to return JSON response with proper headers
+     *
+     * @param array $data Response data to encode as JSON
+     */
+    private function returnJsonResponse($data)
+    {
+        header('Content-Type: application/json');
+        header('Cache-Control: no-cache, must-revalidate');
+        header('Expires: Sat, 26 Jul 1997 05:00:00 GMT');
+        echo json_encode($data, JSON_UNESCAPED_UNICODE);
+    }
 }
diff --git a/src/css/messages.css b/src/css/messages.css
index 879bdb93b..2468e8544 100644
--- a/src/css/messages.css
+++ b/src/css/messages.css
@@ -581,3 +581,79 @@
     display: none;
     margin-left: 65px;
 }
+/* Audio transcription styles */
+.audio-message-container {
+    display: flex;
+    flex-direction: column;
+    gap: 4px;
+    padding: 8px;
+    background-color: #f8f9fa;
+    border-radius: 8px;
+    margin: 8px 0;
+    width: 300px;
+    max-width: 100%;
+}
+.audio-player-wrapper {
+    position: relative;
+    width: 100%;
+    margin-bottom: -4px;
+}
+.audio-message-container audio {
+    width: 100%;
+    border-radius: 4px;
+}
+.transcribe-btn {
+    position: absolute;
+    top: -12px;
+    left: -12px;
+    background-color: transparent;
+    border: none;
+    color: #000;
+    cursor: pointer;
+    font-size: 16px;
+    padding: 2px;
+    height: auto;
+    width: auto;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    flex-shrink: 0;
+    transition: all 0.2s;
+    z-index: 1;
+    text-shadow: 0 0 3px rgba(255, 255, 255, 0.8);
+}
+.transcribe-btn:hover {
+    transform: scale(1.2);
+    text-shadow: 0 0 5px rgba(255, 255, 255, 1);
+}
+.transcribe-btn:active {
+    transform: scale(0.95);
+}
+.transcript-container {
+    display: none;
+    padding: 8px;
+    background-color: #ffffff;
+    border: 1px solid #e0e0e0;
+    border-radius: 4px;
+    white-space: normal;
+    overflow-wrap: break-word;
+    word-wrap: break-word;
+    word-break: break-word;
+    font-size: 14px;
+    color: #333;
+    line-height: 1.4;
+    max-height: 200px;
+    overflow-y: auto;
+    word-spacing: -0.1em;
+}
+.transcript-container.show {
+    display: block;
+}
+.transcript-container.loading {
+    color: #666;
+    font-style: italic;
+}
+.transcript-container.error {
+    color: #d32f2f;
+    font-style: italic;
+}
diff --git a/src/library/media_jobs/AudioTranscriptionJob.php b/src/library/media_jobs/AudioTranscriptionJob.php
new file mode 100644
index 000000000..b93282e43
--- /dev/null
+++ b/src/library/media_jobs/AudioTranscriptionJob.php
@@ -0,0 +1,505 @@
+<?php
+/**
+ * SeekQuarry/Yioop --
+ * Open Source Pure PHP Search Engine, Crawler, and Indexer
+ *
+ * Copyright (C) 2009 - 2025  Chris Pollett chris@pollett.org
+ *
+ * LICENSE:
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ *
+ * END LICENSE
+ *
+ * @author Aditya Prajapati aditya@jkprajapati.tech
+ * @package seek_quarry
+ * @subpackage library
+ * @license https://www.gnu.org/licenses/ GPL3
+ * @link https://www.seekquarry.com/
+ * @copyright 2009 - 2025
+ * @filesource
+ */
+namespace seekquarry\yioop\library\media_jobs;
+
+use seekquarry\yioop\configs as C;
+use seekquarry\yioop\library as L;
+use seekquarry\yioop\library\CrawlConstants;
+use seekquarry\yioop\library\MediaConstants;
+
+/**
+ * MediaJob to handle audio file transcription using Whisper
+ *
+ * This job processes audio files uploaded to group pages and wiki pages
+ * by generating text transcripts using OpenAI's Whisper speech recognition
+ * model. Transcripts are stored alongside the original audio files and
+ * can be displayed to users via the web interface.
+ */
+class AudioTranscriptionJob extends MediaJob
+{
+    /**
+     * Maximum number of concurrent transcription tasks
+     */
+    const MAX_CONCURRENT_TASKS = 3;
+    /**
+     * Maximum retry attempts for failed transcriptions
+     */
+    const MAX_RETRY_ATTEMPTS = 3;
+    /**
+     * Supported audio file types for transcription
+     * @var array
+     */
+    public $supported_audio_types = [
+        'audio/mpeg', 'audio/mp3', 'audio/wav', 'audio/ogg',
+        'audio/mp4', 'audio/webm', 'audio/m4a'
+    ];
+    /**
+     * Initialize the job configuration
+     */
+    public function init()
+    {
+        $this->name_server_does_client_tasks = true;
+        $this->name_server_does_client_tasks_only = true;
+    }
+    /**
+     * Check if prerequisites for transcription are met
+     * Verifies Whisper installation and checks for pending tasks
+     *
+     * @return bool whether prerequisites are satisfied
+     */
+    public function checkPrerequisites()
+    {
+        // Check if Whisper is installed and configured
+        if (!C\nsdefined('WHISPER') || !function_exists("exec")) {
+            L\crawlLog("AudioTranscriptionJob: Whisper not configured or " .
+                "exec() not available");
+            return false;
+        }
+        // Verify Whisper binary exists
+        $whisper_path = C\WHISPER;
+        if (!is_executable($whisper_path)) {
+            L\crawlLog("AudioTranscriptionJob: Whisper binary not found or " .
+                "not executable at: $whisper_path");
+            return false;
+        }
+        // Check for pending transcription tasks
+        $task_dir = C\SCHEDULES_DIR . "/audio_transcribe";
+        if (!file_exists($task_dir)) {
+            return false;
+        }
+        $pending_files = glob($task_dir . "/*.txt");
+        if (empty($pending_files)) {
+            return false;
+        }
+        // Check for tasks that are actually pending
+        foreach ($pending_files as $file) {
+            $task_data = unserialize(file_get_contents($file));
+            if ($task_data) {
+                $status_file = $task_dir . "/" . $task_data['task_id'] .
+                    ".status";
+                if (file_exists($status_file)) {
+                    $status = file_get_contents($status_file);
+                    if ($status === 'pending' || $status === 'failed') {
+                        return true;
+                    }
+                }
+            }
+        }
+        return false;
+    }
+    /**
+     * Handle nondistributed mode by directly processing tasks
+     */
+    public function nondistributedTasks()
+    {
+        L\crawlLog("AudioTranscriptionJob: Running in nondistributed mode");
+        $this->prepareTasks();
+        if (!empty($this->tasks)) {
+            foreach ($this->tasks as $task) {
+                $result = $this->doTasks($task);
+                if (!$result['success']) {
+                    L\crawlLog("AudioTranscriptionJob: Task failed - " .
+                        $result['error']);
+                }
+            }
+        }
+    }
+    /**
+     * Prepare tasks for audio transcription
+     * Scans for audio files that need transcription and filters by status
+     */
+    public function prepareTasks()
+    {
+        $task_dir = C\SCHEDULES_DIR . "/audio_transcribe";
+        if (!file_exists($task_dir)) {
+            return;
+        }
+        $tasks = [];
+        $files = glob($task_dir . "/*.txt");
+        $current_processing = $this->countProcessingTasks($task_dir);
+        foreach ($files as $file) {
+            // Limit concurrent processing
+            if (count($tasks) + $current_processing >=
+                self::MAX_CONCURRENT_TASKS) {
+                break;
+            }
+            $task_data = unserialize(file_get_contents($file));
+            if (!$task_data || !isset($task_data['task_id'])) {
+                continue;
+            }
+            $status_file = $task_dir . "/" . $task_data['task_id'] . ".status";
+            if (file_exists($status_file)) {
+                $status = file_get_contents($status_file);
+                // Process pending tasks or retry failed tasks
+                if ($status === 'pending' ||
+                    ($status === 'failed' && $this->shouldRetry($task_data))) {
+                    // Validate task data
+                    if ($this->validateTaskData($task_data)) {
+                        $tasks[] = $task_data;
+                    } else {
+                        L\crawlLog("AudioTranscriptionJob: Invalid task data, " .
+                            "removing task " . $task_data['task_id']);
+                        $this->cleanupTask($task_data['task_id'], $task_dir);
+                    }
+                } else if ($status === 'failed' &&
+                    !$this->shouldRetry($task_data)) {
+                    // Handle permanently failed tasks (max retries exceeded)
+                    L\crawlLog("AudioTranscriptionJob: Task " .
+                        $task_data['task_id'] . " permanently failed after " .
+                        ($task_data['retry_count'] ?? 0) . " attempts");
+                    $this->handlePermanentFailure($task_data, $task_dir);
+                }
+            }
+        }
+        $this->tasks = $tasks;
+        L\crawlLog("AudioTranscriptionJob: Prepared " . count($tasks) .
+            " tasks for processing");
+    }
+    /**
+     * Process audio file and generate transcript
+     *
+     * @param array $task Audio file task data
+     * @return array Result of transcription
+     */
+    public function doTasks($task)
+    {
+        $task_id = $task['task_id'];
+        $filename = $task['filename'];
+        L\crawlLog("AudioTranscriptionJob: Starting transcription for " .
+            "task $task_id: $filename");
+        $task_dir = C\SCHEDULES_DIR . "/audio_transcribe";
+        $status_file = $task_dir . "/" . $task_id . ".status";
+        $task_file = $task_dir . "/" . $task_id . ".txt";
+        // Update status to processing
+        file_put_contents($status_file, 'processing');
+        $audio_path = $task['audio_path'];
+        // Validate audio file exists and is readable
+        if (!file_exists($audio_path) || !is_readable($audio_path)) {
+            $error = "Audio file not found or not readable: $audio_path";
+            L\crawlLog("AudioTranscriptionJob: $error");
+            $task['retry_count'] = ($task['retry_count'] ?? 0) + 1;
+            file_put_contents($task_file, serialize($task));
+            file_put_contents($status_file, 'failed');
+            return [
+                'success' => false,
+                'error' => $error,
+                'file' => $filename
+            ];
+        }
+        // Check if audio format is supported, convert if needed
+        $working_audio_path = $audio_path;
+        $converted_file = null;
+        if (!$this->isSupportedAudioFormat($filename)) {
+            L\crawlLog("AudioTranscriptionJob: Unsupported format detected: " .
+                "$filename, attempting conversion");
+            $converted_file = dirname($audio_path) . "/" .
+                pathinfo($filename, PATHINFO_FILENAME) . "_converted.m4a";
+            if (!$this->convertAudioFormat($audio_path, $converted_file)) {
+                $error = "Failed to convert unsupported audio format: " .
+                    $filename;
+                L\crawlLog("AudioTranscriptionJob: $error");
+                $task['retry_count'] = ($task['retry_count'] ?? 0) + 1;
+                file_put_contents($task_file, serialize($task));
+                file_put_contents($status_file, 'failed');
+                return [
+                    'success' => false,
+                    'error' => $error,
+                    'file' => $filename
+                ];
+            }
+            $working_audio_path = $converted_file;
+            L\crawlLog("AudioTranscriptionJob: Successfully converted to: " .
+                $converted_file);
+        }
+        // Check file size
+        $file_size = filesize($audio_path);
+        if ($file_size > C\MAX_AUDIO_TRANSCRIBE_SIZE) {
+            $error = "Audio file too large: " .
+                round($file_size / 1024 / 1024, 2) . "MB";
+            L\crawlLog("AudioTranscriptionJob: $error");
+            file_put_contents($status_file, 'failed');
+            return [
+                'success' => false,
+                'error' => $error,
+                'file' => $filename
+            ];
+        }
+        $transcript_path = dirname($audio_path) . "/" .
+            pathinfo($filename, PATHINFO_FILENAME) . ".txt";
+        $whisper_cmd = C\WHISPER;
+        $output_dir = dirname($transcript_path);
+        $output_format = "txt";
+        $model = "turbo"; // Use turbo model for faster processing
+        $command = sprintf(
+            "%s %s --model %s --output_dir %s --output_format %s 2>&1",
+            escapeshellcmd($whisper_cmd),
+            escapeshellarg($working_audio_path),
+            escapeshellarg($model),
+            escapeshellarg($output_dir),
+            escapeshellarg($output_format)
+        );
+        L\crawlLog("AudioTranscriptionJob: Executing: $command");
+        $start_time = time();
+        exec($command, $output, $return_var);
+        $duration = time() - $start_time;
+        L\crawlLog("AudioTranscriptionJob: Command completed in " .
+            "{$duration}s with return code: $return_var");
+        if ($return_var !== 0) {
+            $error_msg = "Whisper transcription failed (exit code: " .
+                "$return_var): " . implode("\n", $output);
+            L\crawlLog("AudioTranscriptionJob: $error_msg");
+            // Increment retry count
+            $task['retry_count'] = ($task['retry_count'] ?? 0) + 1;
+            file_put_contents($task_file, serialize($task));
+            file_put_contents($status_file, 'failed');
+            return [
+                'success' => false,
+                'error' => "Failed to transcribe audio: " .
+                    (end($output) ?: "Unknown error"),
+                'file' => $filename
+            ];
+        }
+        // Find and move the generated transcript
+        // Whisper generates output based on the input filename
+        $whisper_output = $output_dir . "/" .
+            pathinfo($working_audio_path, PATHINFO_FILENAME) . ".txt";
+        if (file_exists($whisper_output)) {
+            // Validate transcript content
+            $transcript_content = file_get_contents($whisper_output);
+            if (empty(trim($transcript_content))) {
+                $error = "Generated transcript is empty";
+                L\crawlLog("AudioTranscriptionJob: $error");
+                file_put_contents($status_file, 'failed');
+                unlink($whisper_output);
+                return [
+                    'success' => false,
+                    'error' => $error,
+                    'file' => $filename
+                ];
+            }
+            // Move to final location if different
+            if ($whisper_output !== $transcript_path) {
+                if (!rename($whisper_output, $transcript_path)) {
+                    $error = "Failed to move transcript to final location";
+                    L\crawlLog("AudioTranscriptionJob: $error");
+                    file_put_contents($status_file, 'failed');
+                    return [
+                        'success' => false,
+                        'error' => $error,
+                        'file' => $filename
+                    ];
+                }
+            }
+        } else {
+            $error = "Transcript file not generated at expected location: " .
+                $whisper_output;
+            L\crawlLog("AudioTranscriptionJob: $error");
+            file_put_contents($status_file, 'failed');
+            return [
+                'success' => false,
+                'error' => "Transcript file not found after processing",
+                'file' => $filename
+            ];
+        }
+        // Mark as completed and cleanup
+        file_put_contents($status_file, 'completed');
+        unlink($task_file);
+        // Clean up converted file if it was created
+        if ($converted_file && file_exists($converted_file)) {
+            unlink($converted_file);
+            L\crawlLog("AudioTranscriptionJob: Cleaned up converted file: " .
+                $converted_file);
+        }
+        L\crawlLog("AudioTranscriptionJob: Successfully completed " .
+            "transcription for: $filename");
+        return [
+            'success' => true,
+            'file' => $filename,
+            'transcript_path' => $transcript_path,
+            'duration' => $duration
+        ];
+    }
+    /**
+     * Count currently processing tasks to limit concurrency
+     *
+     * @param string $task_dir Task directory path
+     * @return int Number of tasks currently being processed
+     */
+    private function countProcessingTasks($task_dir)
+    {
+        $processing_count = 0;
+        $status_files = glob($task_dir . "/*.status");
+        foreach ($status_files as $status_file) {
+            $status = file_get_contents($status_file);
+            if ($status === 'processing') {
+                $processing_count++;
+            }
+        }
+        return $processing_count;
+    }
+    /**
+     * Validate task data structure and file availability
+     *
+     * @param array $task_data Task data to validate
+     * @return bool True if task data is valid
+     */
+    private function validateTaskData($task_data)
+    {
+        $required_fields = ['task_id', 'audio_path', 'filename'];
+        foreach ($required_fields as $field) {
+            if (!isset($task_data[$field]) || empty($task_data[$field])) {
+                return false;
+            }
+        }
+        // Validate audio file exists
+        if (!file_exists($task_data['audio_path'])) {
+            return false;
+        }
+        // Validate file size
+        $file_size = filesize($task_data['audio_path']);
+        if ($file_size > C\MAX_AUDIO_TRANSCRIBE_SIZE || $file_size === 0) {
+            return false;
+        }
+        return true;
+    }
+    /**
+     * Determine if a failed task should be retried
+     *
+     * @param array $task_data Task data
+     * @return bool True if task should be retried
+     */
+    private function shouldRetry($task_data)
+    {
+        $retry_count = $task_data['retry_count'] ?? 0;
+        return $retry_count < self::MAX_RETRY_ATTEMPTS;
+    }
+    /**
+     * Clean up task files for a given task ID
+     *
+     * @param string $task_id Task identifier
+     * @param string $task_dir Task directory path
+     */
+    private function cleanupTask($task_id, $task_dir)
+    {
+        $task_file = $task_dir . "/" . $task_id . ".txt";
+        $status_file = $task_dir . "/" . $task_id . ".status";
+        if (file_exists($task_file)) {
+            unlink($task_file);
+        }
+        if (file_exists($status_file)) {
+            unlink($status_file);
+        }
+    }
+
+    /**
+     * Handle permanently failed tasks that have exceeded max retry attempts
+     *
+     * @param array $task_data Task data
+     * @param string $task_dir Task directory path
+     */
+    private function handlePermanentFailure($task_data, $task_dir)
+    {
+        $task_id = $task_data['task_id'];
+        $audio_path = $task_data['audio_path'] ?? '';
+        $filename = $task_data['filename'] ?? '';
+        // Update transcript file to show permanent failure
+        if (!empty($audio_path) && file_exists($audio_path)) {
+            $transcript_path = dirname($audio_path) . "/" .
+                pathinfo($filename, PATHINFO_FILENAME) . ".txt";
+            $error_message = "Transcription failed permanently after " .
+                           ($task_data['retry_count'] ?? 0) . " attempts. " .
+                           "The audio format may not be supported or the " .
+                           "file may be corrupted.";
+            file_put_contents($transcript_path, $error_message);
+            L\crawlLog("AudioTranscriptionJob: Updated transcript file with " .
+                "error message: $transcript_path");
+        }
+        // Update status to permanently failed
+        $status_file = $task_dir . "/" . $task_id . ".status";
+        file_put_contents($status_file, 'permanently_failed');
+        // Clean up task file but keep status for debugging
+        $task_file = $task_dir . "/" . $task_id . ".txt";
+        if (file_exists($task_file)) {
+            unlink($task_file);
+        }
+        L\crawlLog("AudioTranscriptionJob: Marked task $task_id as " .
+            "permanently failed");
+    }
+    /**
+     * Convert unsupported audio formats to supported ones using ffmpeg
+     *
+     * @param string $input_path Path to input audio file
+     * @param string $output_path Path for converted output file
+     * @return bool True if conversion succeeded
+     */
+    private function convertAudioFormat($input_path, $output_path)
+    {
+        // Check if ffmpeg is available
+        if (!C\nsdefined('FFMPEG')) {
+            L\crawlLog("AudioTranscriptionJob: FFMPEG not configured, " .
+                "cannot convert audio format");
+            return false;
+        }
+        $ffmpeg_cmd = C\FFMPEG;
+        $command = sprintf(
+            "%s -i %s -c:a aac -b:a 128k %s 2>&1",
+            escapeshellcmd($ffmpeg_cmd),
+            escapeshellarg($input_path),
+            escapeshellarg($output_path)
+        );
+        L\crawlLog("AudioTranscriptionJob: Converting audio format: $command");
+        exec($command, $output, $return_var);
+        if ($return_var === 0 && file_exists($output_path)) {
+            L\crawlLog("AudioTranscriptionJob: Successfully converted " .
+                "audio format");
+            return true;
+        } else {
+            L\crawlLog("AudioTranscriptionJob: Audio conversion failed: " .
+                implode("\n", $output));
+            return false;
+        }
+    }
+    /**
+     * Check if audio file format is supported by Whisper
+     *
+     * @param string $filename Audio filename
+     * @return bool True if format is supported
+     */
+    private function isSupportedAudioFormat($filename)
+    {
+        $supported_extensions = [ 'mp3', 'mp4', 'mpeg', 'mpga', 'm4a', 'wav',
+        'webm', 'flac', 'ogg', 'oga' ];
+        $extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
+        return in_array($extension, $supported_extensions);
+    }
+}
\ No newline at end of file
diff --git a/src/locale/ar/configure.ini b/src/locale/ar/configure.ini
index 9c3b89ce6..6e9d90681 100755
--- a/src/locale/ar/configure.ini
+++ b/src/locale/ar/configure.ini
@@ -58,6 +58,26 @@ admin_controller_no_sort = "لا ترتيب"
 admin_controller_sort_ascending = "تصاعدي"
 admin_controller_sort_descending = "تنازلي"
 ;
+; ApiController.php
+api_invalid_csrf = "رمز CSRF غير صالح"
+api_transcription_not_configured = "لم يتم تكوين نسخ الصوت في هذا النظام"
+api_invalid_request_data = "بيانات الطلب غير صالحة"
+api_missing_required_field = "حقل مطلوب مفقود: %s"
+api_audio_file_not_accessible = "ملف الصوت غير قابل للوصول"
+api_audio_file_too_large = "ملف الصوت كبير جدا"
+api_failed_create_directory = "فشل في إنشاء دليل النسخ"
+api_transcription_task_exists = "مهمة النسخ موجودة بالفعل لهذا الملف"
+api_failed_create_task_file = "فشل في إنشاء ملف المهمة"
+api_failed_create_status_file = "فشل في إنشاء ملف الحالة"
+api_transcription_task_created = "تم إنشاء مهمة النسخ بنجاح"
+api_invalid_task_id = "معرف المهمة غير صالح أو مفقود"
+api_task_not_found = "لم يتم العثور على مهمة النسخ"
+api_failed_read_transcript = "فشل في قراءة ملف النسخ"
+api_transcript_not_found = "لم يتم العثور على ملف النسخ"
+api_transcription_failed = "فشل في نسخ الصوت"
+api_transcription_processing = "النسخ قيد التنفيذ حاليا"
+api_transcription_queued = "النسخ في طابور انتظار المعالجة"
+;
 ; StaticController.php
 static_controller_logout_successful = "خروج ناجح"
 static_controller_complete_title = "يووب - %s"
diff --git a/src/locale/bn/configure.ini b/src/locale/bn/configure.ini
index 389d58303..4668810bf 100755
--- a/src/locale/bn/configure.ini
+++ b/src/locale/bn/configure.ini
@@ -58,6 +58,26 @@ admin_controller_no_sort = "নো সর্ট"
 admin_controller_sort_ascending = "আস্কেন্ডিং"
 admin_controller_sort_descending = "অবমুক্ত"
 ;
+; ApiController.php
+api_invalid_csrf = "অবৈধ CSRF টোকেন"
+api_transcription_not_configured = "এই সিস্টেমে অডিও ট্রানসক্রিপশন কনফিগার করা হয়নি"
+api_invalid_request_data = "অবৈধ অনুরোধ ডেটা"
+api_missing_required_field = "প্রয়োজনীয় ফিল্ড অনুপস্থিত: %s"
+api_audio_file_not_accessible = "অডিও ফাইল অ্যাক্সেসযোগ্য নয়"
+api_audio_file_too_large = "অডিও ফাইল অত্যন্ত বড়"
+api_failed_create_directory = "ট্রানসক্রিপশন ডিরেক্টরি তৈরি করতে ব্যর্থ"
+api_transcription_task_exists = "এই ফাইলের জন্য ট্রানসক্রিপশন টাস্ক ইতিমধ্যে বিদ্যমান"
+api_failed_create_task_file = "টাস্ক ফাইল তৈরি করতে ব্যর্থ"
+api_failed_create_status_file = "স্ট্যাটাস ফাইল তৈরি করতে ব্যর্থ"
+api_transcription_task_created = "ট্রানসক্রিপশন টাস্ক সফলভাবে তৈরি হয়েছে"
+api_invalid_task_id = "অবৈধ বা অনুপস্থিত টাস্ক ID"
+api_task_not_found = "ট্রানসক্রিপশন টাস্ক পাওয়া যায়নি"
+api_failed_read_transcript = "ট্রানসক্রিপ্ট ফাইল পড়তে ব্যর্থ"
+api_transcript_not_found = "ট্রানসক্রিপ্ট ফাইল পাওয়া যায়নি"
+api_transcription_failed = "অডিও ট্রানসক্রিপশন ব্যর্থ"
+api_transcription_processing = "ট্রানসক্রিপশন বর্তমানে চলমান"
+api_transcription_queued = "ট্রানসক্রিপশন প্রক্রিয়াকরণের জন্য সারিতে রয়েছে"
+;
 ; StaticController.php
 static_controller_logout_successful = "লগআউট সফল"
 static_controller_complete_title = "ইওপ - %s"
diff --git a/src/locale/de/configure.ini b/src/locale/de/configure.ini
index 9359bd520..5292bca97 100755
--- a/src/locale/de/configure.ini
+++ b/src/locale/de/configure.ini
@@ -58,6 +58,26 @@ admin_controller_no_sort = "Sans trier"
 admin_controller_sort_ascending = "Aufsteigend"
 admin_controller_sort_descending = "Absteigend"
 ;
+; ApiController.php
+api_invalid_csrf = "Ungültiger CSRF-Token"
+api_transcription_not_configured = "Audio-Transkription ist auf diesem System nicht konfiguriert"
+api_invalid_request_data = "Ungültige Anfragedaten"
+api_missing_required_field = "Erforderliches Feld fehlt: %s"
+api_audio_file_not_accessible = "Audio-Datei ist nicht zugänglich"
+api_audio_file_too_large = "Audio-Datei ist zu groß"
+api_failed_create_directory = "Fehler beim Erstellen des Transkriptions-Verzeichnisses"
+api_transcription_task_exists = "Transkriptions-Aufgabe existiert bereits für diese Datei"
+api_failed_create_task_file = "Fehler beim Erstellen der Aufgabendatei"
+api_failed_create_status_file = "Fehler beim Erstellen der Statusdatei"
+api_transcription_task_created = "Transkriptions-Aufgabe erfolgreich erstellt"
+api_invalid_task_id = "Ungültige oder fehlende Aufgaben-ID"
+api_task_not_found = "Transkriptions-Aufgabe nicht gefunden"
+api_failed_read_transcript = "Fehler beim Lesen der Transkriptionsdatei"
+api_transcript_not_found = "Transkriptionsdatei nicht gefunden"
+api_transcription_failed = "Audio-Transkription fehlgeschlagen"
+api_transcription_processing = "Transkription läuft gerade"
+api_transcription_queued = "Transkription ist zur Verarbeitung eingereiht"
+;
 ; StaticController.php
 static_controller_logout_successful = "Ausloggen erfolgreich"
 static_controller_complete_title = "Yioop - %s"
diff --git a/src/locale/el_GR/configure.ini b/src/locale/el_GR/configure.ini
index 726b5f824..ddb4006ab 100644
--- a/src/locale/el_GR/configure.ini
+++ b/src/locale/el_GR/configure.ini
@@ -58,6 +58,26 @@ admin_controller_no_sort = "Δεν σορτ"
 admin_controller_sort_ascending = "Αναβαθμός"
 admin_controller_sort_descending = "Παγία"
 ;
+; ApiController.php
+api_invalid_csrf = "Μη έγκυρο διακριτικό CSRF"
+api_transcription_not_configured = "Η περιγραφή ήχου δεν έχει ρυθμιστεί σε αυτό το σύστημα"
+api_invalid_request_data = "Μη έγκυρα δεδομένα αιτήματος"
+api_missing_required_field = "Λείπει απαραίτητο πεδίο: %s"
+api_audio_file_not_accessible = "Το αρχείο ήχου δεν είναι προσβάσιμο"
+api_audio_file_too_large = "Το αρχείο ήχου είναι πολύ μεγάλο"
+api_failed_create_directory = "Απέτυχε η δημιουργία καταλόγου περιγραφής"
+api_transcription_task_exists = "Η εργασία περιγραφής υπάρχει ήδη για αυτό το αρχείο"
+api_failed_create_task_file = "Απέτυχε η δημιουργία αρχείου εργασίας"
+api_failed_create_status_file = "Απέτυχε η δημιουργία αρχείου κατάστασης"
+api_transcription_task_created = "Η εργασία περιγραφής δημιουργήθηκε επιτυχώς"
+api_invalid_task_id = "Μη έγκυρο ή ελλείπον ID εργασίας"
+api_task_not_found = "Η εργασία περιγραφής δεν βρέθηκε"
+api_failed_read_transcript = "Απέτυχε η ανάγνωση αρχείου περιγραφής"
+api_transcript_not_found = "Το αρχείο περιγραφής δεν βρέθηκε"
+api_transcription_failed = "Η περιγραφή ήχου απέτυχε"
+api_transcription_processing = "Η περιγραφή είναι υπό επεξεργασία"
+api_transcription_queued = "Η περιγραφή είναι σε ουρά για επεξεργασία"
+;
 ; StaticController.php
 static_controller_logout_successful = "Εξέλιξη με επιτυχία"
 static_controller_complete_title = "Γιοόπ - %s"
diff --git a/src/locale/en_US/configure.ini b/src/locale/en_US/configure.ini
index 4648b5f6b..c7e450e9b 100644
--- a/src/locale/en_US/configure.ini
+++ b/src/locale/en_US/configure.ini
@@ -58,6 +58,26 @@ admin_controller_no_sort = "No sort"
 admin_controller_sort_ascending = "Ascending"
 admin_controller_sort_descending = "Descending"
 ;
+; ApiController.php
+api_invalid_csrf = "Invalid CSRF token"
+api_transcription_not_configured = "Audio transcription is not configured on this system"
+api_invalid_request_data = "Invalid request data"
+api_missing_required_field = "Missing required field: %s"
+api_audio_file_not_accessible = "Audio file is not accessible"
+api_audio_file_too_large = "Audio file is too large"
+api_failed_create_directory = "Failed to create transcription directory"
+api_transcription_task_exists = "Transcription task already exists for this file"
+api_failed_create_task_file = "Failed to create task file"
+api_failed_create_status_file = "Failed to create status file"
+api_transcription_task_created = "Transcription task created successfully"
+api_invalid_task_id = "Invalid or missing task ID"
+api_task_not_found = "Transcription task not found"
+api_failed_read_transcript = "Failed to read transcript file"
+api_transcript_not_found = "Transcript file not found"
+api_transcription_failed = "Audio transcription failed"
+api_transcription_processing = "Transcription is currently in progress"
+api_transcription_queued = "Transcription is queued for processing"
+;
 ; StaticController.php
 static_controller_logout_successful = "Logout Successful"
 static_controller_complete_title = "Yioop - %s"
diff --git a/src/locale/es/configure.ini b/src/locale/es/configure.ini
index c1e167b70..7b36d3aac 100755
--- a/src/locale/es/configure.ini
+++ b/src/locale/es/configure.ini
@@ -58,6 +58,26 @@ admin_controller_no_sort = "No ordenar"
 admin_controller_sort_ascending = "Creciente"
 admin_controller_sort_descending = "Decreciente"
 ;
+; ApiController.php
+api_invalid_csrf = "Token CSRF inválido"
+api_transcription_not_configured = "La transcripción de audio no está configurada en este sistema"
+api_invalid_request_data = "Datos de solicitud inválidos"
+api_missing_required_field = "Falta el campo requerido: %s"
+api_audio_file_not_accessible = "El archivo de audio no es accesible"
+api_audio_file_too_large = "El archivo de audio es demasiado grande"
+api_failed_create_directory = "Error al crear el directorio de transcripción"
+api_transcription_task_exists = "Ya existe una tarea de transcripción para este archivo"
+api_failed_create_task_file = "Error al crear el archivo de tarea"
+api_failed_create_status_file = "Error al crear el archivo de estado"
+api_transcription_task_created = "Tarea de transcripción creada exitosamente"
+api_invalid_task_id = "ID de tarea inválido o faltante"
+api_task_not_found = "Tarea de transcripción no encontrada"
+api_failed_read_transcript = "Error al leer el archivo de transcripción"
+api_transcript_not_found = "Archivo de transcripción no encontrado"
+api_transcription_failed = "La transcripción de audio falló"
+api_transcription_processing = "La transcripción está actualmente en progreso"
+api_transcription_queued = "La transcripción está en cola para procesamiento"
+;
 ; StaticController.php
 static_controller_logout_successful = "Cerrado con éxito"
 static_controller_complete_title = "Yioop - %s"
diff --git a/src/locale/fa/configure.ini b/src/locale/fa/configure.ini
index df5d0993a..bb300108a 100755
--- a/src/locale/fa/configure.ini
+++ b/src/locale/fa/configure.ini
@@ -58,6 +58,26 @@ admin_controller_no_sort = "بدون مرتب سازی"
 admin_controller_sort_ascending = "صعودی"
 admin_controller_sort_descending = "نزولی"
 ;
+; ApiController.php
+api_invalid_csrf = "نشانه CSRF نامعتبر"
+api_transcription_not_configured = "رونویسی صوتی در این سیستم پیکربندی نشده است"
+api_invalid_request_data = "داده‌های درخواست نامعتبر"
+api_missing_required_field = "فیلد اجباری گم شده: %s"
+api_audio_file_not_accessible = "فایل صوتی قابل دسترسی نیست"
+api_audio_file_too_large = "فایل صوتی خیلی بزرگ است"
+api_failed_create_directory = "ایجاد دایرکتوری رونویسی شکست خورد"
+api_transcription_task_exists = "وظیفه رونویسی برای این فایل قبلاً وجود دارد"
+api_failed_create_task_file = "ایجاد فایل وظیفه شکست خورد"
+api_failed_create_status_file = "ایجاد فایل وضعیت شکست خورد"
+api_transcription_task_created = "وظیفه رونویسی با موفقیت ایجاد شد"
+api_invalid_task_id = "شناسه وظیفه نامعتبر یا گم شده"
+api_task_not_found = "وظیفه رونویسی یافت نشد"
+api_failed_read_transcript = "خواندن فایل رونویسی شکست خورد"
+api_transcript_not_found = "فایل رونویسی یافت نشد"
+api_transcription_failed = "رونویسی صوتی شکست خورد"
+api_transcription_processing = "رونویسی در حال پردازش است"
+api_transcription_queued = "رونویسی در صف پردازش است"
+;
 ; StaticController.php
 static_controller_logout_successful = "خروج موفقیت آمیز"
 static_controller_complete_title = "Yioop - %s"
diff --git a/src/locale/fr_FR/configure.ini b/src/locale/fr_FR/configure.ini
index e7cdb3688..25790a720 100755
--- a/src/locale/fr_FR/configure.ini
+++ b/src/locale/fr_FR/configure.ini
@@ -58,6 +58,26 @@ admin_controller_no_sort = "Aucun tri"
 admin_controller_sort_ascending = "Trie croissant"
 admin_controller_sort_descending = "Trie décroissant"
 ;
+; ApiController.php
+api_invalid_csrf = "Jeton CSRF invalide"
+api_transcription_not_configured = "La transcription audio n'est pas configurée sur ce système"
+api_invalid_request_data = "Données de requête invalides"
+api_missing_required_field = "Champ obligatoire manquant : %s"
+api_audio_file_not_accessible = "Le fichier audio n'est pas accessible"
+api_audio_file_too_large = "Le fichier audio est trop volumineux"
+api_failed_create_directory = "Échec de création du répertoire de transcription"
+api_transcription_task_exists = "Une tâche de transcription existe déjà pour ce fichier"
+api_failed_create_task_file = "Échec de création du fichier de tâche"
+api_failed_create_status_file = "Échec de création du fichier de statut"
+api_transcription_task_created = "Tâche de transcription créée avec succès"
+api_invalid_task_id = "ID de tâche invalide ou manquant"
+api_task_not_found = "Tâche de transcription introuvable"
+api_failed_read_transcript = "Échec de lecture du fichier de transcription"
+api_transcript_not_found = "Fichier de transcription introuvable"
+api_transcription_failed = "La transcription audio a échoué"
+api_transcription_processing = "La transcription est actuellement en cours"
+api_transcription_queued = "La transcription est en file d'attente pour traitement"
+;
 ; StaticController.php
 static_controller_logout_successful = "Déconnexion réussie"
 static_controller_complete_title = "Yioop - %s"
diff --git a/src/locale/he/configure.ini b/src/locale/he/configure.ini
index c066e964d..c409b5590 100755
--- a/src/locale/he/configure.ini
+++ b/src/locale/he/configure.ini
@@ -58,6 +58,26 @@ admin_controller_no_sort = "Keine Sortierung"
 admin_controller_sort_ascending = "Steigend"
 admin_controller_sort_descending = "Absteigend"
 ;
+; ApiController.php
+api_invalid_csrf = "אסימון CSRF לא חוקי"
+api_transcription_not_configured = "תמלול שמע לא מוגדר במערכת זו"
+api_invalid_request_data = "נתוני בקשה לא חוקיים"
+api_missing_required_field = "שדה חובה חסר: %s"
+api_audio_file_not_accessible = "קובץ שמע לא נגיש"
+api_audio_file_too_large = "קובץ שמע גדול מדי"
+api_failed_create_directory = "נכשל ביצירת תיקיית תמלול"
+api_transcription_task_exists = "משימת תמלול כבר קיימת עבור קובץ זה"
+api_failed_create_task_file = "נכשל ביצירת קובץ משימה"
+api_failed_create_status_file = "נכשל ביצירת קובץ מצב"
+api_transcription_task_created = "משימת תמלול נוצרה בהצלחה"
+api_invalid_task_id = "מזהה משימה לא חוקי או חסר"
+api_task_not_found = "משימת תמלול לא נמצאה"
+api_failed_read_transcript = "נכשל בקריאת קובץ תמלול"
+api_transcript_not_found = "קובץ תמלול לא נמצא"
+api_transcription_failed = "תמלול שמע נכשל"
+api_transcription_processing = "תמלול בעיבוד כרגע"
+api_transcription_queued = "תמלול בתור לעיבוד"
+;
 ; StaticController.php
 static_controller_logout_successful = "Ausloggen erfolgreich"
 static_controller_complete_title = "Yioop - %s"
diff --git a/src/locale/hi/configure.ini b/src/locale/hi/configure.ini
index 23ea82ef6..74a95195f 100755
--- a/src/locale/hi/configure.ini
+++ b/src/locale/hi/configure.ini
@@ -58,6 +58,26 @@ admin_controller_no_sort = "कोई सॉर्ट करें"
 admin_controller_sort_ascending = "आरोही"
 admin_controller_sort_descending = "उतरते"
 ;
+; ApiController.php
+api_invalid_csrf = "अमान्य CSRF टोकन"
+api_transcription_not_configured = "इस सिस्टम में ऑडियो ट्रांसक्रिप्शन कॉन्फ़िगर नहीं है"
+api_invalid_request_data = "अमान्य अनुरोध डेटा"
+api_missing_required_field = "आवश्यक फ़ील्ड गायब: %s"
+api_audio_file_not_accessible = "ऑडियो फ़ाइल उपलब्ध नहीं है"
+api_audio_file_too_large = "ऑडियो फ़ाइल बहुत बड़ी है"
+api_failed_create_directory = "ट्रांसक्रिप्शन डायरेक्टरी बनाने में विफल"
+api_transcription_task_exists = "इस फ़ाइल के लिए ट्रांसक्रिप्शन कार्य पहले से मौजूद है"
+api_failed_create_task_file = "कार्य फ़ाइल बनाने में विफल"
+api_failed_create_status_file = "स्थिति फ़ाइल बनाने में विफल"
+api_transcription_task_created = "ट्रांसक्रिप्शन कार्य सफलतापूर्वक बनाया गया"
+api_invalid_task_id = "अमान्य या गायब कार्य ID"
+api_task_not_found = "ट्रांसक्रिप्शन कार्य नहीं मिला"
+api_failed_read_transcript = "ट्रांसक्रिप्ट फ़ाइल पढ़ने में विफल"
+api_transcript_not_found = "ट्रांसक्रिप्ट फ़ाइल नहीं मिली"
+api_transcription_failed = "ऑडियो ट्रांसक्रिप्शन विफल"
+api_transcription_processing = "ट्रांसक्रिप्शन वर्तमान में प्रगति में है"
+api_transcription_queued = "ट्रांसक्रिप्शन प्रोसेसिंग के लिए क्यू में है"
+;
 ; StaticController.php
 static_controller_logout_successful = "लॉगआउट सफल"
 static_controller_complete_title = "Yioop - %s"
diff --git a/src/locale/id/configure.ini b/src/locale/id/configure.ini
index 41ca29bf7..0e59b0cfb 100755
--- a/src/locale/id/configure.ini
+++ b/src/locale/id/configure.ini
@@ -58,6 +58,26 @@ admin_controller_no_sort = "Ada semacam"
 admin_controller_sort_ascending = "Ascending"
 admin_controller_sort_descending = "Turun"
 ;
+; ApiController.php
+api_invalid_csrf = "Token CSRF tidak valid"
+api_transcription_not_configured = "Transkripsi audio tidak dikonfigurasi pada sistem ini"
+api_invalid_request_data = "Data permintaan tidak valid"
+api_missing_required_field = "Field wajib hilang: %s"
+api_audio_file_not_accessible = "File audio tidak dapat diakses"
+api_audio_file_too_large = "File audio terlalu besar"
+api_failed_create_directory = "Gagal membuat direktori transkripsi"
+api_transcription_task_exists = "Tugas transkripsi sudah ada untuk file ini"
+api_failed_create_task_file = "Gagal membuat file tugas"
+api_failed_create_status_file = "Gagal membuat file status"
+api_transcription_task_created = "Tugas transkripsi berhasil dibuat"
+api_invalid_task_id = "ID tugas tidak valid atau hilang"
+api_task_not_found = "Tugas transkripsi tidak ditemukan"
+api_failed_read_transcript = "Gagal membaca file transkripsi"
+api_transcript_not_found = "File transkripsi tidak ditemukan"
+api_transcription_failed = "Transkripsi audio gagal"
+api_transcription_processing = "Transkripsi sedang dalam proses"
+api_transcription_queued = "Transkripsi sedang mengantri untuk diproses"
+;
 ; StaticController.php
 static_controller_logout_successful = "Logout Sukses"
 static_controller_complete_title = "Yioop - %s"
diff --git a/src/locale/it/configure.ini b/src/locale/it/configure.ini
old mode 100755
new mode 100644
index ec4f6803a..cd5e5c7f4
--- a/src/locale/it/configure.ini
+++ b/src/locale/it/configure.ini
@@ -58,6 +58,26 @@ admin_controller_no_sort = "Nessun ordinamento"
 admin_controller_sort_ascending = "Ascendente"
 admin_controller_sort_descending = "Discendente"
 ;
+; ApiController.php
+api_invalid_csrf = "Token CSRF non valido"
+api_transcription_not_configured = "La trascrizione audio non è configurata su questo sistema"
+api_invalid_request_data = "Dati della richiesta non validi"
+api_missing_required_field = "Campo obbligatorio mancante: %s"
+api_audio_file_not_accessible = "Il file audio non è accessibile"
+api_audio_file_too_large = "Il file audio è troppo grande"
+api_failed_create_directory = "Impossibile creare la directory di trascrizione"
+api_transcription_task_exists = "L'attività di trascrizione esiste già per questo file"
+api_failed_create_task_file = "Impossibile creare il file di attività"
+api_failed_create_status_file = "Impossibile creare il file di stato"
+api_transcription_task_created = "Attività di trascrizione creata con successo"
+api_invalid_task_id = "ID attività non valido o mancante"
+api_task_not_found = "Attività di trascrizione non trovata"
+api_failed_read_transcript = "Impossibile leggere il file di trascrizione"
+api_transcript_not_found = "File di trascrizione non trovato"
+api_transcription_failed = "Trascrizione audio fallita"
+api_transcription_processing = "La trascrizione è attualmente in corso"
+api_transcription_queued = "La trascrizione è in coda per l'elaborazione"
+;
 ; StaticController.php
 static_controller_logout_successful = "Logout Successo"
 static_controller_complete_title = "Yioop - %s"
@@ -5708,4 +5728,3 @@ wordfilter_plugin_settings_saved = "Filtro Parola Impostazioni Salvate!"
 wordfilter_plugin_defaults_restored = "Predefinito!"
 wordfilter_plugin_preferences = "Filtro Parola Preferenze"
 wordfilter_plugin_factory_settings = "Impostazioni Di Fabbrica"
-wordfilter_plugin_save = "Salva"
diff --git a/src/locale/ja/configure.ini b/src/locale/ja/configure.ini
old mode 100755
new mode 100644
index f19d339dc..9aa147906
--- a/src/locale/ja/configure.ini
+++ b/src/locale/ja/configure.ini
@@ -58,6 +58,26 @@ admin_controller_no_sort = "ソートしない"
 admin_controller_sort_ascending = "昇順"
 admin_controller_sort_descending = "降順"
 ;
+; ApiController.php
+api_invalid_csrf = "無効なCSRFトークン"
+api_transcription_not_configured = "このシステムでは音声文字起こしが設定されていません"
+api_invalid_request_data = "無効なリクエストデータ"
+api_missing_required_field = "必須フィールドが不足しています: %s"
+api_audio_file_not_accessible = "音声ファイルにアクセスできません"
+api_audio_file_too_large = "音声ファイルが大きすぎます"
+api_failed_create_directory = "文字起こしディレクトリの作成に失敗しました"
+api_transcription_task_exists = "このファイルの文字起こしタスクは既に存在します"
+api_failed_create_task_file = "タスクファイルの作成に失敗しました"
+api_failed_create_status_file = "ステータスファイルの作成に失敗しました"
+api_transcription_task_created = "文字起こしタスクが正常に作成されました"
+api_invalid_task_id = "無効または不足しているタスクID"
+api_task_not_found = "文字起こしタスクが見つかりません"
+api_failed_read_transcript = "文字起こしファイルの読み取りに失敗しました"
+api_transcript_not_found = "文字起こしファイルが見つかりません"
+api_transcription_failed = "音声文字起こしに失敗しました"
+api_transcription_processing = "文字起こしは現在進行中です"
+api_transcription_queued = "文字起こしは処理待ちです"
+;
 ; StaticController.php
 static_controller_logout_successful = "使用に成功"
 static_controller_complete_title = "Yioop-%s"
@@ -5708,4 +5728,3 @@ wordfilter_plugin_settings_saved = "Wordフィルター設定を保存した!"
 wordfilter_plugin_defaults_restored = "デフォルトに戻!"
 wordfilter_plugin_preferences = "Wordフィルタの設定"
 wordfilter_plugin_factory_settings = "工場出荷時の設定"
-wordfilter_plugin_save = "保存"
diff --git a/src/locale/kn/configure.ini b/src/locale/kn/configure.ini
index 9b4b722fa..1a6f7555b 100755
--- a/src/locale/kn/configure.ini
+++ b/src/locale/kn/configure.ini
@@ -58,6 +58,26 @@ admin_controller_no_sort = "ಯಾವುದೇ ರೀತಿಯ"
 admin_controller_sort_ascending = "ಆರೋಹಣ"
 admin_controller_sort_descending = "ಅವರೋಹಣ"
 ;
+; ApiController.php
+api_invalid_csrf = "ಅವೈಧ CSRF ಟೋಕೆನ್"
+api_transcription_not_configured = "ಆಡಿಯೋ ಟ್ರಾನ್ಸ್ಕ್ರಿಪ್ಶನ್ ಇಸ ಸಿಸ್ಟಂನಲ್ಲಿ ನಿರ್ಧರಿಸಲಾಗಿಲ್ಲ"
+api_invalid_request_data = "ಅವೈಧ ಅನುರೋಧ ಡೇಟಾ"
+api_missing_required_field = "ಆವಶ್ಯಕ ಕ್ಷೇತ್ರ ಕಾಣಿಸಲ್ಲಿ: %s"
+api_audio_file_not_accessible = "ಆಡಿಯೋ ಫೈಲ್ ಪ್ರವೇಶಿಸಲು ಆಗಲ್ಲ"
+api_audio_file_too_large = "ಆಡಿಯೋ ಫೈಲ್ ತುಂಬಲೆ ವಡ್ಡಿದೆ"
+api_failed_create_directory = "ಟ್ರಾನ್ಸ್ಕ್ರಿಪ್ಶನ್ ಡೈರೆಕ್ಟರಿ ಸೃಷ್ಟಿಸಲು ಅಸಫಲವಾವಿದೆ"
+api_transcription_task_exists = "ಇ ಫೈಲ್ಗವಸ್ತು ಟ್ರಾನ್ಸ್ಕ್ರಿಪ್ಶನ್ ಕಾರ್ಯ ಈಗಾಗಲೇ ಅಸ್ತಿತ್ವದೆ"
+api_failed_create_task_file = "ಕಾರ್ಯ ಫೈಲ್ ಸೃಷ್ಟಿಸಲು ಅಸಫಲವಾವಿದೆ"
+api_failed_create_status_file = "ಸ್ಥಿತಿ ಫೈಲ್ ಸೃಷ್ಟಿಸಲು ಅಸಫಲವಾವಿದೆ"
+api_transcription_task_created = "ಟ್ರಾನ್ಸ್ಕ್ರಿಪ್ಶನ್ ಕಾರ್ಯ ಯಶಸ್ವಿಯಾಗಿ ಸೃಷ್ಟಿಸಲಾವಿದೆ"
+api_invalid_task_id = "ಅವೈಧ ಅಥವಾ ಕಾಣಿಸಲ್ಲೌ ಕಾರ್ಯ ID"
+api_task_not_found = "ಟ್ರಾನ್ಸ್ಕ್ರಿಪ್ಶನ್ ಕಾರ್ಯ ಸಿಕ್ಕಲಿಲ್ಲ"
+api_failed_read_transcript = "ಟ್ರಾನ್ಸ್ಕ್ರಿಪ್ಟ್ ಫೈಲ್ ಓದಲು ಅಸಫಲವಾವಿದೆ"
+api_transcript_not_found = "ಟ್ರಾನ್ಸ್ಕ್ರಿಪ್ಟ್ ಫೈಲ್ ಸಿಕ್ಕಲಿಲ್ಲ"
+api_transcription_failed = "ಆಡಿಯೋ ಟ್ರಾನ್ಸ್ಕ್ರಿಪ್ಶನ್ ಅಸಫಲವಾಯಿತು"
+api_transcription_processing = "ಟ್ರಾನ್ಸ್ಕ್ರಿಪ್ಶನ್ ಇದ್ದು ಪ್ರಗತಿಯಲ್ಲಿದೆ"
+api_transcription_queued = "ಟ್ರಾನ್ಸ್ಕ್ರಿಪ್ಶನ್ ಪ್ರಕ್ರಿಯೆಗವಸ್ತು ಸಾಲಿನಲ್ಲಿದೆ"
+;
 ; StaticController.php
 static_controller_logout_successful = "ಲಾಗ್ಔಟ್ ಯಶಸ್ವಿ"
 static_controller_complete_title = "Yioop - %s"
diff --git a/src/locale/ko/configure.ini b/src/locale/ko/configure.ini
old mode 100755
new mode 100644
index 72b84ddc3..a9af4977f
--- a/src/locale/ko/configure.ini
+++ b/src/locale/ko/configure.ini
@@ -58,6 +58,26 @@ admin_controller_no_sort = "어떠한 종류"
 admin_controller_sort_ascending = "오름차순"
 admin_controller_sort_descending = "내림차순"
 ;
+; ApiController.php
+api_invalid_csrf = "유효하지 않은 CSRF 토큰"
+api_transcription_not_configured = "이 시스템에서는 오디오 전사가 구성되지 않았습니다"
+api_invalid_request_data = "유효하지 않은 요청 데이터"
+api_missing_required_field = "필수 필드가 누락되었습니다: %s"
+api_audio_file_not_accessible = "오디오 파일에 액세스할 수 없습니다"
+api_audio_file_too_large = "오디오 파일이 너무 큽니다"
+api_failed_create_directory = "전사 디렉터리 생성에 실패했습니다"
+api_transcription_task_exists = "이 파일에 대한 전사 작업이 이미 존재합니다"
+api_failed_create_task_file = "작업 파일 생성에 실패했습니다"
+api_failed_create_status_file = "상태 파일 생성에 실패했습니다"
+api_transcription_task_created = "전사 작업이 성공적으로 생성되었습니다"
+api_invalid_task_id = "유효하지 않거나 누락된 작업 ID"
+api_task_not_found = "전사 작업을 찾을 수 없습니다"
+api_failed_read_transcript = "전사 파일 읽기에 실패했습니다"
+api_transcript_not_found = "전사 파일을 찾을 수 없습니다"
+api_transcription_failed = "오디오 전사에 실패했습니다"
+api_transcription_processing = "전사가 현재 진행 중입니다"
+api_transcription_queued = "전사가 처리 대기 중입니다"
+;
 ; StaticController.php
 static_controller_logout_successful = "성공적인 로그아웃"
 static_controller_complete_title = "Yioop-%s"
@@ -5708,4 +5728,3 @@ wordfilter_plugin_settings_saved = "단어는 필터링 설정을 저장됩니
 wordfilter_plugin_defaults_restored = "기본값으로 복원!"
 wordfilter_plugin_preferences = "단어는 필터링 환경설정"
 wordfilter_plugin_factory_settings = "공장 설정"
-wordfilter_plugin_save = "저장"
diff --git a/src/locale/nl/configure.ini b/src/locale/nl/configure.ini
index 9a07c33b1..53c9f009d 100644
--- a/src/locale/nl/configure.ini
+++ b/src/locale/nl/configure.ini
@@ -58,6 +58,26 @@ admin_controller_no_sort = "niet sorteren"
 admin_controller_sort_ascending = "oplopend"
 admin_controller_sort_descending = "aflopend"
 ;
+; ApiController.php
+api_invalid_csrf = "Ongeldig CSRF-token"
+api_transcription_not_configured = "Audiotranscriptie is niet geconfigureerd op dit systeem"
+api_invalid_request_data = "Ongeldige verzoekgegevens"
+api_missing_required_field = "Ontbrekend verplicht veld: %s"
+api_audio_file_not_accessible = "Audiobestand is niet toegankelijk"
+api_audio_file_too_large = "Audiobestand is te groot"
+api_failed_create_directory = "Kan transcriptiemap niet aanmaken"
+api_transcription_task_exists = "Transcriptietaak bestaat al voor dit bestand"
+api_failed_create_task_file = "Kan taakbestand niet aanmaken"
+api_failed_create_status_file = "Kan statusbestand niet aanmaken"
+api_transcription_task_created = "Transcriptietaak succesvol aangemaakt"
+api_invalid_task_id = "Ongeldig of ontbrekend taak-ID"
+api_task_not_found = "Transcriptietaak niet gevonden"
+api_failed_read_transcript = "Kan transcriptiebestand niet lezen"
+api_transcript_not_found = "Transcriptiebestand niet gevonden"
+api_transcription_failed = "Audiotranscriptie mislukt"
+api_transcription_processing = "Transcriptie is momenteel in uitvoering"
+api_transcription_queued = "Transcriptie staat in de wachtrij voor verwerking"
+;
 ; StaticController.php
 static_controller_logout_successful = "Afmelden Succesvolle"
 static_controller_complete_title = "Yioop! -%S"
diff --git a/src/locale/pl/configure.ini b/src/locale/pl/configure.ini
index e90171775..ef5c9cd46 100755
--- a/src/locale/pl/configure.ini
+++ b/src/locale/pl/configure.ini
@@ -58,6 +58,26 @@ admin_controller_no_sort = "Nie ma jak"
 admin_controller_sort_ascending = "Rosnąco"
 admin_controller_sort_descending = "Malejąco"
 ;
+; ApiController.php
+api_invalid_csrf = "Nieprawidłowy token CSRF"
+api_transcription_not_configured = "Transkrypcja audio nie jest skonfigurowana w tym systemie"
+api_invalid_request_data = "Nieprawidłowe dane żądania"
+api_missing_required_field = "Brak wymaganego pola: %s"
+api_audio_file_not_accessible = "Plik audio nie jest dostępny"
+api_audio_file_too_large = "Plik audio jest za duży"
+api_failed_create_directory = "Nie udało się utworzyć katalogu transkrypcji"
+api_transcription_task_exists = "Zadanie transkrypcji już istnieje dla tego pliku"
+api_failed_create_task_file = "Nie udało się utworzyć pliku zadania"
+api_failed_create_status_file = "Nie udało się utworzyć pliku statusu"
+api_transcription_task_created = "Zadanie transkrypcji zostało pomyślnie utworzone"
+api_invalid_task_id = "Nieprawidłowy lub brakujący identyfikator zadania"
+api_task_not_found = "Nie znaleziono zadania transkrypcji"
+api_failed_read_transcript = "Nie udało się odczytać pliku transkrypcji"
+api_transcript_not_found = "Nie znaleziono pliku transkrypcji"
+api_transcription_failed = "Transkrypcja audio nie powiodła się"
+api_transcription_processing = "Transkrypcja jest obecnie w toku"
+api_transcription_queued = "Transkrypcja jest w kolejce do przetworzenia"
+;
 ; StaticController.php
 static_controller_logout_successful = "Wyjść Z Powodzeniem"
 static_controller_complete_title = "Yioop - %s"
diff --git a/src/locale/pt/configure.ini b/src/locale/pt/configure.ini
old mode 100755
new mode 100644
index 1354f2458..95c6cd624
--- a/src/locale/pt/configure.ini
+++ b/src/locale/pt/configure.ini
@@ -58,6 +58,26 @@ admin_controller_no_sort = "Sem classificação"
 admin_controller_sort_ascending = "Crescente"
 admin_controller_sort_descending = "Descendente"
 ;
+; ApiController.php
+api_invalid_csrf = "Token CSRF inválido"
+api_transcription_not_configured = "A transcrição de áudio não está configurada neste sistema"
+api_invalid_request_data = "Dados de solicitação inválidos"
+api_missing_required_field = "Campo obrigatório ausente: %s"
+api_audio_file_not_accessible = "O arquivo de áudio não está acessível"
+api_audio_file_too_large = "O arquivo de áudio é muito grande"
+api_failed_create_directory = "Falha ao criar diretório de transcrição"
+api_transcription_task_exists = "A tarefa de transcrição já existe para este arquivo"
+api_failed_create_task_file = "Falha ao criar arquivo de tarefa"
+api_failed_create_status_file = "Falha ao criar arquivo de status"
+api_transcription_task_created = "Tarefa de transcrição criada com sucesso"
+api_invalid_task_id = "ID de tarefa inválido ou ausente"
+api_task_not_found = "Tarefa de transcrição não encontrada"
+api_failed_read_transcript = "Falha ao ler arquivo de transcrição"
+api_transcript_not_found = "Arquivo de transcrição não encontrado"
+api_transcription_failed = "Falha na transcrição de áudio"
+api_transcription_processing = "A transcrição está atualmente em andamento"
+api_transcription_queued = "A transcrição está na fila para processamento"
+;
 ; StaticController.php
 static_controller_logout_successful = "Faça O Logout De Sucesso"
 static_controller_complete_title = "Yioop - %s"
@@ -5708,4 +5728,3 @@ wordfilter_plugin_settings_saved = "Filtro De Palavras Configura&ccedil;&otilde;
 wordfilter_plugin_defaults_restored = "Padr&otilde;es restaurado!"
 wordfilter_plugin_preferences = "Filtro De Palavras Prefer&ecirc;ncias"
 wordfilter_plugin_factory_settings = "As Configura&ccedil;&otilde;es De F&aacute;brica"
-wordfilter_plugin_save = "Salvar"
diff --git a/src/locale/ru/configure.ini b/src/locale/ru/configure.ini
old mode 100755
new mode 100644
index a667c1168..6e431fc54
--- a/src/locale/ru/configure.ini
+++ b/src/locale/ru/configure.ini
@@ -58,6 +58,26 @@ admin_controller_no_sort = "Нет вроде"
 admin_controller_sort_ascending = "Возрастанию"
 admin_controller_sort_descending = "По убыванию"
 ;
+; ApiController.php
+api_invalid_csrf = "Недействительный токен CSRF"
+api_transcription_not_configured = "Транскрипция аудио не настроена в этой системе"
+api_invalid_request_data = "Недействительные данные запроса"
+api_missing_required_field = "Отсутствует обязательное поле: %s"
+api_audio_file_not_accessible = "Аудиофайл недоступен"
+api_audio_file_too_large = "Аудиофайл слишком большой"
+api_failed_create_directory = "Не удалось создать каталог транскрипции"
+api_transcription_task_exists = "Задача транскрипции уже существует для этого файла"
+api_failed_create_task_file = "Не удалось создать файл задачи"
+api_failed_create_status_file = "Не удалось создать файл статуса"
+api_transcription_task_created = "Задача транскрипции успешно создана"
+api_invalid_task_id = "Недействительный или отсутствующий ID задачи"
+api_task_not_found = "Задача транскрипции не найдена"
+api_failed_read_transcript = "Не удалось прочитать файл транскрипции"
+api_transcript_not_found = "Файл транскрипции не найден"
+api_transcription_failed = "Транскрипция аудио не удалась"
+api_transcription_processing = "Транскрипция в настоящее время выполняется"
+api_transcription_queued = "Транскрипция поставлена в очередь на обработку"
+;
 ; StaticController.php
 static_controller_logout_successful = "Выйти Успешно"
 static_controller_complete_title = "Yioop - %s"
@@ -5708,4 +5728,3 @@ wordfilter_plugin_settings_saved = "Настройки Фильтра Слово
 wordfilter_plugin_defaults_restored = "По умолчанию восстановлены!"
 wordfilter_plugin_preferences = "Слово Настройки Фильтра "
 wordfilter_plugin_factory_settings = "Заводские Настройки "
-wordfilter_plugin_save = "Сохранить"
diff --git a/src/locale/te/configure.ini b/src/locale/te/configure.ini
index 26c0bc20a..692215168 100644
--- a/src/locale/te/configure.ini
+++ b/src/locale/te/configure.ini
@@ -58,6 +58,26 @@ admin_controller_no_sort = "క్రమం లో లేదు "
 admin_controller_sort_ascending = "ఆరోహణ క్రమం "
 admin_controller_sort_descending = "అవరోహణ క్రమం"
 ;
+; ApiController.php
+api_invalid_csrf = "అవైధ CSRF టోకెన్"
+api_transcription_not_configured = "ఆడియో ట్రాన్స్క్రిప్షన్ ఈ సిస్టమ్లో కాన్ఫిగర్ చేయలేదు"
+api_invalid_request_data = "అవైధ అనురోధ డేటా"
+api_missing_required_field = "అవసరమైన క్షేత్రం లేదు: %s"
+api_audio_file_not_accessible = "ఆడియో ఫైల్ ప్రవేశించలేము"
+api_audio_file_too_large = "ఆడియో ఫైల్ చాలా పెద్దగా ఉంది"
+api_failed_create_directory = "ట్రాన్స్క్రిప్షన్ డైరెక్టరీ సృష్టించడంలో విఫలమైంది"
+api_transcription_task_exists = "ఈ ఫైల్ కోసం ట్రాన్స్క్రిప్షన్ కార్యం ఇది ఇదే ఉంది"
+api_failed_create_task_file = "కార్య ఫైల్ సృష్టించడంలో విఫలమైంది"
+api_failed_create_status_file = "స్థితి ఫైల్ సృష్టించడంలో విఫలమైంది"
+api_transcription_task_created = "ట్రాన్స్క్రిప్షన్ కార్యం విజయవంతంగా సృష్టించబడింది"
+api_invalid_task_id = "అవైధ లేదా లేని కార్య ID"
+api_task_not_found = "ట్రాన్స్క్రిప్షన్ కార్యం కనుగొనబడలేదు"
+api_failed_read_transcript = "ట్రాన్స్క్రిప్ట్ ఫైల్ పఢడంలో విఫలమైంది"
+api_transcript_not_found = "ట్రాన్స్క్రిప్ట్ ఫైల్ కనుగొనబడలేదు"
+api_transcription_failed = "ఆడియో ట్రాన్స్క్రిప్షన్ విఫలమైంది"
+api_transcription_processing = "ట్రాన్స్క్రిప్షన్ ఈసారి ప్రగతిలో ఉంది"
+api_transcription_queued = "ట్రాన్స్క్రిప్షన్ ప్రక్రియ కోసం వెసితోంది"
+;
 ; StaticController.php
 static_controller_logout_successful = "లాగ్అవుట్ విజయవంతం!"
 static_controller_complete_title = "Yioop! -%s"
diff --git a/src/locale/th/configure.ini b/src/locale/th/configure.ini
index a7418287e..efc916990 100755
--- a/src/locale/th/configure.ini
+++ b/src/locale/th/configure.ini
@@ -58,6 +58,26 @@ admin_controller_no_sort = "ไม่ค่อ"
 admin_controller_sort_ascending = "การส่งจดหมาย"
 admin_controller_sort_descending = "ถอดรหัสแฟ้มประเภท raw"
 ;
+; ApiController.php
+api_invalid_csrf = "โทเคน CSRF ไม่ถูกต้อง"
+api_transcription_not_configured = "การถอดเสียงเป็นข้อความไม่ได้ถูกกำหนดค่าในระบบนี้"
+api_invalid_request_data = "ข้อมูลคำขอไม่ถูกต้อง"
+api_missing_required_field = "ขาดฟิลด์ที่จำเป็น: %s"
+api_audio_file_not_accessible = "ไฟล์เสียงไม่สามารถเข้าถึงได้"
+api_audio_file_too_large = "ไฟล์เสียงใหญ่เกินไป"
+api_failed_create_directory = "ไม่สามารถสร้างไดเร็กทอรีสำหรับการถอดเสียง"
+api_transcription_task_exists = "งานถอดเสียงมีอยู่แล้วสำหรับไฟล์นี้"
+api_failed_create_task_file = "ไม่สามารถสร้างไฟล์งาน"
+api_failed_create_status_file = "ไม่สามารถสร้างไฟล์สถานะ"
+api_transcription_task_created = "สร้างงานถอดเสียงเสร็จสมบูรณ์"
+api_invalid_task_id = "ID งานไม่ถูกต้องหรือขาดหายไป"
+api_task_not_found = "ไม่พบงานถอดเสียง"
+api_failed_read_transcript = "ไม่สามารถอ่านไฟล์บันทึก"
+api_transcript_not_found = "ไม่พบไฟล์บันทึก"
+api_transcription_failed = "การถอดเสียงล้มเหลว"
+api_transcription_processing = "การถอดเสียงกำลังดำเนินการอยู่"
+api_transcription_queued = "การถอดเสียงอยู่ในคิวสำหรับการประมวลผล"
+;
 ; StaticController.php
 static_controller_logout_successful = "ความสำเร็จออกจากระบบ Comment"
 static_controller_complete_title = "Yioop-ทั้งหมด %s"
diff --git a/src/locale/tl/configure.ini b/src/locale/tl/configure.ini
index 8d487690c..a100d2ea2 100644
--- a/src/locale/tl/configure.ini
+++ b/src/locale/tl/configure.ini
@@ -58,6 +58,26 @@ admin_controller_no_sort = "Walang-uri-uriin"
 admin_controller_sort_ascending = "Pataas"
 admin_controller_sort_descending = "Pababang"
 ;
+; ApiController.php
+api_invalid_csrf = "Hindi valid na CSRF token"
+api_transcription_not_configured = "Hindi naka-configure ang audio transcription sa system na ito"
+api_invalid_request_data = "Hindi valid na request data"
+api_missing_required_field = "Nawawalang kinakailangang field: %s"
+api_audio_file_not_accessible = "Hindi ma-access ang audio file"
+api_audio_file_too_large = "Masyadong malaki ang audio file"
+api_failed_create_directory = "Nabigo sa paggawa ng transcription directory"
+api_transcription_task_exists = "May existing na transcription task para sa file na ito"
+api_failed_create_task_file = "Nabigo sa paggawa ng task file"
+api_failed_create_status_file = "Nabigo sa paggawa ng status file"
+api_transcription_task_created = "Matagumpay na nagawa ang transcription task"
+api_invalid_task_id = "Hindi valid o nawawalang task ID"
+api_task_not_found = "Hindi nahanap ang transcription task"
+api_failed_read_transcript = "Nabigo sa pagbasa ng transcript file"
+api_transcript_not_found = "Hindi nahanap ang transcript file"
+api_transcription_failed = "Nabigo ang audio transcription"
+api_transcription_processing = "Kasalukuyang ginagawa ang transcription"
+api_transcription_queued = "Naka-queue ang transcription para sa processing"
+;
 ; StaticController.php
 static_controller_logout_successful = "Logout Matagumpay"
 static_controller_complete_title = "Yioop - %s"
diff --git a/src/locale/tr/configure.ini b/src/locale/tr/configure.ini
index 5d0615ed1..79a654fe3 100755
--- a/src/locale/tr/configure.ini
+++ b/src/locale/tr/configure.ini
@@ -58,6 +58,26 @@ admin_controller_no_sort = "Hayır sıralama"
 admin_controller_sort_ascending = "Artan"
 admin_controller_sort_descending = "Azalan"
 ;
+; ApiController.php
+api_invalid_csrf = "Geçersiz CSRF token"
+api_transcription_not_configured = "Bu sistemde ses transkripsiyon yapılandırılmamış"
+api_invalid_request_data = "Geçersiz istek verisi"
+api_missing_required_field = "Eksik gerekli alan: %s"
+api_audio_file_not_accessible = "Ses dosyası erişilebilir değil"
+api_audio_file_too_large = "Ses dosyası çok büyük"
+api_failed_create_directory = "Transkripsiyon dizini oluşturulamadı"
+api_transcription_task_exists = "Bu dosya için transkripsiyon görevi zaten mevcut"
+api_failed_create_task_file = "Görev dosyası oluşturulamadı"
+api_failed_create_status_file = "Durum dosyası oluşturulamadı"
+api_transcription_task_created = "Transkripsiyon görevi başarıyla oluşturuldu"
+api_invalid_task_id = "Geçersiz veya eksik görev ID'si"
+api_task_not_found = "Transkripsiyon görevi bulunamadı"
+api_failed_read_transcript = "Transkripsiyon dosyası okunamadı"
+api_transcript_not_found = "Transkripsiyon dosyası bulunamadı"
+api_transcription_failed = "Ses transkripsiyon başarısız oldu"
+api_transcription_processing = "Transkripsiyon şu anda devam ediyor"
+api_transcription_queued = "Transkripsiyon işleme için sıraya alındı"
+;
 ; StaticController.php
 static_controller_logout_successful = "Çıkış Başarılı"
 static_controller_complete_title = "Yioop - %s"
diff --git a/src/locale/vi_VN/configure.ini b/src/locale/vi_VN/configure.ini
index 8b5780a76..c63bff776 100755
--- a/src/locale/vi_VN/configure.ini
+++ b/src/locale/vi_VN/configure.ini
@@ -58,6 +58,26 @@ admin_controller_no_sort = "Không sắp xếp"
 admin_controller_sort_ascending = "Tăng dần"
 admin_controller_sort_descending = "Giảm dần"
 ;
+; ApiController.php
+api_invalid_csrf = "Mã thông báo CSRF không hợp lệ"
+api_transcription_not_configured = "Chức năng phiên âm âm thanh chưa được cấu hình trên hệ thống này"
+api_invalid_request_data = "Dữ liệu yêu cầu không hợp lệ"
+api_missing_required_field = "Thiếu trường bắt buộc: %s"
+api_audio_file_not_accessible = "Tập tin âm thanh không thể truy cập"
+api_audio_file_too_large = "Tập tin âm thanh quá lớn"
+api_failed_create_directory = "Không thể tạo thư mục phiên âm"
+api_transcription_task_exists = "Tác vụ phiên âm đã tồn tại cho tập tin này"
+api_failed_create_task_file = "Không thể tạo tập tin tác vụ"
+api_failed_create_status_file = "Không thể tạo tập tin trạng thái"
+api_transcription_task_created = "Tác vụ phiên âm được tạo thành công"
+api_invalid_task_id = "ID tác vụ không hợp lệ hoặc bị thiếu"
+api_task_not_found = "Không tìm thấy tác vụ phiên âm"
+api_failed_read_transcript = "Không thể đọc tập tin phiên âm"
+api_transcript_not_found = "Không tìm thấy tập tin phiên âm"
+api_transcription_failed = "Phiên âm âm thanh thất bại"
+api_transcription_processing = "Phiên âm hiện đang được xử lý"
+api_transcription_queued = "Phiên âm đang chờ xử lý"
+;
 ; StaticController.php
 static_controller_logout_successful = "Đăng Xuất Thành Công"
 static_controller_complete_title = "Yioop - %s"
diff --git a/src/locale/zh_CN/configure.ini b/src/locale/zh_CN/configure.ini
old mode 100755
new mode 100644
index 152fb5f3e..742a2e3f6
--- a/src/locale/zh_CN/configure.ini
+++ b/src/locale/zh_CN/configure.ini
@@ -58,6 +58,26 @@ admin_controller_no_sort = "没有排序"
 admin_controller_sort_ascending = "升"
 admin_controller_sort_descending = "降"
 ;
+; ApiController.php
+api_invalid_csrf = "无效的CSRF令牌"
+api_transcription_not_configured = "此系统未配置音频转录"
+api_invalid_request_data = "无效的请求数据"
+api_missing_required_field = "缺少必需字段：%s"
+api_audio_file_not_accessible = "音频文件无法访问"
+api_audio_file_too_large = "音频文件太大"
+api_failed_create_directory = "创建转录目录失败"
+api_transcription_task_exists = "此文件的转录任务已存在"
+api_failed_create_task_file = "创建任务文件失败"
+api_failed_create_status_file = "创建状态文件失败"
+api_transcription_task_created = "转录任务创建成功"
+api_invalid_task_id = "无效或缺少任务ID"
+api_task_not_found = "找不到转录任务"
+api_failed_read_transcript = "读取转录文件失败"
+api_transcript_not_found = "找不到转录文件"
+api_transcription_failed = "音频转录失败"
+api_transcription_processing = "转录正在进行中"
+api_transcription_queued = "转录已排队等待处理"
+;
 ; StaticController.php
 static_controller_logout_successful = "注销的成功"
 static_controller_complete_title = "Yioop-%s"
@@ -5708,4 +5728,3 @@ wordfilter_plugin_settings_saved = "字过滤设置保存！"
 wordfilter_plugin_defaults_restored = "Defaults恢复！"
 wordfilter_plugin_preferences = "字滤波器的喜好"
 wordfilter_plugin_factory_settings = "工厂设置"
-wordfilter_plugin_save = "保存"
diff --git a/src/models/GroupModel.php b/src/models/GroupModel.php
index 479136305..91b0ae860 100644
--- a/src/models/GroupModel.php
+++ b/src/models/GroupModel.php
@@ -2750,12 +2750,27 @@ class GroupModel extends Model implements MediaConstants
                 'audio/ogg', 'audio/opus',
                 'audio/vorbis', 'audio/vnd.rn-realaudio', 'audio/vnd.wave',
                 'audio/webm'])) {
-                $replace_string = "<audio controls='controls' $autoplay".
-                    " class='audio' id='".
-                    L\crawlHash($resource_name)."' >\n".
-                    "<source src='$resource_url'  type='$mime_type' >\n".
+                $audio_id = L\crawlHash($resource_name);
+                $replace_string = "<div class='audio-message-container'>\n".
+                    "<div class='audio-player-wrapper'>\n".
+                    "<audio controls='controls' $autoplay class='audio' " .
+                        "id='$audio_id'>\n".
+                    "<source src='$resource_url' type='$mime_type'>\n".
                     $resource_description."\n".
-                    "</audio>";
+                    "</audio>\n";
+                if (C\nsdefined('WHISPER')) {
+                    $replace_string .= "<button onclick='transcribeAudio(\"" .
+                        htmlspecialchars($resource_name, ENT_QUOTES) . "\", \"" .
+                        htmlspecialchars($current_folder, ENT_QUOTES) . "\", \"" .
+                        htmlspecialchars($csrf_token, ENT_QUOTES) . "\")' " .
+                        "class='transcribe-btn' title='Show transcript'>" .
+                            "📝</button>\n";
+                }
+                $replace_string .= "</div>\n".
+                    "<div class='transcript-container' " .
+                        "id='transcript-$audio_id'></div>\n".
+                    "</div>";
+
                 $parsed_page = preg_replace('/'.preg_quote($match_string, '/')
                     .'/u', $replace_string, $parsed_page);
             } else if ($mime_type == 'application/epub+zip' &&
@@ -3993,6 +4008,46 @@ EOD;
                 }
             }
         }
+        if (C\nsdefined('WHISPER') && in_array($mime_type, [
+            'audio/mpeg', 'audio/mp3', 'audio/wav', 'audio/ogg',
+            'audio/mp4', 'audio/webm', 'audio/m4a', 'audio/aiff',
+            'audio/basic', 'audio/L24'])) {
+            $task_dir = C\SCHEDULES_DIR . "/audio_transcribe";
+            if (!file_exists($task_dir)) {
+                if (!mkdir($task_dir, 0777, true)) {
+                    L\crawlLog("Failed to create audio transcription " .
+                        "task directory");
+                    return;
+                }
+            }
+            $task_id = L\crawlHash($file_name . $group_id . $page_id . time());
+            $task_file = $task_dir . "/" . $task_id . ".txt";
+            $status_file = $task_dir . "/" . $task_id . ".status";
+            $task_data = [
+                'audio_path' => "$folder/$file_name",
+                'filename' => $file_name,
+                'task_id' => $task_id,
+                'group_id' => $group_id,
+                'page_id' => $page_id,
+                'sub_path' => $sub_path,
+                'created_at' => time(),
+                'status' => 'pending',
+                'retry_count' => 0
+            ];
+            if (file_put_contents($task_file, serialize($task_data)) !==
+                false && file_put_contents($status_file, 'pending') !== false) {
+                L\crawlLog("AudioTranscription: Created task for $file_name " .
+                          "(ID: $task_id)");
+                $transcript_path = "$folder/" .
+                    pathinfo($file_name, PATHINFO_FILENAME) . ".txt";
+                file_put_contents($transcript_path,
+                    "Transcription pending...");
+                $vcs->createVersion($transcript_path, "", $timestamp);
+            } else {
+                L\crawlLog("AudioTranscription: Failed to create task files " .
+                    "for $file_name");
+            }
+        }
     }
     /**
      * Creates a symlink between two resource folders (and their associate
diff --git a/src/scripts/basic.js b/src/scripts/basic.js
index 9101f8c3a..3317a5ef9 100755
--- a/src/scripts/basic.js
+++ b/src/scripts/basic.js
@@ -1090,3 +1090,128 @@ function stopRecording()
         media_recorder.stream.getTracks().forEach(track => track.stop());
     }
 }
+/**
+ * Creates a hash string from input text
+ * Used for generating unique IDs for DOM elements
+ * @param {string} text - Text to hash
+ * @return {string} Hashed string
+ */
+function crawlHash(text) {
+    let hash = 0;
+    if (text.length === 0) return hash.toString();
+    for (let i = 0; i < text.length; i++) {
+        const char = text.charCodeAt(i);
+        hash = ((hash << 5) - hash) + char;
+        hash = hash & hash; // Convert to 32-bit integer
+    }
+    return Math.abs(hash).toString(16).toLowerCase();
+}
+/**
+ * Handles displaying transcripts for audio messages
+ * Fetches transcript from server if available, otherwise shows placeholder
+ * @param {string} filename - Name of the audio file
+ * @param {string} folder - Folder path containing the audio file
+ * @param {string} csrf_token - CSRF token for API requests
+ */
+function transcribeAudio(filename, folder, csrf_token) {
+    let audio_element = document.querySelector(
+        `audio source[src*="${filename}"]`)?.parentElement;
+    if (!audio_element) {
+        const audio_elements = document.querySelectorAll('audio');
+        for (let audio of audio_elements) {
+            const source = audio.querySelector('source');
+            if (source && source.src.includes(filename)) {
+                audio_element = audio;
+                break;
+            }
+        }
+    }
+    if (!audio_element) {
+        console.error('Could not find audio element for:', filename);
+        return;
+    }
+    const transcript_container = document.getElementById(
+        'transcript-' + audio_element.id);
+    if (!transcript_container) {
+        console.error('Could not find transcript container for:',
+            audio_element.id);
+        return;
+    }
+    if (transcript_container.classList.contains('show')) {
+        transcript_container.classList.remove('show');
+        return;
+    }
+    transcript_container.classList.add('show', 'loading');
+    transcript_container.textContent = 'Loading transcript...';
+    const audio_source = audio_element.querySelector('source');
+    if (!audio_source) {
+        console.error('Could not find audio source');
+        transcript_container.textContent =
+            'Error: Could not find audio source.';
+        transcript_container.className = 'transcript-container show error';
+        return;
+    }
+    const base_url = audio_source.src.replace(
+        /\.(mp3|wav|ogg|m4a|webm|mp4|aiff|basic|L24)$/i, '');
+    const transcript_url = base_url + '.txt';
+    fetch(transcript_url, {
+        method: 'GET',
+        headers: {
+            'Cache-Control': 'no-cache'
+        }
+    })
+    .then(response => {
+        if (!response.ok) {
+            throw new Error(`HTTP ${response.status}: ${response.statusText}`);
+        }
+        return response.text();
+    })
+    .then(transcript => {
+        const trimmed_transcript = transcript.trim();
+        if (!trimmed_transcript) {
+            throw new Error('Transcript is empty');
+        }
+        if (trimmed_transcript === 'Transcription pending...' ||
+            trimmed_transcript === 'Transcription in progress...') {
+            transcript_container.textContent =
+                'Transcription is being processed. Please try again in ' +
+                'a few minutes.';
+            transcript_container.className =
+                'transcript-container show loading';
+            return;
+        }
+        if (trimmed_transcript.includes('Transcription failed permanently') ||
+            trimmed_transcript.includes('failed permanently after')) {
+            transcript_container.textContent = trimmed_transcript;
+            transcript_container.className = 'transcript-container show error';
+            return;
+        }
+        if (transcript.includes('\0') || transcript.length > 50000) {
+            throw new Error('Invalid transcript content');
+        }
+        const cleaned_transcript = trimmed_transcript
+            .replace(/\s+/g, ' ')
+            .replace(/\n{3,}/g, '\n\n')
+            .trim();
+        transcript_container.textContent = cleaned_transcript;
+        transcript_container.className = 'transcript-container show';
+    })
+    .catch(error => {
+        let error_message = 'Transcript not available yet.';
+        if (error.message.includes('404')) {
+            error_message =
+                'Transcript not found. Processing may still be in progress.';
+        } else if (error.message.includes('403') ||
+            error.message.includes('401')) {
+            error_message = 'Access denied to transcript file.';
+        } else if (error.message.includes('500')) {
+            error_message = 'Server error while fetching transcript.';
+        } else if (error.message.includes('network') ||
+            error.message.includes('fetch')) {
+            error_message = 'Network error. Please check your connection.';
+        }
+        transcript_container.textContent = error_message +
+            ' Please try again later.';
+        transcript_container.className = 'transcript-container show error';
+    });
+}
