dsh-deepmoyu 2.3.0 → 2.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +91 -153
- package/CHANGELOG.md +27 -0
- package/CONTRIBUTING.md +51 -0
- package/CONTRIBUTORS.md +3 -7
- package/NOTICE.md +3 -3
- package/README.md +141 -81
- package/SECURITY.md +21 -0
- package/assets/screenshots/memory.png +0 -0
- package/assets/screenshots/menu.png +0 -0
- package/assets/screenshots/workspace.png +0 -0
- package/client.js +1348 -441
- package/cordis.patch.yml +1 -1
- package/docs/ASSETS.md +25 -0
- package/docs/DEPLOYMENT.md +102 -0
- package/docs/PRIVACY.md +32 -0
- package/docs/RELEASE_AUDIT.md +70 -0
- package/docs/RELEASING.md +94 -0
- package/docs/TEST_MATRIX.md +77 -0
- package/docs/assets-permissions.json +20 -0
- package/docs/tool-evidence.json +196 -0
- package/index.js +953 -270
- package/lib/asr.py +41 -35
- package/lib/background-output.mjs +64 -17
- package/lib/background-tools.mjs +1 -0
- package/lib/companion.mjs +43 -0
- package/lib/config-policy.mjs +20 -0
- package/lib/favops.py +9 -2
- package/lib/ids.mjs +41 -0
- package/lib/login-session.mjs +34 -0
- package/lib/login.mjs +5 -4
- package/lib/mcp.mjs +3 -2
- package/lib/memory-center.mjs +74 -0
- package/lib/mode.mjs +6 -7
- package/lib/model-relay.mjs +5 -4
- package/lib/open-directory.mjs +93 -0
- package/lib/pace.mjs +92 -12
- package/lib/persona.mjs +46 -0
- package/lib/product-status.mjs +123 -0
- package/lib/prompts.mjs +40 -5
- package/lib/pv-production.mjs +280 -0
- package/lib/pv-session.mjs +316 -0
- package/lib/pv.mjs +398 -306
- package/lib/pv.py +84 -22
- package/lib/pv_cover.py +108 -26
- package/lib/pv_editorial.py +336 -0
- package/lib/pv_verify.py +267 -0
- package/lib/python-env.mjs +61 -0
- package/lib/reply.mjs +10 -3
- package/lib/run-settings.mjs +6 -0
- package/lib/sticker-automation.mjs +5 -4
- package/lib/sticker.mjs +135 -25
- package/lib/tool-scope.mjs +65 -0
- package/lib/uvfetch.mjs +165 -153
- package/lib/verify.mjs +111 -127
- package/marketplace/pyyxzi__dsh-deepmoyu.yml +7 -0
- package/package.json +23 -12
- package/presets/deepmoyu.patch.yml +1 -1
- package/prompts/duty.md +4 -3
- package/screenshots.json +5 -0
- package/selfcheck.mjs +21 -6
- package/vendor/bilibili-mcp/requirements.in +9 -0
- package/vendor/bilibili-mcp/requirements.txt +1936 -11
- package/vendor/bilibili-mcp/runtime_guard.py +35 -10
- package/lib/__pycache__/pv.cpython-310.pyc +0 -0
- package/lib/__pycache__/pv_cover.cpython-310.pyc +0 -0
- package/tests/automation-routes.test.mjs +0 -183
- package/tests/automation.test.mjs +0 -79
- package/tests/background-tools.test.mjs +0 -182
- package/tests/background-window.test.mjs +0 -75
- package/tests/dup-cross-channel.test.mjs +0 -158
- package/tests/manual-browsing.test.mjs +0 -671
- package/tests/mcp-restart.test.mjs +0 -131
- package/tests/memory-chain.test.mjs +0 -413
- package/tests/model-relay.test.mjs +0 -116
- package/tests/persona.test.mjs +0 -67
- package/tests/pet-run-routes.test.mjs +0 -127
- package/tests/pet-run-settings.test.mjs +0 -38
- package/tests/pet-session.test.mjs +0 -125
- package/tests/pv-stage1.test.mjs +0 -203
- package/tests/sticker-routes.test.mjs +0 -141
- package/tests/sticker-upload-routes.test.mjs +0 -97
- package/tests/sticker-upload.test.mjs +0 -122
- package/tests/sticker.test.mjs +0 -169
- package/tests/test_guard_cross_channel.py +0 -141
- package/tests/test_pv_cover.py +0 -95
- package/tests/test_pv_stage1.py +0 -250
- package/tests/test_reply_pictures.py +0 -255
- package/tests/token-ledger.test.mjs +0 -78
- package/tests/verify_pv_sample.py +0 -91
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
"""Verify an actual local MP4 against measured utterance timing and decoded PCM.
|
|
2
|
-
|
|
3
|
-
Usage: python verify_pv_sample.py <sample.mp4> <render-directory>
|
|
4
|
-
Produces decoded first/middle/last frames, clean speech WAV, audio-check.json.
|
|
5
|
-
It does not perform ASR or claim that an agent listened to the audio.
|
|
6
|
-
"""
|
|
7
|
-
from __future__ import annotations
|
|
8
|
-
|
|
9
|
-
import argparse
|
|
10
|
-
import array
|
|
11
|
-
import json
|
|
12
|
-
import math
|
|
13
|
-
import subprocess
|
|
14
|
-
import wave
|
|
15
|
-
from pathlib import Path
|
|
16
|
-
|
|
17
|
-
import imageio_ffmpeg
|
|
18
|
-
from PIL import Image
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def waveform(path):
|
|
22
|
-
with wave.open(str(path), "rb") as handle:
|
|
23
|
-
assert handle.getsampwidth() == 2, "expected 16-bit PCM"
|
|
24
|
-
samples = array.array("h")
|
|
25
|
-
samples.frombytes(handle.readframes(handle.getnframes()))
|
|
26
|
-
channels, rate, frames = handle.getnchannels(), handle.getframerate(), handle.getnframes()
|
|
27
|
-
peak = max((abs(value) for value in samples), default=0)
|
|
28
|
-
rms = math.sqrt(sum(value * value for value in samples) / max(1, len(samples)))
|
|
29
|
-
return {"duration": frames / rate, "sample_rate": rate, "channels": channels,
|
|
30
|
-
"peak_dbfs": round(20 * math.log10(max(1, peak) / 32768), 3),
|
|
31
|
-
"rms_dbfs": round(20 * math.log10(max(1, rms) / 32768), 3),
|
|
32
|
-
"clipped_samples": sum(abs(value) >= 32767 for value in samples),
|
|
33
|
-
"sample_count": len(samples)}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
def verify(video, work):
|
|
37
|
-
timeline = json.loads((work / "timeline.json").read_text(encoding="utf-8"))
|
|
38
|
-
fps, duration = timeline["fps"], timeline["duration"]
|
|
39
|
-
frames_expected = round(fps * duration)
|
|
40
|
-
selected = {max(1, int(timeline["cues"][0]["start"] * fps) + 1): "first",
|
|
41
|
-
frames_expected // 2: "middle", frames_expected - 2: "last"}
|
|
42
|
-
reader = imageio_ffmpeg.read_frames(str(video), pix_fmt="rgb24")
|
|
43
|
-
metadata = next(reader)
|
|
44
|
-
count = 0
|
|
45
|
-
for index, pixels in enumerate(reader):
|
|
46
|
-
count += 1
|
|
47
|
-
if index in selected:
|
|
48
|
-
Image.frombytes("RGB", metadata["size"], pixels).save(work / f"decoded_{selected[index]}.png")
|
|
49
|
-
assert count == frames_expected, (count, frames_expected)
|
|
50
|
-
assert abs(metadata["fps"] - fps) < .001
|
|
51
|
-
assert metadata.get("audio_codec"), "MP4 has no audio stream"
|
|
52
|
-
|
|
53
|
-
decoded = work / "decoded_audio.wav"
|
|
54
|
-
subprocess.run([imageio_ffmpeg.get_ffmpeg_exe(), "-y", "-v", "error", "-i", str(video),
|
|
55
|
-
"-vn", "-acodec", "pcm_s16le", "-ar", "24000", str(decoded)], check=True)
|
|
56
|
-
mixed = waveform(decoded)
|
|
57
|
-
speech = waveform(work / "narr.wav")
|
|
58
|
-
assert abs(mixed["duration"] - duration) < .12, (mixed["duration"], duration)
|
|
59
|
-
assert abs(speech["duration"] - duration) <= 1 / 24000
|
|
60
|
-
assert mixed["clipped_samples"] == 0
|
|
61
|
-
assert mixed["rms_dbfs"] > -40
|
|
62
|
-
|
|
63
|
-
checks = []
|
|
64
|
-
previous_end = 0
|
|
65
|
-
for cue in timeline["cues"]:
|
|
66
|
-
take = waveform(work / "wav" / f"{cue['shot']:02d}.wav")
|
|
67
|
-
assert abs(take["duration"] - cue["audio_duration"]) <= 1 / 24000
|
|
68
|
-
assert abs((cue["end"] - cue["start"]) - take["duration"]) < .000002
|
|
69
|
-
assert cue["start"] >= previous_end
|
|
70
|
-
assert cue["mouth_frames"] > 0
|
|
71
|
-
assert take["rms_dbfs"] > -40
|
|
72
|
-
previous_end = cue["end"]
|
|
73
|
-
checks.append({"shot": cue["shot"], "speaker": cue["speaker"], "engine": cue["engine"],
|
|
74
|
-
"duration": take["duration"], "rms_dbfs": take["rms_dbfs"],
|
|
75
|
-
"mouth_frames": cue["mouth_frames"]})
|
|
76
|
-
assert duration - previous_end >= .5 - 1 / 24000
|
|
77
|
-
result = {"status": "passed", "video": video.name, "frame_count": count,
|
|
78
|
-
"size": list(metadata["size"]), "fps": metadata["fps"], "duration": duration,
|
|
79
|
-
"audio": mixed, "speech": speech, "cue_checks": checks,
|
|
80
|
-
"limitations": ["Acoustic and timing checks only; no ASR or human listening test.",
|
|
81
|
-
"Mouth motion uses RMS, not phoneme alignment."]}
|
|
82
|
-
(work / "audio-check.json").write_text(json.dumps(result, ensure_ascii=False, indent=2), encoding="utf-8")
|
|
83
|
-
print(json.dumps(result, ensure_ascii=False, indent=2))
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
if __name__ == "__main__":
|
|
87
|
-
parser = argparse.ArgumentParser()
|
|
88
|
-
parser.add_argument("video", type=Path)
|
|
89
|
-
parser.add_argument("work", type=Path)
|
|
90
|
-
args = parser.parse_args()
|
|
91
|
-
verify(args.video, args.work)
|