room vid automation
This commit is contained in:
File diff suppressed because one or more lines are too long
12
config.json
12
config.json
@@ -37,21 +37,21 @@
|
|||||||
"defaultSceneName": "fgfm",
|
"defaultSceneName": "fgfm",
|
||||||
"commercialSceneName": "commercial",
|
"commercialSceneName": "commercial",
|
||||||
"currentActivitySceneItemName": "now-showing-txt",
|
"currentActivitySceneItemName": "now-showing-txt",
|
||||||
"initialQueueSize": 1,
|
"initialQueueSize": 3,
|
||||||
"recentlyPlayedMemory": 5,
|
"recentlyPlayedMemory": 5,
|
||||||
"roomGrindChance": 25,
|
"roomGrindChance": 25,
|
||||||
"roomGrindPlaytime": 1800,
|
"roomGrindPlaytime": 900,
|
||||||
|
"roomShuffleChance": 85,
|
||||||
|
"roomVidPlaytime": 150,
|
||||||
|
"roomShuffleCount": 6,
|
||||||
"videoPollSize": 5,
|
"videoPollSize": 5,
|
||||||
"videoPollIntervalMinutes": 15,
|
"videoPollIntervalMinutes": 15,
|
||||||
"commercialsEnabled": false,
|
"commercialsEnabled": false,
|
||||||
"commercialInterval": 3600,
|
"commercialInterval": 3600,
|
||||||
"auwChance": 25,
|
"auwChance": 25,
|
||||||
"defaultSRVolume": 75,
|
"defaultSRVolume": 50,
|
||||||
"vodConfigFile": "./conf/vods.json",
|
"vodConfigFile": "./conf/vods.json",
|
||||||
"roomConfigFile": "./conf/rooms.json",
|
"roomConfigFile": "./conf/rooms.json",
|
||||||
"roomVidsBasePath": "Y:\\media\\videos\\ALttP\\my-vids\\room-vids",
|
"roomVidsBasePath": "Y:\\media\\videos\\ALttP\\my-vids\\room-vids",
|
||||||
"roomVidPlaytime": 60,
|
|
||||||
"roomShuffleChance": 85,
|
|
||||||
"roomShuffleCount": 15,
|
|
||||||
"debug": false
|
"debug": false
|
||||||
}
|
}
|
||||||
11
fgfm.TODO
11
fgfm.TODO
@@ -1,6 +1,10 @@
|
|||||||
TODO:
|
TODO:
|
||||||
|
✔ Fix queue to only return first 20 or so @done (18-09-26 12:04)
|
||||||
|
✔ Update the queue to support looping @done (18-09-26 18:28)
|
||||||
|
✔ specify # of loops in video object @done (18-09-26 18:28)
|
||||||
|
✔ change source to loop? calculate time? @done (18-09-26 18:28)
|
||||||
☐ Room vid requests / import
|
☐ Room vid requests / import
|
||||||
☐ Importing
|
✔ Importing @done (18-09-26 08:25)
|
||||||
- Read the Y:\media\videos\ALttP\my-vids\room-vids directory
|
- Read the Y:\media\videos\ALttP\my-vids\room-vids directory
|
||||||
- Go through each folder and get all the .mp4 files
|
- Go through each folder and get all the .mp4 files
|
||||||
- Store the following as metadata:
|
- Store the following as metadata:
|
||||||
@@ -10,6 +14,11 @@ TODO:
|
|||||||
- dungeon (parse from root folder name)
|
- dungeon (parse from root folder name)
|
||||||
- room ID
|
- room ID
|
||||||
- video length
|
- video length
|
||||||
|
☐ Requests
|
||||||
|
☐ Web interface? Twitch extension?
|
||||||
|
☐ Improvements
|
||||||
|
☐ When playing a room back, loop it at slower speeds for a few iterations
|
||||||
|
✔ Look into just making the video loop instead of hiding at the end @done (18-09-26 18:29)
|
||||||
☐ Support viewer skip voting
|
☐ Support viewer skip voting
|
||||||
☐ Remove currently playing video from vote choices
|
☐ Remove currently playing video from vote choices
|
||||||
☐ Command to add sets of videos to the queue at once (like the entire ttas or all gold segments)
|
☐ Command to add sets of videos to the queue at once (like the entire ttas or all gold segments)
|
||||||
|
|||||||
41
fgfm.js
41
fgfm.js
@@ -68,7 +68,7 @@ const twitchInit = (config) => {
|
|||||||
botChat.addListener('error', twitchErrorHandler);
|
botChat.addListener('error', twitchErrorHandler);
|
||||||
editorChat.addListener('error', twitchErrorHandler);
|
editorChat.addListener('error', twitchErrorHandler);
|
||||||
|
|
||||||
resolve({"botChat": botChat, "editorChat": editorChat});
|
resolve({"botChat": botChat, "editorChat": editorChat, "controlRoom": controlRoom});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -116,19 +116,18 @@ const streamInit = (config, twitch) => {
|
|||||||
if (typeof loop === 'undefined' || loop === true) {
|
if (typeof loop === 'undefined' || loop === true) {
|
||||||
loops = Math.floor(config.roomVidPlaytime / room.videoData.length);
|
loops = Math.floor(config.roomVidPlaytime / room.videoData.length);
|
||||||
}
|
}
|
||||||
console.log(`Adding ${loops} instances of room video for ${room.dungeonName} - ${room.roomName} to the queue`);
|
console.log(`Adding room video for ${room.dungeonName} - ${room.roomName} to the queue (${loops} loops)`);
|
||||||
|
|
||||||
let video = {
|
let video = {
|
||||||
"filePath": `${config.roomVidsBasePath}${room.winPath}`,
|
"filePath": `${config.roomVidsBasePath}${room.winPath}`,
|
||||||
"sceneItem": (room.videoData.width === 960) ? "4x3ph" : "16x9ph",
|
"sceneItem": (room.videoData.width === 960) ? "4x3ph" : "16x9ph",
|
||||||
"length": room.videoData.length,
|
"length": room.videoData.length,
|
||||||
"label": room.roomName,
|
"label": room.roomName,
|
||||||
"chatName": room.roomName
|
"chatName": room.roomName,
|
||||||
|
"loops": loops
|
||||||
};
|
};
|
||||||
|
|
||||||
for (var i = 0; i < loops; i++) {
|
state.videoQueue.push(video);
|
||||||
state.videoQueue.push(video);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Picks the next video in the queue (shuffles if empty)
|
// Picks the next video in the queue (shuffles if empty)
|
||||||
@@ -261,7 +260,7 @@ const streamInit = (config, twitch) => {
|
|||||||
let commandNoPrefix = commandParts[0] || '';
|
let commandNoPrefix = commandParts[0] || '';
|
||||||
|
|
||||||
// ADMIN COMMANDS
|
// ADMIN COMMANDS
|
||||||
if (config.twitch.admins.includes(from) || from === config.twitch.username.toLowerCase()) {
|
if (config.twitch.admins.includes(from)) {
|
||||||
|
|
||||||
// SHOW/HIDE SOURCE
|
// SHOW/HIDE SOURCE
|
||||||
if (commandNoPrefix === 'show' || commandNoPrefix === 'hide') {
|
if (commandNoPrefix === 'show' || commandNoPrefix === 'hide') {
|
||||||
@@ -286,21 +285,25 @@ const streamInit = (config, twitch) => {
|
|||||||
|
|
||||||
obs.toggleVisible(sceneItem).catch(console.error);
|
obs.toggleVisible(sceneItem).catch(console.error);
|
||||||
|
|
||||||
// ROOM VIDS
|
// ROOM VID REQUESTS
|
||||||
} else if (commandNoPrefix === 'room') {
|
} else if (commandNoPrefix === 'room') {
|
||||||
let roomId = commandParts[1] || false;
|
let roomId = commandParts[1] || false;
|
||||||
if (roomId.length !== 4) {
|
let room;
|
||||||
twitch.botChat.say(to, `Please provide a 4-digit room ID!`);
|
|
||||||
return;
|
if (roomId !== false) {
|
||||||
|
let roomIndex = config.rooms.findIndex(e => e.id === parseInt(roomId));
|
||||||
|
|
||||||
|
if (roomIndex === -1) {
|
||||||
|
twitch.botChat.say(to, `No room found matching that ID!`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
room = config.rooms[roomIndex];
|
||||||
|
} else {
|
||||||
|
// choose a room at random
|
||||||
|
room = config.rooms.sort(util.randSort).slice(0, 1).shift();
|
||||||
}
|
}
|
||||||
|
|
||||||
let roomIndex = config.rooms.findIndex(e => e.dungeonId === roomId.substring(0,2) && e.roomId === roomId.substring(2,4));
|
|
||||||
if (roomIndex === -1) {
|
|
||||||
twitch.botChat.say(to, `No room found matching that ID!`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let room = config.rooms[roomIndex];
|
|
||||||
addRoomVideo(room);
|
addRoomVideo(room);
|
||||||
twitch.botChat.say(to, `Added ${room.dungeonName} - ${room.roomName} to the queue!`);
|
twitch.botChat.say(to, `Added ${room.dungeonName} - ${room.roomName} to the queue!`);
|
||||||
|
|
||||||
@@ -436,7 +439,7 @@ const streamInit = (config, twitch) => {
|
|||||||
// QUEUE STATUS
|
// QUEUE STATUS
|
||||||
} else if (commandNoPrefix === 'queue') {
|
} else if (commandNoPrefix === 'queue') {
|
||||||
if (state.videoQueue.length > 0) {
|
if (state.videoQueue.length > 0) {
|
||||||
let chatQueue = state.videoQueue.map((c, i) => {
|
let chatQueue = state.videoQueue.slice(0, 10).map((c, i) => {
|
||||||
return `[${i+1}] ${c.chatName}`;
|
return `[${i+1}] ${c.chatName}`;
|
||||||
});
|
});
|
||||||
twitch.botChat.say(to, chatQueue.join(' | '));
|
twitch.botChat.say(to, chatQueue.join(' | '));
|
||||||
|
|||||||
41
lib/ghobs.js
41
lib/ghobs.js
@@ -12,7 +12,7 @@ function GHOBS(config) {
|
|||||||
console.log(`Success! We're connected to OBS!`);
|
console.log(`Success! We're connected to OBS!`);
|
||||||
this.websocket.getCurrentScene().then(res => this.currentScene = res.name);
|
this.websocket.getCurrentScene().then(res => this.currentScene = res.name);
|
||||||
this.websocket.onSwitchScenes(data => {
|
this.websocket.onSwitchScenes(data => {
|
||||||
console.log(`New Active Scene: ${data.sceneName}`);
|
//console.log(`New Active Scene: ${data.sceneName}`);
|
||||||
this.currentScene = data.sceneName;
|
this.currentScene = data.sceneName;
|
||||||
});
|
});
|
||||||
resolve();
|
resolve();
|
||||||
@@ -32,28 +32,51 @@ function GHOBS(config) {
|
|||||||
this.playVideoInScene = (video, scene, callback) => {
|
this.playVideoInScene = (video, scene, callback) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let originalScene = this.currentScene || false;
|
let originalScene = this.currentScene || false;
|
||||||
console.log(`Changing scene from ${originalScene} to ${scene}`);
|
//console.log(`Changing scene from ${originalScene} to ${scene}`);
|
||||||
this.websocket.setCurrentScene({"scene-name": scene})
|
this.websocket.setCurrentScene({"scene-name": scene})
|
||||||
.then(res => {
|
.then(res => {
|
||||||
// set the file path on the source
|
// set the file path on the source
|
||||||
console.log(`Setting file path to: ${video.filePath}`);
|
//console.log(`Setting file path to: ${video.filePath}`);
|
||||||
this.websocket.setSourceSettings({"sourceName": video.sceneItem, "sourceSettings": {"local_file": video.filePath}})
|
let sourceSettings = {
|
||||||
|
"local_file": video.filePath,
|
||||||
|
"looping": (typeof video.loops !== 'undefined' && video.loops > 1)
|
||||||
|
};
|
||||||
|
sourceSettings.loop = sourceSettings.looping;
|
||||||
|
|
||||||
|
// @TODO support any sourceSetting
|
||||||
|
//
|
||||||
|
/*{ close_when_inactive: true,
|
||||||
|
local_file: 'Y:\\media\\videos\\ALttP\\my-vids\\room-vids\\11-mire\\38-wizzpot-rta-hook-610.mp4',
|
||||||
|
loop: true,
|
||||||
|
looping: false,
|
||||||
|
restart_on_activate: false,
|
||||||
|
speed_percent: 100 }*/
|
||||||
|
|
||||||
|
//this.websocket.getSourceSettings({"sourceName": video.sceneItem}).then(console.log);
|
||||||
|
|
||||||
|
this.websocket.setSourceSettings({"sourceName": video.sceneItem, "sourceSettings": sourceSettings})
|
||||||
// show the video scene item
|
// show the video scene item
|
||||||
.then(data => {console.log(`Showing ${video.sceneItem}`); this.websocket.setSceneItemProperties({"item": video.sceneItem, "scene-name": scene, "visible": true})})
|
.then(() => this.websocket.setSceneItemProperties({"item": video.sceneItem, "scene-name": scene, "visible": true}))
|
||||||
// when the video is over, hide it and trigger the user callback, but resolve promise immediately with the timer
|
// when the video is over, hide it and trigger the user callback, but resolve promise immediately with the timer
|
||||||
.then(data => {
|
.then(data => {
|
||||||
|
// adjust timeout length to allow the requested number of loops to complete
|
||||||
|
if (sourceSettings.loop === true) {
|
||||||
|
video.length *= video.loops;
|
||||||
|
console.log(`Video is set to loop, adjusted length to ${video.length}`);
|
||||||
|
}
|
||||||
|
|
||||||
resolve(setTimeout(() => {
|
resolve(setTimeout(() => {
|
||||||
console.log(`Hiding ${video.sceneItem}`);
|
//console.log(`Hiding ${video.sceneItem}`);
|
||||||
this.websocket.setSceneItemProperties({"item": video.sceneItem, "scene-name": scene, "visible": false});
|
this.websocket.setSceneItemProperties({"item": video.sceneItem, "scene-name": scene, "visible": false});
|
||||||
if (originalScene) {
|
if (originalScene) {
|
||||||
console.log(`Switching scene back to ${originalScene}`);
|
//console.log(`Switching scene back to ${originalScene}`);
|
||||||
this.websocket.setCurrentScene({"scene-name": originalScene});
|
this.websocket.setCurrentScene({"scene-name": originalScene});
|
||||||
}
|
}
|
||||||
if (typeof callback !== 'undefined') {
|
if (typeof callback !== 'undefined') {
|
||||||
console.log('Triggering user callback');
|
//console.log('Triggering user callback');
|
||||||
callback(data);
|
callback(data);
|
||||||
}
|
}
|
||||||
}, video.length*1000))
|
}, parseInt(video.length*1000)))
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch(reject);
|
.catch(reject);
|
||||||
|
|||||||
@@ -18,14 +18,15 @@ populateDatabase();
|
|||||||
|
|
||||||
async function populateDatabase() {
|
async function populateDatabase() {
|
||||||
let database = [];
|
let database = [];
|
||||||
await util.asyncForEach(roomVidFiles, async (file) => {
|
await util.asyncForEach(roomVidFiles, async (file, index) => {
|
||||||
// @TODO: ignore anything that's not an mp4
|
// ignore anything that's not an mp4
|
||||||
let shortPath = file.replace(roomVidPath, '');
|
let shortPath = file.replace(roomVidPath, '');
|
||||||
if (!/\.mp4$/.test(shortPath)) {
|
if (!/\.mp4$/.test(shortPath)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let entry = {
|
let entry = {
|
||||||
|
id: index+1,
|
||||||
shortPath: shortPath,
|
shortPath: shortPath,
|
||||||
winPath: shortPath.replace(/\//g, '\\')
|
winPath: shortPath.replace(/\//g, '\\')
|
||||||
};
|
};
|
||||||
@@ -39,6 +40,8 @@ async function populateDatabase() {
|
|||||||
entry.roomName = matches[4];
|
entry.roomName = matches[4];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @TODO support some other paths / structures
|
||||||
|
|
||||||
entry.videoData = await getVideoMetadata(file);
|
entry.videoData = await getVideoMetadata(file);
|
||||||
database.push(entry);
|
database.push(entry);
|
||||||
console.log('added entry', entry);
|
console.log('added entry', entry);
|
||||||
|
|||||||
Reference in New Issue
Block a user