// 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. #include "backgroundDisplay.h" #include "lyricResolve.h" #include "musicPlayer.h" #include "timeline.h" #include #include #include #include #include // ---- 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 --audio --lyrics \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 stop{false}; g_timeline.start(); bd::GridSize videoGrid; int videoOffsetX = 0; std::atomic gridReady{false}; bd::PlayVideoOptions vo; vo.loop = true; vo.intensity = 1f; // brightness settings 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); }