You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by fm...@apache.org on 2026/05/04 11:45:55 UTC

(camel) 02/02: CAMEL-23390: Use DefaultProducer for audio transcription

This is an automated email from the ASF dual-hosted git repository.

Croway pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git

commit ea92d45791db0ab0747de68525558170aac40255
Author: Croway <fe...@gmail.com>
AuthorDate: Thu Apr 30 16:01:53 2026 +0200

    CAMEL-23390: Use DefaultProducer for audio transcription
    
    The audio transcription producer performs synchronous blocking work,
    so DefaultProducer is a better fit than DefaultAsyncProducer.
---
 .../apache/camel/catalog/components/openai.json    |   7 +-
 .../org/apache/camel/component/openai/openai.json  |   7 +-
 .../openai/OpenAIAudioTranscriptionProducer.java   |  59 +++---
 .../camel/component/openai/OpenAIConstants.java    |   8 +
 .../openai/OpenAIAudioTranscriptionMockTest.java   |  19 ++
 .../OpenAIAudioTranscriptionVerboseMockTest.java   |  66 ++++++
 .../component/ComponentsBuilderFactory.java        |   2 +-
 .../dsl/OpenaiComponentBuilderFactory.java         |  20 +-
 .../builder/endpoint/StaticEndpointBuilders.java   |  20 +-
 .../endpoint/dsl/OpenAIEndpointBuilderFactory.java | 230 +++++++++++++++++++--
 .../openai/mock/AudioTranscriptionExpectation.java |   4 +
 .../mock/AudioTranscriptionResponseBuilder.java    |  23 ++-
 12 files changed, 408 insertions(+), 57 deletions(-)

diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/openai.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/openai.json
index 4b9f9e775b86..e261cc641659 100644
--- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/openai.json
+++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/openai.json
@@ -67,8 +67,11 @@
     "CamelOpenAIAudioModel": { "index": 30, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The model to use for audio transcription", "constantName": "org.apache.camel.component.openai.OpenAIConstants#AUDIO_MODEL" },
     "CamelOpenAIAudioLanguage": { "index": 31, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The language of the input audio (ISO-639-1)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#AUDIO_LANGUAGE" },
     "CamelOpenAIAudioResponseFormat": { "index": 32, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The response format for audio transcription (json, text, srt, verbose_json, vtt)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#AUDIO_RESPONSE_FORMAT" },
-    "CamelOpenAIAudioDuration": { "index": 33, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Double", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Duration of the audio in seconds (verbose_json only)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#AUDIO_DURATION" },
-    "CamelOpenAIAudioDetectedLanguage": { "index": 34, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Language detected in the audio (verbose_json only)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#AUDIO_DETECTED_LANGUAGE" }
+    "CamelOpenAIAudioTemperature": { "index": 33, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Double", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Sampling temperature for audio transcription (0.0 to 1.0)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#AUDIO_TEMPERATURE" },
+    "CamelOpenAIAudioPrompt": { "index": 34, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Optional text to guide the model's style or continue a previous audio segment", "constantName": "org.apache.camel.component.openai.OpenAIConstants#AUDIO_PROMPT" },
+    "CamelOpenAIAudioTimestampGranularities": { "index": 35, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Comma-separated timestamp granularities: word, segment, or word,segment (verbose_json only)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#AUDIO_TIMESTAMP_GRANULARITIES" },
+    "CamelOpenAIAudioDuration": { "index": 36, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Double", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Duration of the audio in seconds (verbose_json only)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#AUDIO_DURATION" },
+    "CamelOpenAIAudioDetectedLanguage": { "index": 37, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Language detected in the audio (verbose_json only)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#AUDIO_DETECTED_LANGUAGE" }
   },
   "properties": {
     "operation": { "index": 0, "kind": "path", "displayName": "Operation", "group": "producer", "label": "", "required": true, "type": "enum", "javaType": "org.apache.camel.component.openai.OpenAIOperations", "enum": [ "chat-completion", "embeddings", "tool-execution", "audio-transcription" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The operation to perform: 'chat-completion', 'embeddings', 'tool-execution', or 'audio-transcription'" },
diff --git a/components/camel-ai/camel-openai/src/generated/resources/META-INF/org/apache/camel/component/openai/openai.json b/components/camel-ai/camel-openai/src/generated/resources/META-INF/org/apache/camel/component/openai/openai.json
index 4b9f9e775b86..e261cc641659 100644
--- a/components/camel-ai/camel-openai/src/generated/resources/META-INF/org/apache/camel/component/openai/openai.json
+++ b/components/camel-ai/camel-openai/src/generated/resources/META-INF/org/apache/camel/component/openai/openai.json
@@ -67,8 +67,11 @@
     "CamelOpenAIAudioModel": { "index": 30, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The model to use for audio transcription", "constantName": "org.apache.camel.component.openai.OpenAIConstants#AUDIO_MODEL" },
     "CamelOpenAIAudioLanguage": { "index": 31, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The language of the input audio (ISO-639-1)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#AUDIO_LANGUAGE" },
     "CamelOpenAIAudioResponseFormat": { "index": 32, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The response format for audio transcription (json, text, srt, verbose_json, vtt)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#AUDIO_RESPONSE_FORMAT" },
-    "CamelOpenAIAudioDuration": { "index": 33, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Double", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Duration of the audio in seconds (verbose_json only)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#AUDIO_DURATION" },
-    "CamelOpenAIAudioDetectedLanguage": { "index": 34, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Language detected in the audio (verbose_json only)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#AUDIO_DETECTED_LANGUAGE" }
+    "CamelOpenAIAudioTemperature": { "index": 33, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Double", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Sampling temperature for audio transcription (0.0 to 1.0)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#AUDIO_TEMPERATURE" },
+    "CamelOpenAIAudioPrompt": { "index": 34, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Optional text to guide the model's style or continue a previous audio segment", "constantName": "org.apache.camel.component.openai.OpenAIConstants#AUDIO_PROMPT" },
+    "CamelOpenAIAudioTimestampGranularities": { "index": 35, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Comma-separated timestamp granularities: word, segment, or word,segment (verbose_json only)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#AUDIO_TIMESTAMP_GRANULARITIES" },
+    "CamelOpenAIAudioDuration": { "index": 36, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "Double", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Duration of the audio in seconds (verbose_json only)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#AUDIO_DURATION" },
+    "CamelOpenAIAudioDetectedLanguage": { "index": 37, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Language detected in the audio (verbose_json only)", "constantName": "org.apache.camel.component.openai.OpenAIConstants#AUDIO_DETECTED_LANGUAGE" }
   },
   "properties": {
     "operation": { "index": 0, "kind": "path", "displayName": "Operation", "group": "producer", "label": "", "required": true, "type": "enum", "javaType": "org.apache.camel.component.openai.OpenAIOperations", "enum": [ "chat-completion", "embeddings", "tool-execution", "audio-transcription" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The operation to perform: 'chat-completion', 'embeddings', 'tool-execution', or 'audio-transcription'" },
diff --git a/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIAudioTranscriptionProducer.java b/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIAudioTranscriptionProducer.java
index 33e781354b8e..9ede8199c267 100644
--- a/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIAudioTranscriptionProducer.java
+++ b/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIAudioTranscriptionProducer.java
@@ -28,16 +28,16 @@ import com.openai.models.audio.AudioResponseFormat;
 import com.openai.models.audio.transcriptions.TranscriptionCreateParams;
 import com.openai.models.audio.transcriptions.TranscriptionCreateResponse;
 import com.openai.models.audio.transcriptions.TranscriptionVerbose;
-import org.apache.camel.AsyncCallback;
 import org.apache.camel.Exchange;
 import org.apache.camel.Message;
-import org.apache.camel.support.DefaultAsyncProducer;
+import org.apache.camel.WrappedFile;
+import org.apache.camel.support.DefaultProducer;
 import org.apache.camel.util.ObjectHelper;
 
 /**
  * OpenAI producer for audio transcription.
  */
-public class OpenAIAudioTranscriptionProducer extends DefaultAsyncProducer {
+public class OpenAIAudioTranscriptionProducer extends DefaultProducer {
 
     public OpenAIAudioTranscriptionProducer(OpenAIEndpoint endpoint) {
         super(endpoint);
@@ -49,19 +49,7 @@ public class OpenAIAudioTranscriptionProducer extends DefaultAsyncProducer {
     }
 
     @Override
-    public boolean process(Exchange exchange, AsyncCallback callback) {
-        try {
-            processInternal(exchange);
-            callback.done(true);
-            return true;
-        } catch (Exception e) {
-            exchange.setException(e);
-            callback.done(true);
-            return true;
-        }
-    }
-
-    private void processInternal(Exchange exchange) throws Exception {
+    public void process(Exchange exchange) throws Exception {
         OpenAIConfiguration config = getEndpoint().getConfiguration();
         Message in = exchange.getIn();
 
@@ -74,9 +62,11 @@ public class OpenAIAudioTranscriptionProducer extends DefaultAsyncProducer {
         String language = resolveParameter(in, OpenAIConstants.AUDIO_LANGUAGE, config.getAudioLanguage(), String.class);
         String responseFormat = resolveParameter(in, OpenAIConstants.AUDIO_RESPONSE_FORMAT,
                 config.getAudioResponseFormat(), String.class);
-        Double temperature = resolveParameter(in, null, config.getAudioTemperature(), Double.class);
-        String prompt = resolveParameter(in, null, config.getAudioPrompt(), String.class);
-        String timestampGranularities = resolveParameter(in, null, config.getAudioTimestampGranularities(), String.class);
+        Double temperature = resolveParameter(in, OpenAIConstants.AUDIO_TEMPERATURE, config.getAudioTemperature(),
+                Double.class);
+        String prompt = resolveParameter(in, OpenAIConstants.AUDIO_PROMPT, config.getAudioPrompt(), String.class);
+        String timestampGranularities = resolveParameter(in, OpenAIConstants.AUDIO_TIMESTAMP_GRANULARITIES,
+                config.getAudioTimestampGranularities(), String.class);
 
         TranscriptionCreateParams.Builder paramsBuilder = TranscriptionCreateParams.builder()
                 .model(model);
@@ -98,9 +88,14 @@ public class OpenAIAudioTranscriptionProducer extends DefaultAsyncProducer {
         if (ObjectHelper.isNotEmpty(timestampGranularities)) {
             List<TranscriptionCreateParams.TimestampGranularity> granularities = new ArrayList<>();
             for (String g : timestampGranularities.split(",")) {
-                granularities.add(TranscriptionCreateParams.TimestampGranularity.of(g.trim()));
+                String trimmed = g.trim();
+                if (!trimmed.isEmpty()) {
+                    granularities.add(TranscriptionCreateParams.TimestampGranularity.of(trimmed));
+                }
+            }
+            if (!granularities.isEmpty()) {
+                paramsBuilder.timestampGranularities(granularities);
             }
-            paramsBuilder.timestampGranularities(granularities);
         }
 
         TranscriptionCreateParams params = paramsBuilder.build();
@@ -128,14 +123,18 @@ public class OpenAIAudioTranscriptionProducer extends DefaultAsyncProducer {
     private void setFileInput(TranscriptionCreateParams.Builder paramsBuilder, Message in) {
         Object body = in.getBody();
 
+        if (body instanceof WrappedFile<?> wrappedFile) {
+            body = wrappedFile.getFile();
+        }
+
         if (body instanceof File file) {
             paramsBuilder.file(file.toPath());
         } else if (body instanceof Path path) {
             paramsBuilder.file(path);
         } else if (body instanceof byte[] bytes) {
-            paramsBuilder.file(multipartWithFilename(new ByteArrayInputStream(bytes)));
+            paramsBuilder.file(multipartWithFilename(new ByteArrayInputStream(bytes), resolveFilename(in)));
         } else if (body instanceof InputStream inputStream) {
-            paramsBuilder.file(multipartWithFilename(inputStream));
+            paramsBuilder.file(multipartWithFilename(inputStream, resolveFilename(in)));
         } else {
             InputStream converted = in.getBody(InputStream.class);
             if (converted == null) {
@@ -144,14 +143,22 @@ public class OpenAIAudioTranscriptionProducer extends DefaultAsyncProducer {
                                                    + (body != null ? body.getClass().getName() : "null")
                                                    + ". Supported: File, Path, InputStream, byte[]");
             }
-            paramsBuilder.file(multipartWithFilename(converted));
+            paramsBuilder.file(multipartWithFilename(converted, resolveFilename(in)));
+        }
+    }
+
+    private String resolveFilename(Message in) {
+        String filename = in.getHeader(Exchange.FILE_NAME_ONLY, String.class);
+        if (ObjectHelper.isNotEmpty(filename)) {
+            return filename;
         }
+        return "audio";
     }
 
-    private MultipartField<InputStream> multipartWithFilename(InputStream stream) {
+    private MultipartField<InputStream> multipartWithFilename(InputStream stream, String filename) {
         return MultipartField.<InputStream> builder()
                 .value(stream)
-                .filename("audio")
+                .filename(filename)
                 .build();
     }
 
diff --git a/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIConstants.java b/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIConstants.java
index 473eaeada6bf..6464d792d601 100644
--- a/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIConstants.java
+++ b/components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/OpenAIConstants.java
@@ -110,6 +110,14 @@ public final class OpenAIConstants {
     @Metadata(description = "The response format for audio transcription (json, text, srt, verbose_json, vtt)",
               javaType = "String")
     public static final String AUDIO_RESPONSE_FORMAT = "CamelOpenAIAudioResponseFormat";
+    @Metadata(description = "Sampling temperature for audio transcription (0.0 to 1.0)", javaType = "Double")
+    public static final String AUDIO_TEMPERATURE = "CamelOpenAIAudioTemperature";
+    @Metadata(description = "Optional text to guide the model's style or continue a previous audio segment",
+              javaType = "String")
+    public static final String AUDIO_PROMPT = "CamelOpenAIAudioPrompt";
+    @Metadata(description = "Comma-separated timestamp granularities: word, segment, or word,segment (verbose_json only)",
+              javaType = "String")
+    public static final String AUDIO_TIMESTAMP_GRANULARITIES = "CamelOpenAIAudioTimestampGranularities";
 
     // Audio Transcription Output Headers
     @Metadata(description = "Duration of the audio in seconds (verbose_json only)", javaType = "Double")
diff --git a/components/camel-ai/camel-openai/src/test/java/org/apache/camel/component/openai/OpenAIAudioTranscriptionMockTest.java b/components/camel-ai/camel-openai/src/test/java/org/apache/camel/component/openai/OpenAIAudioTranscriptionMockTest.java
index eeca71767c58..427791d4325c 100644
--- a/components/camel-ai/camel-openai/src/test/java/org/apache/camel/component/openai/OpenAIAudioTranscriptionMockTest.java
+++ b/components/camel-ai/camel-openai/src/test/java/org/apache/camel/component/openai/OpenAIAudioTranscriptionMockTest.java
@@ -21,9 +21,11 @@ import java.io.File;
 import java.io.InputStream;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.util.concurrent.TimeUnit;
 
 import org.apache.camel.Exchange;
 import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
 import org.apache.camel.test.infra.openai.mock.OpenAIMock;
 import org.apache.camel.test.junit6.CamelTestSupport;
 import org.junit.jupiter.api.Test;
@@ -61,6 +63,11 @@ public class OpenAIAudioTranscriptionMockTest extends CamelTestSupport {
                 from("direct:transcribe-no-model")
                         .to("openai:audio-transcription?apiKey=dummy&baseUrl="
                             + openAIMock.getBaseUrl() + "/v1");
+
+                from("file:" + tempDir.toString() + "?noop=true&initialDelay=0&delay=100")
+                        .to("openai:audio-transcription?audioModel=whisper-1&apiKey=dummy&baseUrl="
+                            + openAIMock.getBaseUrl() + "/v1")
+                        .to("mock:transcribed");
             }
         };
     }
@@ -126,6 +133,18 @@ public class OpenAIAudioTranscriptionMockTest extends CamelTestSupport {
         assertEquals(TRANSCRIPTION_TEXT, result.getMessage().getBody(String.class));
     }
 
+    @Test
+    void testTranscriptionFromFileComponent() throws Exception {
+        MockEndpoint mock = getMockEndpoint("mock:transcribed");
+        mock.expectedMessageCount(1);
+        mock.expectedBodiesReceived(TRANSCRIPTION_TEXT);
+
+        Files.write(tempDir.resolve("recording.wav"), new byte[] { 0x00, 0x01, 0x02 });
+
+        mock.await(10, TimeUnit.SECONDS);
+        mock.assertIsSatisfied();
+    }
+
     @Test
     void testTranscriptionMissingModel() {
         byte[] audioBytes = new byte[] { 0x00, 0x01, 0x02 };
diff --git a/components/camel-ai/camel-openai/src/test/java/org/apache/camel/component/openai/OpenAIAudioTranscriptionVerboseMockTest.java b/components/camel-ai/camel-openai/src/test/java/org/apache/camel/component/openai/OpenAIAudioTranscriptionVerboseMockTest.java
new file mode 100644
index 000000000000..da75fdf5797c
--- /dev/null
+++ b/components/camel-ai/camel-openai/src/test/java/org/apache/camel/component/openai/OpenAIAudioTranscriptionVerboseMockTest.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.openai;
+
+import org.apache.camel.Exchange;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.test.infra.openai.mock.OpenAIMock;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+public class OpenAIAudioTranscriptionVerboseMockTest extends CamelTestSupport {
+
+    private static final String TRANSCRIPTION_TEXT = "The stale smell of old beer lingers.";
+
+    @RegisterExtension
+    public OpenAIMock openAIMock = new OpenAIMock().builder()
+            .whenTranscription()
+            .replyWithTranscription(TRANSCRIPTION_TEXT)
+            .withDuration(5.2)
+            .withLanguage("en")
+            .end()
+            .build();
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                from("direct:transcribe-verbose")
+                        .to("openai:audio-transcription?audioModel=whisper-1&audioResponseFormat=verbose_json"
+                            + "&apiKey=dummy&baseUrl=" + openAIMock.getBaseUrl() + "/v1");
+            }
+        };
+    }
+
+    @Test
+    void testVerboseResponseSetsHeaders() {
+        Exchange result = template.request("direct:transcribe-verbose",
+                e -> e.getIn().setBody(new byte[] { 0x00, 0x01, 0x02 }));
+
+        assertNotNull(result);
+        assertNull(result.getException());
+        assertEquals(TRANSCRIPTION_TEXT, result.getMessage().getBody(String.class));
+        assertEquals(5.2, result.getMessage().getHeader(OpenAIConstants.AUDIO_DURATION, Double.class), 0.001);
+        assertEquals("en", result.getMessage().getHeader(OpenAIConstants.AUDIO_DETECTED_LANGUAGE, String.class));
+    }
+}
diff --git a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/ComponentsBuilderFactory.java b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/ComponentsBuilderFactory.java
index bfcc887afa17..244293d099f0 100644
--- a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/ComponentsBuilderFactory.java
+++ b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/ComponentsBuilderFactory.java
@@ -3773,7 +3773,7 @@ public interface ComponentsBuilderFactory {
     }
     /**
      * OpenAI (camel-openai)
-     * OpenAI endpoint for chat completion and embeddings.
+     * OpenAI endpoint for chat completion, embeddings, and audio transcription.
      * 
      * Category: ai
      * Since: 4.17
diff --git a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/OpenaiComponentBuilderFactory.java b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/OpenaiComponentBuilderFactory.java
index 27e93f41ffda..8d9bf939bf2f 100644
--- a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/OpenaiComponentBuilderFactory.java
+++ b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/OpenaiComponentBuilderFactory.java
@@ -24,7 +24,7 @@ import org.apache.camel.builder.component.ComponentBuilder;
 import org.apache.camel.component.openai.OpenAIComponent;
 
 /**
- * OpenAI endpoint for chat completion and embeddings.
+ * OpenAI endpoint for chat completion, embeddings, and audio transcription.
  * 
  * Generated by camel build tools - do NOT edit this file!
  */
@@ -33,7 +33,7 @@ public interface OpenaiComponentBuilderFactory {
 
     /**
      * OpenAI (camel-openai)
-     * OpenAI endpoint for chat completion and embeddings.
+     * OpenAI endpoint for chat completion, embeddings, and audio transcription.
      * 
      * Category: ai
      * Since: 4.17
@@ -65,6 +65,21 @@ public interface OpenaiComponentBuilderFactory {
             return this;
         }
     
+        /**
+         * Default model for audio transcription endpoints.
+         * 
+         * The option is a: &lt;code&gt;java.lang.String&lt;/code&gt; type.
+         * 
+         * Group: producer
+         * 
+         * @param audioModel the value to set
+         * @return the dsl builder
+         */
+        default OpenaiComponentBuilder audioModel(java.lang.String audioModel) {
+            doSetProperty("audioModel", audioModel);
+            return this;
+        }
+    
         
         /**
          * Default base URL for all endpoints.
@@ -191,6 +206,7 @@ public interface OpenaiComponentBuilderFactory {
                 Object value) {
             switch (name) {
             case "apiKey": ((OpenAIComponent) component).setApiKey((java.lang.String) value); return true;
+            case "audioModel": ((OpenAIComponent) component).setAudioModel((java.lang.String) value); return true;
             case "baseUrl": ((OpenAIComponent) component).setBaseUrl((java.lang.String) value); return true;
             case "embeddingModel": ((OpenAIComponent) component).setEmbeddingModel((java.lang.String) value); return true;
             case "lazyStartProducer": ((OpenAIComponent) component).setLazyStartProducer((boolean) value); return true;
diff --git a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/StaticEndpointBuilders.java b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/StaticEndpointBuilders.java
index 988bffc6c715..3a5a55431e67 100644
--- a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/StaticEndpointBuilders.java
+++ b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/StaticEndpointBuilders.java
@@ -12546,7 +12546,7 @@ public class StaticEndpointBuilders {
     }
     /**
      * OpenAI (camel-openai)
-     * OpenAI endpoint for chat completion and embeddings.
+     * OpenAI endpoint for chat completion, embeddings, and audio transcription.
      * 
      * Category: ai
      * Since: 4.17
@@ -12555,10 +12555,10 @@ public class StaticEndpointBuilders {
      * Syntax: <code>openai:operation</code>
      * 
      * Path parameter: operation (required)
-     * The operation to perform: 'chat-completion', 'embeddings', or
-     * 'tool-execution'
-     * There are 3 enums and the value can be one of: chat-completion,
-     * embeddings, tool-execution
+     * The operation to perform: 'chat-completion', 'embeddings',
+     * 'tool-execution', or 'audio-transcription'
+     * There are 4 enums and the value can be one of: chat-completion,
+     * embeddings, tool-execution, audio-transcription
      * 
      * @param path operation
      * @return the dsl builder
@@ -12568,7 +12568,7 @@ public class StaticEndpointBuilders {
     }
     /**
      * OpenAI (camel-openai)
-     * OpenAI endpoint for chat completion and embeddings.
+     * OpenAI endpoint for chat completion, embeddings, and audio transcription.
      * 
      * Category: ai
      * Since: 4.17
@@ -12577,10 +12577,10 @@ public class StaticEndpointBuilders {
      * Syntax: <code>openai:operation</code>
      * 
      * Path parameter: operation (required)
-     * The operation to perform: 'chat-completion', 'embeddings', or
-     * 'tool-execution'
-     * There are 3 enums and the value can be one of: chat-completion,
-     * embeddings, tool-execution
+     * The operation to perform: 'chat-completion', 'embeddings',
+     * 'tool-execution', or 'audio-transcription'
+     * There are 4 enums and the value can be one of: chat-completion,
+     * embeddings, tool-execution, audio-transcription
      * 
      * @param componentName to use a custom component name for the endpoint
      * instead of the default name
diff --git a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/OpenAIEndpointBuilderFactory.java b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/OpenAIEndpointBuilderFactory.java
index 3231c4da3a30..779c8c3c9633 100644
--- a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/OpenAIEndpointBuilderFactory.java
+++ b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/OpenAIEndpointBuilderFactory.java
@@ -27,7 +27,7 @@ import org.apache.camel.builder.EndpointProducerBuilder;
 import org.apache.camel.builder.endpoint.AbstractEndpointBuilder;
 
 /**
- * OpenAI endpoint for chat completion and embeddings.
+ * OpenAI endpoint for chat completion, embeddings, and audio transcription.
  * 
  * Generated by camel build tools - do NOT edit this file!
  */
@@ -100,6 +100,109 @@ public interface OpenAIEndpointBuilderFactory {
             doSetProperty("apiKey", apiKey);
             return this;
         }
+        /**
+         * The language of the input audio in ISO-639-1 format (e.g., 'en').
+         * Improves accuracy and latency.
+         * 
+         * The option is a: <code>java.lang.String</code> type.
+         * 
+         * Group: producer
+         * 
+         * @param audioLanguage the value to set
+         * @return the dsl builder
+         */
+        default OpenAIEndpointBuilder audioLanguage(String audioLanguage) {
+            doSetProperty("audioLanguage", audioLanguage);
+            return this;
+        }
+        /**
+         * The model to use for audio transcription (e.g., whisper-1,
+         * gpt-4o-transcribe).
+         * 
+         * The option is a: <code>java.lang.String</code> type.
+         * 
+         * Group: producer
+         * 
+         * @param audioModel the value to set
+         * @return the dsl builder
+         */
+        default OpenAIEndpointBuilder audioModel(String audioModel) {
+            doSetProperty("audioModel", audioModel);
+            return this;
+        }
+        /**
+         * Optional text to guide the model's style or continue a previous audio
+         * segment.
+         * 
+         * The option is a: <code>java.lang.String</code> type.
+         * 
+         * Group: producer
+         * 
+         * @param audioPrompt the value to set
+         * @return the dsl builder
+         */
+        default OpenAIEndpointBuilder audioPrompt(String audioPrompt) {
+            doSetProperty("audioPrompt", audioPrompt);
+            return this;
+        }
+        /**
+         * The format of the transcription output.
+         * 
+         * The option is a: <code>java.lang.String</code> type.
+         * 
+         * Default: json
+         * Group: producer
+         * 
+         * @param audioResponseFormat the value to set
+         * @return the dsl builder
+         */
+        default OpenAIEndpointBuilder audioResponseFormat(String audioResponseFormat) {
+            doSetProperty("audioResponseFormat", audioResponseFormat);
+            return this;
+        }
+        /**
+         * Sampling temperature for transcription (0.0 to 1.0).
+         * 
+         * The option is a: <code>java.lang.Double</code> type.
+         * 
+         * Group: producer
+         * 
+         * @param audioTemperature the value to set
+         * @return the dsl builder
+         */
+        default OpenAIEndpointBuilder audioTemperature(Double audioTemperature) {
+            doSetProperty("audioTemperature", audioTemperature);
+            return this;
+        }
+        /**
+         * Sampling temperature for transcription (0.0 to 1.0).
+         * 
+         * The option will be converted to a <code>java.lang.Double</code> type.
+         * 
+         * Group: producer
+         * 
+         * @param audioTemperature the value to set
+         * @return the dsl builder
+         */
+        default OpenAIEndpointBuilder audioTemperature(String audioTemperature) {
+            doSetProperty("audioTemperature", audioTemperature);
+            return this;
+        }
+        /**
+         * Comma-separated timestamp granularities: 'word', 'segment', or
+         * 'word,segment'. Only applicable with verbose_json response format.
+         * 
+         * The option is a: <code>java.lang.String</code> type.
+         * 
+         * Group: producer
+         * 
+         * @param audioTimestampGranularities the value to set
+         * @return the dsl builder
+         */
+        default OpenAIEndpointBuilder audioTimestampGranularities(String audioTimestampGranularities) {
+            doSetProperty("audioTimestampGranularities", audioTimestampGranularities);
+            return this;
+        }
         /**
          * When true and MCP servers are configured, automatically execute tool
          * calls and loop back to the model. When false, tool calls are returned
@@ -972,7 +1075,8 @@ public interface OpenAIEndpointBuilderFactory {
     public interface OpenAIBuilders {
         /**
          * OpenAI (camel-openai)
-         * OpenAI endpoint for chat completion and embeddings.
+         * OpenAI endpoint for chat completion, embeddings, and audio
+         * transcription.
          * 
          * Category: ai
          * Since: 4.17
@@ -985,7 +1089,8 @@ public interface OpenAIEndpointBuilderFactory {
         }
         /**
          * OpenAI (camel-openai)
-         * OpenAI endpoint for chat completion and embeddings.
+         * OpenAI endpoint for chat completion, embeddings, and audio
+         * transcription.
          * 
          * Category: ai
          * Since: 4.17
@@ -994,10 +1099,10 @@ public interface OpenAIEndpointBuilderFactory {
          * Syntax: <code>openai:operation</code>
          * 
          * Path parameter: operation (required)
-         * The operation to perform: 'chat-completion', 'embeddings', or
-         * 'tool-execution'
-         * There are 3 enums and the value can be one of: chat-completion,
-         * embeddings, tool-execution
+         * The operation to perform: 'chat-completion', 'embeddings',
+         * 'tool-execution', or 'audio-transcription'
+         * There are 4 enums and the value can be one of: chat-completion,
+         * embeddings, tool-execution, audio-transcription
          * 
          * @param path operation
          * @return the dsl builder
@@ -1007,7 +1112,8 @@ public interface OpenAIEndpointBuilderFactory {
         }
         /**
          * OpenAI (camel-openai)
-         * OpenAI endpoint for chat completion and embeddings.
+         * OpenAI endpoint for chat completion, embeddings, and audio
+         * transcription.
          * 
          * Category: ai
          * Since: 4.17
@@ -1016,10 +1122,10 @@ public interface OpenAIEndpointBuilderFactory {
          * Syntax: <code>openai:operation</code>
          * 
          * Path parameter: operation (required)
-         * The operation to perform: 'chat-completion', 'embeddings', or
-         * 'tool-execution'
-         * There are 3 enums and the value can be one of: chat-completion,
-         * embeddings, tool-execution
+         * The operation to perform: 'chat-completion', 'embeddings',
+         * 'tool-execution', or 'audio-transcription'
+         * There are 4 enums and the value can be one of: chat-completion,
+         * embeddings, tool-execution, audio-transcription
          * 
          * @param componentName to use a custom component name for the endpoint
          * instead of the default name
@@ -1408,6 +1514,106 @@ public interface OpenAIEndpointBuilderFactory {
         public String openAIOriginalText() {
             return "CamelOpenAIOriginalText";
         }
+        /**
+         * The model to use for audio transcription.
+         * 
+         * The option is a: {@code String} type.
+         * 
+         * Group: producer
+         * 
+         * @return the name of the header {@code OpenAIAudioModel}.
+         */
+        public String openAIAudioModel() {
+            return "CamelOpenAIAudioModel";
+        }
+        /**
+         * The language of the input audio (ISO-639-1).
+         * 
+         * The option is a: {@code String} type.
+         * 
+         * Group: producer
+         * 
+         * @return the name of the header {@code OpenAIAudioLanguage}.
+         */
+        public String openAIAudioLanguage() {
+            return "CamelOpenAIAudioLanguage";
+        }
+        /**
+         * The response format for audio transcription (json, text, srt,
+         * verbose_json, vtt).
+         * 
+         * The option is a: {@code String} type.
+         * 
+         * Group: producer
+         * 
+         * @return the name of the header {@code OpenAIAudioResponseFormat}.
+         */
+        public String openAIAudioResponseFormat() {
+            return "CamelOpenAIAudioResponseFormat";
+        }
+        /**
+         * Sampling temperature for audio transcription (0.0 to 1.0).
+         * 
+         * The option is a: {@code Double} type.
+         * 
+         * Group: producer
+         * 
+         * @return the name of the header {@code OpenAIAudioTemperature}.
+         */
+        public String openAIAudioTemperature() {
+            return "CamelOpenAIAudioTemperature";
+        }
+        /**
+         * Optional text to guide the model's style or continue a previous audio
+         * segment.
+         * 
+         * The option is a: {@code String} type.
+         * 
+         * Group: producer
+         * 
+         * @return the name of the header {@code OpenAIAudioPrompt}.
+         */
+        public String openAIAudioPrompt() {
+            return "CamelOpenAIAudioPrompt";
+        }
+        /**
+         * Comma-separated timestamp granularities: word, segment, or
+         * word,segment (verbose_json only).
+         * 
+         * The option is a: {@code String} type.
+         * 
+         * Group: producer
+         * 
+         * @return the name of the header {@code
+         * OpenAIAudioTimestampGranularities}.
+         */
+        public String openAIAudioTimestampGranularities() {
+            return "CamelOpenAIAudioTimestampGranularities";
+        }
+        /**
+         * Duration of the audio in seconds (verbose_json only).
+         * 
+         * The option is a: {@code Double} type.
+         * 
+         * Group: producer
+         * 
+         * @return the name of the header {@code OpenAIAudioDuration}.
+         */
+        public String openAIAudioDuration() {
+            return "CamelOpenAIAudioDuration";
+        }
+        /**
+         * Language detected in the audio (verbose_json only).
+         * 
+         * The option is a: {@code String} type.
+         * 
+         * Group: producer
+         * 
+         * @return the name of the header {@code OpenAIAudioDetectedLanguage}.
+         */
+        public String openAIAudioDetectedLanguage() {
+            return "CamelOpenAIAudioDetectedLanguage";
+        }
     }
     static OpenAIEndpointBuilder endpointBuilder(String componentName, String path) {
         class OpenAIEndpointBuilderImpl extends AbstractEndpointBuilder implements OpenAIEndpointBuilder, AdvancedOpenAIEndpointBuilder {
diff --git a/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/AudioTranscriptionExpectation.java b/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/AudioTranscriptionExpectation.java
index 51fd82708434..824b1b026618 100644
--- a/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/AudioTranscriptionExpectation.java
+++ b/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/AudioTranscriptionExpectation.java
@@ -49,6 +49,10 @@ public class AudioTranscriptionExpectation {
         this.duration = duration;
     }
 
+    public boolean isVerbose() {
+        return duration > 0;
+    }
+
     @Override
     public String toString() {
         return String.format("AudioTranscriptionExpectation{text='%s', language='%s', duration=%.1f}",
diff --git a/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/AudioTranscriptionResponseBuilder.java b/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/AudioTranscriptionResponseBuilder.java
index 63861bec042a..ae675f20b673 100644
--- a/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/AudioTranscriptionResponseBuilder.java
+++ b/test-infra/camel-test-infra-openai-mock/src/main/java/org/apache/camel/test/infra/openai/mock/AudioTranscriptionResponseBuilder.java
@@ -29,10 +29,29 @@ public class AudioTranscriptionResponseBuilder {
     }
 
     public String createTranscriptionResponse(AudioTranscriptionExpectation expectation) throws Exception {
+        if (expectation.isVerbose()) {
+            return createVerboseResponse(expectation);
+        }
+        return createSimpleResponse(expectation);
+    }
+
+    private String createSimpleResponse(AudioTranscriptionExpectation expectation) throws Exception {
         record TranscriptionResponse(String text) {
         }
 
-        TranscriptionResponse response = new TranscriptionResponse(expectation.getTranscriptionText());
-        return objectMapper.writeValueAsString(response);
+        return objectMapper.writeValueAsString(new TranscriptionResponse(expectation.getTranscriptionText()));
+    }
+
+    private String createVerboseResponse(AudioTranscriptionExpectation expectation) throws Exception {
+        record VerboseTranscriptionResponse(String text, String language, double duration, java.util.List<?> segments,
+                java.util.List<?> words) {
+        }
+
+        return objectMapper.writeValueAsString(new VerboseTranscriptionResponse(
+                expectation.getTranscriptionText(),
+                expectation.getLanguage(),
+                expectation.getDuration(),
+                java.util.List.of(),
+                java.util.List.of()));
     }
 }