Scripting & Storage Hooks
plugNmeet offers powerful hooking mechanisms in both the recorder and server components to allow for advanced customization and automation of your media workflows. A "script" can be any executable file (a shell script, a compiled Go program, a NodeJS script, etc.) that can read from stdin and write to stdout.
Recorder: Scripting Hooks
Scripting hooks allow you to automate tasks at different stages of the recording and transcoding process. This is especially powerful in a multi-server setup where recording and transcoding happen on different machines.
All scripts follow a standard interface: they receive a JSON payload via stdin and can optionally return a modified JSON payload via stdout. If multiple scripts are defined for a single hook, they form a pipeline: the stdout of the first script becomes the stdin for the second, and so on.
Hook Stages
-
post_recording: Runs on the RECORDER after the raw file is saved.- Purpose: Upload the raw file to shared storage (NFS, S3, etc.).
- Action: Should return JSON with the
file_pathupdated to the new network-accessible location for the transcoder.
-
pre_transcoding: Runs on the TRANSCODER beforeffmpegstarts.- Purpose: Download the file from shared storage to a local path.
- Action: Should return JSON with the
file_pathupdated to the final local path forffmpegto use.
-
post_transcoding: Runs on the TRANSCODER afterffmpegfinishes.- Purpose: Final cleanup, notification, or upload of the processed file.
- Action: Can optionally return JSON with the
file_pathupdated (e.g., to an S3 URL) to be sent to the main plugNmeet server.
Configuration
Add the path to your executable(s) in the hooks section of your recorder's config.yaml.
# config.yaml
hooks:
post_recording:
- "./scripts/post-recording/upload.sh"
pre_transcoding:
- "./scripts/pre-transcoding/download.sh"
post_transcoding:
- "./scripts/post-transcoding/upload-to-s3.sh"
- "./scripts/post-transcoding/notify-slack.sh"
Data Payload (stdin)
Your script will receive a JSON object with the following structure:
{
"task": "single",
"recording_id": "REC_ax9s3djn2s",
"room_table_id": 123,
"room_id": "room01",
"room_sid": "SID_d82k3s9d2l",
"file_name": "REC_ax9s3djn2s.mp4",
"file_path": "/path/to/recording/files/node_01/room01/REC_ax9s3djn2s.mp4",
"file_paths": ["/path/to/segment1.mp4"],
"file_size": 123.45,
"recorder_id": "node_01"
}
Script Examples
Bash with jq
This example reads the file_path from stdin, logs it, and passes the original JSON to stdout.
#!/bin/bash
# scripts/post-recording/upload.sh
# Read the entire stdin
JSON_DATA=$(cat)
# Use jq to extract the file_path
FILE_PATH=$(echo $JSON_DATA | jq -r '.file_path')
# Log the action (e.g., to a file or stderr)
echo "Uploading file: $FILE_PATH" >&2
# Here, you would add your upload logic (e.g., aws s3 cp ...)
# For this example, we'll just pass the data through.
# Return the original JSON to stdout for the next script in the chain
echo $JSON_DATA
Node.js
This example parses the stdin data, logs the recording_id, and returns a modified JSON object to stdout.
#!/usr/bin/env node
// scripts/post-recording/upload.js
let data = '';
process.stdin.on('readable', () => {
let chunk;
while (null !== (chunk = process.stdin.read())) {
data += chunk;
}
});
process.stdin.on('end', () => {
if (!data) {
process.exit(0);
}
const scriptData = JSON.parse(data);
// Your logic here
console.error(`Processing recording: ${scriptData.recording_id}`);
// Example: Modify the file_path after an upload
scriptData.file_path = `s3://my-bucket/${scriptData.file_name}`;
// Output the modified JSON to stdout
process.stdout.write(JSON.stringify(scriptData));
});
Server: Storage Hooks
Storage hooks allow you to override the default local file storage and integrate with any external storage provider (e.g., S3, Google Cloud Storage) using custom scripts. This is ideal for cloud-native or multi-server deployments where you need centralized, scalable storage.
These hooks are primarily used for room artifacts. The upload_hook is triggered when the server generates an artifact. The download_hook and delete_hook are used for both artifacts and recordings.
- Room Artifacts: This includes artifact types such as
MEETING_SUMMARY,SPEECH_TRANSCRIPTION, andMEETING_ANALYTICS. For a complete list, please refer to the official protobuf definition. - Recordings: The server does not use the
upload_hookfor recordings. Instead, the recorder should upload the final recording to your external storage directly via itspost_transcodinghook. The server then uses thedownload_hookanddelete_hookto manage access to that recording.
If this section is omitted from your config.yaml, the server will store all files on the local disk.
Configuration
Add the storage_hooks section to your server's config.yaml.
# config.yaml
storage_hooks:
upload_hook:
- "./scripts/server_hooks/upload.sh"
download_hook:
- "./scripts/server_hooks/download.sh"
delete_hook:
- "./scripts/server_hooks/delete.sh"
Hook Types & Payloads
Upload Hook
- Request (
stdin) - Used for artifacts only.{"source_file_path": "/path/on/disk/analytics.json","service_type": "artifact","room_id": "room01","room_sid": "SID_d82k3s9d2l","room_table_id": 123} - Response (
stdout): The final script must return thelogical_path.{"logical_path": "artifacts/room01/analytics_SID_d82k3s9d2l.json","error": ""}
logical_path?The logical_path is a unique identifier that your upload script creates and understands. It can be any string format you choose, such as an S3 object key (my-bucket/artifacts/file.json), a file ID from a storage service, or a simple filename. This same value will be passed back to your download_hook and delete_hook scripts later, so they know which file to act on.
Download Hook
- Request (
stdin):{"logical_path": "artifacts/room01/analytics_SID_d82k3s9d2l.json","service_type": "artifact"} - Response (
stdout): The final script must return anactionand a corresponding value.{"action": "redirect","redirect_url": "https://s3.presigned.url/...","error": ""}
The action determines how the server will provide the file to the user:
-
redirect(Recommended): Your script should return aredirect_url. The server will then send a307 Temporary Redirectto the client, allowing them to download the file directly from your external storage. This is highly efficient as the file does not pass through your server. A common use case is generating a pre-signed URL for an S3 object. -
serve_local: Your script must first download the file from your external storage to a temporary location on the server's local disk. It must then return the fulllocal_pathand the correctmime_typefor the file (e.g.,application/json,video/mp4). The server will read the file from this path and stream it to the client with the properContent-Typeheader. This method consumes more server resources and is generally not recommended unless direct redirection is not possible.
Delete Hook
- Request (
stdin):{"logical_path": "artifacts/room01/analytics_SID_d82k3s9d2l.json","service_type": "artifact"} - Response (
stdout): Optional, for logging purposes.{"msg": "File deleted successfully","error": ""}