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.
153 lines
4.9 KiB
153 lines
4.9 KiB
|
12 months ago
|
// 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
|