Node fluent-ffmpeg screenshots on Windows - node.js

The fluent-ffmpeg package has a function that will create screenshots at even intervals in a video for a given count. The problem with this is that it often spawns a ENAMETOOLONG error on Windows.
To get around this, I copied the relevant parts of the source code that I needed and instead of running it all as one long command, split it up into n many commands (where n is the amount of screenshots).
This seems to work at first. It (sometimes) gets the first screenshot, but then it throws the same ENAMETOOLONG error. How can I get around this?
Here is what I have attmpted:
const count = 50 // just an example
const dest = path.join(os.tmpdir(), `screenshots_${Date.now()}`)
const interval = 100 / (1 + count)
let timemarks = Array(count)
.fill(0).map((_, i) => (interval * (i + 1)) + '%')
.map(mark => {
if (('' + mark).match(/^([\d.]+)%$/)) {
// #ts-ignore
return videoDuration * parseFloat(mark) / 100
} else {
return mark
}
})
.map(mark => {
if (typeof mark === 'number') {
return mark
}
if (mark.indexOf(':') === -1 && mark.indexOf('.') >= 0) {
return Number(mark)
}
const parts = mark.split(':')
let secs = Number(parts.pop())
if (parts.length) {
secs += Number(parts.pop()) * 60
}
if (parts.length) {
secs += Number(parts.pop()) * 3600
}
return secs
})
.sort((a, b) => a - b)
// my first attempt:
for (const mark of timemarks) {
this.video
.screenshot({
count: 1,
folder: dest,
timemarks: [mark],
})
}
// second attempt:
let i = 0
for (const mark of timemarks) {
const filename = `frame_${i.toString().padStart(count.toString().length, '0')}.jpg`
this.video
.setStartTime(mark)
.output(path.join(dest, filename))
.frames(1)
.run()
i++
}
For context, this.video is just ffmpeg(videoFilePath).
My first attempt, as I previously stated, just throws the same error and doesn't really get anywhere. Sometimes I get the first screenshot, most times I do not.
The second attempt doesn't even do anything. It seems to take it's time doing something, but I don't actually get any screenshots. If they saved anywhere, I don't know where.
Is anyone able to help me with this?

Related

Calling Match Template in Promise.All No Performace Improvement?

I am using OpenCV4NodeJS-prebuilt for my project to use match template.
I Created Two Files one being Index.js and other named matchTemlate.js
In Index.js i call match template:
const { matchTemplate } = require("./matchTemplate");
...
let a = async function () {
let tm = performance.now();
try {
await Promise.all([
matchTemplate(baseImage, templateR),
matchTemplate(baseImage, templateL)
]).then(result => {
const c = result.map((ob) => (ob.C)) // confidence
top = c[0] > c[1] ? result[0].Y + 8 : result[1].Y + 11
})
} catch (error) {
console.log(error)
}
tm = performance.now() - tm;
console.log(tm)
}
and this is matchTemplate.js
const cv = require('opencv4nodejs-prebuilt')
exports.matchTemplate = async function (inputFile, templateImage) {
// eslint-disable-next-line no-unused-expressions
const matS = await cv.imdecodeAsync(templateImage)
console.time('templateMatching')
const matched = inputFile.matchTemplate(matS, 3)
console.timeEnd('templateMatching')
const minMax = matched.minMaxLoc()
return ({ Y: minMax.maxLoc.y, C: minMax.maxVal })
}
The log output of matchTemplate is:
templateMatching: 892.648ms templateMatching: 890.387ms
and The Log output of index.js is:
TemplateMatching: 1824.8019220000133
Why there is no improvement is speed ?
while the execution is done in parallel why it's still taking time equal to time taken by both ?
I tried Promise.all method to call ghostscript via gs4fb npm package and convert PDF to Image and the time improvement was there.
By time improvement i mean the difference of total time taken by Promise.all method and calling the functions one by one.

Why is my console logging: `TimeoutOverflowWarning: 4294967296000 does not fit into a 32-bit signed integer`

I am following the course stream-adventure. One of the assignments is to make an http server which converts all the requests to uppercase and return it in the response.
Now I managed to get it working and the assignment passes. However, the console gives me a TimeoutOverflowWarning.
(node:15710) TimeoutOverflowWarning: 4294967296000 does not fit into a 32-bit signed integer.
Timer duration was truncated to 2147483647.
(node:15710) TimeoutOverflowWarning: 4294967296000 does not fit into a 32-bit signed integer.
Timer duration was truncated to 2147483647.
I'm wondering if it's a memory leak or something caused by my code, or if it is something else. Because in the error message 32-bit is mentioned, I wonder if it's related to that I'm using a Macbook Pro from 2016 which runs in 64 bit. (node v10.17.0)
The code:
'use-strict'
const through = require('through2')
const http = require('http')
const port = process.argv[2]
const uppercaser = through(function (buffer, _, next) {
this.push(buffer.toString().toUpperCase())
next()
});
const server = http.createServer(function (req, res) {
if (req.method === 'POST') {
res.writeHead(200, { 'Content-Type': 'text/plain' })
req.pipe(uppercaser).pipe(res)
} else {
res.writeHead(404)
res.end()
}
});
server.listen(port)
Google searches give various causes of this problem (example 1, example 2) and it seems that most of the solutions are fixed in library used.
When I find this type of error is when I use a setInterval function.
This is because the upper limit of the timeout is 2147483647 which is the max limit of 32-bit int.
If you want you can make your own setInterval wrapper
function setDaysTimeout(callback,days) {
// 86400 seconds in a day
let msInDay = 86400*1000;
let dayCount = 0;
let timer = setInterval(function() {
dayCount++; // a day has passed
if (dayCount === days) {
clearInterval(timer);
callback.apply(this, []);
}
}, msInDay);
}
And use it like this:
setDaysTimeout(function() {
console.log('Four days gone');
}, 4); // fire after 4 days
This is the problem related to setTimeout \ setInterval functions. They have a limit of 32-bit as you can see. So that the node is warning you about it:
> setTimeout(console.log, +Infinity)
> (node:19903) TimeoutOverflowWarning: Infinity does not fit into a 32-bit signed integer.
Timeout duration was set to 1.
Since your code does not have any of those, it seems like the problem with some code in library.
I'd recommend to run node with --trace-warnings flag to find the source of warning:
--trace-warnings
Print stack traces for process warnings (including deprecations).
The upper limit of the timeout is 2147483647, which is the max limit of 32-bit int. In order to avoid this you can wait for the max time and then wait for any remaining time.
const timeout = async (callback, time, callbackArguments) => {
if (!callback || typeof callback !== 'function') throw new Error('Invalid Callback')
let args = ((callbackArguments && typeof callbackArguments === 'object' &&
callbackArguments.length > 0) ? callbackArguments : [])
let max = 2147483647
if (time > max) {
let t = Math.floor(time / max)
let r = time%max
for (let i = 0; i < t; i++) await (() => new Promise(res => setTimeout(() => res(), max)))();
if (r) {
return setTimeout(() => callback(...args), r)
} else {
return callback(...args)
}
} else {
return setTimeout(() => callback(...args), time)
}
}
Use like so
let a = 2
let b = 5
let c = (a, b) => console.log(!a && !b ? 0 : !a && b ? b : a && !b ? a : a + b)
let now = new Date().getTime()
let later = now + 2147483647*5
timeout(c, later-now, [a,b])

How to limit command to the server where the command was called?

I created a Discord bot which has some commands that generates riddels and riddels answers.
I discovered a bug that was triggered: if I invite the bot in two different servers and try to run one command in one of this two servers, it will run it in the second one and that's actually bad because I wanted each server to be independent. If the command is run on one server, it shouldn't be run on another.
I tried to save server id and tried to write some conditions with it but didn't work.
on the first server:
on the second server:
I want to make the command works per server not get shared in all of them
let CommandRunned = CommandRunnedWithSpace.trim().toLowerCase()
let argsWithOutSpace = receivedMess.content.toLowerCase().slice(mentionNumber).trim().split(' ');
const cmd = argsWithOutSpace.shift().toLowerCase();
const args = argsWithOutSpace.join(' ');
if (CommandRunned === 'startriddle') {
if (RiddleMap.get('check') === true) {
receivedMess.reply("Riddle Already Started (can't start two riddels at the sametime)");
} else {
currentCluesArray = [];
clueI = 0;
getRndNum = (min, max) => {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
let randomNum = getRndNum(0, RiddlesApi.Riddles.length - 1)
RiddlesApi.Riddles.filter((Riddel) => {
if (Riddel.id === randomNum) {
RiddleMessage = Riddel.Riddle
answer = Riddel.Answer
clues = Riddel.clues
}
})
StartRiddleModuel.startRiddle(receivedMess, RiddleMessage);
RiddleMap.set('check', true);
}
}
if (cmd === 'answer') {
if (answerCooldown.has(receivedMess.author.id)) {
receivedMess.delete();
receivedMess.reply('You have to wait ' + answercdseconds + ' seconds befor answering again')
} else {
if (RiddleMap.get('check') === true) {
if (args === answer) {
RiddleMap.set('check', false);
receivedMess.reply("Correct :hushed: ")
receivedMess.react('✅');
} else if (args === '') {
receivedMess.reply("Well you didnt enter anything.How you want me to know your answer then.")
receivedMess.react('💢');
} else {
receivedMess.reply("That wasnt the right answer try again :smirk: ")
receivedMess.react('❌');
}
answerCooldown.add(receivedMess.author.id);
} else {
receivedMess.reply("No Riddles did started");
}
}
}
Without having complete code it's pretty hard to point out which lines of code should be changed. However, the general idea is to compare guild ID each time you run the command. Under Message object, you can find guild ID like this:
msg.guild.id
When user requests a riddle, you should save the guild ID and then compare it with the ID from the answer command.
However, there's another solution which could better fit your needs: awaitMessages method from TextChannel class.
The most important benefit is that it's used per channel, so you don't need to add any logic like the one I described above.
Example from documentation should help you to properly incorporate it in your script (comments and minor changes mine):
// Here you need to create a filter to 'catch' only desired messages
// E.g. filter I use to check if my bot was pinged is as below
// message.mentions.users.some(mention => mention.id === client.user.id)
const filter = m => m.content.includes('answer');
// Here you specify max: 1 as you want to catch and respond to each message
channel.awaitMessages(filter, { max: 1, time: 60000, errors: ['time'] })
.then(collected => console.log('If correct command was sent, program enters here'))
.catch(collected => console.log('In case errors or timeout, put commands here'));
The only thing missing is to loop awaitMessages (so that it doesn't end after one message is found) and set separate timer so you know when the time for answering ends.

Unexpected Identifier in node.js

I'm attempting to run a code with node.js and currently I am getting the error:
Unexpected identifier
The code file is as follows:
/*
This script will bruteforce a live match from the TPP API
in Defiance mode and estimate winrates.
Requirements:
-An oauth for the TPP API: https://twitchplayspokemon.tv/show_oauth_token
-Node.js: https://nodejs.org/en/download/
-sync-request for Node.js: `npm install -g sync-request` (NPM should come
installed with Node.js)
-Pokemon Showdown: https://github.com/Zarel/Pokemon-Showdown
You don't need to install its dependencies, the simulator itself does not
require them.
-My PBR mod for Pokemon Showdown: https://github.com/mathfreak231/random-
showdown-mods
enter code here`enter code here`This is required to accurately simulate PBR.
Note that it is still in development.
Setup steps:
Copy the pbr directory from random-showdown-mods into Pokemon-Showdown/mods.
Fill in the relative path to Pokemon-Showdown/sim below
Fill in your TPP API oauth token below
Change the number constants if you wish
Then just `node defiancechan.js`, sit back, and enjoy your simulation!
Note that the PBR mod is *not* entirely accurate. Known inaccuracies
include,
but are not limited to: draws are not handled properly, and Fury Cutter
still resets when it shouldn't.
*/
// Replace this with a relative path to Pokemon-Showdown/sim
const Sim = require('./../Pokemon-Showdown/sim');
// Replace this with your TPP oauth token
const TPP_OAUTH = "abc123xyz69420";
// Absolute maximum sample size.
const MAX_SAMPLE_SIZE = 1000;
// z* for your desired confidence level. This is used to calculate
// the margin of sampling error. Some common values:
// For 95% confidence: 1.959963986
// For 99% confidence: 2.575829303
// In practice this means that C% of your simulations will
// have the true proportion within the margin of error from
// the sampled proportion.
// If you don't know what any of this means, don't bother changing
// or paying attention to the "Margin of error" output at the end.
const Z_STAR = 2.575829303;
// Maximum time to run the simulation for, in milliseconds.
const MAX_TIME = 30000;
// If this is true, it prints some match logs (1 in DEBUG_EVERY) to stdout
while it's bashing them.
const DEBUG = false;
const DEBUG_EVERY = 50;
// Set this to require a .json file to bash a match there instead of a live
match.
const TEST_MATCH_PATH = null;
/////////////////////////////////////////
// DON'T EDIT ANYTHING BELOW THIS LINE //
/////////////////////////////////////////
const request = require('sync-request');
const PBR_FORMAT = {
id: 'tppbr',
name: 'TPPBR',
mod: 'pbr',
ruleset: ["PBR Sleep Clause", "PBR Freeze Clause", "PBR Self-Destruct
Clause", "PBR Destiny Bond Clause"],
banlist: [],
unbanlist: [],
debug: true,
}
/**
* because pokecat TriHard
*/
function convert_stats_table(stats) {
let newstats = {};
for (statname in stats) {
newstats[statname.toLowerCase()] = stats[statname];
}
return newstats;
}
function main() {
console.log("Getting current match...");
let match;
if (TEST_MATCH_PATH) {
match = require(TEST_MATCH_PATH);
} else {
const res = request('GET',
"https://twitchplayspokemon.tv/api/current_match", {
headers: {
'OAuth-Token': TPP_OAUTH
},
});
match = JSON.parse(res.getBody('utf8'));
}
console.log("Done.");
if ((match.base_gimmicks.includes('blind_bet') ||
match.base_gimmicks.includes('secrecy')) && !match.started) {
throw new Error("Can't analyze a match unless all the Pokemon have been
revealed.");
}
if (!match.base_gimmicks.includes('defiance')) {
console.log("WARNING: This is not a defiance match!");
}
const startTime = Date.now();
let wincounter = {
Blue: 0,
Red: 0,
draw: 0,
};
let teams = [[], []];
for (let i = 0; i < teams.length; i++) {
for (const pokemon of match.teams[i]) {
teams[i].push({
name: pokemon.ingamename,
species: pokemon.species.name, // TODO: handle forms
item: pokemon.item.name,
ability: pokemon.ability.name,
moves: pokemon.moves.map(move => move.name),
nature: pokemon.nature.name,
gender: pokemon.gender ? pokemon.gender.toUpperCase() : 'N',
evs: convert_stats_table(pokemon.evs),
ivs: convert_stats_table(pokemon.ivs),
level: pokemon.level,
shiny: pokemon.shiny,
happiness: pokemon.happiness,
});
}
}
const isRandomOrder = match.base_gimmicks.includes('random_order');
const isTraitor = match.base_gimmicks.includes('traitor');
const isFog = match.base_gimmicks.includes('fog');
let battle;
let i;
console.log("Begin simulation of matches...");
for (i = 1; i <= MAX_SAMPLE_SIZE; i++) {
battle = new Sim.Battle({
formatid: PBR_FORMAT,
});
// prevent battle from starting so we can edit stuff first
battle.started = true;
let newTeams = teams;
if (isRandomOrder || isTraitor) {
// TODO: In what order are these gimmicks applied?
newTeams = battle.deepClone(teams);
if (isRandomOrder) {
for (let team of newTeams) {
battle.shuffle(team);
}
}
if (isTraitor) {
// swap 2 pokes, same position
const p = battle.random(3);
const temp = newTeams[0][p];
newTeams[0][p] = newTeams[1][p];
newTeams[1][p] = temp;
}
}
battle.join('p1', 'Blue', 1, newTeams[0]);
battle.join('p2', 'Red', 2, newTeams[1]);
for (const side of battle.sides) {
for (const pokemon of side.pokemon) {
pokemon.originalPosition = pokemon.position;
for (const [m, move] of pokemon.baseMoveSlots.entries()) {
// change move PP
const moveData = battle.getMove(move.id);
const oldMoveData = match.teams[side.n]
[pokemon.position].moves[m];
move.pp = oldMoveData.pp;
move.maxpp = moveData.pp * (5 + oldMoveData.pp_ups) / 5;
}
pokemon.clearVolatile(); // actually update the moveslots TriHard
}
}
if (match.stage) {
battle.colosseum = match.stage;
} else if (isFog && battle.randomChance(1,2)) {
// TODO: confirm this is correct
battle.colosseum = 'courtyard';
} else {
battle.colosseum = battle.sample([
'gateway', 'mainstreet', 'waterfall', 'neon', 'crystal',
'sunnypark', 'magma', 'sunset', 'stargazer', 'lagoon',
]);
}
battle.started = false;
battle.start();
if (isFog) battle.setWeather('fog');
while (!battle.ended) {
if (battle.turn > 500) {
console.log('===BEGIN BAD MATCH LOG EXPORT===');
console.log(battle.log.join('\n'));
console.log('===END BAD MATCH LOG EXPORT===');
console.log('===BEGIN BAD MATCH INPUT LOG EXPORT===');
console.log(battle.inputLog.join('\n'));
console.log('===END BAD MATCH INPUT LOG EXPORT===');
throw new Error("The match fucked up somehow.");
}
for (const side of battle.sides) {
let result;
switch (side.currentRequest) {
case 'switch':
// TPPBR switching rules.
let target = null;
for (let i = side.active.length; i < side.pokemon.length; i++) {
const pokemon = side.pokemon[i];
if (!(pokemon.fainted) && (!target || pokemon.originalPosition
< target.originalPosition)) {
target = pokemon;
}
}
result = battle.choose(side.id, `switch ${target.position +
1}`);
if (!result) throw new Error(side.choice.error);
break;
case 'move':
// Same as TPPBR, choose random moves until one works
let tries = 0;
do {
result = battle.choose(side.id, `move ${battle.random(4) + 1}`);
if (++tries > 50) throw new Error(`${side.id} stuck on a move
choice: ${side.choice.error}`);
} while (!result);
break;
}
}
}
wincounter[battle.winner || 'draw']++;
if (DEBUG && i % DEBUG_EVERY === 0) {
console.log('===BEGIN RANDOM MATCH LOG EXPORT===');
console.log(battle.log.join('\n'));
console.log('===END RANDOM MATCH LOG EXPORT===');
console.log('===BEGIN RANDOM MATCH INPUT LOG EXPORT===');
console.log(battle.inputLog.join('\n'));
console.log('===END RANDOM MATCH INPUT LOG EXPORT===');
}
battle.destroy();
if (MAX_TIME && Date.now() - startTime >= MAX_TIME) {
break;
}
}
console.log(`Simulated ${i} battles in ${Date.now() - startTime}ms.`);
console.log(`Blue win chance: ${wincounter['Blue'] / i}`);
console.log(`Red win chance: ${wincounter['Red'] / i}`);
console.log(`Draw chance: ${wincounter['draw'] / i}`);
// Large Counts condition
if (wincounter['Blue'] >= 10 && wincounter['Red'] >= 10) {
let favoredWinrate = Math.max(wincounter['Blue'], wincounter['Red']) /
i;
let standardError = Math.sqrt(favoredWinrate * (1-favoredWinrate) / i);
console.log(`Margin of error: ${Z_STAR * standardError}`);
} else {
console.log("Counts are too small to infer a margin of error.");
}
}
main();
I've filled in all the information of my PC and whatever codes and downloads I was supposed to do stated, but I'm not sure what I've done wrong. Help would be very greatly appreciated. Thank you. If it would be easier I'll also provide the Github link I've acquired this code from. https://gist.github.com/mathfreak231/4229a28f419f4aa10fcb2eba9fdd57e9
I'm not sure if this is just how SO formats it, but there are a few extraneous linebreaks here:
On the line with ruleset: ["PBR Sleep Clause", "PBR Freeze Clause", "PBR Self-Destruct. The next line begins with Clause, which is not a valid JS keyword, and thus the error
On the line with throw new Error("Can't analyze a match unless all the Pokemon have been, again an unnecessary linebreak.
Comment line beginning with // If this is true, it prints some match logs (1 in DEBUG_EVERY) to stdout has a linebreak.
Comment line beginning with // Set this to require a .json file to bash a match there instead of a live has a linebreak.
In general, please read the error message more explicitly. It'll almost always explicitly tell you where it believes the error to be.

Node and RxJs: How can I avoid a memory leak on a long process?

I'm programming a process that will take a long time running and I'm looking to optimize used memory
I have an stream of data that comes over time and I want a way to clean up this data and declared variables when the stream completes. Any ideas?
I have setted up a quick example of the problem:
var source = Rx.Observable.interval(100)
.take(20)
.map((x) => {
let onlyTrash = _.range(1000000);
return x;
});
let subscription = source.subscribe(
x => console.log(getMemoryUsage()),
err => console.log('Error: ' + err),
() => console.log('Completed: ' + getMemoryUsage())
);
I can not show this in a Fiddle because it's referencing to process to get memory usage
If you run this example you will notice that memory usage is increasing in each tick but It doesn't decrease on completion, this is the problem.
Here is a example of the getMemoryUsage function if you want to try it in your node
var lastMemoryUsageRegister;
function getMemoryUsage() : string {
var memoryUsage : string;
if (!lastMemoryUsageRegister) {
memoryUsage = process.memoryUsage().rss.toString();
} else {
let diff = process.memoryUsage().rss - lastMemoryUsageRegister;
memoryUsage = (diff > 0 ? '+' : '') + diff;
}
lastMemoryUsageRegister = process.memoryUsage().rss;
return memoryUsage;
}
You could wrap your 'trash' in a another Observable and make use of Disposable to clean up, like this:
.flatMap((x) => {
return Rx.Observable.create(obs => {
let onlyTrash = _.range(1000000);
let disposable = Rx.Disposable.create(() => {
onlyTrash = undefined; // free up reference
});
obs.onNext(x);
obs.onCompleted();
return disposable;
});
});
Make sure you change it to flatMap rather than just map

Resources