|
|
@ -6,19 +6,44 @@ void ofTeleprompter::setup() { |
|
|
|
ofSetVerticalSync(false); |
|
|
|
setupGUI(); |
|
|
|
loadText(); |
|
|
|
|
|
|
|
textFont.load("Avara.otf", 32); |
|
|
|
detailsFont.load("Avara-Bold.otf", 24); |
|
|
|
|
|
|
|
// Prepare first line for teleprompter
|
|
|
|
currentSentence = script[currentLine].sentence; |
|
|
|
displayedSentence.clear(); |
|
|
|
currentLetterIndex = 0; |
|
|
|
lastWordTime = ofGetElapsedTimeMillis(); |
|
|
|
} |
|
|
|
|
|
|
|
void ofTeleprompter::update() { |
|
|
|
// set labels
|
|
|
|
currentSpeaker = script[currentLine].speaker; |
|
|
|
currentEmotion = script[currentLine].emotion; |
|
|
|
currentLineIndex = ofToString(currentLine); |
|
|
|
|
|
|
|
// Teleprompter logic (letter by letter)
|
|
|
|
if (currentLetterIndex < currentSentence.size()) { |
|
|
|
uint64_t now = ofGetElapsedTimeMillis(); |
|
|
|
if (now - lastWordTime > wordDelay) { |
|
|
|
displayedSentence += currentSentence[currentLetterIndex]; |
|
|
|
currentLetterIndex++; |
|
|
|
lastWordTime = now; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
void ofTeleprompter::draw() { |
|
|
|
gui.draw(); |
|
|
|
drawText(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
void ofTeleprompter::setupGUI() { |
|
|
|
nextLine.addListener(this, &ofTeleprompter::nextLinePressed); |
|
|
|
|
|
|
|
gui.setDefaultWidth(400); |
|
|
|
gui.setup(); |
|
|
|
gui.add(currentLineIndex.setup("Current Line Index", "NULL")); |
|
|
|
gui.add(currentSpeaker.setup("Current Speaker", "NULL")); |
|
|
@ -34,9 +59,112 @@ void ofTeleprompter::setupGUI() { |
|
|
|
|
|
|
|
void ofTeleprompter::nextLinePressed() { |
|
|
|
ofLog() << "Next Line!"; |
|
|
|
currentLine++; |
|
|
|
|
|
|
|
// Prepare teleprompter effect for letter-by-letter
|
|
|
|
currentSentence = script[currentLine].sentence; |
|
|
|
displayedSentence.clear(); |
|
|
|
currentLetterIndex = 0; |
|
|
|
lastWordTime = ofGetElapsedTimeMillis(); |
|
|
|
} |
|
|
|
|
|
|
|
void ofTeleprompter::loadText() { |
|
|
|
|
|
|
|
script.clear(); |
|
|
|
ofFile jsonFile(filePath); |
|
|
|
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("speaker", ""); |
|
|
|
l.sentence = entry.value("content", ""); |
|
|
|
l.emotion = emotions[(int)randomIdx]; |
|
|
|
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::white); |
|
|
|
|
|
|
|
// --- Display speaker and emotion centered at the top ---
|
|
|
|
std::string speakerText = "Speaker: " + script[currentLine].speaker; |
|
|
|
std::string emotionText = "Emotion: " + script[currentLine].emotion; |
|
|
|
|
|
|
|
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::yellow); |
|
|
|
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 = (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) { |
|
|
|
facesDetected = ofToString(numOfFacesDetected); |
|
|
|
} |