Skip to content

Commit

Permalink
Move voice conversion to ffmpeg on hass
Browse files Browse the repository at this point in the history
  • Loading branch information
tectonick committed Jan 18, 2025
1 parent bb39694 commit 56af39e
Show file tree
Hide file tree
Showing 8 changed files with 34 additions and 36 deletions.
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ The bot is hosted on a VPS located at gateway.hackem.cc. The service, embassy-ap

Node v20.10.0
All main dependencies in the cloud and internal service are installed using npm i
[Deprecated] To convert the doorcam stream to jpg, you need to install ffmpeg on the bot service and add it to PATH.

## Local deployment

Expand Down
6 changes: 3 additions & 3 deletions api/embassy/routers/devices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ function linkMacPage(mac: string) {
<a onclick="event.target.innerText = 'You can close this window now'; window.close();" \
style="display:block;height: 100dvh;margin: 0;box-sizing: border-box;text-align: center;padding: 35vh 10vw;background: brown;text-decoration: none;font-size: 9vw;color: white;box-shadow: 0 0 10vw black inset;text-shadow: 0 0 1vw #000000;" \
href=https://t.me/${embassyApiConfig.tgbot.username}?start=setmac__${mac.replaceAll(
":",
"-"
)}>Press to set your mac to ${mac}</a>\
":",
"-"
)}>Press to set your mac to ${mac}</a>\
</body>\
</html>`;
}
Expand Down
3 changes: 2 additions & 1 deletion config/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,8 @@
"entity": "media_player.lenovodash",
"ttspath": "/api/services/script/announce_in_space",
"playpath": "/api/services/media_player/play_media",
"stoppath": "/api/services/media_player/media_stop"
"stoppath": "/api/services/media_player/media_stop",
"voicepath": "/api/services/script/convert_voice_and_play"
},
"browser": {
"target": "aee0b5cb4cd6bef1303d9e2c4be1a22e",
Expand Down
1 change: 1 addition & 0 deletions config/schema.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ export interface SpeakerConfig {
ttspath: string;
playpath: string;
stoppath: string;
voicepath: string;
}

export interface DevicesConfig {
Expand Down
2 changes: 0 additions & 2 deletions deploy/Containerfile
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ RUN npm run build
FROM docker.io/library/node:lts-alpine
WORKDIR /app

RUN apk add --update --no-cache ffmpeg

COPY --from=build /app/node_modules ./node_modules
COPY --from=build /app/dist ./

Expand Down
1 change: 1 addition & 0 deletions deploy/embassy-api/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ services:
- data-volume:/app/data/db
- ./sec:/app/config/sec
- ./static:/app/static
- /root/.ssh:/root/.ssh
env_file:
- .env
restart: unless-stopped
Expand Down
43 changes: 14 additions & 29 deletions services/hass.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
import path from "node:path";
import { promises as fs } from "fs";
import os from "os";

import config from "config";
import fetch, { Response } from "node-fetch";

import { CamConfig, EmbassyApiConfig } from "@config";
import { getBufferFromResponse } from "@utils/network";
import { downloadTmpFile } from "@utils/filesystem";
import { convertMedia } from "@utils/media";

import { EmbassyBaseIP } from "./embassy";
import { getBufferFromResponse, runSSHCommand } from "@utils/network";

// Configs
const embassyApiConfig = config.get<EmbassyApiConfig>("embassy-api");
Expand Down Expand Up @@ -90,33 +85,23 @@ export async function sayInSpace(text: string): Promise<void> {
if (response.status !== 200) throw Error("Speaker request failed");
}

async function serveStaticFile(localPath: string, urlPath: string): Promise<string> {
const staticRootPath = path.join(__dirname, "..", embassyApiConfig.service.static);
const staticFilePath = path.join(staticRootPath, urlPath);

await fs.mkdir(path.parse(staticFilePath).dir, { recursive: true });
await fs.copyFile(localPath, staticFilePath);

return `${EmbassyBaseIP}/${urlPath}`;
}

export async function playInSpace(link: string): Promise<void> {
const requiresConversion = link.endsWith(".oga");

let linkToPlay = link;

if (requiresConversion) {
const { tmpPath, cleanup } = await downloadTmpFile(link, ".oga");
const convertedFilePath = await convertMedia(tmpPath, "mp3");

linkToPlay = await serveStaticFile(convertedFilePath, `/tmp/${Date.now()}.mp3`);

cleanup();
if (link.endsWith(".oga")) {
// ignore errors, ffmpeg writes to stderr
await runSSHCommand(
"hass.lan",
22269,
"hassio",
os.homedir() + "/.ssh/hass",
`wget -O /media/tmp/voice.oga ${link}`
).catch(() => null);
await postToHass(embassyApiConfig.speaker.voicepath, {});
return;
}

const response = await postToHass(embassyApiConfig.speaker.playpath, {
entity_id: embassyApiConfig.speaker.entity,
media_content_id: linkToPlay,
media_content_id: link,
media_content_type: "music",
});

Expand Down
13 changes: 13 additions & 0 deletions utils/network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,19 @@ export async function arp(ip: string, networkRange: string): Promise<string> {
return mac;
}

export function runSSHCommand(host: string, port: number, username: string, key: string, command: string) {
const ssh = new NodeSSH();

return ssh
.connect({
host,
username,
port,
privateKeyPath: key,
})
.then(() => ssh.exec(command, [""]).finally(() => ssh.dispose()));
}

export class NeworkDevicesLocator {
static async getDevicesFromKeenetic(routerip: string, username: string, password: string) {
const ssh = new NodeSSH();
Expand Down

0 comments on commit 56af39e

Please sign in to comment.