Update to local copy that supports animations
Also some lint fixes, but not all because this is a big enough change.
This commit is contained in:
parent
603fad46f9
commit
1e88c125f9
16 changed files with 179 additions and 99 deletions
81
main.pyw
81
main.pyw
|
@ -1,12 +1,13 @@
|
|||
import array
|
||||
import config
|
||||
import os
|
||||
import pyaudio
|
||||
import random
|
||||
import typing
|
||||
import tkinter
|
||||
import typing
|
||||
from tkinter import filedialog
|
||||
|
||||
import pyaudio
|
||||
|
||||
import config
|
||||
|
||||
# Hide Pygame "Hello world" stuff. Why does this require an envvar :/
|
||||
os.environ["PYGAME_HIDE_SUPPORT_PROMPT"] = "1"
|
||||
|
@ -16,12 +17,13 @@ from pygame.locals import *
|
|||
|
||||
__author__ = "Trysdyn Black"
|
||||
__license__ = "ACSL v1.4"
|
||||
__version__ = "1.0.0"
|
||||
__copyright__ = "Copyright 2022, Trysdyn Black"
|
||||
__version__ = "1.1.0"
|
||||
__copyright__ = "Copyright 2025, Trysdyn Black"
|
||||
|
||||
|
||||
class Audio:
|
||||
"""Audio subsystem that monitors a microphone and determines avatar state.
|
||||
"""
|
||||
Audio subsystem that monitors a microphone and determines avatar state.
|
||||
|
||||
Methods
|
||||
-------
|
||||
|
@ -32,6 +34,8 @@ class Audio:
|
|||
|
||||
def __init__(self, channels: int, rate: int, frames: int, threshold: int = 5000, smoothing: int = 0) -> None:
|
||||
"""
|
||||
Init.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
channels : int
|
||||
|
@ -60,7 +64,8 @@ class Audio:
|
|||
self.this_smooth = 0
|
||||
|
||||
def get_state(self) -> bool:
|
||||
"""Check audio buffer and indicate if the avatar's mouth should be open this frame.
|
||||
"""
|
||||
Check audio buffer and indicate if the avatar's mouth should be open this frame.
|
||||
|
||||
Returns
|
||||
-------
|
||||
|
@ -84,7 +89,8 @@ class Audio:
|
|||
|
||||
|
||||
class Tube:
|
||||
"""Encapsulates avatar and windowing logic, handles drawing.
|
||||
"""
|
||||
Encapsulates avatar and windowing logic, handles drawing.
|
||||
|
||||
Methods
|
||||
-------
|
||||
|
@ -104,10 +110,13 @@ class Tube:
|
|||
blink_frames: int = 0,
|
||||
shake_intensity: int = 0,
|
||||
shake_delay: int = 0,
|
||||
animation_delay: int = 100,
|
||||
win_size: typing.Optional[tuple] = None,
|
||||
bg_color: tuple = (0, 255, 0),
|
||||
) -> None:
|
||||
"""
|
||||
Init.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image_closed : string
|
||||
|
@ -134,6 +143,9 @@ class Tube:
|
|||
shake_delay : int
|
||||
How many frames to prevent shaking after completing a prior shake.
|
||||
Defaults to 0.
|
||||
animation_delay : int
|
||||
How many frames to wait between animation frames.
|
||||
Defaults to 10.
|
||||
win_size : tuple(int, int) or None
|
||||
Window size as a tuple of ints (width, height).
|
||||
Defaults to None, which uses the image size of image_closed instead.
|
||||
|
@ -141,6 +153,7 @@ class Tube:
|
|||
Background color as a tuple of ints (red, green, blue).
|
||||
Defaults to (0, 255, 0) for greenscreen green.
|
||||
"""
|
||||
self.frame_num = 0
|
||||
self.open_frames = 0
|
||||
self.blinked_frames = 0
|
||||
|
||||
|
@ -149,22 +162,38 @@ class Tube:
|
|||
pygame.display.set_caption("pyngtube")
|
||||
|
||||
# Closed mouth image is mandatory
|
||||
self.image_closed = pygame.image.load(image_closed).convert_alpha()
|
||||
if not isinstance(image_closed, list):
|
||||
image_closed = [image_closed]
|
||||
self.image_closed = []
|
||||
for i in image_closed:
|
||||
self.image_closed.append(pygame.image.load(i).convert_alpha())
|
||||
|
||||
# Open mouth image is optional
|
||||
if image_open:
|
||||
self.image_open = pygame.image.load(image_open).convert_alpha()
|
||||
if not isinstance(image_open, list):
|
||||
image_open = [image_open]
|
||||
self.image_open = []
|
||||
for i in image_open:
|
||||
self.image_open.append(pygame.image.load(i).convert_alpha())
|
||||
else:
|
||||
self.image_open = None
|
||||
|
||||
# Blinking images are optional
|
||||
if image_blink_closed:
|
||||
self.image_blink_closed = pygame.image.load(image_blink_closed).convert_alpha()
|
||||
if not isinstance(image_blink_closed, list):
|
||||
image_blink_closed = [image_blink_closed]
|
||||
self.image_blink_closed = []
|
||||
for i in image_blink_closed:
|
||||
self.image_blink_closed.append(pygame.image.load(i).convert_alpha())
|
||||
else:
|
||||
self.image_blink_closed = None
|
||||
|
||||
if image_blink_open:
|
||||
self.image_blink_open = pygame.image.load(image_blink_open).convert_alpha()
|
||||
if not isinstance(image_blink_open, list):
|
||||
image_blink_open = [image_blink_open]
|
||||
self.image_blink_open = []
|
||||
for i in image_blink_open:
|
||||
self.image_blink_open.append(pygame.image.load(i).convert_alpha())
|
||||
else:
|
||||
self.image_blink_open = None
|
||||
|
||||
|
@ -172,22 +201,24 @@ class Tube:
|
|||
self.blink_chance = blink_chance
|
||||
self.blink_frames = blink_frames
|
||||
|
||||
self.animation_delay = animation_delay
|
||||
|
||||
# If we only have a closed-mouth image (why?) use it for open-mouth too
|
||||
using_image_open = self.image_open if self.image_open else self.image_closed
|
||||
using_image_open = self.image_open or self.image_closed
|
||||
|
||||
# State map for which image to use. (Mouth Opened?, Blinked?)
|
||||
# If we don't have blinked images, we use the non-blinked images for those slots
|
||||
self.state_map = {
|
||||
(False, False): self.image_closed,
|
||||
(True, False): using_image_open,
|
||||
(False, True): self.image_blink_closed if self.image_blink_closed else self.image_closed,
|
||||
(True, True): self.image_blink_open if self.image_blink_open else using_image_open,
|
||||
(False, True): self.image_blink_closed or self.image_closed,
|
||||
(True, True): self.image_blink_open or using_image_open,
|
||||
}
|
||||
|
||||
# If we specified a window size, use it. If not use closed image size
|
||||
# Then we pad by 10px to allow for shaking
|
||||
if not win_size:
|
||||
win_size = self.image_closed.get_size()
|
||||
win_size = self.image_closed[0].get_size()
|
||||
|
||||
self.win_size = (
|
||||
win_size[0] + (shake_intensity * 2),
|
||||
|
@ -202,11 +233,12 @@ class Tube:
|
|||
self.shake_delay = shake_delay
|
||||
|
||||
# Create our drawing buffer and initialize true display size
|
||||
self.buf = pygame.surface.Surface(self.image_closed.get_size())
|
||||
self.buf = pygame.surface.Surface(self.image_closed[0].get_size())
|
||||
self.display = pygame.display.set_mode(self.win_size, pygame.RESIZABLE)
|
||||
|
||||
def should_blink(self) -> bool:
|
||||
"""Check if we're currently blinking and check blink_chance to indicate if we should be blinking this frame.
|
||||
"""
|
||||
Check if we're currently blinking and check blink_chance to indicate if we should be blinking this frame.
|
||||
|
||||
Returns
|
||||
-------
|
||||
|
@ -228,8 +260,8 @@ class Tube:
|
|||
return False
|
||||
|
||||
def update(self, opened: bool, blinked: bool) -> None:
|
||||
"""Perform updates and drawing for one frame. This should be called once a frame and fed avatar state info on
|
||||
open mouth and blinking. This handles all the other placement, style, and drawing functions from there.
|
||||
"""
|
||||
Perform updates and drawing for one frame.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
|
@ -238,6 +270,12 @@ class Tube:
|
|||
blinked : bool
|
||||
true if the avatar's eyes should be closed this frame.
|
||||
"""
|
||||
self.frame_num += 1
|
||||
frame = int(self.frame_num / self.animation_delay)
|
||||
if frame >= len(self.state_map[(False, False)]):
|
||||
self.frame_num = 0
|
||||
frame = 0
|
||||
|
||||
# Blank with bg_color
|
||||
self.display.fill(self.bg_color)
|
||||
self.buf.fill(self.bg_color)
|
||||
|
@ -248,7 +286,7 @@ class Tube:
|
|||
i = self.shake_intensity
|
||||
|
||||
# Figure out which image we're drawing this frame using our state map
|
||||
this_frame_image = self.state_map[(opened, blinked)]
|
||||
this_frame_image = self.state_map[(opened, blinked)][frame]
|
||||
|
||||
# If the mic state is opened and an open mouth image exists, blit it
|
||||
# to the drawing buffer. Otherwise use the closed mouth image.
|
||||
|
@ -315,6 +353,7 @@ if __name__ == "__main__":
|
|||
blink_frames=c.blink_frames,
|
||||
bg_color=c.bg_color,
|
||||
shake_delay=c.shake_delay,
|
||||
animation_delay=c.animation_delay,
|
||||
shake_intensity=c.shake_intensity,
|
||||
)
|
||||
rehash = False
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue