feat: prepare Orion transcription pilot

This commit is contained in:
kiri committed 2026-09-06 01:04:41 +02:00
1 parent ec54ddd0f8
commit 656f144267
4 files changed
+99 -32

No files matched your search

+57 -19
View File
@@ -92,8 +92,9 @@ process_recording() (
local transcript_dir=$3
local archive_dir=$4
local failed_dir=$5
local base stem transcript archive original_path recorded_at transcribed_at
local base stem transcript archive original_path recorded_at transcribed_at duration
local work_dir log_file raw_output rendered_output draft state_file recovery_status
local diarization_succeeded=false
base=$(basename "$source")
stem=${base%.*}
@@ -129,6 +130,20 @@ process_recording() (
raw_output="$work_dir/transcript"
rendered_output="$work_dir/transcript.md"
if ! duration=$(ffprobe \
-v error \
-show_entries format=duration \
-of default=noprint_wrappers=1:nokey=1 \
"$source" 2>"$log_file"); then
fail_recording "$source" "$failed_dir" "Media validation failed" "$log_file"
return 1
fi
if [[ ! $duration =~ ^[0-9]+([.][0-9]+)?$ || -z ${duration//[.0]/} ]]; then
printf 'Invalid media duration: %s\n' "$duration" >"$log_file"
fail_recording "$source" "$failed_dir" "Media validation failed" "$log_file"
return 1
fi
if ! ffmpeg \
-nostdin \
-hide_banner \
@@ -148,18 +163,24 @@ process_recording() (
--file "$work_dir/audio.wav" \
--language auto \
--output-json-full \
--output-file "$raw_output" >>"$log_file" 2>&1 || [[ ! -s "${raw_output}.json" ]]; then
--output-txt \
--output-file "$raw_output" >>"$log_file" 2>&1 \
|| [[ ! -s "${raw_output}.json" || ! -s "${raw_output}.txt" ]]; then
fail_recording "$source" "$failed_dir" "Whisper transcription failed" "$log_file"
return 1
fi
if ! diarize-transcript \
if [[ -f "$TRANSCRIBE_DIARIZATION_MODEL/.complete" && -f "$TRANSCRIBE_DIARIZATION_MODEL/config.yaml" ]] \
&& diarize-transcript \
--audio "$work_dir/audio.wav" \
--model "$TRANSCRIBE_DIARIZATION_MODEL" \
--transcript "${raw_output}.json" \
--output "$rendered_output" >>"$log_file" 2>&1 || [[ ! -s "$rendered_output" ]]; then
fail_recording "$source" "$failed_dir" "Speaker diarization failed" "$log_file"
return 1
--output "$rendered_output" >>"$log_file" 2>&1 \
&& [[ -s "$rendered_output" ]]; then
diarization_succeeded=true
else
printf 'Speaker diarization unavailable; publishing unlabeled transcript\n' >&2
cp -- "${raw_output}.txt" "$rendered_output"
fi
draft=$(mktemp "$transcript_dir/.transcript.XXXXXX")
@@ -168,12 +189,17 @@ process_recording() (
printf -- '- Original recording: %s\n' "$original_path"
printf -- '- Recording timestamp: %s\n' "$recorded_at"
printf -- '- Transcription timestamp: %s\n' "$transcribed_at"
printf -- '- Engine: whisper.cpp %s (Vulkan)\n' "$TRANSCRIBE_ENGINE_VERSION"
printf -- '- Duration: %s seconds\n' "$duration"
printf -- '- Engine: whisper.cpp %s (%s)\n' "$TRANSCRIBE_ENGINE_VERSION" "$TRANSCRIBE_ENGINE_ACCELERATION"
printf -- '- Model: %s\n' "$TRANSCRIBE_MODEL_NAME"
printf -- '- Model SHA-256: %s\n' "$TRANSCRIBE_MODEL_SHA256"
if [[ $diarization_succeeded == true ]]; then
printf -- '- Diarization engine: pyannote.audio %s (CPU)\n' "$TRANSCRIBE_DIARIZATION_ENGINE_VERSION"
printf -- '- Diarization model: %s\n' "$TRANSCRIBE_DIARIZATION_MODEL_NAME"
printf -- '- Diarization model revision: %s\n\n' "$TRANSCRIBE_DIARIZATION_MODEL_REVISION"
else
printf -- '- Diarization: unavailable; unlabeled fallback published\n\n'
fi
cat -- "$rendered_output"
} >"$draft"
chmod 0644 "$draft"
@@ -203,7 +229,8 @@ process_scope() {
local processing_dir="$recordings_dir/processing"
local archive_dir="$recordings_dir/archive"
local failed_dir="$recordings_dir/failed"
local source processing_target recording_status scope_status=0
local source processing_target recording_status signature index scope_status=0
local -a sources signatures
if ! mkdir -p "$inbox_dir" "$processing_dir" "$archive_dir" "$failed_dir" "$transcript_dir"; then
return 1
@@ -219,8 +246,26 @@ process_scope() {
fi
done < <(find "$processing_dir" -maxdepth 1 -type f -print0 | sort -z)
while true; do
sources=()
signatures=()
while IFS= read -r -d '' source; do
if ! is_recording "$source"; then
if is_recording "$source" && signature=$(stat -c '%s:%y:%i' -- "$source"); then
sources+=("$source")
signatures+=("$signature")
fi
done < <(find "$inbox_dir" -maxdepth 1 -type f -print0 | sort -z)
if [[ ${#sources[@]} -eq 0 ]]; then
break
fi
sleep "${TRANSCRIBE_SETTLE_SECONDS:-300}"
for index in "${!sources[@]}"; do
source=${sources[$index]}
if [[ ! -f "$source" ]] || [[ $(stat -c '%s:%y:%i' -- "$source") != "${signatures[$index]}" ]]; then
continue
fi
@@ -235,7 +280,8 @@ process_scope() {
if [[ $recording_status -ne 0 ]]; then
scope_status=1
fi
done < <(find "$inbox_dir" -maxdepth 1 -type f -print0 | sort -z)
done
done
return "$scope_status"
}
@@ -243,14 +289,6 @@ process_scope() {
runtime_dir="${XDG_RUNTIME_DIR:?}/transcribe-recordings"
mkdir -p "$runtime_dir"
if [[ ! -f "$TRANSCRIBE_DIARIZATION_MODEL/.complete" || ! -f "$TRANSCRIBE_DIARIZATION_MODEL/config.yaml" ]]; then
printf 'Speaker diarization model is not installed; run prepare-transcription-diarization-model\n' >&2
exit 1
fi
set +o errexit
process_scope "$TRANSCRIBE_SYNC_ROOT/recordings" "$TRANSCRIBE_SYNC_ROOT/notes/transcripts"
personal_status=$?
process_scope "$TRANSCRIBE_SYNC_ROOT/work/recordings" "$TRANSCRIBE_SYNC_ROOT/work/notes/transcripts"
work_status=$?
exit "$((personal_status != 0 || work_status != 0))"
exit $?
+1 -5
View File
@@ -64,11 +64,7 @@ in
${pkgs.coreutils}/bin/mkdir -p \
"${home}/sync/notes/daily" \
"${home}/sync/notes/meetings" \
"${home}/sync/notes/transcripts" \
"${home}/sync/recordings/inbox" \
"${home}/sync/recordings/processing" \
"${home}/sync/recordings/archive" \
"${home}/sync/recordings/failed"
"${home}/sync/notes/transcripts"
'';
};
+40 -8
View File
@@ -11,6 +11,7 @@ in
...
}:
let
isOrion = config.facts.machine.name == "orion";
modelName = "large-v3-turbo-q5_0";
modelHash = "sha256-OUIhcJzVrR9AxG5gMcphvOiJMebgiMGIKUxtWlX/p+I=";
modelSha256 = builtins.convertHash {
@@ -37,6 +38,8 @@ in
${builtins.readFile ./_diarize-transcript.py}
'';
};
whisper = if isOrion then pkgs.whisper-cpp else pkgs.whisper-cpp-vulkan;
acceleration = if isOrion then "CPU" else "Vulkan";
prepareDiarizationModel = pkgs.writeShellApplication {
name = "prepare-transcription-diarization-model";
runtimeInputs = with pkgs; [
@@ -72,18 +75,28 @@ in
diarizeTranscript
ffmpeg
findutils
whisper-cpp-vulkan
whisper
];
text = builtins.readFile ./_transcribe-recordings.sh;
};
home = config.home.homeDirectory;
in
{
home.packages = with pkgs; [
ffmpeg
home.packages = [
pkgs.ffmpeg
prepareDiarizationModel
whisper-cpp-vulkan
whisper
];
home.activation.createTranscriptionLayout = lib.hm.dag.entryAfter [ "writeBoundary" ] ''
${pkgs.coreutils}/bin/mkdir -p \
"${home}/sync/notes/transcripts" \
"${home}/sync/recordings/inbox" \
"${home}/sync/recordings/processing" \
"${home}/sync/recordings/archive" \
"${home}/sync/recordings/failed"
'';
systemd.user.services.transcribe-recordings = {
Unit.Description = "Transcribe synchronized recordings with whisper.cpp";
@@ -91,7 +104,8 @@ in
Type = "oneshot";
ExecStart = lib.getExe transcribeRecordings;
Environment = [
"TRANSCRIBE_ENGINE_VERSION=${pkgs.whisper-cpp-vulkan.version}"
"TRANSCRIBE_ENGINE_ACCELERATION=${acceleration}"
"TRANSCRIBE_ENGINE_VERSION=${whisper.version}"
"TRANSCRIBE_MODEL=${model}"
''"TRANSCRIBE_MODEL_NAME=${modelName} multilingual"''
"TRANSCRIBE_MODEL_SHA256=${modelSha256}"
@@ -99,12 +113,30 @@ in
"TRANSCRIBE_DIARIZATION_MODEL=${diarizationModel}"
"TRANSCRIBE_DIARIZATION_MODEL_NAME=${diarizationModelName}"
"TRANSCRIBE_DIARIZATION_MODEL_REVISION=${diarizationModelRevision}"
"TRANSCRIBE_SYNC_ROOT=${config.home.homeDirectory}/sync"
"TRANSCRIBE_SETTLE_SECONDS=300"
"TRANSCRIBE_SYNC_ROOT=${home}/sync"
];
};
}
// lib.optionalAttrs isOrion {
Restart = "on-failure";
RestartSec = 60;
};
systemd.user.timers.transcribe-recordings = {
Install.WantedBy = lib.optionals isOrion [ "default.target" ];
};
systemd.user.paths.transcribe-recordings = lib.mkIf isOrion {
Unit.Description = "Watch for synchronized recordings";
Path = {
PathChanged = "${home}/sync/recordings/inbox";
Unit = "transcribe-recordings.service";
};
Install.WantedBy = [ "paths.target" ];
};
systemd.user.timers.transcribe-recordings = lib.mkIf (!isOrion) {
Unit.Description = "Transcribe new recordings every two minutes";
Timer = {
+1
View File
@@ -27,6 +27,7 @@ in
nixosModules.radicale
nixosModules.actual
nixosModules.gitea
nixosModules.transcription
./_hardware.nix
./_disk.nix
];