diff --git a/model_specs/qwen3_asr.json b/model_specs/qwen3_asr.json index 75d770bc9..bf61f59ca 100644 --- a/model_specs/qwen3_asr.json +++ b/model_specs/qwen3_asr.json @@ -59,6 +59,13 @@ "description": "Opt-in guard for word timestamp output: keep repaired forced-aligner word spans inside the local audio chunk. Defaults to false to preserve existing timestamp repair behavior.", "required": false, "default": false + }, + { + "name": "qwen3_asr.preserve_punctuation", + "type": "bool", + "description": "Opt-in text output mode for timestamped chunked ASR: preserve ASR punctuation in text_output instead of rebuilding text from aligned words.", + "required": false, + "default": false } ] }, diff --git a/src/models/qwen3_asr/session.cpp b/src/models/qwen3_asr/session.cpp index e66fb184f..5358a9ed0 100644 --- a/src/models/qwen3_asr/session.cpp +++ b/src/models/qwen3_asr/session.cpp @@ -88,6 +88,15 @@ bool request_clamp_timestamps_to_audio(const runtime::TaskRequest & request) { return false; } +bool request_preserve_punctuation(const runtime::TaskRequest & request) { + if (const auto value = runtime::find_option( + request.options, + {"qwen3_asr.preserve_punctuation", "preserve_punctuation"})) { + return runtime::parse_bool_option(*value, "qwen3_asr.preserve_punctuation"); + } + return false; +} + void log_chunk_word_diagnostics( int64_t chunk_index, int64_t chunk_count, @@ -345,7 +354,8 @@ runtime::TaskResult Qwen3ASRSession::run(const runtime::TaskRequest & request) { assets_->config.sample_rate); } if (merged.text_output.has_value()) { - if (request_return_timestamps(request) && !merged.word_timestamps.empty()) { + if (request_return_timestamps(request) && !merged.word_timestamps.empty() && + !request_preserve_punctuation(request)) { std::ostringstream word_text; for (const auto & word : merged.word_timestamps) { if (word_text.tellp() > 0) {