Program Listing for File transcript_result.hpp#
↰ Return to documentation for file (src/analysis/transcript_result.hpp)
#pragma once
#include <QString>
#include <QVector>
#include <cstdint>
namespace mosaic {
/// One transcribed segment. Mirrors run_diarize.py's assign_speakers() output
/// schema exactly (see analysis/diarize/pipeline.py).
struct TranscriptSegment {
int64_t startMs = 0;
int64_t endMs = 0;
QString speaker; ///< Empty = no diarization turn overlapped, or diarization wasn't run.
QString text;
};
/// Why a transcript did or didn't get speaker labels.
///
/// Mirrors DIARIZATION_STATUSES in analysis/diarize/pipeline.py; the two must
/// be kept in step. Exists because "diarization: false" alone is unactionable:
/// no Hugging Face token, a gated model refusing to load, and a model that ran
/// and heard nobody all produce a transcript with every speaker empty, which
/// renders identically — a blank Speaker column and an unshaded waveform. The
/// reason used to live only in the run's stdout, which has scrolled away by
/// the time anyone wonders.
enum class DiarizationStatus {
/// No status key in the file: written before this field existed. Falls
/// back to has_diarization() for wording rather than guessing a reason.
Unknown,
Ok,
SkippedByUser, ///< "Transcript only" was ticked.
NoToken, ///< No Hugging Face token was supplied.
LoadFailed, ///< The pipeline would not load — usually gated-model terms.
RunFailed, ///< It loaded but threw while diarizing this file.
NoTurns, ///< It ran and heard nobody.
/// It heard people, but none of their turns overlapped a transcribed
/// segment. Distinct from NoTurns because the cause and the advice
/// differ: the microphone did capture speech.
NoOverlap,
};
/// One-line statement of what happened, for a UI banner. Empty for Ok.
/// @param hasDiarization The file's legacy boolean, used only when the status
/// is Unknown.
[[nodiscard]] QString diarization_status_headline(DiarizationStatus status, bool hasDiarization);
/// What the operator should actually do about it — the half a bare error
/// message always leaves out. Empty when there is nothing useful to say.
[[nodiscard]] QString diarization_status_remedy(DiarizationStatus status);
/// Parses a "<name>.transcript.json" file written by analysis/run_diarize.py
/// into a queryable in-memory structure, for the Analysis tab's Speaker
/// Diarization plugin (transcript table + playback-synced highlighting).
///
/// Usage:
/// @code
/// auto result = TranscriptResult::load(jsonPath);
/// if (result.is_valid()) { ... }
/// @endcode
class TranscriptResult {
public:
TranscriptResult() = default;
/// Parses jsonPath. Returns a default-constructed (is_valid() == false)
/// result if the file is missing or malformed.
static TranscriptResult load(const QString& jsonPath);
[[nodiscard]] bool is_valid() const { return valid_; }
[[nodiscard]] bool has_diarization() const { return hasDiarization_; }
/// Why speaker labels are or aren't present. Unknown for files written
/// before this field existed.
[[nodiscard]] DiarizationStatus diarization_status() const { return diarizationStatus_; }
/// The Python side's own sentence about it — for LoadFailed this is the
/// verbatim exception text, which is the only thing that distinguishes a
/// bad token from an unaccepted licence. May be empty.
[[nodiscard]] const QString& diarization_detail() const { return diarizationDetail_; }
[[nodiscard]] const QString& source_audio() const { return sourceAudio_; }
[[nodiscard]] const QString& language() const { return language_; }
[[nodiscard]] const QVector<TranscriptSegment>& segments() const { return segments_; }
/// Binary search by start time (segments() is chronological, matching
/// run_diarize.py's write order). Returns nullptr if ms falls in a gap
/// between segments, before the first, or after the last one ends.
[[nodiscard]] const TranscriptSegment* segment_at(int64_t ms) const;
private:
bool valid_ = false;
bool hasDiarization_ = false;
DiarizationStatus diarizationStatus_ = DiarizationStatus::Unknown;
QString diarizationDetail_;
QString sourceAudio_;
QString language_;
QVector<TranscriptSegment> segments_;
};
} // namespace mosaic