From a842d429b7d14b49ce615763fd2912fb58861705 Mon Sep 17 00:00:00 2001 From: amass <168062547@qq.com> Date: Wed, 22 Oct 2025 21:51:15 +0800 Subject: [PATCH] add tool. --- .clangd | 5 + CMakeLists.txt | 14 +++ SubtitleShifter.cpp | 239 +++++++++++++++++++++++++++++++++++++++++ rename.cpp | 91 ++++++++++++++++ resources/build.sh | 40 ++++++- resources/wsl2_ssh.ps1 | 9 ++ 6 files changed, 397 insertions(+), 1 deletion(-) create mode 100644 .clangd create mode 100644 SubtitleShifter.cpp create mode 100644 rename.cpp create mode 100644 resources/wsl2_ssh.ps1 diff --git a/.clangd b/.clangd new file mode 100644 index 0000000..7762c3c --- /dev/null +++ b/.clangd @@ -0,0 +1,5 @@ +CompileFlags: + CompilationDatabase: build + Add: [ + "-I/opt/Libraries/boost_1_89_0/include", + ] \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 7d53e7c..eb0df74 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,21 @@ cmake_minimum_required(VERSION 3.17) project(Bilby) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +find_package(Boost REQUIRED COMPONENTS json program_options) add_executable(Bilby main.cpp +) + +add_executable(shifter + SubtitleShifter.cpp +) + +target_link_libraries(shifter + PRIVATE Boost::program_options ) \ No newline at end of file diff --git a/SubtitleShifter.cpp b/SubtitleShifter.cpp new file mode 100644 index 0000000..aa02656 --- /dev/null +++ b/SubtitleShifter.cpp @@ -0,0 +1,239 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct Subtitle { + std::string layer; + std::string startTime; + std::string endTime; + std::string style; + std::string name; + std::string marginL; + std::string marginR; + std::string marginV; + std::string effect; + std::string text; + std::string originalLine; + bool isDialogue; +}; + +std::size_t findNthCharacter(const std::string &str, char character, int n) { + std::size_t pos = -1; + for (int i = 0; i < n; ++i) { + pos = str.find(character, pos + 1); + if (pos == std::string::npos) { + return std::string::npos; + } + } + return pos; +} + +/** + * @brief 将时间字符串转换为毫秒 + * + * @param timeStr + * @return int + */ +int timeToMilliseconds(const std::string &timeStr) { + int hours, minutes, seconds, hundredths; + char colon, dot; + + std::istringstream iss(timeStr); + iss >> hours >> colon >> minutes >> colon >> seconds >> dot >> hundredths; + + return (hours * 3600 + minutes * 60 + seconds) * 1000 + hundredths * 10; +} + +/** + * @brief 将毫秒转换为时间字符串 + * + * @param ms + * @return std::string + */ +std::string millisecondsToTime(int ms) { + if (ms < 0) ms = 0; + + int totalSeconds = ms / 1000; + int hundredths = (ms % 1000) / 10; + int hours = totalSeconds / 3600; + int minutes = (totalSeconds % 3600) / 60; + int seconds = totalSeconds % 60; + + std::ostringstream oss; + oss << hours << ":" << (minutes < 10 ? "0" : "") << minutes << ":" << (seconds < 10 ? "0" : "") << seconds << "." + << (hundredths < 10 ? "0" : "") << hundredths; + + return oss.str(); +} + +/** + * @brief 偏移单个字幕的时间 + * + * @param sub + * @param offsetMs + * @return Subtitle + */ +Subtitle shiftSubtitle(const Subtitle &sub, int offsetMs) { + if (!sub.isDialogue) { + return sub; + } + + Subtitle shifted = sub; + + // 转换开始时间并偏移 + int startMs = timeToMilliseconds(sub.startTime); + startMs += offsetMs; + shifted.startTime = millisecondsToTime(startMs); + + // 转换结束时间并偏移 + int endMs = timeToMilliseconds(sub.endTime); + endMs += offsetMs; + shifted.endTime = millisecondsToTime(endMs); + + // 重新构建行 + std::ostringstream oss; + oss << "Dialogue: " << shifted.layer << "," << shifted.startTime << "," << shifted.endTime << "," << shifted.style + << "," << shifted.name << "," << shifted.marginL << "," << shifted.marginR << "," << shifted.marginV << "," + << shifted.effect << "," << shifted.text; + + shifted.originalLine = oss.str(); + + return shifted; +} + +Subtitle parseLine(const std::string &line) { + constexpr std::string_view dialogue = "Dialogue:"; + Subtitle sub; + sub.originalLine = line; + sub.isDialogue = false; + + // 检查是否是Dialogue行 + + if (line.find(dialogue) == 0) { + sub.isDialogue = true; + + std::size_t textStart = findNthCharacter(line, ',', 9); + if ((textStart != std::string::npos) && (textStart < line.length())) { + sub.text = line.substr(textStart + 1); + + std::string attr = line.substr(dialogue.length(), textStart - dialogue.length()); + boost::algorithm::trim(attr); + + std::vector splits; + boost::algorithm::split(splits, attr, boost::is_any_of(","), boost::algorithm::token_compress_off); + sub.layer = splits[0]; + sub.startTime = splits[1]; + sub.endTime = splits[2]; + sub.style = splits[3]; + sub.name = splits[4]; + sub.marginL = splits[5]; + sub.marginR = splits[6]; + sub.marginV = splits[7]; + sub.effect = splits[8]; + } + } + + return sub; +} + +std::string generateTargetFilename(const std::string &filename, const std::string &prefix, const std::string &suffix) { + auto start = filename.find(prefix); + auto end = filename.find(suffix); + return filename.substr(start + prefix.length(), end - (start + prefix.length())); +} + +bool processASSFile(const std::filesystem::path &inputPath, std::filesystem::path &outputPath, int offsetMs) { + std::ifstream ifs(inputPath); + std::ofstream ofs(outputPath); + + if (!ifs.is_open()) { + std::cerr << "无法打开输入文件: " << inputPath << std::endl; + return false; + } + + if (!ofs.is_open()) { + std::cerr << "无法创建输出文件: " << outputPath << std::endl; + return false; + } + std::string line; + + while (std::getline(ifs, line)) { + Subtitle sub = parseLine(line); + if (sub.isDialogue) { + Subtitle shifted = shiftSubtitle(sub, offsetMs); + ofs << shifted.originalLine << std::endl; + } else { + ofs << line << std::endl; + } + } + + return true; +} + +void processDirectory(const std::filesystem::path &source, const std::filesystem::path &target, int offsetMs) { + if (!std::filesystem::exists(source)) { + std::cerr << "错误: 源目录不存在: " << source << std::endl; + return; + } + + if (!std::filesystem::exists(target)) { + std::cout << "创建目标目录: " << target << std::endl; + if (!std::filesystem::create_directories(target)) { + std::cerr << "错误: 无法创建目标目录: " << target << std::endl; + return; + } + } + + std::vector assFiles; + try { + for (const auto &entry : std::filesystem::directory_iterator(source)) { + if (entry.is_regular_file() && entry.path().extension() == ".ass") { + assFiles.push_back(entry.path()); + } + } + } catch (const std::filesystem::filesystem_error &ex) { + std::cerr << "文件系统错误: " << ex.what() << std::endl; + return; + } + + if (assFiles.empty()) { + std::cout << "在目录 " << source << " 中未找到.ass文件" << std::endl; + return; + } + + std::cout << "找到 " << assFiles.size() << " 个.ass文件" << std::endl; + + for (const auto &sourceFile : assFiles) { + + // 生成目标文件名 + std::string targetFilename = generateTargetFilename( + sourceFile.filename().string(), "MONSTER.TV.2004.DVDRip-Hi.x264.AC3.1280.EP", "-nezumi.zh_CN"); + + // 确保文件扩展名为.ass + if (std::filesystem::path(targetFilename).extension() != ".ass") { + targetFilename += ".ass"; + } + + std::filesystem::path targetFile = target / targetFilename; + // std::cout << sourceFile << " --> " << targetFile << std::endl; + + // 处理文件 + processASSFile(sourceFile, targetFile, offsetMs); + } +} + +// 1. 找出指定目录下后缀为 `.ass` 下的文件 +// 2. 如果目标文件夹不存在,则创建目标文件夹 +// 3. 对每个源文件,提取文件名,然后按照模板提出字串,和目标文件夹拼凑成新的目标文件 +int main(int argc, char const *argv[]) { + processDirectory("/mnt/e/Downloads/[X2] MONSTER 2004 [Chs Subtitle]", + "/mnt/e/Downloads/[X2] MONSTER 2004 [Chs Subtitle]/output",700); +} + diff --git a/rename.cpp b/rename.cpp new file mode 100644 index 0000000..d6ac23f --- /dev/null +++ b/rename.cpp @@ -0,0 +1,91 @@ +#include +#include +#include +#include +#include + +// g++ -std=c++20 rename.cpp -o rename + + +// title 字段需要,可以先随便填 +constexpr auto Template = R"( + + + 黑色五叶草 + 黑色五叶草 + {} + {} + {} + +)"; + +struct SeasonRange { + int start; + int end; + int season; +}; +const std::vector ranges = { + {1, 170, 1}, // 001-048 season 1 48集 +}; + +// 文件夹内有 001.mkv-328.mkv 文件,使用C++ STL filesystem 根据上面的对应关系生成 001.nfo-328.nfo, 使用 std::format 传入字符串 +// Template 输出最终内容写入对应的nfo文件 + +int main(int argc, char const *argv[]) { + std::cout << "this is a simple rename app." << std::endl; + for (const auto &entry : std::filesystem::directory_iterator(".")) { + if (!entry.is_regular_file()) continue; + + const auto &path = entry.path(); + if (path.extension() != ".mkv") continue; + + const std::string filename = path.filename().string(); + const std::string stem = path.stem().string(); + std::cout <<"stem: "<= range.start && episode_number <= range.end) { + season = range.season; + episode_in_season = episode_number - range.start + 1; + break; + } + } + + if (season == -1) { + std::cerr << "未找到对应的季度: " << episode_number << std::endl; + continue; + } + + // 生成XML内容 + std::string xml_content = std::format(Template, season, episode_in_season, filename); + + // 写入.nfo文件 + std::filesystem::path nfo_path = path; + nfo_path.replace_extension(".nfo"); + std::ofstream out_file(nfo_path); + out_file << xml_content; + std::cout <<"filename: "</dev/null 2>&1; then + cmake_exe=cmake +else + cmake_exe=/opt/Qt/Tools/CMake/bin/cmake +fi + +function cmake_scan() { + echo "scanning the project..." + if [ ! -d ${build_path} ]; then + mkdir -p ${build_path} + fi + cd ${build_path} + ${cmake_exe} -G Ninja -S ${base_path} -B ${build_path} \ + -DCMAKE_BUILD_TYPE=Debug \ + -DMbedTLS_DIR=${libraries_root}/mbedtls-3.6.5/lib/cmake/MbedTLS \ + -Dnng_DIR=${libraries_root}/nng-1.11/lib/cmake/nng \ + -DBoost_DIR=${libraries_root}/boost_1_89_0/lib/cmake/Boost-1.89.0 +} + +function build() { + echo "building the project..." + if [ ! -f "${build_path}/CMakeCache.txt" ]; then + cmake_scan + fi + if [ $? -ne 0 ]; then + exit 1 + fi + ${cmake_exe} --build ${build_path} --target all + if [ $? -ne 0 ]; then + exit 1 + fi +} function changelog(){ current_tag=$(git describe --tags --abbrev=0) @@ -15,9 +50,12 @@ function main() { case $cmd in changelog) changelog + ;; + build) + build ;; *) - changelog + build ;; esac } diff --git a/resources/wsl2_ssh.ps1 b/resources/wsl2_ssh.ps1 new file mode 100644 index 0000000..abf1f9d --- /dev/null +++ b/resources/wsl2_ssh.ps1 @@ -0,0 +1,9 @@ +wsl -u root -e sh -c "service ssh start" + +$wsl_ip = (wsl hostname -I).trim() +netsh interface portproxy delete v4tov4 listenport=1022 +netsh interface portproxy add v4tov4 listenport=1022 connectport=1022 connectaddress=$wsl_ip + +netsh interface portproxy delete v4tov4 listenport=2049 +netsh interface portproxy add v4tov4 listenport=2049 connectport=2049 connectaddress=$wsl_ip +