diff --git a/lifelog-recorder/scripts/reprocess-history.sh b/lifelog-recorder/scripts/reprocess-history.sh new file mode 100755 index 0000000..b651ede --- /dev/null +++ b/lifelog-recorder/scripts/reprocess-history.sh @@ -0,0 +1,104 @@ +#!/usr/bin/env bash +# 一次性:把所有旧的逐句 .opus 重新合并成「块」,清空旧识别(segments/transcripts/ +# speaker_profiles),让设备上 RecorderService 用**本地模型**(pyannote+paraformer+campplus) +# 按新 block 管线整体重跑声纹+文字。 +# +# 本脚本只做机械活:解码+重打包成 blocks//{audio.pcm,manifest.json} + 清 DB。 +# 真正的识别 100% 在 Pixel 端上模型跑,不用任何云/Claude。 +# +# 用法: bash reprocess-history.sh +# 跑完后:打开 app → 录音 → 开始录音,RecorderService 会自动处理所有块。 +set -euo pipefail + +PKG=com.lemon.lifelog +DBR=/data/data/$PKG/databases/lifelog.db +SEGR=/data/data/$PKG/files/segments +BLKR=/data/data/$PKG/files/blocks +GAP_MS=$((5*60*1000)) # 静音≥5min 切块 +CAP_MS=$((10*60*1000)) # 块≥10min 封顶(控内存/单次 diarize 时长) +SR=16000 + +WORK=$(mktemp -d /tmp/reprocess.XXXXXX) +echo "工作目录: $WORK" + +echo "[1/8] 停 app" +adb shell am force-stop $PKG + +echo "[2/8] 备份 + 拉 DB" +TS=$(date +%Y%m%d-%H%M%S) +adb shell "su -c 'cp $DBR /sdcard/_bk.db'"; adb pull /sdcard/_bk.db "$WORK/backup-$TS.db" >/dev/null; adb shell "su -c 'rm /sdcard/_bk.db'" +adb shell "su -c 'cp ${DBR}* /sdcard/ 2>/dev/null'" +for f in lifelog.db lifelog.db-wal lifelog.db-shm; do adb pull /sdcard/$f "$WORK/$f" >/dev/null 2>&1 || true; done +adb shell "su -c 'rm /sdcard/lifelog.db* 2>/dev/null'" +sqlite3 "$WORK/lifelog.db" "PRAGMA wal_checkpoint(TRUNCATE);" >/dev/null 2>&1 || true +echo " 备份: $WORK/backup-$TS.db" + +echo "[3/8] 拉所有 .opus" +adb shell "su -c 'rm -rf /sdcard/_seg && cp -r $SEGR /sdcard/_seg'" +adb pull /sdcard/_seg "$WORK/segments" >/dev/null 2>&1 +adb shell "su -c 'rm -rf /sdcard/_seg'" + +echo "[4/8] 分块 + 解码重打包(ffmpeg)" +python3 - "$WORK/lifelog.db" "$WORK/segments" "$WORK/blocks" "$GAP_MS" "$CAP_MS" "$SR" <<'PY' +import sqlite3, subprocess, os, json, sys +db, segdir, outdir, GAP, CAP, SR = sys.argv[1], sys.argv[2], sys.argv[3], int(sys.argv[4]), int(sys.argv[5]), int(sys.argv[6]) +con = sqlite3.connect(db) +rows = con.execute("SELECT id,startTs,durationMs,audioPath FROM segments ORDER BY startTs").fetchall() +# 按静音间隔 + 时长封顶分块 +blocks, cur = [], [] +for r in rows: + if cur: + prev = cur[-1] + prev_end = prev[1] + prev[2] + span = r[1] - cur[0][1] + if (r[1] - prev_end) >= GAP or span >= CAP: + blocks.append(cur); cur = [] + cur.append(r) +if cur: blocks.append(cur) + +os.makedirs(outdir, exist_ok=True) +made = 0 +for b in blocks: + start = b[0][1] + bdir = os.path.join(outdir, str(start)); os.makedirs(bdir, exist_ok=True) + pcm = bytearray(); runs = []; off = 0 + for seg in b: + rel = seg[3].split('/files/segments/')[-1] + local = os.path.join(segdir, rel) + if not os.path.exists(local): + continue + raw = subprocess.run(["ffmpeg","-v","quiet","-i",local,"-ar",str(SR),"-ac","1","-f","s16le","-"], + capture_output=True).stdout + n = len(raw)//2 + if n == 0: + continue + runs.append({"ts": seg[1], "off": off, "len": n}); off += n; pcm += raw + if off == 0: + os.rmdir(bdir); continue + open(os.path.join(bdir, "audio.pcm"), "wb").write(pcm) + json.dump({"start": start, "sr": SR, "closed": True, "runs": runs}, open(os.path.join(bdir, "manifest.json"), "w")) + made += 1 + print(f" block {start}: {len(b)} segs -> {off/SR:.0f}s speech") +print(f" => {made} blocks from {len(rows)} segments") +PY + +echo "[5/8] 清空旧识别(segments/transcripts/speaker_profiles)" +sqlite3 "$WORK/lifelog.db" "DELETE FROM transcripts; DELETE FROM segments; DELETE FROM speaker_profiles; PRAGMA wal_checkpoint(TRUNCATE); VACUUM;" +sqlite3 "$WORK/lifelog.db" "PRAGMA integrity_check;" | head -1 + +echo "[6/8] 推块到设备 + 清旧 .opus + 清旧 blocks" +APP_UID=$(adb shell "su -c 'stat -c %u /data/data/$PKG'" | tr -d '\r') +echo " app uid=$APP_UID" +adb shell "su -c 'rm -rf $BLKR; mkdir -p $BLKR'" +adb shell "su -c 'rm -rf $SEGR'" # 旧逐句 .opus 全删(内容已并进块);RecorderService 会重建并写新块 .opus +adb push "$WORK/blocks" /sdcard/_blk >/dev/null +adb shell "su -c 'cp -r /sdcard/_blk/* $BLKR/ && chown -R $APP_UID:$APP_UID $BLKR && chcon -R u:object_r:app_data_file:s0 $BLKR; rm -rf /sdcard/_blk'" +echo " pushed $(adb shell "su -c 'ls $BLKR | wc -l'" | tr -d '\r') blocks" + +echo "[7/8] 推回清空后的 DB" +adb push "$WORK/lifelog.db" /sdcard/_db >/dev/null +adb shell "su -c 'cp /sdcard/_db $DBR && rm -f ${DBR}-wal ${DBR}-shm && chown $APP_UID:$APP_UID $DBR && chcon u:object_r:app_data_file:s0 $DBR; rm -f /sdcard/_db'" + +echo "[8/8] 完成。现在打开 app → 录音 → 开始录音,端上本地模型会自动重跑所有块。" +echo " 监控: adb logcat -s BlockProcessor:I SpeakerClusterer:I" +echo " 备份在: $WORK/backup-$TS.db"