79 lines
2.8 KiB
Python

#!/usr/bin/env python3
"""Marcus animated face on the Shining LED mask.
Connects to the mask, uploads a face frame-set once (DIY images, persisted on
the mask's flash), then runs a live animated face: idle blinking + glancing, and
a moving mouth while "speaking" -- all via fast PLAY commands (no upload logo).
Usage:
python main.py # demo: idle, then a simulated talking burst
python main.py --reload # force re-uploading the frame set
python main.py --address AA:BB:CC:DD:EE:FF
python main.py --talk # start speaking immediately and stay talking
Marcus / Sanad integration:
face = FaceAnimator(mask); await face.start()
face.set_speaking(True) # call when TTS playback starts
face.set_speaking(False) # call when it ends
# ...or face.set_mouth(0..3) from live audio amplitude for rough lip-sync.
"""
import argparse
import asyncio
from faceanim import FaceAnimator
from mask import ShiningMask
from exceptions import MaskNotFound
async def run(args):
mask = ShiningMask(address=args.address, name_prefix=args.name_prefix)
print("connecting to mask ...")
try:
await mask.connect(timeout=20.0, attempts=8)
except MaskNotFound as exc:
print(f"could not find the mask: {exc}")
return
print("connected.")
face = FaceAnimator(mask, fps=args.fps, brightness=args.brightness)
print("loading face frames (one-time ~25s if not already on the mask) ...")
await face.start(reload=args.reload)
print("face is live. Ctrl+C to stop.")
try:
if args.talk:
face.set_speaking(True)
while True:
await asyncio.sleep(1.0)
else:
# demo: alternate idle and 'speaking' so you can see both modes
while True:
print(" [idle] blinking / glancing ~6s")
await asyncio.sleep(6)
print(" [speaking] mouth animating ~6s")
face.set_speaking(True)
await asyncio.sleep(6)
face.set_speaking(False)
except KeyboardInterrupt:
print("\nstopping ...")
finally:
await face.stop()
await mask.disconnect()
def main():
ap = argparse.ArgumentParser(description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter)
ap.add_argument("--address", help="connect to a specific BLE MAC instead of scanning")
ap.add_argument("--name-prefix", default="MASK")
ap.add_argument("--reload", action="store_true", help="force re-upload of the frame set")
ap.add_argument("--talk", action="store_true", help="start in talking mode and stay there")
ap.add_argument("--fps", type=float, default=8.0)
ap.add_argument("--brightness", type=int, default=95)
asyncio.run(run(ap.parse_args()))
if __name__ == "__main__":
main()