예제 #1
0
    def newWakeword(self, username: str):
        for i in range(1, 4):
            file = Path(f'/tmp/{i}_raw.wav')
            if file.exists():
                file.unlink()

        self._wakeword = Wakeword(username)
예제 #2
0
	def newWakeword(self, username: str):
		for i in range(1, 4):
			file = Path(f'/tmp/{i}_raw.wav')
			if file.exists():
				file.unlink()

		self._wakeword = Wakeword(username)
		self._sampleRate = self.ConfigManager.getAliceConfigByName('micSampleRate')
		self._channels = self.ConfigManager.getAliceConfigByName('micChannels')
예제 #3
0
 def newWakeword(self, username: str):
     self._wakeword = Wakeword(username)
예제 #4
0
class WakewordRecorder(Manager):
    def __init__(self):
        super().__init__()

        self._state = WakewordRecorderState.IDLE

        self._audio = None
        self._wakeword: Optional[Wakeword] = None
        self._userTuning = 0
        self._wakewordUploadThreads = list()
        self._sampleRate = self.AudioServer.SAMPLERATE
        self._channels = 1
        self._gainFix = 0

    def onStart(self):
        super().onStart()
        self._sampleRate = self.AudioServer.SAMPLERATE

    def onStop(self):
        super().onStop()

        for thread in self._wakewordUploadThreads:
            if thread.isAlive():
                thread.join(timeout=2)

    def onCaptured(self, session: DialogSession):
        if self.state == WakewordRecorderState.RECORDING:
            self._workAudioFile()

    def newWakeword(self, username: str):
        self._wakeword = Wakeword(username)

    def cancelWakeword(self):
        self.state = WakewordRecorderState.IDLE
        if self._wakeword:
            self._wakeword.clearTmp()

        self._wakeword = None
        self._userTuning = 0

    def startCapture(self):
        self.DialogManager.disableCaptureFeedback()
        self._state = WakewordRecorderState.RECORDING

    def addRawSample(self, filepath: Path) -> Path:
        filepath = self.wakeword.addRawSample(filepath)
        self._workAudioFile(filepath)
        return filepath

    def getRawSample(self, sampleNumber: int = None):
        return self.wakeword.getRawSample(sampleNumber)

    def getTrimmedSample(self, sampleNumber: int = None):
        return self.wakeword.getTrimmedSample(sampleNumber)

    def _workAudioFile(self, filepath: Path = None):
        self._state = WakewordRecorderState.TRIMMING

        if not filepath:
            filepath = self.wakeword.getRawSample()

        sound = AudioSegment.from_file(filepath, format='wav')

        if self._gainFix > 0:
            sound.append(self._gainFix)

        startTrim = self.detectLeadingSilence(sound)
        endTrim = self.detectLeadingSilence(sound.reverse())
        duration = len(sound)
        trimmed = sound[startTrim:duration - endTrim]

        reworked = trimmed.set_frame_rate(self.AudioServer.SAMPLERATE)
        reworked = reworked.set_channels(1)

        tempFile = Path(filepath.parent, 'tmp.wav')
        if tempFile.exists():
            tempFile.unlink()

        reworked.export(tempFile, format='wav')

        self._wakeword.addTrimmedSample(tempFile,
                                        int(filepath.stem.replace('_raw', '')))
        self._state = WakewordRecorderState.CONFIRMING

    def getLastSampleNumber(self) -> int:
        if self._wakeword and self._wakeword.getTrimmedSample():
            return len(self._wakeword.trimmedSamples.keys())
        return 1

    def trimMore(self):
        self._userTuning += 3
        self._workAudioFile()

    def trimLess(self):
        self._userTuning -= 2
        self._workAudioFile()

    def detectLeadingSilence(self, sound: AudioSegment) -> int:
        average = sound.dBFS
        pos = 0
        while sound[pos:pos + 10].dBFS < (
                average + self._userTuning) and pos < len(sound):
            pos += 10

        return pos

    def tryCaptureFix(self):
        self._sampleRate /= 2
        self._channels = 1

    def removeRawSample(self, sampleNumber: int = None):
        self._wakeword.removeRawSample(sampleNumber)

    def finalizeWakeword(self):
        self.logInfo(f'Finalyzing wakeword')
        self._state = WakewordRecorderState.FINALIZING
        path = self._wakeword.save()
        self.ThreadManager.newThread(name='SatelliteWakewordUpload',
                                     target=self._upload,
                                     args=[path, self._wakeword.username],
                                     autostart=True)
        self.cancelWakeword()
        self.WakewordManager.restartEngine()

    def uploadToNewDevice(self, uid: str):
        directory = Path(self.Commons.rootDir(),
                         'trained/hotwords/snips_hotword')
        for fiile in directory.iterdir():
            if (directory / fiile).is_file():
                continue

            self._upload(directory / fiile, uid)

    def _upload(self, path: Path, uid: str = ''):
        wakewordName, zipPath = self._prepareHotword(path)

        for device in self.DeviceManager.getDevicesWithAbilities(
                abilities=[DeviceAbility.CAPTURE_SOUND], connectedOnly=True):
            if uid and device.uid != uid:
                continue

            port = 8600 + len(self._wakewordUploadThreads)

            payload = {
                'ip': self.Commons.getLocalIp(),
                'port': port,
                'name': wakewordName
            }

            if uid:
                payload['uid'] = uid

            self.MqttManager.publish(topic=constants.TOPIC_NEW_HOTWORD,
                                     payload=payload)
            thread = WakewordUploadThread(host=self.Commons.getLocalIp(),
                                          zipPath=zipPath,
                                          port=port)
            self._wakewordUploadThreads.append(thread)
            thread.start()

    def _prepareHotword(self, path: Path) -> tuple:
        wakewordName = path.name
        zipPath = path.parent / (wakewordName + '.zip')

        self.logInfo(f'Cleaning up {wakewordName}')
        if zipPath.exists():
            zipPath.unlink()

        self.logInfo(f'Packing wakeword {wakewordName}')
        shutil.make_archive(base_name=zipPath.with_suffix(''),
                            format='zip',
                            root_dir=str(path))

        return wakewordName, zipPath

    def getUserWakeword(self, username: str) -> Optional[str]:
        wakeword = Path(
            f'{self.Commons.rootDir()}/trained/hotwords/snips_hotword/{username}'
        )
        if not wakeword.exists():
            return None
        return wakeword

    def getUserWakewordSensitivity(self, username: str) -> Optional[float]:
        # TODO user wakeword sensitivity
        return self.ConfigManager.getAliceConfigByName('wakewordSensitivity')

    def setUserWakewordSensitivity(self, username: str,
                                   sensitivity: float) -> bool:
        # TODO user wakeword sensitivity
        return True

    # wakewords = self.ConfigManager.getSnipsConfiguration(parent='snips-hotword', key='model')
    # rebuild = list()
    #
    # if sensitivity > 1:
    # 	sensitivity = 1
    # elif sensitivity < 0:
    # 	sensitivity = 0
    #
    # usernameMatch = re.compile(f'.*/{username}=[0-9.]+$')
    # sensitivitySub = re.compile('=[0-9.]+$')
    # update = False
    # for wakeword in wakewords:
    # 	match = re.search(usernameMatch, wakeword)
    # 	if not match:
    # 		rebuild.append(wakeword)
    # 		continue
    #
    # 	update = True
    # 	updated = re.sub(sensitivitySub, f'={round(float(sensitivity), 2)}', wakeword)
    # 	rebuild.append(updated)
    #
    # 	self.WakewordManager.restartEngine()
    #
    # return update

    @property
    def state(self) -> WakewordRecorderState:
        return self._state

    @state.setter
    def state(self, value: WakewordRecorderState):
        self._state = value

    @property
    def wakeword(self) -> Wakeword:
        return self._wakeword