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.

162 lines
5.2 KiB

6 days ago
  1. const express = require('express');
  2. const http = require('http');
  3. const socketIo = require('socket.io');
  4. const multer = require('multer');
  5. const fs = require('fs');
  6. const path = require('path');
  7. const { OBSWebSocket } = require('obs-websocket-js');
  8. const app = express();
  9. const server = http.createServer(app);
  10. const io = socketIo(server);
  11. const obs = new OBSWebSocket();
  12. const OBS_HOST = 'localhost'; // If OBS is running on the same machine
  13. const OBS_PORT = 4455; // Default WebSocket port
  14. const OBS_PASSWORD = 'jBHD5lLvKtHIjIwC'; // Add your WebSocket password here if set
  15. // Set up file storage for uploaded speeches
  16. const upload = multer({ dest: 'uploads/' });
  17. let speechText = [];
  18. let currentPosition = 0;
  19. // File to store speech lines
  20. const SPEECH_LINES_FILE = 'speechLines.txt';
  21. const SRT_FILE_NAME = 'captions.srt';
  22. // Serve static files (HTML views)
  23. app.use(express.static(path.join(__dirname, 'public')));
  24. // Connect to OBS WebSocket
  25. async function connectToOBS() {
  26. try {
  27. await obs.connect(`ws://${OBS_HOST}:${OBS_PORT}`, OBS_PASSWORD);
  28. console.log('Connected to OBS WebSocket');
  29. } catch (error) {
  30. console.error('Failed to connect to OBS WebSocket:', error);
  31. }
  32. }
  33. connectToOBS();
  34. // Load speech lines from file
  35. function loadSpeechLines() {
  36. if (fs.existsSync(SPEECH_LINES_FILE)) {
  37. const fileContents = fs.readFileSync(SPEECH_LINES_FILE, 'utf-8');
  38. speechText = fileContents.split('\n').map(line => line.trim()).filter(line => line.length > 0);
  39. currentPosition = 0; // Reset the position to the start
  40. console.log('Loaded speech lines from file.');
  41. }
  42. }
  43. // Load speech lines when the server starts
  44. loadSpeechLines();
  45. // Serve speaker view
  46. app.get('/speaker', (req, res) => {
  47. res.sendFile(path.join(__dirname, 'public', 'speaker.html'));
  48. });
  49. // Serve audience/projector view
  50. app.get('/audience', (req, res) => {
  51. res.sendFile(path.join(__dirname, 'public', 'audience.html'));
  52. });
  53. // Endpoint to handle file upload
  54. app.post('/upload', upload.single('speech'), (req, res) => {
  55. const filePath = path.join(process.cwd(), req.file.path);
  56. const fileContents = fs.readFileSync(filePath, 'utf-8');
  57. speechText = fileContents.split('\n').map(line => line.trim()).filter(line => line.length > 0);
  58. currentPosition = 0;
  59. // Save the loaded speech lines to a file
  60. fs.writeFileSync(SPEECH_LINES_FILE, speechText.join('\n'));
  61. io.emit('load_speech', speechText);
  62. res.send('File uploaded and speech loaded.');
  63. });
  64. // Handle WebSocket connections
  65. io.on('connection', (socket) => {
  66. console.log('Client connected');
  67. // Send the full speech to any newly connected client
  68. if (speechText.length > 0) {
  69. socket.emit('load_speech', speechText);
  70. }
  71. // Handle position updates from the speaker view
  72. socket.on('update_position', (newPosition) => {
  73. currentPosition = newPosition;
  74. io.emit('update_caption', currentPosition); // Send to all clients
  75. const currentLine = speechText[currentPosition];
  76. updateOBSSubtitle(currentLine);
  77. generateSRT(currentLine);
  78. });
  79. });
  80. async function updateOBSSubtitle(captionText) {
  81. try {
  82. await obs.call('SendStreamCaption', {
  83. captionText: captionText
  84. });
  85. console.log('Updated OBS Text Source:', captionText);
  86. } catch (error) {
  87. console.error('Error updating OBS Text Source:', error);
  88. }0
  89. }
  90. async function generateSRT(captionText) {
  91. try {
  92. // Check if recording is enabled
  93. const recordStatus = await obs.call('GetRecordStatus');
  94. if (recordStatus && recordStatus.outputActive) {
  95. // Get the recording timecode
  96. const seconds = recordStatus.outputDuration / 1000; // Assuming this returns a time in seconds
  97. // Format timecode for SRT (HH:MM:SS,ms)
  98. const startTime = formatTimecode(seconds);
  99. const endTime = formatTimecode(seconds + 5); // Assume each line is displayed for 5 seconds
  100. // Create SRT entry
  101. const srtEntry = `${currentPosition + 1}\n${startTime} --> ${endTime}\n${captionText}\n\n`;
  102. fs.appendFileSync(SRT_FILE_NAME, srtEntry); // Append to SRT file
  103. console.log('Updated SRT file:', SRT_FILE_NAME);
  104. }
  105. } catch (error) {
  106. console.error('Error generating SRT:', error);
  107. }
  108. }
  109. function formatTimecode(seconds) {
  110. const date = new Date(0);
  111. date.setSeconds(seconds);
  112. const hours = String(date.getUTCHours()).padStart(2, '0');
  113. const minutes = String(date.getUTCMinutes()).padStart(2, '0');
  114. const secs = String(date.getUTCSeconds()).padStart(2, '0');
  115. const millis = String((seconds % 1) * 1000).padStart(3, '0');
  116. return `${hours}:${minutes}:${secs},${millis}`;
  117. }
  118. const PORT = process.env.PORT || 3000;
  119. server.listen(PORT, () => {
  120. console.log(`Server running on port ${PORT}`);
  121. });
  122. const ngrok = require("@ngrok/ngrok");
  123. async function startTunnel() {
  124. const listener = await ngrok.forward({
  125. addr: PORT,
  126. authtoken: "2nCZjA9FOMFVdHAhIDyI0Tn8y0A_5oFkSL7kXve8RcCeJMXKU",
  127. domain: "explicitly-trusty-javelin.ngrok-free.app",
  128. proto: "http"
  129. });
  130. console.log(`Ingress established at: ${listener.url()}`);
  131. }
  132. startTunnel();