@@ -204,14 +204,14 @@ struct cli_args {
204204 bool quiet = false ;
205205 bool list_devices = false ; // --list-devices: print devices and exit
206206 bool batch_jsonl = false ; // --batch-jsonl: output JSONL
207- std::string output_path; // -o: write raw text here
208- int repeat = 1 ;
209- int n_threads = 0 ; // 0 = library default (all cores)
210- int n_ctx = 0 ; // 0 = model's true max; >0 lowers the cap
211- transcribe_kv_type kv_type = TRANSCRIBE_KV_TYPE_AUTO ;
212- transcribe_backend_request backend = TRANSCRIBE_BACKEND_AUTO ;
213- int gpu_device = 0 ; // --device N: 0 = auto, >0 = registry index
214- transcribe_timestamp_kind timestamps = TRANSCRIBE_TIMESTAMPS_AUTO ;
207+ std::string output_path; // -o/--output : write raw text here
208+ int repeat = 1 ;
209+ int n_threads = 0 ; // 0 = library default (all cores)
210+ int n_ctx = 0 ; // 0 = model's true max; >0 lowers the cap
211+ transcribe_kv_type kv_type = TRANSCRIBE_KV_TYPE_AUTO ;
212+ transcribe_backend_request backend = TRANSCRIBE_BACKEND_AUTO ;
213+ int gpu_device = 0 ; // --device N: 0 = auto, >0 = registry index
214+ transcribe_timestamp_kind timestamps = TRANSCRIBE_TIMESTAMPS_AUTO ;
215215
216216 // Whisper-family knobs. Ignored for non-Whisper models.
217217 std::string initial_prompt; // --initial-prompt TEXT
@@ -696,18 +696,21 @@ void log_cb(transcribe_log_level level, const char * msg, void * userdata) {
696696 std::fprintf (stderr, " %s %s%s" , prefix, msg, (msg && *msg && msg[std::strlen (msg) - 1 ] == ' \n ' ) ? " " : " \n " );
697697}
698698
699- // Write transcription text to the output file if -o was given.
700- // Appends so it works for both single-file and batch mode.
701- static void write_output_file (const std::string & path, const char * text) {
702- if (path.empty () || text == nullptr || text[0 ] == ' \0 ' ) {
703- return ;
699+ bool write_output_file (std::ofstream * output, const std::string & path, const char * text) {
700+ if (output == nullptr ) {
701+ return true ;
704702 }
705- std::ofstream fout (path, std::ios::binary | std::ios::app);
706- if (!fout) {
707- std::fprintf (stderr, " error: cannot open %s for writing\n " , path.c_str ());
708- } else {
709- fout << text << ' \n ' ;
703+ const char * value = text != nullptr ? text : " " ;
704+ *output << value;
705+ if (value[0 ] == ' \0 ' || value[std::strlen (value) - 1 ] != ' \n ' ) {
706+ *output << ' \n ' ;
710707 }
708+ output->flush ();
709+ if (!*output) {
710+ std::fprintf (stderr, " error: cannot write %s\n " , path.c_str ());
711+ return false ;
712+ }
713+ return true ;
711714}
712715
713716} // namespace
@@ -735,6 +738,18 @@ int main(int argc, char ** argv) {
735738 transcribe_log_set (log_cb, nullptr );
736739 }
737740
741+ std::ofstream output_file;
742+ std::ofstream * output = nullptr ;
743+ if (!args.output_path .empty ()) {
744+ output_file.open (args.output_path , std::ios::binary | std::ios::trunc);
745+ if (!output_file) {
746+ std::fprintf (stderr, " error: cannot open %s for writing\n " , args.output_path .c_str ());
747+ return EXIT_FAILURE ;
748+ }
749+ output = &output_file;
750+ }
751+ bool output_ok = true ;
752+
738753 // Batch mode: --batch reads a file list, one wav path per line. Loads
739754 // the model ONCE and reuses the context across all files. Outputs one
740755 // JSONL line per file to stdout when --batch-jsonl is set, otherwise
@@ -979,7 +994,7 @@ int main(int argc, char ** argv) {
979994 std::printf (" ERROR: %s\n " , transcribe_status_string (ust));
980995 }
981996 }
982- write_output_file (args.output_path , text);
997+ output_ok = write_output_file (output, args.output_path , text) && output_ok ;
983998 std::fflush (stdout);
984999 }
9851000 }
@@ -1118,7 +1133,7 @@ int main(int argc, char ** argv) {
11181133 std::printf (" ERROR: %s\n " , transcribe_status_string (run_st));
11191134 }
11201135 }
1121- write_output_file (args.output_path , text);
1136+ output_ok = write_output_file (output, args.output_path , text) && output_ok ;
11221137 std::fflush (stdout);
11231138 }
11241139 }
@@ -1132,7 +1147,7 @@ int main(int argc, char ** argv) {
11321147 transcribe_model_free (model);
11331148 // OUTPUT_TRUNCATED is result-bearing and does not fail the batch, but
11341149 // hard per-utterance failures must remain visible to automation.
1135- return n_fail > 0 ? EXIT_FAILURE : EXIT_SUCCESS ;
1150+ return n_fail > 0 || !output_ok ? EXIT_FAILURE : EXIT_SUCCESS ;
11361151 }
11371152
11381153 // Single-file mode.
@@ -1365,7 +1380,7 @@ int main(int argc, char ** argv) {
13651380 if (result_present) {
13661381 const char * text = transcribe_full_text (ctx);
13671382 std::printf (" text: %s\n " , (text && *text) ? text : " (empty)" );
1368- write_output_file (args.output_path , text);
1383+ output_ok = write_output_file (output, args.output_path , text) && output_ok ;
13691384
13701385 // A truncated decode hit the model's context/output budget before
13711386 // end-of-stream; the text above is incomplete.
@@ -1443,7 +1458,7 @@ int main(int argc, char ** argv) {
14431458 transcribe_session_free (ctx);
14441459 transcribe_model_free (model);
14451460
1446- if (run_st != TRANSCRIBE_OK ) {
1461+ if (run_st != TRANSCRIBE_OK || !output_ok ) {
14471462 return EXIT_FAILURE ;
14481463 }
14491464 } else {
0 commit comments