add tool.
This commit is contained in:
5
.clangd
Normal file
5
.clangd
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
CompileFlags:
|
||||||
|
CompilationDatabase: build
|
||||||
|
Add: [
|
||||||
|
"-I/opt/Libraries/boost_1_89_0/include",
|
||||||
|
]
|
||||||
@@ -1,7 +1,21 @@
|
|||||||
cmake_minimum_required(VERSION 3.17)
|
cmake_minimum_required(VERSION 3.17)
|
||||||
|
|
||||||
project(Bilby)
|
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
|
add_executable(Bilby
|
||||||
main.cpp
|
main.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
add_executable(shifter
|
||||||
|
SubtitleShifter.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(shifter
|
||||||
|
PRIVATE Boost::program_options
|
||||||
)
|
)
|
||||||
239
SubtitleShifter.cpp
Normal file
239
SubtitleShifter.cpp
Normal file
@@ -0,0 +1,239 @@
|
|||||||
|
#include <boost/algorithm/string/classification.hpp>
|
||||||
|
#include <boost/algorithm/string/split.hpp>
|
||||||
|
#include <boost/algorithm/string/trim.hpp>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <ostream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
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<std::string> 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<std::filesystem::path> 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);
|
||||||
|
}
|
||||||
|
|
||||||
91
rename.cpp
Normal file
91
rename.cpp
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
#include <filesystem>
|
||||||
|
#include <format>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
// g++ -std=c++20 rename.cpp -o rename
|
||||||
|
|
||||||
|
|
||||||
|
// title 字段需要,可以先随便填
|
||||||
|
constexpr auto Template = R"(
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
|
<episodedetails>
|
||||||
|
<title>黑色五叶草</title>
|
||||||
|
<showtitle>黑色五叶草</showtitle>
|
||||||
|
<season>{}</season>
|
||||||
|
<episode>{}</episode>
|
||||||
|
<original_filename>{}</original_filename>
|
||||||
|
</episodedetails>
|
||||||
|
)";
|
||||||
|
|
||||||
|
struct SeasonRange {
|
||||||
|
int start;
|
||||||
|
int end;
|
||||||
|
int season;
|
||||||
|
};
|
||||||
|
const std::vector<SeasonRange> 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: "<<stem<<std::endl;
|
||||||
|
|
||||||
|
// 提取前导数字部分
|
||||||
|
size_t num_length = 0;
|
||||||
|
while (num_length < stem.length() && std::isdigit(stem[num_length])) {
|
||||||
|
num_length++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (num_length == 0) {
|
||||||
|
std::cerr << "跳过无数字前缀的文件: " << filename << std::endl;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const int episode_number = std::stoi(stem.substr(0, num_length));
|
||||||
|
|
||||||
|
// 确定季度和集数
|
||||||
|
int season = -1;
|
||||||
|
int episode_in_season = -1;
|
||||||
|
for (const auto &range : ranges) {
|
||||||
|
if (episode_number >= 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: "<<filename<< ", episode_number: "<<episode_number<<", episode_in_season: "<<episode_in_season<< std::endl;
|
||||||
|
std::cout << "已生成: " << nfo_path.filename() << std::endl;
|
||||||
|
} catch (const std::exception &e) {
|
||||||
|
std::cerr << "处理文件 " << filename << " 时出错: " << e.what() << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -1,6 +1,41 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
|
base_path=$(pwd)
|
||||||
|
build_path=${base_path}/build
|
||||||
|
libraries_root="/opt/Libraries"
|
||||||
|
|
||||||
|
if command -v cmake >/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(){
|
function changelog(){
|
||||||
current_tag=$(git describe --tags --abbrev=0)
|
current_tag=$(git describe --tags --abbrev=0)
|
||||||
@@ -15,9 +50,12 @@ function main() {
|
|||||||
case $cmd in
|
case $cmd in
|
||||||
changelog)
|
changelog)
|
||||||
changelog
|
changelog
|
||||||
|
;;
|
||||||
|
build)
|
||||||
|
build
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
changelog
|
build
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|||||||
9
resources/wsl2_ssh.ps1
Normal file
9
resources/wsl2_ssh.ps1
Normal file
@@ -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
|
||||||
|
|
||||||
Reference in New Issue
Block a user