voice_audio_multi/tests/unittests/test_file_sink_json.cpp
Fabio c36510c21e
Some checks failed
Linux Build / Linux cpu (push) Has been cancelled
Linux Build / Linux cpu-clang (push) Has been cancelled
Linux Build / Linux vulkan (push) Has been cancelled
mac-build / macOS CPU (push) Has been cancelled
Nix Build / Nix (cpu) (push) Has been cancelled
Nix Build / Nix (vulkan) (push) Has been cancelled
windows-build / Windows CPU (push) Has been cancelled
Initial import: audio.cpp with Orange Pi documentation
2026-11-04 19:05:22 +01:00

145 lines
6.3 KiB
C++

#include "../../app/workflow/file_sink.h"
#include "engine/framework/io/json.h"
#include <filesystem>
#include <fstream>
#include <iostream>
#include <iterator>
#include <random>
#include <stdexcept>
#include <string>
#include <vector>
namespace {
void require(bool condition, const std::string & message) {
if (!condition) {
throw std::runtime_error(message);
}
}
std::string read_file(const std::filesystem::path & path) {
std::ifstream input(path, std::ios::binary);
require(input.good(), "failed to open test output " + path.string());
return {std::istreambuf_iterator<char>(input), std::istreambuf_iterator<char>()};
}
std::string multiline_text() {
return "He said \"hello\".\nPath: C:\\audio\r\nSpeaker\ttext: \xc3\xa9";
}
void test_word_timestamp_json_escapes_control_characters() {
engine::runtime::WordTimestamp word;
word.span.start_sample = 1;
word.span.end_sample = 2;
word.word = multiline_text();
const std::string json = minitts::app::word_timestamps_to_json({word});
const auto parsed = engine::io::json::parse(json);
require(parsed.as_array()[0].require("word").as_string() == word.word, "word text must round-trip exactly");
require(json.find("\\\"hello\\\"") != std::string::npos, "quotes must be escaped");
require(json.find("C:\\\\audio") != std::string::npos, "backslashes must be escaped");
require(json.find("\\n") != std::string::npos, "newline must be escaped");
require(json.find("\\r") != std::string::npos, "carriage return must be escaped");
require(json.find("\\t") != std::string::npos, "tab must be escaped");
require(json.find("\xc3\xa9") != std::string::npos, "ordinary UTF-8 must be retained");
require(json.find("start_sample\":1") != std::string::npos, "word start number schema");
require(json.find("end_sample\":2") != std::string::npos, "word end number schema");
}
void test_emit_task_result_serializes_all_file_outputs() {
const auto root = std::filesystem::temp_directory_path() /
("audio_cpp_file_sink_json_test_" + std::to_string(std::random_device{}()));
// Refuse a collision instead of deleting or reusing another test's files.
require(std::filesystem::create_directory(root), "could not create isolated output directory");
struct Cleanup {
std::filesystem::path path;
~Cleanup() {
std::error_code ignored;
std::filesystem::remove_all(path, ignored);
}
} cleanup{root};
const auto artifact_dir = root / "artifacts";
const auto segments_path = root / "segments.json";
const auto turns_path = root / "turns.json";
const auto words_path = root / "words.json";
const std::string value = multiline_text();
engine::runtime::TaskResult result;
result.speech_segments.push_back({{10, 20}, 0.75f, value});
result.speaker_turns.push_back({{30, 40}, value, 0.5f, value});
result.word_timestamps.push_back({{50, 60}, value, 0.25f});
engine::runtime::VoiceArtifact artifact;
artifact.kind = engine::runtime::ArtifactKind::Custom;
artifact.id = value;
artifact.payload = {std::byte{0x01}, std::byte{0x02}, std::byte{0x03}};
artifact.meta.emplace("label\"key", value);
result.output_artifacts.push_back(std::move(artifact));
minitts::app::emit_task_result(
result,
std::nullopt,
std::nullopt,
artifact_dir,
segments_path,
turns_path,
words_path);
const auto segments = engine::io::json::parse(read_file(segments_path));
const auto turns = engine::io::json::parse(read_file(turns_path));
const auto words = engine::io::json::parse(read_file(words_path));
require(segments.is_array() && segments.as_array().size() == 1, "speech segment output schema");
require(turns.is_array() && turns.as_array().size() == 1, "speaker turn output schema");
require(words.is_array() && words.as_array().size() == 1, "word output schema");
require(segments.as_array()[0].require("start_sample").as_i64() == 10, "segment start number");
require(turns.as_array()[0].require("end_sample").as_i64() == 40, "turn end number");
require(words.as_array()[0].require("start_sample").as_i64() == 50, "word start number");
require(segments.as_array()[0].require("text").as_string() == value, "segment text round-trip");
require(turns.as_array()[0].require("text").as_string() == value, "speaker text round-trip");
require(words.as_array()[0].require("word").as_string() == value, "word output round-trip");
const auto artifact_path = artifact_dir / (minitts::app::safe_output_name(value) + ".json");
const std::string artifact_json = read_file(artifact_path);
const auto artifact_value = engine::io::json::parse(artifact_json);
require(artifact_value.require("bytes").as_i64() == 3, "artifact byte count schema");
require(artifact_json.find("label\\\"key") != std::string::npos, "artifact metadata key escape");
require(artifact_value.require("meta").require("label\"key").as_string() == value,
"artifact metadata text round-trip");
minitts::app::AppBatchResult batch;
minitts::app::AppRequestResult item;
item.id = value;
batch.results.push_back(std::move(item));
batch.chapters.push_back({"chapter\"1", 7, 9});
const auto manifest_path = root / "batch.json";
minitts::app::FileOutputPolicy policy;
policy.batch_manifest_out = manifest_path;
minitts::app::emit_batch_result(batch, policy);
const std::string manifest_json = read_file(manifest_path);
const auto manifest = engine::io::json::parse(manifest_json);
require(manifest.require("requests").as_array().size() == 1, "batch request schema");
require(manifest.require("chapters").as_array()[0].require("start_sample").as_i64() == 7,
"batch chapter start number");
require(manifest.require("requests").as_array()[0].require("id").as_string() == value,
"batch request text round-trip");
require(manifest_json.find("chapter\\\"1") != std::string::npos, "chapter id quote escape");
}
} // namespace
int main() {
try {
test_word_timestamp_json_escapes_control_characters();
test_emit_task_result_serializes_all_file_outputs();
} catch (const std::exception & error) {
std::cerr << error.what() << '\n';
return 1;
}
std::cout << "file_sink_json_test passed\n";
return 0;
}