// index.js const puppeteer = require('puppeteer'); const { PuppeteerScreenRecorder } = require('puppeteer-screen-recorder'); const ffmpeg = require('fluent-ffmpeg'); const getVideoDurationInSeconds = require('get-video-duration').getVideoDurationInSeconds; async function rotateVideo(inputPath, outputPath) { // Get original duration const originalDuration = await getVideoDurationInSeconds(inputPath); const targetDuration = 30; const speedFactor = originalDuration / targetDuration; return new Promise((resolve, reject) => { ffmpeg(inputPath) .videoFilters([ { filter: 'crop', options: '1920:1080:0:0' }, { filter: 'transpose', options: '2' }, { filter: 'setpts', options: `PTS/${speedFactor}` } ]) // Optional: speed up audio as well (max 2x per atempo, so chain if needed) .audioFilters( speedFactor <= 2 ? { filter: 'atempo', options: speedFactor } : [ { filter: 'atempo', options: 2 }, { filter: 'atempo', options: speedFactor / 2 } ] ) .save(outputPath) .on('end', () => { console.log('Crop, rotation, and speed-up complete!'); resolve(); }) .on('error', err => reject(err)); }); } function addMusicToVideo(videoPath, musicPath, outputPath, musicVolume = 0.1) { return new Promise((resolve, reject) => { ffmpeg() .addInput(videoPath) .addInput(musicPath) .complexFilter([ // If your video has NO audio, just set the music volume and map '[1:a]volume=' + musicVolume + '[aud]' ]) .outputOptions([ '-map 0:v', // video from first input '-map [aud]', // audio from [aud] '-shortest', // end when shortest ends '-c:v copy' // copy video, do not re-encode ]) .save(outputPath) .on('end', () => { console.log('Music added and volume set!'); resolve(); }) .on('error', reject); }); } (async () => { const browser = await puppeteer.launch({ headless: false }); const page = await browser.newPage(); // Set to vertical/portrait mode await page.setViewport({ width: 1920, height: 1080, deviceScaleFactor: 1, }); await page.goto('https://tacticfront.io/join/aQoyBBX7'); // Click "Pause game" on load await page.waitForSelector('button[title="Pause game"]'); await page.click('button[title="Pause game"]'); console.log('Game paused. Waiting for unpause (second click)...'); // Set up a promise in Node const clickedPromise = new Promise(resolve => { page.exposeFunction('notifyPauseButtonClicked', resolve); }); // Attach a page-wide click handler in the browser context await page.evaluate(() => { // Remove any existing listener for safety window._pauseButtonHandler && document.removeEventListener('click', window._pauseButtonHandler); window._pauseButtonHandler = function (e) { if ( e.target && e.target.tagName === 'BUTTON' && e.target.title === 'Pause game' ) { window.notifyPauseButtonClicked(); document.removeEventListener('click', window._pauseButtonHandler); // Only once } }; document.addEventListener('click', window._pauseButtonHandler); }); console.log('Game paused. Waiting for unpause (manual click)...'); await clickedPromise; // Start recording console.log("Recorder Start") const recorder = new PuppeteerScreenRecorder(page); await recorder.start('./replay.mp4'); // Stop recording on game end let recordingStopped = false; async function finishRecording() { if (!recordingStopped) { recordingStopped = true; await recorder.stop(); await browser.close(); // Now rotate the video await rotateVideo('replay.mp4', 'output_vertical.mp4'); await addMusicToVideo('output_vertical.mp4', './music/a-hero-of-the-80s-126684.mp3', 'final_output.mp4', 0.05); } } page.on('console', async msg => { if (msg.text().includes('local server ending game')) { console.log('Detected "local server ending game" in console. Stopping recording...'); await finishRecording(); } }); setTimeout(async () => { if (!recordingStopped) { console.log('Timeout reached. Stopping recording...'); await finishRecording(); } }, 10 * 60 * 1000); // 5 minutes })(); // Spina has won the game //local server ending game