#!/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"