Repository navigation
Expand file tree
/
Copy pathpdf_to_audio.cpp
More file actions
268 lines (238 loc) · 10.2 KB
/
Copy pathpdf_to_audio.cpp
File metadata and controls
268 lines (238 loc) · 10.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
#include "helper.h"
#include <iostream>
#include <fstream>
#include <filesystem>
#include <string>
#include <vector>
namespace fs = std::filesystem;
struct PDFToAudioArgs {
std::string pdf_path;
std::string output_path;
std::string voice_style = "../assets/voice_styles/M1.json";
std::string onnx_dir = "../assets/onnx";
std::string pdftotext_path;
int first_page = -1;
int last_page = -1;
int total_step = 5;
float speed = 1.05f;
bool remove_footnotes = false;
bool debug = false;
bool verbose = true;
};
void printUsage(const char* program_name) {
std::cout << "PDF to Audio Converter using Supertonic TTS\n\n"
<< "Usage: " << program_name << " --pdf <pdf_file> [options]\n\n"
<< "Required Arguments:\n"
<< " --pdf <path> Path to input PDF file\n\n"
<< "Optional Arguments:\n"
<< " --output <path> Output audio file path (default: <pdf_name>.wav)\n"
<< " --first-page <num> First page to read (default: all pages)\n"
<< " --last-page <num> Last page to read (default: all pages)\n"
<< " --voice-style <path> Voice style JSON file (default: ../assets/voice_styles/M1.json)\n"
<< " --onnx-dir <path> ONNX models directory (default: ../assets/onnx)\n"
<< " --total-step <num> Denoising steps, higher=better quality (default: 5)\n"
<< " --speed <float> Speech speed multiplier (default: 1.05, range: 0.9-1.5)\n"
<< " --pdftotext-path <path> Path to pdftotext executable (default: $XPDF_HOME/bin64/pdftotext)\n"
<< " --remove-footnotes Remove footnotes, references, and citations from text\n"
<< " --debug Save extracted text to file for debugging\n"
<< " --quiet Suppress verbose output\n"
<< " --help Show this help message\n\n"
<< "Environment Variables:\n"
<< " XPDF_HOME Base directory of xpdf-tools installation\n\n"
<< "Examples:\n"
<< " # Convert entire PDF with default settings\n"
<< " " << program_name << " --pdf document.pdf\n\n"
<< " # Convert pages 10-20 with female voice\n"
<< " " << program_name << " --pdf book.pdf --first-page 10 --last-page 20 \\\n"
<< " --voice-style ../assets/voice_styles/F1.json --output chapter2.wav\n\n"
<< " # High quality conversion with slower speed\n"
<< " " << program_name << " --pdf article.pdf --total-step 10 --speed 0.95\n\n";
}
PDFToAudioArgs parseArgs(int argc, char* argv[]) {
PDFToAudioArgs args;
if (argc < 2) {
printUsage(argv[0]);
exit(1);
}
for (int i = 1; i < argc; i++) {
std::string arg = argv[i];
if (arg == "--help" || arg == "-h") {
printUsage(argv[0]);
exit(0);
}
else if (arg == "--pdf" && i + 1 < argc) {
args.pdf_path = argv[++i];
}
else if (arg == "--output" && i + 1 < argc) {
args.output_path = argv[++i];
}
else if (arg == "--first-page" && i + 1 < argc) {
args.first_page = std::stoi(argv[++i]);
}
else if (arg == "--last-page" && i + 1 < argc) {
args.last_page = std::stoi(argv[++i]);
}
else if (arg == "--voice-style" && i + 1 < argc) {
args.voice_style = argv[++i];
}
else if (arg == "--onnx-dir" && i + 1 < argc) {
args.onnx_dir = argv[++i];
}
else if (arg == "--total-step" && i + 1 < argc) {
args.total_step = std::stoi(argv[++i]);
}
else if (arg == "--speed" && i + 1 < argc) {
args.speed = std::stof(argv[++i]);
}
else if (arg == "--pdftotext-path" && i + 1 < argc) {
args.pdftotext_path = argv[++i];
}
else if (arg == "--remove-footnotes") {
args.remove_footnotes = true;
}
else if (arg == "--debug") {
args.debug = true;
}
else if (arg == "--quiet") {
args.verbose = false;
}
else {
std::cerr << "Unknown argument: " << arg << "\n\n";
printUsage(argv[0]);
exit(1);
}
}
// Validate required arguments
if (args.pdf_path.empty()) {
std::cerr << "Error: --pdf argument is required\n\n";
printUsage(argv[0]);
exit(1);
}
// Check if PDF file exists
if (!fs::exists(args.pdf_path)) {
std::cerr << "Error: PDF file not found: " << args.pdf_path << "\n";
exit(1);
}
// Set default output path if not specified
if (args.output_path.empty()) {
fs::path pdf_path(args.pdf_path);
args.output_path = pdf_path.stem().string() + ".wav";
}
return args;
}
int main(int argc, char* argv[]) {
try {
std::cout << "=== PDF to Audio Converter ===\n\n";
// --- 1. Parse arguments --- //
PDFToAudioArgs args = parseArgs(argc, argv);
if (args.verbose) {
std::cout << "Configuration:\n"
<< " PDF file: " << args.pdf_path << "\n"
<< " Output: " << args.output_path << "\n";
if (args.first_page > 0 || args.last_page > 0) {
std::cout << " Pages: ";
if (args.first_page > 0) std::cout << args.first_page;
else std::cout << "1";
std::cout << " to ";
if (args.last_page > 0) std::cout << args.last_page;
else std::cout << "end";
std::cout << "\n";
}
std::cout << " Voice style: " << args.voice_style << "\n"
<< " Quality steps: " << args.total_step << "\n"
<< " Speed: " << args.speed << "x\n\n";
}
// --- 2. Extract text from PDF --- //
std::string text_raw;
std::string text;
if (args.debug) {
// Extract without cleaning first for debug
text_raw = timer("Extracting text from PDF", [&]() {
return extractTextFromPDF(
args.pdf_path,
args.first_page,
args.last_page,
args.pdftotext_path,
false // No cleaning for raw output
);
});
// Save raw extracted text
fs::path pdf_path(args.pdf_path);
std::string debug_file_raw = pdf_path.stem().string() + "_extracted_raw.txt";
std::ofstream out_raw(debug_file_raw);
out_raw << text_raw;
out_raw.close();
std::cout << "[DEBUG] Raw extracted text saved to: " << debug_file_raw << "\n";
// Now apply cleaning if requested
if (args.remove_footnotes) {
text = cleanFootnotes(text_raw);
std::string debug_file_clean = pdf_path.stem().string() + "_extracted_cleaned.txt";
std::ofstream out_clean(debug_file_clean);
out_clean << text;
out_clean.close();
std::cout << "[DEBUG] Cleaned text saved to: " << debug_file_clean << "\n";
} else {
text = text_raw;
}
} else {
// Normal extraction
text = timer("Extracting text from PDF", [&]() {
return extractTextFromPDF(
args.pdf_path,
args.first_page,
args.last_page,
args.pdftotext_path,
args.remove_footnotes
);
});
}
if (args.verbose) {
std::cout << "Extracted " << text.length() << " characters\n";
// Show preview of extracted text
std::string preview = text.substr(0, std::min<size_t>(200, text.length()));
if (text.length() > 200) preview += "...";
std::cout << "Preview: " << preview << "\n\n";
}
// --- 3. Initialize ONNX Runtime and load TTS model --- //
Ort::Env env(ORT_LOGGING_LEVEL_WARNING, "PDFToAudio");
Ort::MemoryInfo memory_info = Ort::MemoryInfo::CreateCpu(
OrtAllocatorType::OrtArenaAllocator, OrtMemType::OrtMemTypeDefault
);
auto text_to_speech = loadTextToSpeech(env, args.onnx_dir, false);
std::cout << std::endl;
// --- 4. Load voice style --- //
auto style = loadVoiceStyle({args.voice_style}, args.verbose);
// --- 5. Synthesize speech (long-form mode with automatic chunking) --- //
auto result = timer("Synthesizing speech from text", [&]() {
return text_to_speech->call(
memory_info,
text,
style,
args.total_step,
args.speed
);
});
// --- 6. Extract audio and save to file --- //
int sample_rate = text_to_speech->getSampleRate();
int wav_len = static_cast<int>(sample_rate * result.duration[0]);
std::vector<float> wav_out(
result.wav.begin(),
result.wav.begin() + wav_len
);
writeWavFile(args.output_path, wav_out, sample_rate);
if (args.verbose) {
float duration_sec = result.duration[0];
int duration_min = static_cast<int>(duration_sec / 60);
int duration_sec_remainder = static_cast<int>(duration_sec) % 60;
std::cout << "\n=== Conversion completed successfully! ===\n"
<< "Output file: " << args.output_path << "\n"
<< "Duration: " << duration_min << "m " << duration_sec_remainder << "s\n"
<< "Sample rate: " << sample_rate << " Hz\n";
}
clearTensorBuffers();
return 0;
} catch (const std::exception& e) {
std::cerr << "\n[ERROR] " << e.what() << "\n";
return 1;
}
}