FER Macbeth Project
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.
 
 
 

327 lines
11 KiB

#include "ofTeleprompter.h"
void ofTeleprompter::setup() {
ofBackground(255);
ofSetVerticalSync(false);
setupGUI();
/* load both texts */
loadText(script, filePath);
loadText(scriptContemporary, filePathContemp);
activeScript = &script;
textFont.load("Roboto-SemiBold.ttf", 24);
detailsFont.load("Roboto-SemiBold.ttf", 22);
// Prepare first line for teleprompter
currentSentence = (*activeScript)[currentLine].sentence;
displayedSentence.clear();
currentLetterIndex = 0;
lastWordTime = ofGetElapsedTimeMillis();
// Setup the LLMThread
llmThread.setup("http://localhost:8000/generate");
}
void ofTeleprompter::update() {
if(ofGetFrameNum() < 2) {
currentSpeaker = (*activeScript)[currentLine].speaker;
currentEmotion = (*activeScript)[currentLine].emotion;
currentSentence = (*activeScript)[currentLine].sentence;
}
currentLineIndex = ofToString(currentLine + 1) + " / " + ofToString(script.size() + 1);
// Teleprompter logic (letter by letter)
if (currentLetterIndex < currentSentence.size()) {
uint64_t now = ofGetElapsedTimeMillis();
if (now - lastWordTime > wordDelay) {
displayedSentence += currentSentence[currentLetterIndex];
currentLetterIndex++;
lastWordTime = now;
}
}
// Waits for llm thread to send a response before displaying!
if (waitingForLLM && llmThread.isResultReady()) {
llmResponse = llmThread.getResult();
if (llmResponse.empty()) {
ofLogError() << "LLM response is empty!";
} else {
ofJson json = ofJson::parse(llmResponse);
std::string responseText = json.value("response", "");
size_t start = responseText.find('(');
size_t end = responseText.find(')');
std::string speaker, sentence;
if (start != std::string::npos && end != std::string::npos && end > start) {
speaker = responseText.substr(start + 1, end - start - 1);
size_t colon = responseText.find(':', end);
if (colon != std::string::npos) {
sentence = responseText.substr(colon + 1);
sentence.erase(0, sentence.find_first_not_of(" \t"));
}
}
ofLog() << speaker;
currentSentence = sentence;
currentSpeaker = speaker;
currentEmotion = currentEmotionDetetced;
displayedSentence.clear();
currentLetterIndex = 0;
lastWordTime = ofGetElapsedTimeMillis();
waitingForLLM = false;
}
}
}
void ofTeleprompter::draw() {
gui.draw();
drawText();
}
void ofTeleprompter::setupGUI() {
nextLine.addListener(this, &ofTeleprompter::nextLinePressed);
reset.addListener(this, &ofTeleprompter::resetScript);
useLLMOnly.addListener(this, &ofTeleprompter::toggleOffText);
useTextOnly.addListener(this, &ofTeleprompter::toggleOffLLM);
useContempTextOnly.addListener(this, &ofTeleprompter::toggleContempScript);
gui.setDefaultWidth(400);
gui.setup();
gui.add(currentLineIndex.setup("Current Line Index", "NULL"));
gui.add(currentSpeaker.setup("Current Speaker", "NULL"));
gui.add(currentEmotion.setup("Current Emotion", "NULL"));
gui.add(facesDetected.setup("Faces Detected", "NULL"));
gui.add(emotionIntensity.setup("Intensity", "NULL"));
gui.add(emotionDetected.setup("Emotion Detected", "NULL"));
gui.add(temperature.setup("Temperature", 0.7, 0, 1.5));
gui.add(useLLMOnly.setup("Use LLM Only", false));
gui.add(useTextOnly.setup("Use Text Only", false));
gui.add(useContempTextOnly.setup("Use Contept Text Only", false));
gui.add(useGeneratedFeedback.setup("Use LLM Feedback", false));
gui.add(nextLine.setup("Next Line"));
gui.add(reset.setup("Reset Script"));
}
void ofTeleprompter::loadText(std::vector<Line> & _script, std::string & _file) {
_script.clear();
ofFile jsonFile(_file);
if(jsonFile.exists()) {
ofJson json = ofLoadJson(jsonFile);
int idx = 0;
for (const auto& entry : json) {
Line l;
int randomIdx = ofRandom(7);
l.idx = idx++;
l.speaker = entry.value("first_speaker", "");
l.sentence = entry.value("first_text", "");
l.emotion = entry.value("first_emotion", "");
_script.push_back(l);
}
} else {
ofLogError() << "JSON file not found: " << filePath;
}
// Random Check
if (!_script.empty()) {
int randomIdx = ofRandom(_script.size()); // returns float
int idx = static_cast<int>(randomIdx); // convert to int
ofLog() << "Random line: " << _script[idx].speaker << ": " << _script[idx].sentence;
ofLog() << "Number of lines: " << _script.size();
}
// Set initial current line
currentLine = 0;
}
void ofTeleprompter::drawText() {
ofSetColor(ofColor::red);
// --- Display speaker and emotion centered at the top ---
std::string speakerText = "Speaker: " + currentSpeaker.getParameter().toString();
std::string emotionText = "Emotion: " + currentEmotion.getParameter().toString();
ofRectangle speakerBox = detailsFont.getStringBoundingBox(speakerText, 0, 0);
float speakerX = (ofGetWidth() - speakerBox.width) / 2.0f;
float speakerY = 128; // Top margin
ofRectangle emotionBox = detailsFont.getStringBoundingBox(emotionText, 0, 0);
float emotionX = (ofGetWidth() - emotionBox.width) / 2.0f;
float emotionY = speakerY + speakerBox.height + 10; // 10px below speaker
detailsFont.drawString(speakerText, speakerX, speakerY);
detailsFont.drawString(emotionText, emotionX, emotionY);
// -------
ofSetColor(ofColor::black);
float margin = 128; // pixels
float maxWidth = ofGetWidth() - margin * 2;
std::string wrapped = wrapStringToWidth(displayedSentence, maxWidth);
// Split wrapped into lines
std::vector<std::string> lines;
std::istringstream iss(wrapped);
std::string line;
while (std::getline(iss, line)) {
lines.push_back(line);
}
// Calculate total height for vertical centering
float totalHeight = lines.size() * textFont.getLineHeight();
float startY = (ofGetHeight() / 2.0f) - (totalHeight / 2.0f);
// Draw each line centered horizontally
for (size_t i = 0; i < lines.size(); ++i) {
ofRectangle bbox = textFont.getStringBoundingBox(lines[i], 0, 0);
float x = 128;//(ofGetWidth() - bbox.width) / 2.0f;
float y = startY + i * textFont.getLineHeight();
textFont.drawString(lines[i], x, y);
}
}
std::string ofTeleprompter::wrapStringToWidth(const std::string& text, float maxWidth) {
std::istringstream iss(text);
std::string word;
std::string wrapped, line;
while (iss >> word) {
std::string testLine = line.empty() ? word : line + " " + word;
ofRectangle bbox = textFont.getStringBoundingBox(testLine, 0, 0);
if (bbox.width > maxWidth && !line.empty()) {
wrapped += line + "\n";
line = word;
} else {
line = testLine;
}
}
if (!line.empty()) wrapped += line;
return wrapped;
}
void ofTeleprompter::updateCVData(int numOfFacesDetected, std::string emotion, float intensity) {
emotionDetected = emotion;
currentEmotionDetetced = emotion;
currentEmotionIntensity = intensity;
// Debug Values
facesDetected = ofToString(numOfFacesDetected);
emotionIntensity = ofToString(intensity);
}
void ofTeleprompter::nextLinePressed() {
// Check if llm thread is already running
if (waitingForLLM) {
ofLogWarning() << "LLM is still generating. Please wait.";
return;
}
ofLog() << "Next Line!";
if (currentLine < script.size()) {
currentLine++;
}
// If values reach a certain threshold or LLM only is on, and useTextOnly is false -> request a reponse from the llm
if (((currentEmotionIntensity > 0.8 && currentEmotionDetetced != "neutral") || useLLMOnly) && !useTextOnly) {
ofLog() << "Generate Line!";
std::string speaker = (*activeScript)[currentLine - 1].speaker;
std::string sentence = (*activeScript)[currentLine - 1].sentence;
std::string emotion = (*activeScript)[currentLine].emotion;
if (useGeneratedFeedback) {
speaker = currentSpeaker;
sentence = currentSentence;
emotion = currentEmotion;
ofLog() << "Using Generated Feedback";
}
llmThread.requestPrompt(speaker, sentence, currentEmotionDetetced, temperature);
waitingForLLM = true;
// Don't set currentSentence yet!
} else {
currentSpeaker = (*activeScript)[currentLine].speaker;
currentEmotion = (*activeScript)[currentLine].emotion;
currentSentence = (*activeScript)[currentLine].sentence;
displayedSentence.clear();
currentLetterIndex = 0;
lastWordTime = ofGetElapsedTimeMillis();
}
}
void ofTeleprompter::resetScript() {
// Need to reset the text, id, etc.
ofLog() << "Reset script.";
currentLine = 0;
// Prepare teleprompter effect for letter-by-letter
currentSpeaker = (*activeScript)[currentLine].speaker;
currentEmotion = (*activeScript)[currentLine].emotion;
currentSentence = (*activeScript)[currentLine].sentence;
displayedSentence.clear();
currentLetterIndex = 0;
lastWordTime = ofGetElapsedTimeMillis();
}
void ofTeleprompter::toggleOffLLM(bool & val) {
if (val) {
useLLMOnly = false;
}
}
void ofTeleprompter::toggleOffText(bool & val) {
if (val) {
useTextOnly = false;
}
}
void ofTeleprompter::toggleContempScript(bool & val) {
if (val) {
activeScript = &scriptContemporary;
} else {
activeScript = &script;
}
ofLog() << "Script Size:" + (*activeScript).size();
}
void ofTeleprompter::keyPressed(int key){
if(key == 'f' || key == 'F'){
ofToggleFullscreen();
}
if(key == OF_KEY_RIGHT) {
nextLinePressed();
}
if(key == OF_KEY_LEFT) {
pastLine();
}
if(key == 'r' || key == 'R'){
resetScript();
}
}
void ofTeleprompter::pastLine() {
if (currentLine < script.size()) {
currentLine--;
currentSpeaker = (*activeScript)[currentLine].speaker;
currentEmotion = (*activeScript)[currentLine].emotion;
currentSentence = (*activeScript)[currentLine].sentence;
displayedSentence.clear();
currentLetterIndex = 0;
lastWordTime = ofGetElapsedTimeMillis();
}
}