You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

171 lines
5.5 KiB

const express = require('express');
const http = require('http');
const socketIo = require('socket.io');
const multer = require('multer');
const fs = require('fs');
const path = require('path');
const { OBSWebSocket } = require('obs-websocket-js');
const app = express();
const server = http.createServer(app);
const io = socketIo(server);
const obs = new OBSWebSocket();
const OBS_HOST = 'localhost'; // If OBS is running on the same machine
const OBS_PORT = 4455; // Default WebSocket port
const OBS_PASSWORD = 'jBHD5lLvKtHIjIwC'; // Add your WebSocket password here if set
// Set up file storage for uploaded speeches
const upload = multer({ dest: 'uploads/' });
let speechText = [];
let currentPosition = 0;
// File to store speech lines
const SPEECH_LINES_FILE = 'speechLines.txt';
const SRT_FILE_NAME = 'captions.srt';
// Serve static files (HTML views)
app.use(express.static(path.join(__dirname, 'public')));
// Connect to OBS WebSocket
async function connectToOBS() {
try {
await obs.connect(`ws://${OBS_HOST}:${OBS_PORT}`, OBS_PASSWORD);
console.log('Connected to OBS WebSocket');
} catch (error) {
console.error('Failed to connect to OBS WebSocket:', error);
}
}
connectToOBS();
// Load speech lines from file
function loadSpeechLines() {
if (fs.existsSync(SPEECH_LINES_FILE)) {
const fileContents = fs.readFileSync(SPEECH_LINES_FILE, 'utf-8');
speechText = fileContents.split('\n').map(line => line.trim()).filter(line => line.length > 0);
currentPosition = 0; // Reset the position to the start
console.log('Loaded speech lines from file.');
}
}
// Load speech lines when the server starts
loadSpeechLines();
// Serve speaker view
app.get('/speaker', (req, res) => {
res.sendFile(path.join(__dirname, 'public', 'speaker.html'));
});
// Serve speaker view
app.get('/live-typing', (req, res) => {
res.sendFile(path.join(__dirname, 'public', 'live_typing.html'));
});
// Serve audience/projector view
app.get('/audience', (req, res) => {
res.sendFile(path.join(__dirname, 'public', 'audience.html'));
});
// Endpoint to handle file upload
app.post('/upload', upload.single('speech'), (req, res) => {
const filePath = path.join(process.cwd(), req.file.path);
const fileContents = fs.readFileSync(filePath, 'utf-8');
speechText = fileContents.split('\n').map(line => line.trim()).filter(line => line.length > 0);
currentPosition = 0;
// Save the loaded speech lines to a file
fs.writeFileSync(SPEECH_LINES_FILE, speechText.join('\n'));
io.emit('load_speech', speechText);
res.send('File uploaded and speech loaded.');
});
// Handle WebSocket connections
io.on('connection', (socket) => {
console.log('Client connected');
// Send the full speech to any newly connected client
if (speechText.length > 0) {
socket.emit('load_speech', speechText);
}
// Handle position updates from the speaker view
socket.on('update_position', (newPosition) => {
currentPosition = newPosition;
io.emit('update_caption', currentPosition); // Send to all clients
const currentLine = speechText[currentPosition];
updateOBSSubtitle(currentLine);
generateSRT(currentLine);
});
socket.on('live_typing', (text) => {
socket.broadcast.emit('live_typing', text); // Broadcast to audience
});
});
async function updateOBSSubtitle(captionText) {
try {
await obs.call('SendStreamCaption', {
captionText: captionText
});
console.log('Updated OBS Text Source:', captionText);
} catch (error) {
console.error('Error updating OBS Text Source:', error);
}0
}
async function generateSRT(captionText) {
try {
// Check if recording is enabled
const recordStatus = await obs.call('GetRecordStatus');
if (recordStatus && recordStatus.outputActive) {
// Get the recording timecode
const seconds = recordStatus.outputDuration / 1000; // Assuming this returns a time in seconds
// Format timecode for SRT (HH:MM:SS,ms)
const startTime = formatTimecode(seconds);
const endTime = formatTimecode(seconds + 5); // Assume each line is displayed for 5 seconds
// Create SRT entry
const srtEntry = `${currentPosition + 1}\n${startTime} --> ${endTime}\n${captionText}\n\n`;
fs.appendFileSync(SRT_FILE_NAME, srtEntry); // Append to SRT file
console.log('Updated SRT file:', SRT_FILE_NAME);
}
} catch (error) {
console.error('Error generating SRT:', error);
}
}
function formatTimecode(seconds) {
const date = new Date(0);
date.setSeconds(seconds);
const hours = String(date.getUTCHours()).padStart(2, '0');
const minutes = String(date.getUTCMinutes()).padStart(2, '0');
const secs = String(date.getUTCSeconds()).padStart(2, '0');
const millis = String((seconds % 1) * 1000).padStart(3, '0');
return `${hours}:${minutes}:${secs},${millis}`;
}
const PORT = process.env.PORT || 3000;
server.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
const ngrok = require("@ngrok/ngrok");
async function startTunnel() {
const listener = await ngrok.forward({
addr: PORT,
authtoken: "2nCZjA9FOMFVdHAhIDyI0Tn8y0A_5oFkSL7kXve8RcCeJMXKU",
domain: "explicitly-trusty-javelin.ngrok-free.app",
proto: "http"
});
console.log(`Ingress established at: ${listener.url()}`);
}
startTunnel();