Skip to content

Commit

Permalink
Add support for audio only recordings (#38)
Browse files Browse the repository at this point in the history
* Implement support for doing audio only recordings
* Have audio codec specific bitrate options and use audio_bitrate as fallback default
* Add option to set audio only recordings as the default
  • Loading branch information
mwalbeck authored Sep 30, 2021
1 parent 2556058 commit 45bf906
Show file tree
Hide file tree
Showing 6 changed files with 202 additions and 76 deletions.
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
__pycache__
*.mp4
*.wav
*.flac
*.mp3
*.opus
*.aac
geckodriver.log
/config.json
/config.toml
Expand Down
40 changes: 22 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,24 +69,28 @@ You can have a look at the following instructions for nginx: https://docs.nginx.

## Configuration options

| option | default | description |
| ----------------------- | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| base_url | "" | The base URL of your Nextcloud instance. Should include the http / https and have no leading slash. |
| log_level | "warning" | The log level that should be used. |
| recording_dir | "." | The directory where the recordings should be stored. |
| grid_view | False | If set to true the recording uses grid view instead of speaker view. The recording user is partially hidden in that it isn't visible but the empty slot in the grid view is still there. |
| video_width | 1280 | The virtual display and recording width in pixels. |
| video_height | 720 | The virtual display and recording height in pixels. |
| color_depth | 24 | The color depth to use for the virtual framebuffer. |
| framerate | 30 | The framerate that should be used for the recording. |
| audio_codec | "aac" | The audio codec to use for the recording. |
| audio_bitrate | "160k" | The audio bitrate to use. |
| audio_thread_queue_size | 128 | |
| video_codec | "libx264" | The video codec to use for the recording. |
| crf | 25 | The crf to use for the H.264 encoding. |
| video_thread_queue_size | 32 | |
| encoding_preset | "veryfast" | The encoding preset used for the H.264 encoding. |
| encoding_threads | 0 | How many threads to use for the encoding. 0 is auto. |
| option | default | description |
| ----------------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| base_url | "" | The base URL of your Nextcloud instance. Should include the http / https and have no leading slash. |
| log_level | "warning" | The log level that should be used. |
| recording_dir | "." | The directory where the recordings should be stored. |
| grid_view | False | If set to true the recording uses grid view instead of speaker view. The recording user is partially hidden in that it isn't visible but the empty slot in the grid view is still there. |
| video_width | 1280 | The virtual display and recording width in pixels. |
| video_height | 720 | The virtual display and recording height in pixels. |
| color_depth | 24 | The color depth to use for the virtual framebuffer. |
| framerate | 30 | The framerate that should be used for the recording. |
| audio_only | False | Specify whether audio only recording should be the default |
| audio_codec | "wave" | The audio codec to use for audio only recordings. The following options are supported: wave, flac, opus, aac and mp3. The audio codec used for video recordings is currently determined by the chosen video codec. |
| audio_bitrate | "160k" | The default audio bitrate to use. This will be used as a fallback if a codec specific audio bitrate hasn't been set. Please note that some codecs supports higher bitrates than others. |
| audio_aac_bitrate | "" | Bitrate to use for AAC |
| audio_mp3_bitrate | "" | Bitrate to use for mp3 |
| audio_opus_bitrate | "" | Bitrate to use for opus |
| audio_thread_queue_size | 128 | |
| video_codec | "x264" | The video codec to use for the recording. Currently only x264 is supported. |
| crf | 25 | The crf to use for the H.264 encoding. |
| video_thread_queue_size | 32 | |
| encoding_preset | "veryfast" | The encoding preset used for the H.264 encoding. |
| encoding_threads | 0 | How many threads to use for the encoding. 0 is auto. |

## Development setup
To setup a dev environment for coding, clone the repository and then run `make dev-setup` to setup a virtual environment with the needed dependencies.
Expand Down
5 changes: 3 additions & 2 deletions talked/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,11 @@
"video_height": 720,
"color_depth": 24,
"framerate": 30,
"audio_codec": "aac",
"audio_only": False,
"audio_codec": "wave",
"audio_bitrate": "160k",
"audio_thread_queue_size": 128,
"video_codec": "libx264",
"video_codec": "x264",
"crf": 25,
"video_thread_queue_size": 32,
"encoding_preset": "veryfast",
Expand Down
147 changes: 147 additions & 0 deletions talked/ffmpeg.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import os
import time
import logging
from talked import config

# Dict containing specific ffmpeg parameters
# and related options for each supported video codec
video_codecs = {
"x264": {
"args": [
"-pix_fmt",
"yuv420p",
"-c:v",
"libx264",
"-crf",
str(config["crf"]),
"-preset",
config["encoding_preset"],
],
"audio_codec": "aac",
"file_extension": "mp4",
},
}

# Dict containing specific ffmpeg parameters
# and related options for each supported audio codec
audio_codecs = {
"aac": {
"args": [
"-c:a",
"aac",
"-b:a",
config.get("audio_aac_bitrate", config["audio_bitrate"]),
],
"file_extension": "aac",
},
"mp3": {
"args": [
"-c:a",
"libmp3lame",
"-b:a",
config.get("audio_mp3_bitrate", config["audio_bitrate"]),
],
"file_extension": "mp3",
},
"opus": {
"args": [
"-c:a",
"libopus",
"-b:a",
config.get("audio_opus_bitrate", config["audio_bitrate"]),
],
"file_extension": "opus",
},
"flac": {
"args": [
"-c:a",
"flac",
],
"file_extension": "flac",
},
"wave": {
"args": [],
"file_extension": "wav",
},
}

# The base ffmpeg parameters, these parameters will always be used.
ffmpeg_base = [
"ffmpeg",
"-nostdin",
"-nostats",
"-hide_banner",
"-loglevel",
"warning",
"-fflags",
"+igndts",
]

# The ffmpeg parameters controlling the video input
ffmpeg_video_input = [
"-f",
"x11grab",
"-video_size",
f"{config['video_width']}x{config['video_height']}",
"-framerate",
str(config["framerate"]),
"-draw_mouse",
"0",
"-thread_queue_size",
str(config["video_thread_queue_size"]),
"-i",
]

# The ffmpeg parameters controlling the audio input
ffmpeg_audio_input = [
"-f",
"pulse",
"-ac",
"2",
"-channel_layout",
"stereo",
"-thread_queue_size",
str(config["audio_thread_queue_size"]),
"-i",
"0",
]


def assemble_command(audio_only: bool):
if audio_only:
try:
audio_codec = audio_codecs[config["audio_codec"]]
except KeyError:
logging.critical("The chosen audio codec isn't supported")
raise RuntimeError

file_extension = audio_codec["file_extension"]
else:
try:
video_codec = video_codecs[config["video_codec"]]
except KeyError:
logging.critical("The chosen video codec isn't supported")
raise RuntimeError

audio_codec = audio_codecs[video_codec["audio_codec"]]
file_extension = video_codec["file_extension"]

command = ffmpeg_base + ffmpeg_audio_input

if not audio_only:
command += ffmpeg_video_input
command += [os.environ["DISPLAY"]]
command += video_codec["args"]

command += audio_codec["args"]

command += [
"-threads",
str(config["encoding_threads"]),
(
f"{config['recording_dir']}/"
f"{time.strftime('%Y%m%dT%H%M%S')}_output.{file_extension}"
),
]

return command
9 changes: 7 additions & 2 deletions talked/main.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
from flask import Flask
from flask import jsonify
from flask import request
from talked import recorder
from threading import Thread
from threading import Event
from queue import Queue
from talked import recorder
from talked import __version__
from talked import config

app = Flask(__name__)

Expand All @@ -29,8 +30,12 @@ def start():

token = request.get_json()["token"]

audio_only = request.get_json().get("audio_only", config["audio_only"])

recording.clear()
recording_thread = Thread(target=recorder.start, args=(token, queue, recording))
recording_thread = Thread(
target=recorder.start, args=(token, queue, recording, audio_only)
)
recording_thread.start()
output = queue.get()

Expand Down
72 changes: 18 additions & 54 deletions talked/recorder.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import logging
import sys
import pkgutil
import os
from selenium.webdriver import Firefox
from selenium.webdriver.common.by import By
from selenium.webdriver.firefox.webdriver import WebDriver
Expand All @@ -20,11 +19,12 @@
from threading import Event
from queue import Queue
from talked import config
from talked.ffmpeg import assemble_command

msg_queue = None


def start(token: str, queue: Queue, recording: Event) -> None:
def start(token: str, queue: Queue, recording: Event, audio_only: bool) -> None:
global msg_queue
msg_queue = queue

Expand All @@ -45,58 +45,22 @@ def start(token: str, queue: Queue, recording: Event) -> None:
logging.info(call_link)
browser = launch_browser(call_link)
logging.info("Starting ffmpeg process")
ffmpeg = subprocess.Popen(
[
"ffmpeg",
"-nostdin",
"-nostats",
"-hide_banner",
"-loglevel",
"warning",
"-fflags",
"+igndts",
"-f",
"x11grab",
"-video_size",
f"{config['video_width']}x{config['video_height']}",
"-framerate",
str(config["framerate"]),
"-draw_mouse",
"0",
"-thread_queue_size",
str(config["video_thread_queue_size"]),
"-i",
os.environ["DISPLAY"],
"-f",
"pulse",
"-ac",
"2",
"-channel_layout",
"stereo",
"-thread_queue_size",
str(config["audio_thread_queue_size"]),
"-i",
"0",
"-pix_fmt",
"yuv420p",
"-c:a",
config["audio_codec"],
"-b:a",
config["audio_bitrate"],
"-c:v",
config["video_codec"],
"-crf",
str(config["crf"]),
"-preset",
config["encoding_preset"],
"-threads",
str(config["encoding_threads"]),
(
f"{config['recording_dir']}/"
f"{time.strftime('%Y%m%dT%H%M%S')}_output.mp4"
),
]
)

try:
ffmpeg_command = assemble_command(audio_only)
except RuntimeError:
msg_queue.put(
{
"status": "error",
"message": (
"There was an issue with the recording configuration, "
"please contact an administrator."
),
}
)
graceful_shutdown(browser)

ffmpeg = subprocess.Popen(ffmpeg_command)
logging.info("Recording has started")

msg_queue.put(
Expand Down

0 comments on commit 45bf906

Please sign in to comment.