0

I have a small group of Raspberry Pis, all on the same local network (192.168.1.2xx) All are running Python 3.7.3, one (R Pi CM3) on Raspbian Buster, the other (R Pi 4B 8gig) on Raspberry Pi OS 64.

I have a file on one device (the Pi 4B), located at /tmp/speech.wav, that is generated on the fly, real-time:

192.168.1.201 - /tmp/speech.wav

I have a script that works well on that device, that tells me the play duration time of the .wav file in seconds:

import wave
import contextlib

def getPlayTime():
    fname = '/tmp/speech.wav'
    with contextlib.closing(wave.open(fname,'r')) as f:
        frames = f.getnframes()
        rate = f.getframerate()
        duration = round(frames / float(rate), 2)
        return duration

However - the node that needs to operate on that duration information is running on another node at 192.168.1.210. I cannot simply move the various files all to the same node as there is a LOT going on, things are where they are for a reason.

So what I need to know is how to alter my approach such that I can change the script reference to something like this pseudocode:

fname = '/tmp/speech.wav @ 192.168.1.201'

Is such a thing possible? Searching the web it seems that I am up against millions of people looking for how to obtain IP addresses, fix multiple IP address issues, fix duplicate ip address issues... but I can't seem yet to find how to simply examine a file on a different ip address as I have described here. I have no network security restrictions, so any setting is up for consideration. Help would be much appreciated.

David Xanatos
  • 75
  • 1
  • 9
  • One approach would be to setup a smb server on the PI with the audio file, and mount it using an smb client on the other one to read the file. You would just point your processing utility at the mount point to access the file over the network. – Anon Coward Feb 19 '21 at 01:41
  • You may want to configure a Samba Server and access the files using python module [smbprotocol](https://pypi.org/project/smbprotocol/), i.e.: `with smbclient.open_file(r"\\server\share\directory\file.txt", mode="w") as fd`... – Pedro Lobito Feb 19 '21 at 01:59
  • one way would be to run opennas on the server you need to share the file, then just use locally. – brad Feb 19 '21 at 05:08

2 Answers2

0

There are lots of possibilities, and it probably comes down to how often you need to check the duration, from how many clients, and how often the file changes and whether you have other information that you want to share between the nodes.

Here are some options:

  • set up an SMB (Samba) server on the Pi that has the WAV file and let the other nodes mount the filesystem and access the file as if it was local

  • set up an NFS server on the Pi that has the WAV file and let the other nodes mount the filesystem and access the file as if it was local

  • let other nodes use ssh to login and extract the duration, or scp to retrieve the file - see paramiko in Python

  • set up Redis on one node and throw the WAV file in there so anyone can get it - this is potentially attractive if you have lots of lists, arrays, strings, integers, hashes, queues or sets that you want to share between Raspberry Pis very fast. Example here.


Here is a very simple example of writing a sound track into Redis from one node (say Redis is on 192.168.0.200) and reading it back from any other. Of course, you may just want the writing node to write the duration in there rather than the whole track - which would be more efficient. Or you may want to store loads of other shared data or settings.

This is the writer:

#!/usr/bin/env python3

import redis
from pathlib import Path

host='192.168.1.200'

# Connect to Redis
r = redis.Redis(host)

# Load some music, or otherwise create it
music = Path('song.wav').read_bytes()

# Put music into Redis where others can see it
r.set("music",music)

And this is the reader:

#!/usr/bin/env python3

import redis
from pathlib import Path

host='192.168.1.200'

# Connect to Redis
r = redis.Redis(host)

# Retrieve music track from Redis
music = r.get("music")
print(f'{len(music)} bytes read from Redis')

Then, during testing, you may want to manually push a track into Redis from the Terminal:

redis-cli -x -h 192.168.0.200 set  music < OtherTrack.wav

Or manually retrieve the track from Redis to a file:

redis-cli -h 192.168.0.200 get music > RetrievedFromRedis.wav
Mark Setchell
  • 191,897
  • 31
  • 273
  • 432
0

OK, this is what I finally settled on - and it works great. Using ZeroMQ for message passing, I have the function to get the playtime of the wav, and another gathers data about the speech about to be spoken, then all that is sent to the motor core prior to sending the speech. The motor core handles the timing issues to sync the jaw to the speech. So, I'm not actually putting the code that generates the wav and also returns the length of the wav playback time onto the node that ultimately makes use of it, but it turns out that message passing is fast enough so there is plenty of time space to receive, process and implement the motion control to match the speech perfectly. Posting this here in case it's helpful for folks in the future working on similar issues.

import time
import zmq
import os
import re
import wave
import contextlib

context = zmq.Context()
socket = context.socket(zmq.REP)
socket.bind("tcp://*:5555")  #Listens for speech to output

print("Connecting to Motor Control")
jawCmd = context.socket(zmq.PUB)
jawCmd.connect("tcp://192.168.1.210:5554") #Sends to MotorFunctions for Jaw Movement

def getPlayTime():  # Checks to see if current file duration has changed
    fname = '/tmp/speech.wav'   # and if yes, sends new duration
    with contextlib.closing(wave.open(fname,'r')) as f:
        frames = f.getnframes()
        rate = f.getframerate()
        duration = round(frames / float(rate), 3)
        speakTime = str(duration)
        return speakTime

def set_voice(V,T):
    T2 = '"' + T + '"'
    audioFile = "/tmp/speech.wav"  # /tmp set as tmpfs, or RAMDISK to reduce SD Card write ops

    if V == "A":
        voice = "Allison"
    elif V == "B":
        voice = "Belle"
    elif V == "C":
        voice = "Callie"
    elif V == "D":
        voice = "Dallas"
    elif V == "V":
        voice = "David"
    else:
        voice = "Belle"

    os.system("swift -n " + voice + " -o " + audioFile + " " +T2) # Record audio
    tailTrim = .5                                                 # Calculate Jaw Timing
    speakTime = eval(getPlayTime())                               # Start by getting playlength
    speakTime = round((speakTime - tailTrim), 2)                  # Chop .5 s for trailing silence
    wordList = T.split()
    jawString = []
    for index in range(len(wordList)):
        wordLen = len(wordList[index])
        jawString.append(wordLen)
    jawString = str(jawString)
    speakTime = str(speakTime)
    jawString = speakTime + "|" + jawString  # 3.456|[4, 2, 7, 4, 2, 9, 3, 4, 3, 6] - will split on "|"
    jawCmd.send_string(jawString)   # Send Jaw Operating Sequence
    os.system("aplay " + audioFile) # Play audio

pronunciationDict = {'teh':'the','process':'prawcess','Maeve':'Mayve','Mariposa':'May-reeposah','Lila':'Lala','Trump':'Ass hole'}

def adjustResponse(response):     # Adjusts spellings in output string to create better speech output.
    for key, value in pronunciationDict.items():
        if key in response or key.lower() in response:
            response = re.sub(key, value, response, flags=re.I)
    return response

SpeakText="Speech center connected and online."
set_voice(V,SpeakText) # Cepstral  Voices: A = Allison; B = Belle; C = Callie; D = Dallas; V = David;

while True:
    SpeakText = socket.recv().decode('utf-8') # .decode gets rid of the b' in front of the string
    SpeakTextX = adjustResponse(SpeakText)    # Run the string through the pronunciation dictionary
    print("SpeakText = ",SpeakTextX)

    set_voice(V,SpeakTextX)
    print("Received request: %s" % SpeakTextX)
    socket.send_string(str(SpeakTextX))       # Send data back to source for confirmation
David Xanatos
  • 75
  • 1
  • 9