yum-archive/TaSTT-Whisper

High-performance GPGPU inference of OpenAI's Whisper automatic speech recognition (ASR) model

git clone https://git.yummers.dev/yum-archive/TaSTT-Whisper

KonstantinRefactor, removed a redundant function3f3a9a1

master
4.8 KiB201 linesraw
1#include "textWriter.h"
2#include "../../ComLightLib/comLightClient.h"
3#include <array>
4#define WIN32_LEAN_AND_MEAN
5#include <pathcch.h>
6#include <atlstr.h>
7#include <atlfile.h>
8#pragma comment(lib, "Pathcch.lib")
9
10namespace
11{
12	HRESULT replaceExtension( CString& path, LPCTSTR inputPath, LPCTSTR ext )
13	{
14		path = inputPath;
15
16		const size_t len = (size_t)path.GetLength() + 4;
17		wchar_t* buffer = path.GetBufferSetLength( (int)len );
18		const HRESULT hr = PathCchRenameExtension( buffer, len, ext );
19		path.ReleaseBuffer();
20		return hr;
21	}
22
23	// Abstract base class for text writers
24	class Writer
25	{
26	protected:
27		CAtlFile file;
28		virtual HRESULT impl( const Whisper::sSegment* const segments, const size_t length ) = 0;
29
30	public:
31		HRESULT write( Whisper::iContext* context, LPCTSTR audioPath, LPCTSTR ext )
32		{
33			CString path;
34			CHECK( replaceExtension( path, audioPath, ext ) );
35			CHECK( file.Create( path, GENERIC_WRITE, 0, CREATE_ALWAYS ) );
36
37			using namespace Whisper;
38
39			const eResultFlags resultFlags = eResultFlags::Timestamps | eResultFlags::Tokens;
40			ComLight::CComPtr<iTranscribeResult> result;
41			CHECK( context->getResults( resultFlags, &result ) );
42
43			sTranscribeLength len;
44			CHECK( result->getSize( len ) );
45			const sSegment* const segments = result->getSegments();
46
47			return impl( segments, len.countSegments );
48		}
49	};
50
51	HRESULT writeUtf8Bom( CAtlFile& file )
52	{
53		const std::array<uint8_t, 3> bom = { 0xEF, 0xBB, 0xBF };
54		return file.Write( bom.data(), 3 );
55	}
56
57	void printTime( CStringA& rdi, Whisper::sTimeSpan time, bool comma = false )
58	{
59		Whisper::sTimeSpanFields fields = time;
60		const uint32_t hours = fields.days * 24 + fields.hours;
61		const char separator = comma ? ',' : '.';
62		rdi.AppendFormat( "%02d:%02d:%02d%c%03d",
63			(int)hours,
64			(int)fields.minutes,
65			(int)fields.seconds,
66			separator,
67			fields.ticks / 10'000 );
68	}
69
70	const char* skipBlank( const char* rsi )
71	{
72		while( true )
73		{
74			const char c = *rsi;
75			if( c == ' ' || c == '\t' )
76			{
77				rsi++;
78				continue;
79			}
80			return rsi;
81		}
82	}
83
84	inline const char* cstr( const CStringA& s ) { return s; }
85
86	HRESULT writeString( CAtlFile& file, const CStringA& line )
87	{
88		if( line.GetLength() > 0 )
89			CHECK( file.Write( cstr( line ), (DWORD)line.GetLength() ) );
90		return S_OK;
91	}
92
93	// Writer for UTF-8 text files
94	class TextWriter : public Writer
95	{
96		const bool timestamps;
97
98		HRESULT impl( const Whisper::sSegment* const segments, const size_t length ) override final
99		{
100			CHECK( writeUtf8Bom( file ) );
101			using namespace Whisper;
102
103			CStringA line;
104			for( size_t i = 0; i < length; i++ )
105			{
106				const sSegment& seg = segments[ i ];
107
108				if( timestamps )
109				{
110					line = "[";
111					printTime( line, seg.time.begin );
112					line += " --> ";
113					printTime( line, seg.time.end );
114					line += "]  ";
115				}
116				else
117					line = "";
118
119				line += skipBlank( seg.text );
120				line += "\r\n";
121				CHECK( writeString( file, line ) );
122			}
123			return S_OK;
124		}
125	public:
126		TextWriter( bool tt ) : timestamps( tt ) { }
127	};
128
129	// Writer for SubRip format: https://en.wikipedia.org/wiki/SubRip#SubRip_file_format
130	class SubRipWriter : public Writer
131	{
132		HRESULT impl( const Whisper::sSegment* const segments, const size_t length ) override final
133		{
134			CHECK( writeUtf8Bom( file ) );
135			using namespace Whisper;
136
137			CStringA line;
138			for( size_t i = 0; i < length; i++ )
139			{
140				const sSegment& seg = segments[ i ];
141
142				line.Format( "%zu\r\n", i + 1 );
143				printTime( line, seg.time.begin, true );
144				line += " --> ";
145				printTime( line, seg.time.end, true );
146				line += "\r\n";
147				line += skipBlank( seg.text );
148				line += "\r\n\r\n";
149				CHECK( writeString( file, line ) );
150			}
151			return S_OK;
152		}
153	};
154
155	// Writer for WebVTT format: https://en.wikipedia.org/wiki/WebVTT
156	class VttWriter : public Writer
157	{
158		HRESULT impl( const Whisper::sSegment* const segments, const size_t length ) override final
159		{
160			CHECK( writeUtf8Bom( file ) );
161			using namespace Whisper;
162
163			CStringA line;
164			line = "WEBVTT\r\n\r\n";
165			CHECK( writeString( file, line ) );
166
167			for( size_t i = 0; i < length; i++ )
168			{
169				const sSegment& seg = segments[ i ];
170				line = "";
171
172				printTime( line, seg.time.begin );
173				line += " --> ";
174				printTime( line, seg.time.end );
175				line += "\r\n";
176				line += skipBlank( seg.text );
177				line += "\r\n\r\n";
178				CHECK( writeString( file, line ) );
179			}
180			return S_OK;
181		}
182	};
183}
184
185HRESULT writeText( Whisper::iContext* context, LPCTSTR audioPath, bool timestamps )
186{
187	TextWriter writer{ timestamps };
188	return writer.write( context, audioPath, L".txt" );
189}
190
191HRESULT writeSubRip( Whisper::iContext* context, LPCTSTR audioPath )
192{
193	SubRipWriter writer;
194	return writer.write( context, audioPath, L".srt" );
195}
196
197HRESULT writeWebVTT( Whisper::iContext* context, LPCTSTR audioPath )
198{
199	VttWriter writer;
200	return writer.write( context, audioPath, L".vtt" );
201}