commit
7afa2f350d
8 changed files with 1786 additions and 0 deletions
@ -0,0 +1,152 @@
@@ -0,0 +1,152 @@
|
||||
// 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
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1 @@
@@ -0,0 +1 @@
|
||||
https://pixabay.com/music/search/genre/video%20games/?pagi=2 |
||||
@ -0,0 +1,17 @@
@@ -0,0 +1,17 @@
|
||||
{ |
||||
"name": "replayrecorder", |
||||
"version": "1.0.0", |
||||
"main": "index.js", |
||||
"scripts": { |
||||
"test": "echo \"Error: no test specified\" && exit 1" |
||||
}, |
||||
"author": "", |
||||
"license": "ISC", |
||||
"description": "", |
||||
"dependencies": { |
||||
"fluent-ffmpeg": "^2.1.3", |
||||
"get-video-duration": "^4.1.0", |
||||
"puppeteer": "^24.10.2", |
||||
"puppeteer-screen-recorder": "^3.0.6" |
||||
} |
||||
} |
||||
Loading…
Reference in new issue