feat: make everything threads-safe.
feat: complete `uninstall.sh` logic to make everything synced. feat: complete basic play logic to handle background, music, lyric play.
This commit is contained in:
+143
-4
@@ -1,7 +1,146 @@
|
||||
#include <iostream>
|
||||
// Hatsune Miku's Shoshitsu in Terminal
|
||||
//
|
||||
// The uninstall hook program: plays a short "show" in the terminal — an ASCII
|
||||
// video background at 60% brightness, a music track, and timed lyrics shown
|
||||
// above the video — all synchronised to one shared timeline.
|
||||
//
|
||||
// Compiles and runs on Windows (MSYS2/MinGW) as well as Linux.
|
||||
|
||||
int main()
|
||||
{
|
||||
|
||||
#include "backgroundDisplay.h"
|
||||
#include "lyricResolve.h"
|
||||
#include "musicPlayer.h"
|
||||
#include "timeline.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
// ---- global configuration --------------------------------------------------
|
||||
|
||||
// Debug mode toggles how the show's resources are selected:
|
||||
// true -> behave like a normal binary; read --video / --audio / --lyrics
|
||||
// from the command line (all three are required).
|
||||
// false -> use the hardcoded default paths below (the uninstall-hook path,
|
||||
// invoked by dpkg with no arguments).
|
||||
bool debugMode = true;
|
||||
|
||||
const char* kDefaultVideoPath = "res/video.mp4";
|
||||
const char* kDefaultAudioPath = "res/music.flac";
|
||||
const char* kDefaultLyricPath = "res/lyric.lrc";
|
||||
|
||||
// Shared playback clock. Every module receives a pointer to this so the video,
|
||||
// audio and lyrics stay in sync with each other.
|
||||
Timeline g_timeline;
|
||||
|
||||
// ---- helpers ---------------------------------------------------------------
|
||||
|
||||
static void printUsage(const char* argv0) {
|
||||
std::printf(
|
||||
"Usage: %s --video <file> --audio <file> --lyrics <file>\n"
|
||||
"Play the show in the terminal: ASCII video at 60%% brightness,\n"
|
||||
"music, and lyrics shown above the video on a shared timeline.\n"
|
||||
"(debugMode = true; with debugMode = false the hardcoded defaults\n"
|
||||
"in uninstall.cpp are used and no arguments are needed.)\n",
|
||||
argv0);
|
||||
}
|
||||
|
||||
static int runShow(const std::string& videoPath, const std::string& audioPath,
|
||||
const std::string& lyricPath) {
|
||||
std::atomic<bool> stop{false};
|
||||
g_timeline.start();
|
||||
|
||||
bd::GridSize videoGrid;
|
||||
int videoOffsetX = 0;
|
||||
std::atomic<bool> gridReady{false};
|
||||
|
||||
bd::PlayVideoOptions vo;
|
||||
vo.loop = true;
|
||||
vo.intensity = 0.6f; // 60% brightness
|
||||
vo.topRows = 1; // one row above the video reserved for the lyrics
|
||||
vo.outGrid = &videoGrid;
|
||||
vo.outOffsetX = &videoOffsetX;
|
||||
vo.gridReady = &gridReady;
|
||||
vo.stop = &stop;
|
||||
|
||||
int videoResult = -1;
|
||||
int musicResult = -1;
|
||||
int lyricResult = -1;
|
||||
|
||||
std::thread videoThread([&] {
|
||||
videoResult = bd::playVideo(videoPath, &g_timeline, vo);
|
||||
});
|
||||
std::thread musicThread([&] { musicResult = mp::play(audioPath, &g_timeline, &stop); });
|
||||
|
||||
// Wait for the video to report its grid, so the lyric line can be placed
|
||||
// above the video and kept within the video's width.
|
||||
for (int i = 0; i < 100 && !gridReady.load(); ++i) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
}
|
||||
|
||||
lr::LyricDisplayOptions lo;
|
||||
lo.row = 1; // the row above the video
|
||||
lo.col = (videoGrid.cols > 0) ? videoOffsetX + 1 : 0; // the video's left edge
|
||||
lo.maxWidth = (videoGrid.cols > 0) ? videoGrid.cols : 0; // within the video width
|
||||
std::thread lyricThread([&] {
|
||||
lyricResult = lr::playLyricsFile(lyricPath, lo, &g_timeline, &stop);
|
||||
});
|
||||
|
||||
// The show ends when both the music and the lyrics have finished.
|
||||
musicThread.join();
|
||||
lyricThread.join();
|
||||
stop.store(true); // stop the looping video
|
||||
videoThread.join();
|
||||
|
||||
// Restore a clean terminal.
|
||||
std::printf("\x1b[0m\x1b[2J\x1b[H\x1b[?25h");
|
||||
|
||||
if (videoResult == 1) {
|
||||
std::fprintf(stderr, "uninstall: could not open video '%s'\n", videoPath.c_str());
|
||||
}
|
||||
if (musicResult == 2) {
|
||||
std::fprintf(stderr, "uninstall: could not open audio '%s'\n", audioPath.c_str());
|
||||
} else if (musicResult == 3) {
|
||||
std::fprintf(stderr, "uninstall: no audio device available (playing without sound)\n");
|
||||
}
|
||||
if (lyricResult != 0) {
|
||||
std::fprintf(stderr, "uninstall: could not load lyrics '%s'\n", lyricPath.c_str());
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
std::string videoPath, audioPath, lyricPath;
|
||||
|
||||
if (debugMode) {
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
const std::string a = argv[i];
|
||||
if ((a == "--video" || a == "--audio" || a == "--lyrics") && i + 1 < argc) {
|
||||
const std::string value = argv[++i];
|
||||
if (a == "--video") videoPath = value;
|
||||
else if (a == "--audio") audioPath = value;
|
||||
else if (a == "--lyrics") lyricPath = value;
|
||||
} else if (a == "-h" || a == "--help") {
|
||||
printUsage(argv[0]);
|
||||
return 0;
|
||||
} else {
|
||||
printUsage(argv[0]);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
if (videoPath.empty() || audioPath.empty() || lyricPath.empty()) {
|
||||
std::fprintf(stderr,
|
||||
"error: --video, --audio and --lyrics are all required "
|
||||
"(or set debugMode = false to use the hardcoded defaults).\n");
|
||||
printUsage(argv[0]);
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
videoPath = kDefaultVideoPath;
|
||||
audioPath = kDefaultAudioPath;
|
||||
lyricPath = kDefaultLyricPath;
|
||||
}
|
||||
|
||||
return runShow(videoPath, audioPath, lyricPath);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user