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

6 days ago
6 days ago
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 speaker view
  50. app.get('/live-typing', (req, res) => {
  51. res.sendFile(path.join(__dirname, 'public', 'live_typing.html'));
  52. });
  53. // Serve audience/projector view
  54. app.get('/audience', (req, res) => {
  55. res.sendFile(path.join(__dirname, 'public', 'audience.html'));
  56. });
  57. // Endpoint to handle file upload
  58. app.post('/upload', upload.single('speech'), (req, res) => {
  59. const filePath = path.join(process.cwd(), req.file.path);
  60. const fileContents = fs.readFileSync(filePath, 'utf-8');
  61. speechText = fileContents.split('\n').map(line => line.trim()).filter(line => line.length > 0);
  62. currentPosition = 0;
  63. // Save the loaded speech lines to a file
  64. fs.writeFileSync(SPEECH_LINES_FILE, speechText.join('\n'));
  65. io.emit('load_speech', speechText);
  66. res.send('File uploaded and speech loaded.');
  67. });
  68. // Handle WebSocket connections
  69. io.on('connection', (socket) => {
  70. console.log('Client connected');
  71. // Send the full speech to any newly connected client
  72. if (speechText.length > 0) {
  73. socket.emit('load_speech', speechText);
  74. }
  75. // Handle position updates from the speaker view
  76. socket.on('update_position', (newPosition) => {
  77. currentPosition = newPosition;
  78. io.emit('update_caption', currentPosition); // Send to all clients
  79. const currentLine = speechText[currentPosition];
  80. updateOBSSubtitle(currentLine);
  81. generateSRT(currentLine);
  82. });
  83. socket.on('live_typing', (text) => {
  84. socket.broadcast.emit('live_typing', text); // Broadcast to audience
  85. });
  86. });
  87. async function updateOBSSubtitle(captionText) {
  88. try {
  89. await obs.call('SendStreamCaption', {
  90. captionText: captionText
  91. });
  92. console.log('Updated OBS Text Source:', captionText);
  93. } catch (error) {
  94. console.error('Error updating OBS Text Source:', error);
  95. }0
  96. }
  97. async function generateSRT(captionText) {
  98. try {
  99. // Check if recording is enabled
  100. const recordStatus = await obs.call('GetRecordStatus');
  101. if (recordStatus && recordStatus.outputActive) {
  102. // Get the recording timecode
  103. const seconds = recordStatus.outputDuration / 1000; // Assuming this returns a time in seconds
  104. // Format timecode for SRT (HH:MM:SS,ms)
  105. const startTime = formatTimecode(seconds);
  106. const endTime = formatTimecode(seconds + 5); // Assume each line is displayed for 5 seconds
  107. // Create SRT entry
  108. const srtEntry = `${currentPosition + 1}\n${startTime} --> ${endTime}\n${captionText}\n\n`;
  109. fs.appendFileSync(SRT_FILE_NAME, srtEntry); // Append to SRT file
  110. console.log('Updated SRT file:', SRT_FILE_NAME);
  111. }
  112. } catch (error) {
  113. console.error('Error generating SRT:', error);
  114. }
  115. }
  116. function formatTimecode(seconds) {
  117. const date = new Date(0);
  118. date.setSeconds(seconds);
  119. const hours = String(date.getUTCHours()).padStart(2, '0');
  120. const minutes = String(date.getUTCMinutes()).padStart(2, '0');
  121. const secs = String(date.getUTCSeconds()).padStart(2, '0');
  122. const millis = String((seconds % 1) * 1000).padStart(3, '0');
  123. return `${hours}:${minutes}:${secs},${millis}`;
  124. }
  125. const PORT = process.env.PORT || 3000;
  126. server.listen(PORT, () => {
  127. console.log(`Server running on port ${PORT}`);
  128. });
  129. const ngrok = require("@ngrok/ngrok");
  130. async function startTunnel() {
  131. const listener = await ngrok.forward({
  132. addr: PORT,
  133. authtoken: "2nCZjA9FOMFVdHAhIDyI0Tn8y0A_5oFkSL7kXve8RcCeJMXKU",
  134. domain: "explicitly-trusty-javelin.ngrok-free.app",
  135. proto: "http"
  136. });
  137. console.log(`Ingress established at: ${listener.url()}`);
  138. }
  139. startTunnel();