From 348c0ad9530e5f4ccfc229a1586bbfc1021705f2 Mon Sep 17 00:00:00 2001 From: Konstantin Date: Fri, 3 Feb 2023 14:46:13 +0100 Subject: C++ console example now outputs text files when asked --- Examples/main/main.cpp | 25 ++++- Examples/main/main.vcxproj | 2 + Examples/main/main.vcxproj.filters | 2 + Examples/main/textWriter.cpp | 210 +++++++++++++++++++++++++++++++++++++ Examples/main/textWriter.h | 7 ++ 5 files changed, 245 insertions(+), 1 deletion(-) create mode 100644 Examples/main/textWriter.cpp create mode 100644 Examples/main/textWriter.h diff --git a/Examples/main/main.cpp b/Examples/main/main.cpp index d76bfc8..88ddc6d 100644 --- a/Examples/main/main.cpp +++ b/Examples/main/main.cpp @@ -5,6 +5,7 @@ #include "miscUtils.h" #include #include +#include "textWriter.h" using namespace Whisper; #define STREAM_AUDIO 1 @@ -286,9 +287,31 @@ int wmain( int argc, wchar_t* argv[] ) #endif if( FAILED( hr ) ) { - fwprintf( stderr, L"%s: failed to process audio\n", argv[ 0 ] ); + printError( "Unable to process audio", hr ); return 10; } + + if( params.output_txt ) + { + bool timestamps = !params.no_timestamps; + hr = writeText( context, fname.c_str(), timestamps ); + if( FAILED( hr ) ) + printError( "Unable to produce the text file", hr ); + } + + if( params.output_srt ) + { + hr = writeSubRip( context, fname.c_str() ); + if( FAILED( hr ) ) + printError( "Unable to produce the text file", hr ); + } + + if( params.output_vtt ) + { + hr = writeWebVTT( context, fname.c_str() ); + if( FAILED( hr ) ) + printError( "Unable to produce the text file", hr ); + } } context->timingsPrint(); diff --git a/Examples/main/main.vcxproj b/Examples/main/main.vcxproj index 0c05491..3faf3b9 100644 --- a/Examples/main/main.vcxproj +++ b/Examples/main/main.vcxproj @@ -78,10 +78,12 @@ + + diff --git a/Examples/main/main.vcxproj.filters b/Examples/main/main.vcxproj.filters index 45a6b14..8b6fd03 100644 --- a/Examples/main/main.vcxproj.filters +++ b/Examples/main/main.vcxproj.filters @@ -5,10 +5,12 @@ + + diff --git a/Examples/main/textWriter.cpp b/Examples/main/textWriter.cpp new file mode 100644 index 0000000..6bb5ac4 --- /dev/null +++ b/Examples/main/textWriter.cpp @@ -0,0 +1,210 @@ +#include "textWriter.h" +#include "../../ComLightLib/comLightClient.h" +#include +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#pragma comment(lib, "Pathcch.lib") + +namespace +{ + HRESULT replaceExtension( CString& path, LPCTSTR inputPath, LPCTSTR ext ) + { + path = inputPath; + + const size_t len = (size_t)path.GetLength() + 4; + wchar_t* buffer = path.GetBufferSetLength( (int)len ); + const HRESULT hr = PathCchRenameExtension( buffer, len, ext ); + path.ReleaseBuffer(); + return hr; + } + + // Abstract base class for text writers + class Writer + { + protected: + CAtlFile file; + virtual HRESULT impl( const Whisper::sSegment* const segments, const size_t length ) = 0; + + public: + HRESULT write( Whisper::iContext* context, LPCTSTR audioPath, LPCTSTR ext ) + { + CString path; + CHECK( replaceExtension( path, audioPath, ext ) ); + CHECK( file.Create( path, GENERIC_WRITE, 0, CREATE_ALWAYS ) ); + + using namespace Whisper; + + const eResultFlags resultFlags = eResultFlags::Timestamps | eResultFlags::Tokens; + ComLight::CComPtr result; + CHECK( context->getResults( resultFlags, &result ) ); + + sTranscribeLength len; + CHECK( result->getSize( len ) ); + const sSegment* const segments = result->getSegments(); + + return impl( segments, len.countSegments ); + } + }; + + HRESULT writeUtf8Bom( CAtlFile& file ) + { + const std::array bom = { 0xEF, 0xBB, 0xBF }; + return file.Write( bom.data(), 3 ); + } + + void printTime( CStringA& rdi, Whisper::sTimeSpan time, bool comma ) + { + Whisper::sTimeSpanFields fields = time; + const char separator = comma ? ',' : '.'; + rdi.AppendFormat( "%02d:%02d:%02d%c%03d", + (int)fields.hours, + (int)fields.minutes, + (int)fields.seconds, + separator, + fields.ticks / 10'000 ); + } + + void printTimeStamp( CStringA& rdi, Whisper::sTimeSpan ts ) + { + Whisper::sTimeSpanFields fields = ts; + uint32_t msec = fields.ticks / 10'000; + uint32_t hr = fields.days * 24 + fields.hours; + uint32_t min = fields.minutes; + uint32_t sec = fields.seconds; + rdi.AppendFormat( "%02d:%02d:%02d.%03d", hr, min, sec, msec ); + } + + const char* skipBlank( const char* rsi ) + { + while( true ) + { + const char c = *rsi; + if( c == ' ' || c == '\t' ) + { + rsi++; + continue; + } + return rsi; + } + } + + inline const char* cstr( const CStringA& s ) { return s; } + + HRESULT writeString( CAtlFile& file, const CStringA& line ) + { + if( line.GetLength() > 0 ) + CHECK( file.Write( cstr( line ), (DWORD)line.GetLength() ) ); + return S_OK; + } + + // Writer for UTF-8 text files + class TextWriter : public Writer + { + const bool timestamps; + + HRESULT impl( const Whisper::sSegment* const segments, const size_t length ) override final + { + CHECK( writeUtf8Bom( file ) ); + using namespace Whisper; + + CStringA line; + for( size_t i = 0; i < length; i++ ) + { + const sSegment& seg = segments[ i ]; + + if( timestamps ) + { + line = "["; + printTimeStamp( line, seg.time.begin ); + line += " --> "; + printTimeStamp( line, seg.time.end ); + line += "] "; + } + else + line = ""; + + line += skipBlank( seg.text ); + line += "\r\n"; + CHECK( writeString( file, line ) ); + } + return S_OK; + } + public: + TextWriter( bool tt ) : timestamps( tt ) { } + }; + + // Writer for SubRip format: https://en.wikipedia.org/wiki/SubRip#SubRip_file_format + class SubRipWriter : public Writer + { + HRESULT impl( const Whisper::sSegment* const segments, const size_t length ) override final + { + CHECK( writeUtf8Bom( file ) ); + using namespace Whisper; + + CStringA line; + for( size_t i = 0; i < length; i++ ) + { + const sSegment& seg = segments[ i ]; + + line.Format( "%zu\r\n", i + 1 ); + printTime( line, seg.time.begin, true ); + line += " --> "; + printTime( line, seg.time.end, true ); + line += "\r\n"; + line += skipBlank( seg.text ); + line += "\r\n\r\n"; + CHECK( writeString( file, line ) ); + } + return S_OK; + } + }; + + // Writer for WebVTT format: https://en.wikipedia.org/wiki/WebVTT + class VttWriter : public Writer + { + HRESULT impl( const Whisper::sSegment* const segments, const size_t length ) override final + { + CHECK( writeUtf8Bom( file ) ); + using namespace Whisper; + + CStringA line; + line = "WEBVTT\r\n\r\n"; + CHECK( writeString( file, line ) ); + + for( size_t i = 0; i < length; i++ ) + { + const sSegment& seg = segments[ i ]; + line = ""; + + printTime( line, seg.time.begin, false ); + line += " --> "; + printTime( line, seg.time.end, false ); + line += "\r\n"; + line += skipBlank( seg.text ); + line += "\r\n\r\n"; + CHECK( writeString( file, line ) ); + } + return S_OK; + } + }; +} + +HRESULT writeText( Whisper::iContext* context, LPCTSTR audioPath, bool timestamps ) +{ + TextWriter writer{ timestamps }; + return writer.write( context, audioPath, L".txt" ); +} + +HRESULT writeSubRip( Whisper::iContext* context, LPCTSTR audioPath ) +{ + SubRipWriter writer; + return writer.write( context, audioPath, L".srt" ); +} + +HRESULT writeWebVTT( Whisper::iContext* context, LPCTSTR audioPath ) +{ + VttWriter writer; + return writer.write( context, audioPath, L".vtt" ); +} \ No newline at end of file diff --git a/Examples/main/textWriter.h b/Examples/main/textWriter.h new file mode 100644 index 0000000..a5e46dc --- /dev/null +++ b/Examples/main/textWriter.h @@ -0,0 +1,7 @@ +#pragma once +#include "../../Whisper/API/iContext.cl.h" + +// These functions print output segments into text files of various formats +HRESULT writeText( Whisper::iContext* context, LPCTSTR audioPath, bool timestamps ); +HRESULT writeSubRip( Whisper::iContext* context, LPCTSTR audioPath ); +HRESULT writeWebVTT( Whisper::iContext* context, LPCTSTR audioPath ); \ No newline at end of file -- cgit v1.2.3