updates
This commit is contained in:
1 parent
b90b56a4ae
commit
9beccd31c3
3 files changed
+73
-46
No files matched your search
@@ -2,6 +2,7 @@ import argparse
|
|||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
from itertools import groupby
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
@@ -25,9 +26,13 @@ def load_tokens(path: Path) -> list[Token]:
|
|||||||
|
|
||||||
tokens: list[Token] = []
|
tokens: list[Token] = []
|
||||||
pending = ""
|
pending = ""
|
||||||
|
# Whisper reserves all IDs from end-of-text onward for control tokens.
|
||||||
|
end_of_text = 50256 + int(transcript["model"]["multilingual"])
|
||||||
|
|
||||||
for segment in transcript["transcription"]:
|
for segment in transcript["transcription"]:
|
||||||
for raw_token in segment["tokens"]:
|
for raw_token in segment["tokens"]:
|
||||||
|
if raw_token["id"] >= end_of_text:
|
||||||
|
continue
|
||||||
text = raw_token["text"]
|
text = raw_token["text"]
|
||||||
offsets = raw_token.get("offsets")
|
offsets = raw_token.get("offsets")
|
||||||
|
|
||||||
@@ -81,19 +86,12 @@ def token_speaker(token: Token, turns: list[Turn]) -> str:
|
|||||||
|
|
||||||
def render(tokens: list[Token], turns: list[Turn]) -> str:
|
def render(tokens: list[Token], turns: list[Turn]) -> str:
|
||||||
speaker_numbers: dict[str, int] = {}
|
speaker_numbers: dict[str, int] = {}
|
||||||
blocks: list[tuple[int, str]] = []
|
|
||||||
|
|
||||||
for token in tokens:
|
|
||||||
speaker = token_speaker(token, turns)
|
|
||||||
number = speaker_numbers.setdefault(speaker, len(speaker_numbers) + 1)
|
|
||||||
|
|
||||||
if blocks and blocks[-1][0] == number:
|
|
||||||
blocks[-1] = number, blocks[-1][1] + token.text
|
|
||||||
else:
|
|
||||||
blocks.append((number, token.text))
|
|
||||||
|
|
||||||
rendered = []
|
rendered = []
|
||||||
for number, text in blocks:
|
for speaker, group in groupby(
|
||||||
|
tokens, key=lambda token: token_speaker(token, turns)
|
||||||
|
):
|
||||||
|
number = speaker_numbers.setdefault(speaker, len(speaker_numbers) + 1)
|
||||||
|
text = "".join(token.text for token in group)
|
||||||
text = re.sub(r"\s+", " ", text).strip()
|
text = re.sub(r"\s+", " ", text).strip()
|
||||||
if text:
|
if text:
|
||||||
rendered.append(f"**Speaker {number}:**\n\n{text}")
|
rendered.append(f"**Speaker {number}:**\n\n{text}")
|
||||||
|
|||||||
@@ -17,48 +17,47 @@ move_no_clobber() {
|
|||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
mv -T --no-clobber -- "$source" "$destination"
|
mv -T --no-clobber -- "$source" "$destination" || return 1
|
||||||
! path_exists "$source" && path_exists "$destination"
|
! path_exists "$source" && path_exists "$destination"
|
||||||
}
|
}
|
||||||
|
|
||||||
fail_recording() {
|
fail_recording() (
|
||||||
local source=$1
|
local source=$1
|
||||||
local failed_dir=$2
|
local failed_dir=$2
|
||||||
local message=$3
|
local message=$3
|
||||||
local details=${4:-}
|
local details=${4:-}
|
||||||
local base target error_tmp suffix=0
|
local base target error_tmp suffix=0
|
||||||
|
|
||||||
base=$(basename "$source")
|
base=${source##*/}
|
||||||
target="$failed_dir/$base"
|
target="$failed_dir/$base"
|
||||||
rm -f -- "${source}.transcribe-state"
|
|
||||||
|
|
||||||
while path_exists "$target" || path_exists "${target}.error.txt"; do
|
while path_exists "$target" || path_exists "${target}.error.txt"; do
|
||||||
((suffix += 1))
|
((suffix += 1))
|
||||||
target="$failed_dir/$base.failed-$suffix"
|
target="$failed_dir/$base.failed-$suffix"
|
||||||
done
|
done
|
||||||
|
|
||||||
|
error_tmp=$(mktemp "$failed_dir/.transcription-error.XXXXXX") || return 1
|
||||||
|
trap 'rm -f -- "$error_tmp"' EXIT
|
||||||
|
{
|
||||||
|
printf '%s\n' "$message" || return 1
|
||||||
|
if [[ -n "$details" && -s "$details" ]]; then
|
||||||
|
printf '\n' || return 1
|
||||||
|
cat -- "$details" || return 1
|
||||||
|
fi
|
||||||
|
} >"$error_tmp" || return 1
|
||||||
|
|
||||||
if ! move_no_clobber "$source" "$target"; then
|
if ! move_no_clobber "$source" "$target"; then
|
||||||
printf 'Could not quarantine %s: destination collision or move failure\n' "$source" >&2
|
printf 'Could not quarantine %s: destination collision or move failure\n' "$source" >&2
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
error_tmp=$(mktemp "$failed_dir/.transcription-error.XXXXXX")
|
|
||||||
{
|
|
||||||
printf '%s\n' "$message"
|
|
||||||
if [[ -n "$details" && -s "$details" ]]; then
|
|
||||||
printf '\n'
|
|
||||||
cat -- "$details"
|
|
||||||
fi
|
|
||||||
} >"$error_tmp"
|
|
||||||
|
|
||||||
if ! ln -- "$error_tmp" "${target}.error.txt"; then
|
if ! ln -- "$error_tmp" "${target}.error.txt"; then
|
||||||
printf 'Could not publish error sidecar for %s\n' "$target" >&2
|
printf 'Could not publish error sidecar for %s\n' "$target" >&2
|
||||||
rm -f -- "$error_tmp"
|
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
rm -f -- "$error_tmp"
|
rm -f -- "${source}.transcribe-state" || return 1
|
||||||
printf 'Moved failed recording to %s\n' "$target" >&2
|
printf 'Moved failed recording to %s\n' "$target" >&2
|
||||||
}
|
)
|
||||||
|
|
||||||
recover_completed_recording() {
|
recover_completed_recording() {
|
||||||
local source=$1
|
local source=$1
|
||||||
@@ -71,15 +70,18 @@ recover_completed_recording() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if ! path_exists "$transcript"; then
|
if ! path_exists "$transcript"; then
|
||||||
rm -f -- "$state_file"
|
rm -f -- "$state_file" || return 3
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! move_no_clobber "$source" "$archive"; then
|
if ! move_no_clobber "$source" "$archive"; then
|
||||||
return 2
|
if path_exists "$archive"; then
|
||||||
|
return 2
|
||||||
|
fi
|
||||||
|
return 3
|
||||||
fi
|
fi
|
||||||
|
|
||||||
rm -f -- "$state_file"
|
rm -f -- "$state_file" || return 3
|
||||||
printf 'Recovered completed transcription and archived %s\n' "$archive"
|
printf 'Recovered completed transcription and archived %s\n' "$archive"
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
@@ -93,10 +95,11 @@ process_recording() (
|
|||||||
local archive_dir=$4
|
local archive_dir=$4
|
||||||
local failed_dir=$5
|
local failed_dir=$5
|
||||||
local base stem transcript archive original_path recorded_at transcribed_at duration
|
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 work_dir log_file raw_output rendered_output state_file recovery_status
|
||||||
|
local draft="" diarization_log
|
||||||
local diarization_succeeded=false
|
local diarization_succeeded=false
|
||||||
|
|
||||||
base=$(basename "$source")
|
base=${source##*/}
|
||||||
stem=${base%.*}
|
stem=${base%.*}
|
||||||
transcript="$transcript_dir/$stem.md"
|
transcript="$transcript_dir/$stem.md"
|
||||||
archive="$archive_dir/$base"
|
archive="$archive_dir/$base"
|
||||||
@@ -112,6 +115,10 @@ process_recording() (
|
|||||||
fail_recording "$source" "$failed_dir" "Archive collision after transcription: $archive"
|
fail_recording "$source" "$failed_dir" "Archive collision after transcription: $archive"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
if [[ $recovery_status -ne 1 ]]; then
|
||||||
|
printf 'Could not recover %s; leaving state for retry\n' "$source" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
if path_exists "$transcript"; then
|
if path_exists "$transcript"; then
|
||||||
fail_recording "$source" "$failed_dir" "Transcript already exists: $transcript"
|
fail_recording "$source" "$failed_dir" "Transcript already exists: $transcript"
|
||||||
@@ -122,11 +129,13 @@ process_recording() (
|
|||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
recorded_at=$(date --date="@$(stat -c %Y -- "$source")" --iso-8601=seconds)
|
recorded_at=$(stat -c %Y -- "$source")
|
||||||
|
recorded_at=$(date --date="@$recorded_at" --iso-8601=seconds)
|
||||||
transcribed_at=$(date --utc --iso-8601=seconds)
|
transcribed_at=$(date --utc --iso-8601=seconds)
|
||||||
work_dir=$(mktemp -d "$runtime_dir/work.XXXXXX")
|
work_dir=$(mktemp -d "$runtime_dir/work.XXXXXX")
|
||||||
trap 'rm -r -- "$work_dir"' EXIT
|
trap 'if [[ -n "$draft" ]]; then rm -f -- "$draft"; fi; rm -r -- "$work_dir"' EXIT
|
||||||
log_file="$work_dir/transcription.log"
|
log_file="$work_dir/transcription.log"
|
||||||
|
diarization_log="$work_dir/diarization.log"
|
||||||
raw_output="$work_dir/transcript"
|
raw_output="$work_dir/transcript"
|
||||||
rendered_output="$work_dir/transcript.md"
|
rendered_output="$work_dir/transcript.md"
|
||||||
|
|
||||||
@@ -175,11 +184,14 @@ process_recording() (
|
|||||||
--audio "$work_dir/audio.wav" \
|
--audio "$work_dir/audio.wav" \
|
||||||
--model "$TRANSCRIBE_DIARIZATION_MODEL" \
|
--model "$TRANSCRIBE_DIARIZATION_MODEL" \
|
||||||
--transcript "${raw_output}.json" \
|
--transcript "${raw_output}.json" \
|
||||||
--output "$rendered_output" >>"$log_file" 2>&1 \
|
--output "$rendered_output" >"$diarization_log" 2>&1 \
|
||||||
&& [[ -s "$rendered_output" ]]; then
|
&& [[ -s "$rendered_output" ]]; then
|
||||||
diarization_succeeded=true
|
diarization_succeeded=true
|
||||||
else
|
else
|
||||||
printf 'Speaker diarization unavailable; publishing unlabeled transcript\n' >&2
|
printf 'Speaker diarization unavailable; publishing unlabeled transcript\n' >&2
|
||||||
|
if [[ -s "$diarization_log" ]]; then
|
||||||
|
cat -- "$diarization_log" >&2
|
||||||
|
fi
|
||||||
cp -- "${raw_output}.txt" "$rendered_output"
|
cp -- "${raw_output}.txt" "$rendered_output"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -222,54 +234,70 @@ process_recording() (
|
|||||||
printf 'Transcribed %s to %s\n' "$archive" "$transcript"
|
printf 'Transcribed %s to %s\n' "$archive" "$transcript"
|
||||||
)
|
)
|
||||||
|
|
||||||
process_scope() {
|
process_scope() (
|
||||||
local recordings_dir=$1
|
local recordings_dir=$1
|
||||||
local transcript_dir=$2
|
local transcript_dir=$2
|
||||||
local inbox_dir="$recordings_dir/inbox"
|
local inbox_dir="$recordings_dir/inbox"
|
||||||
local processing_dir="$recordings_dir/processing"
|
local processing_dir="$recordings_dir/processing"
|
||||||
local archive_dir="$recordings_dir/archive"
|
local archive_dir="$recordings_dir/archive"
|
||||||
local failed_dir="$recordings_dir/failed"
|
local failed_dir="$recordings_dir/failed"
|
||||||
local source processing_target recording_status signature index scope_status=0
|
local source processing_target recording_status signature index listing scope_status=0
|
||||||
local -a sources signatures
|
local -a sources signatures
|
||||||
|
|
||||||
mkdir -p "$inbox_dir" "$processing_dir" "$archive_dir" "$failed_dir" "$transcript_dir" || return 1
|
mkdir -p "$inbox_dir" "$processing_dir" "$archive_dir" "$failed_dir" "$transcript_dir" || return 1
|
||||||
|
listing=$(mktemp "$runtime_dir/recordings.XXXXXX") || return 1
|
||||||
|
trap 'rm -f -- "$listing"' EXIT
|
||||||
|
|
||||||
|
find "$processing_dir" -maxdepth 1 -type f -print0 | sort -z >"$listing" || return 1
|
||||||
while IFS= read -r -d '' source; do
|
while IFS= read -r -d '' source; do
|
||||||
if is_recording "$source"; then
|
if is_recording "$source"; then
|
||||||
|
# Keep this call outside conditionals so its subshell's errexit stays active.
|
||||||
process_recording "$source" "$inbox_dir" "$transcript_dir" "$archive_dir" "$failed_dir"
|
process_recording "$source" "$inbox_dir" "$transcript_dir" "$archive_dir" "$failed_dir"
|
||||||
recording_status=$?
|
recording_status=$?
|
||||||
if [[ $recording_status -ne 0 ]]; then
|
if [[ $recording_status -ne 0 ]]; then
|
||||||
scope_status=1
|
scope_status=1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
done < <(find "$processing_dir" -maxdepth 1 -type f -print0 | sort -z)
|
done <"$listing"
|
||||||
|
|
||||||
while true; do
|
while true; do
|
||||||
sources=()
|
sources=()
|
||||||
signatures=()
|
signatures=()
|
||||||
|
|
||||||
|
find "$inbox_dir" -maxdepth 1 -type f -print0 | sort -z >"$listing" || return 1
|
||||||
while IFS= read -r -d '' source; do
|
while IFS= read -r -d '' source; do
|
||||||
if is_recording "$source" && signature=$(stat -c '%s:%y:%i' -- "$source"); then
|
if is_recording "$source"; then
|
||||||
|
if ! signature=$(stat -c '%s:%y:%i' -- "$source"); then
|
||||||
|
if path_exists "$source"; then return 1; fi
|
||||||
|
continue
|
||||||
|
fi
|
||||||
sources+=("$source")
|
sources+=("$source")
|
||||||
signatures+=("$signature")
|
signatures+=("$signature")
|
||||||
fi
|
fi
|
||||||
done < <(find "$inbox_dir" -maxdepth 1 -type f -print0 | sort -z)
|
done <"$listing"
|
||||||
|
|
||||||
if [[ ${#sources[@]} -eq 0 ]]; then
|
if [[ ${#sources[@]} -eq 0 ]]; then
|
||||||
break
|
break
|
||||||
fi
|
fi
|
||||||
|
|
||||||
sleep "${TRANSCRIBE_SETTLE_SECONDS:-300}"
|
sleep "${TRANSCRIBE_SETTLE_SECONDS:-300}" || return 1
|
||||||
|
|
||||||
for index in "${!sources[@]}"; do
|
for index in "${!sources[@]}"; do
|
||||||
source=${sources[$index]}
|
source=${sources[$index]}
|
||||||
if [[ ! -f "$source" ]] || [[ $(stat -c '%s:%y:%i' -- "$source") != "${signatures[$index]}" ]]; then
|
if [[ ! -f "$source" ]]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
if ! signature=$(stat -c '%s:%y:%i' -- "$source"); then
|
||||||
|
if path_exists "$source"; then return 1; fi
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
if [[ $signature != "${signatures[$index]}" ]]; then
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
|
|
||||||
processing_target="$processing_dir/$(basename "$source")"
|
processing_target="$processing_dir/${source##*/}"
|
||||||
if ! move_no_clobber "$source" "$processing_target"; then
|
if ! move_no_clobber "$source" "$processing_target"; then
|
||||||
fail_recording "$source" "$failed_dir" "Processing filename already exists: $processing_target"
|
fail_recording "$source" "$failed_dir" "Could not claim recording: destination collision or move failure: $processing_target" || return 1
|
||||||
scope_status=1
|
scope_status=1
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
@@ -282,7 +310,7 @@ process_scope() {
|
|||||||
done
|
done
|
||||||
|
|
||||||
return "$scope_status"
|
return "$scope_status"
|
||||||
}
|
)
|
||||||
|
|
||||||
runtime_dir="${XDG_RUNTIME_DIR:?}/transcribe-recordings"
|
runtime_dir="${XDG_RUNTIME_DIR:?}/transcribe-recordings"
|
||||||
mkdir -p "$runtime_dir"
|
mkdir -p "$runtime_dir"
|
||||||
|
|||||||
@@ -38,6 +38,7 @@
|
|||||||
kubelogin-oidc
|
kubelogin-oidc
|
||||||
rclone
|
rclone
|
||||||
scc
|
scc
|
||||||
|
visidata
|
||||||
just
|
just
|
||||||
gardenctl
|
gardenctl
|
||||||
gardenlogin
|
gardenlogin
|
||||||
|
|||||||
Reference in new issue
Block a user