Skip to content

Commit 26bb019

Browse files
committed
first commit
0 parents  commit 26bb019

20 files changed

+1302
-0
lines changed

.github/workflows/lint.yml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
name: Lint
2+
on:
3+
push:
4+
branches: "*"
5+
jobs:
6+
lint:
7+
runs-on: ubuntu-latest
8+
steps:
9+
- uses: actions/checkout@v4
10+
- name: Set up Python 3.11
11+
uses: actions/setup-python@v4
12+
with:
13+
python-version: "3.11"
14+
- name: Install dependencies
15+
run: |
16+
python -m pip install --upgrade pip
17+
pip install -r requirements.txt
18+
pip install -r requirements-dev.txt
19+
- name: Run lint tools
20+
run: |
21+
make lint-local

.github/workflows/publish.yml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
name: Build and Publish Docker Image
2+
on:
3+
release:
4+
types: [released]
5+
jobs:
6+
build-and-publish:
7+
runs-on: ubuntu-latest
8+
steps:
9+
- name: Checkout code
10+
uses: actions/checkout@v4
11+
- name: Build Docker image
12+
uses: docker/build-push-action@v5
13+
with:
14+
context: .
15+
file: ./Dockerfile
16+
target: production
17+
push: false
18+
tags: |
19+
"${{ vars.ACR_ADDRESS }}/${{ vars.IMAGE_NAME }}:${{ github.ref_name }}"
20+
- name: Login to Azure ACR
21+
uses: azure/docker-login@v1
22+
with:
23+
login-server: ${{ vars.ACR_ADDRESS }}
24+
username: ${{ secrets.ACR_USERNAME }}
25+
password: ${{ secrets.ACR_PASSWORD }}
26+
- name: Push Docker image to Azure ACR
27+
run: docker push ${{ vars.ACR_ADDRESS }}/${{ vars.IMAGE_NAME }}:${{ github.ref_name }}

.github/workflows/unittests.yml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
name: Unit tests
2+
on:
3+
push:
4+
branches: "*"
5+
jobs:
6+
unittests:
7+
runs-on: ubuntu-latest
8+
steps:
9+
- uses: actions/checkout@v4
10+
- name: Set up Python 3.11
11+
uses: actions/setup-python@v4
12+
with:
13+
python-version: "3.11"
14+
- name: Install dependencies
15+
run: |
16+
python -m pip install --upgrade pip
17+
pip install -r requirements.txt
18+
pip install -r requirements-dev.txt
19+
- name: Run unit tests
20+
run: |
21+
make unittest-local

.gitignore

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Byte-compiled / optimized / DLL files
2+
__pycache__/
3+
*.py[cod]
4+
*$py.class
5+
6+
# Distribution / packaging
7+
dist/
8+
build/
9+
*.egg-info/
10+
11+
# Virtual environments
12+
venv/
13+
env/
14+
ENV/
15+
.venv/
16+
.ENV/
17+
18+
# IDEs and editors
19+
.idea/
20+
.vscode/
21+
*.sublime-project
22+
*.sublime-workspace
23+
24+
# Logs and databases
25+
*.log
26+
*.sqlite3
27+
*.db
28+
29+
# Other
30+
*.pyc
31+
.DS_Store
32+
.env
33+
.coverage

Dockerfile

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
FROM python:3.11-alpine as builder
2+
RUN mkdir /install
3+
WORKDIR /install
4+
COPY requirements.txt /requirements.txt
5+
RUN pip install --prefix="/install" -r /requirements.txt
6+
7+
8+
FROM python:3.11-alpine as production
9+
RUN apk upgrade -U && apk add ffmpeg
10+
COPY --from=builder /install /usr/local
11+
COPY ./stream_transcriber /stream_transcriber
12+
WORKDIR /
13+
EXPOSE 8765
14+
EXPOSE 8000
15+
ENTRYPOINT ["python"]
16+
CMD ["-m", "stream_transcriber.server"]
17+
18+
FROM production as base-dev
19+
RUN apk add --no-cache make
20+
COPY ./requirements-dev.txt /requirements-dev.txt
21+
RUN pip install -r /requirements-dev.txt
22+
23+
FROM base-dev as lint
24+
WORKDIR /
25+
COPY ./Makefile /Makefile
26+
COPY ./setup.cfg /setup.cfg
27+
COPY ./stream_transcriber /stream_transcriber
28+
ENTRYPOINT ["make", "lint-local"]
29+
30+
FROM base-dev as unittest
31+
WORKDIR /
32+
COPY ./stream_transcriber /stream_transcriber
33+
COPY ./unittests /unittests
34+
COPY ./Makefile /Makefile
35+
COPY ./pytest.ini /pytest.ini
36+
ENTRYPOINT [ "make", "unittest-local" ]

Makefile

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
.DEFAULT_GOAL := all
2+
IMAGE_NAME ?= stream-demo-server
3+
TAG ?= manual
4+
DOCKER := DOCKER_BUILDKIT=1 docker
5+
SOURCES := stream_transcriber/
6+
ACR_ADDRESS := speechmatics.azurecr.io
7+
ACR_IMAGE_NAME := ${ACR_ADDRESS}/${IMAGE_NAME}
8+
9+
.PHONY: all lint build publish format build-linux-amd64 lint-local unittest unittest-local
10+
11+
all: lint build
12+
13+
lint:
14+
${DOCKER} build -t ${IMAGE_NAME}:${TAG}-lint --target lint .
15+
${DOCKER} run --rm --name ${IMAGE_NAME}-lint ${IMAGE_NAME}:${TAG}-lint
16+
lint-local:
17+
black --check --diff ${SOURCES}
18+
pylint ${SOURCES}
19+
pycodestyle ${SOURCES}
20+
21+
format:
22+
black ${SOURCES}
23+
24+
unittest:
25+
${DOCKER} build -t ${IMAGE_NAME}:${TAG}-unittest --target unittest .
26+
${DOCKER} run --rm --name ${IMAGE_NAME}-unittest ${IMAGE_NAME}:${TAG}-unittest
27+
unittest-local:
28+
AUTH_TOKEN=token pytest -v unittests
29+
30+
build:
31+
${DOCKER} build -t ${IMAGE_NAME}:${TAG} --target production .
32+
33+
# Build locally an image for linux/amd64
34+
build-linux-amd64:
35+
${DOCKER} build --platform linux/amd64 -t ${IMAGE_NAME}:${TAG} --target production .
36+
37+
publish:
38+
docker tag ${IMAGE_NAME}:${TAG} ${ACR_IMAGE_NAME}:${TAG}
39+
docker image inspect ${ACR_IMAGE_NAME}:${TAG}
40+
docker push ${ACR_IMAGE_NAME}:${TAG}

README.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Stream Radio Server
2+
3+
A Python Websocket Server for transcribing/translating multiple radio streams and allowing clients to subscribe to the results.
4+
5+
## Getting Started
6+
7+
Install all the required dependencies with:
8+
9+
```
10+
brew install ffmpeg
11+
pip3 install -r requirements.txt
12+
```
13+
14+
## Running
15+
16+
Start the server with
17+
18+
```bash
19+
python3 -m stream_transcriber.server --port 8765
20+
```
21+
22+
Connect with your client to e.g. `ws://localhost:8765`,
23+
with https://github.com/vi/websocat this can be done with:
24+
```bash
25+
websocat ws://127.0.0.1:8765
26+
```
27+
> {"message": "Initialised", "info": "Waiting for message specyifing desired stream url"}
28+
29+
The server expects an initial JSON message with the desired language to start streaming:
30+
```json
31+
{"name": "english"}
32+
```
33+
34+
Now the client will receive audio chunks and messages in JSON format until the stream ends or the client disconnects.
35+
36+
## Running tests
37+
38+
Run the following command
39+
40+
```bash
41+
make unittest
42+
```
43+
44+
The above command runs the tests in a docker container with the intended Python version and all dependencies installed. For running the tests directly on your computer run the following command
45+
46+
```bash
47+
make unittest-local
48+
```

profiling/README.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Profiling the server under load
2+
3+
## Dependencies
4+
5+
In addition to the dependencies needed to run the server, you'll need the following:
6+
7+
8+
- cli tools:
9+
- k6
10+
- ffmpeg
11+
- Python packages:
12+
- memory_profiler
13+
- matplotlib
14+
15+
## Run profiling
16+
17+
We can collect some statistics while the server is under load:
18+
19+
1. Start the server with mprofile to get an evolution of memory consumption over time. It'll track also memory of child processes (ffmpeg)
20+
21+
```bash
22+
SM_MANAGEMENT_PLATFORM_URL='<URL_TO_MGMT_PLATFORM>' AUTH_TOKEN='<API_KEY_HERE>' mprof run --multiprocess python3 -m stream_transcriber.server --port 8765
23+
```
24+
25+
2. A simple way to keep an eye of cpu usage while the server is running. In another terminal:
26+
27+
```bash
28+
# 1. Find the pid of the server
29+
ps | grep server.py
30+
31+
# 2. Watch snapshots every 1s
32+
watch -n 1 'ps -p <pid> -o %cpu,%mem,cmd'
33+
```
34+
35+
1. Generate some load using [k6](https://k6.io)
36+
37+
```bash
38+
k6 run profiling/client-load.js
39+
```
40+
NOTE: for really high numbers of clients you might hit the max number of file descriptors allowed to be open. Find how to change it for your OS. In MacOS the number can be retrieved with `ulimit -n`. It can be changed with `ulimit -n <amount>`
41+
42+
4. The snapshots every 1 second of cpu and mem will be showing in the separate terminal.
43+
44+
5. To visualize the graph of memory consumption over time, Ctrl + C in the terminal in which the server is running to stop it from running. Now use:
45+
46+
```bash
47+
mprof plot
48+
```

profiling/client-load.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import ws from 'k6/ws';
2+
import { check } from 'k6';
3+
4+
export const options = {
5+
discardresponsebodies: true,
6+
scenarios: {
7+
users: {
8+
executor: "ramping-vus",
9+
startvus: 1,
10+
stages: [
11+
{ duration: '1m', target: 1 },
12+
{ duration: '2m', target: 200 },
13+
{ duration: '5m', target: 200 },
14+
{ duration: '2m', target: 1 },
15+
{ duration: '1m', target: 1 },
16+
],
17+
},
18+
},
19+
};
20+
21+
export default function () {
22+
const url = 'ws://127.0.0.1:8765';
23+
const res = ws.connect(url, function (socket) {
24+
socket.on('open', function open() {
25+
console.log('connected')
26+
const streams = ["english", "german", "french", "spanish"];
27+
const random = Math.floor(Math.random() * streams.length);
28+
socket.send(`{"name": "${streams[random]}"}`)
29+
});
30+
// socket.on('message', (data) => console.log('Message received: ', data));
31+
socket.on('close', () => console.log('disconnected'));
32+
});
33+
check(res, { 'status is 101': (r) => r && r.status === 101 });
34+
}

pytest.ini

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[pytest]
2+
addopts = -ra --full-trace --cov=stream_transcriber --cov-branch -o asyncio_mode=auto
3+
pythonpath = stream_transcriber
4+
testpaths = unittests

requirements-dev.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
pycodestyle==2.11.0
2+
pylint==3.0.1
3+
black==23.9.1
4+
pytest==7.4.2
5+
pytest-asyncio==0.21.1
6+
pytest-cov==4.1.0

requirements.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
websockets~=11.0.3
2+
httpx[http2]~=0.23
3+
polling2~=0.5
4+
toml~=0.10.2
5+
prometheus-client~=0.16.0
6+
speechmatics-python~=1.9.0

setup.cfg

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[pycodestyle]
2+
max-line-length = 120

stream_transcriber/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)