From 18025ba0d00bf37a4a372f1e86720624350c1097 Mon Sep 17 00:00:00 2001 From: My-Responsitories <107370289+My-Responsitories@users.noreply.github.com> Date: Tue, 13 Feb 2024 21:38:06 +0800 Subject: [PATCH 1/3] merge duplicate comments --- danmakuC/ass.pyi | 3 ++- danmakuC/bilibili.py | 3 ++- danmakuC/csrc/ass.cpp | 34 ++++++++++++++++++++++++++++------ danmakuC/niconico.py | 3 ++- 4 files changed, 34 insertions(+), 9 deletions(-) diff --git a/danmakuC/ass.pyi b/danmakuC/ass.pyi index ede26eb..8433055 100644 --- a/danmakuC/ass.pyi +++ b/danmakuC/ass.pyi @@ -11,7 +11,8 @@ class Ass: duration_marquee: float = 5.0, duration_still: float = 5.0, filter: str = "", - reduced: bool = False): ... + reduced: bool = False, + merge_duplicate: bool = False): ... def add_comment(self, progress: float, ctime: int, content: str, font_size: float, mode: int, color: int) -> bool: """add a comment to Ass object, return True if add success""" diff --git a/danmakuC/bilibili.py b/danmakuC/bilibili.py index 3e445cd..c89ab57 100644 --- a/danmakuC/bilibili.py +++ b/danmakuC/bilibili.py @@ -20,10 +20,11 @@ def proto2ass( duration_still: float = 5.0, comment_filter: str = "", reduced: bool = False, + merge_duplicate: bool = False, out_filename: str = "", ) -> Optional[str]: ass = Ass(width, height, reserve_blank, font_face, font_size, alpha, duration_marquee, - duration_still, comment_filter, reduced) + duration_still, comment_filter, reduced, merge_duplicate) if isinstance(proto_file, io.IOBase): proto_file = proto_file.read() diff --git a/danmakuC/csrc/ass.cpp b/danmakuC/csrc/ass.cpp index 5cdad9e..05ea7b5 100644 --- a/danmakuC/csrc/ass.cpp +++ b/danmakuC/csrc/ass.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include #include @@ -168,6 +168,14 @@ int convert_type2(int row, int height, int reserve_blank) { return height - reserve_blank - row; } +void merge_comment(Comment& comment, unsigned int count) { + if (count > 1) { + string tmp = fmt::format(" *{}", count); + comment.content.append(tmp); + comment.max_len += comment.size * tmp.size(); + } +} + class Ass { public: int width; @@ -180,6 +188,7 @@ class Ass { float duration_still; string filter; bool reduced; + bool merge_duplicate; // others vector comments; vector bili_player_size; @@ -187,10 +196,11 @@ class Ass { string head; string body = ""; bool need_clear = false; + unordered_map comments_map; - Ass(int w, int h, int rb, const string& ff, float fs, float a, float dm, float ds, const string& flt, bool rd) : + Ass(int w, int h, int rb, const string& ff, float fs, float a, float dm, float ds, const string& flt, bool rd, bool md) : width(w), height(h), reserve_blank(rb), font_face(ff), font_size(fs), alpha(a), duration_marquee(dm), - duration_still(ds), filter(flt), reduced(rd) { + duration_still(ds), filter(flt), reduced(rd), merge_duplicate(md) { head = fmt::format("[Script Info]\n" "; Script generated by danmakuC (based on Danmaku2ASS)\n" "; https://github.com/HFrost0/danmakuC\n" @@ -224,13 +234,23 @@ class Ass { bool add_comment(float progress, int ctime, const string& content, float fontsize, int mode, int color) { // need clear need_clear = true; + + string cm = ass_escape(content); + if (merge_duplicate) { + auto it = comments_map.find(cm); + if (it != comments_map.end()) { + it->second++; + return false; + } + else + comments_map[cm] = 1; + } // content regex filter if (filter != "" && regex_search(content, regex(filter))) return false; // calculate extra filed - Comment comment = Comment(progress, ctime, content, fontsize, mode, color); + Comment comment = Comment(progress, ctime, cm, fontsize, mode, color); if (comment.mode != 4) { - comment.content = ass_escape(comment.content); comment.size = int(comment.font_size) * font_size / 25.0; vector parts; boost::split(parts, comment.content, boost::is_any_of("\n")); @@ -270,6 +290,8 @@ class Ass { vector> rows(4, vector(height - reserve_blank + 1, nullptr)); for (size_t idx = 0; idx < comments.size(); ++idx) { Comment& c = comments[idx]; + if (merge_duplicate) + merge_comment(c, comments_map[c.content]); if (c.mode != 4) { // not a bilipos int row = 0; int row_max = height - reserve_blank - c.part_size; @@ -372,7 +394,7 @@ namespace py = pybind11; PYBIND11_MODULE(ass, m) { m.doc() = "pybind11 ass extension"; // optional module docstring py::class_(m, "Ass") - .def(py::init()) + .def(py::init()) .def("add_comment", &Ass::add_comment) .def("to_string", &Ass::to_string) .def("write_to_file", &Ass::write_to_file); diff --git a/danmakuC/niconico.py b/danmakuC/niconico.py index 47bef06..88d01ea 100644 --- a/danmakuC/niconico.py +++ b/danmakuC/niconico.py @@ -19,10 +19,11 @@ def proto2ass( duration_still: float = 5.0, comment_filter: str = "", reduced: bool = False, + merge_duplicate: bool = False, out_filename: str = "", ) -> Optional[str]: ass = Ass(width, height, reserve_blank, font_face, font_size, alpha, duration_marquee, - duration_still, comment_filter, reduced) + duration_still, comment_filter, reduced, merge_duplicate) if isinstance(proto_file, bytes): proto_file = io.BytesIO(proto_file) comment = NNDCommentProto() From d7e0add22465442c20645aa001d14e87be39dc5c Mon Sep 17 00:00:00 2001 From: My-Responsitories <107370289+My-Responsitories@users.noreply.github.com> Date: Tue, 13 Feb 2024 21:54:37 +0800 Subject: [PATCH 2/3] support multiple regular expressions --- danmakuC/csrc/ass.cpp | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/danmakuC/csrc/ass.cpp b/danmakuC/csrc/ass.cpp index 05ea7b5..71a7280 100644 --- a/danmakuC/csrc/ass.cpp +++ b/danmakuC/csrc/ass.cpp @@ -186,7 +186,7 @@ class Ass { float alpha; float duration_marquee; float duration_still; - string filter; + // string filter; bool reduced; bool merge_duplicate; // others @@ -196,11 +196,12 @@ class Ass { string head; string body = ""; bool need_clear = false; + vector filters; unordered_map comments_map; Ass(int w, int h, int rb, const string& ff, float fs, float a, float dm, float ds, const string& flt, bool rd, bool md) : width(w), height(h), reserve_blank(rb), font_face(ff), font_size(fs), alpha(a), duration_marquee(dm), - duration_still(ds), filter(flt), reduced(rd), merge_duplicate(md) { + duration_still(ds), reduced(rd), merge_duplicate(md) { head = fmt::format("[Script Info]\n" "; Script generated by danmakuC (based on Danmaku2ASS)\n" "; https://github.com/HFrost0/danmakuC\n" @@ -229,6 +230,13 @@ class Ass { bili_player_size = {891, 589}; // Bilibili player version 2021 (flex) vector target_size{width, height}; zoom_factor = get_zoom_factor(bili_player_size, target_size); + if (!flt.empty()) + { + vector flts; + boost::split(flts, flt, boost::is_any_of("\n")); + for (const string& str : flts) + filters.emplace_back(str); + } } bool add_comment(float progress, int ctime, const string& content, float fontsize, int mode, int color) { @@ -246,14 +254,16 @@ class Ass { comments_map[cm] = 1; } // content regex filter - if (filter != "" && regex_search(content, regex(filter))) - return false; + for (const regex& flt : filters) { + if (regex_search(content, flt)) + return false; + } // calculate extra filed Comment comment = Comment(progress, ctime, cm, fontsize, mode, color); if (comment.mode != 4) { comment.size = int(comment.font_size) * font_size / 25.0; vector parts; - boost::split(parts, comment.content, boost::is_any_of("\n")); + boost::split(parts, content, boost::is_any_of("\n")); comment.part_size = comment.size * parts.size(); int max_len = 0; for (string& p: parts) { From 173ac8e1ad5f4f1b79c4860af1afeb335525b97e Mon Sep 17 00:00:00 2001 From: My-Responsitories <107370289+My-Responsitories@users.noreply.github.com> Date: Wed, 14 Feb 2024 14:39:05 +0800 Subject: [PATCH 3/3] refine code --- danmakuC/csrc/ass.cpp | 75 +++++++++++++++++++++---------------------- 1 file changed, 36 insertions(+), 39 deletions(-) diff --git a/danmakuC/csrc/ass.cpp b/danmakuC/csrc/ass.cpp index 71a7280..f0a4a8b 100644 --- a/danmakuC/csrc/ass.cpp +++ b/danmakuC/csrc/ass.cpp @@ -40,9 +40,9 @@ size_t utf8_len(const string& utf8) { return wstring_convert, char32_t>{}.from_bytes(utf8).size(); } -int find_alternative_row(vector>& rows, Comment& c, int height, int reserve_blank) { +int find_alternative_row(array, 4>& rows, Comment& c, int height) { int res = 0; - for (int row = 0; row < height - reserve_blank - ceil(c.part_size); ++row) { + for (int row = 0; row < height - ceil(c.part_size); ++row) { if (rows[c.mode][row] == nullptr) return row; else if (rows[c.mode][row]->progress < rows[c.mode][res]->progress) @@ -51,23 +51,22 @@ int find_alternative_row(vector>& rows, Comment& c, int height, return res; } -void mark_comment_row(vector>& rows, Comment& c, int row) { +void mark_comment_row(array, 4>& rows, Comment& c, int row) { for (size_t i = row; i < row + ceil(c.part_size) && i < rows[0].size(); ++i) rows[c.mode][i] = &c; } -void unmark_rows(vector>& rows, int mode) { +void unmark_rows(array, 4>& rows, int mode) { for (size_t i = 0; i < rows[mode].size(); ++i) rows[mode][i] = nullptr; } -int test_free_row(vector>& rows, Comment& c, int row, int width, int height, - int reserve_blank, float duration_marquee, float duration_still) { +int test_free_row(array, 4>& rows, Comment& c, int row, int width, int height, + float duration_marquee, float duration_still) { int res = 0; - int row_max = height - reserve_blank; Comment* target_row = nullptr; if (c.mode == 1 || c.mode == 2) { - while (row < row_max && res < c.part_size) { + while (row < height && res < c.part_size) { if (target_row != rows[c.mode][row]) { target_row = rows[c.mode][row]; if (target_row != nullptr && target_row->progress + duration_still > c.progress) @@ -83,7 +82,7 @@ int test_free_row(vector>& rows, Comment& c, int row, int width threshold_time = c.progress - duration_marquee * (1 - width / float(div)); else threshold_time = c.progress - duration_marquee; - while (row < row_max && res < c.part_size) { + while (row < height && res < c.part_size) { if (target_row != rows[c.mode][row]) { target_row = rows[c.mode][row]; if (target_row != nullptr) { @@ -100,7 +99,7 @@ int test_free_row(vector>& rows, Comment& c, int row, int width return res; } -vector get_zoom_factor(vector& source_size, vector& target_size) { +array get_zoom_factor(array& source_size, array& target_size) { float source_aspect = float(source_size[0]) / source_size[1]; float target_aspect = float(target_size[0]) / target_size[1]; if (target_aspect < source_aspect) { // narrower @@ -125,14 +124,15 @@ string ass_escape(string s) { string s2 = boost::replace_all_copy(s, R"(\)", R"(\)" + ZERO_WIDTH_SPACE); // escape "}" and "{" (override block chars) with backslash - s2 = std::regex_replace(s2, std::regex(R"(([}{]))"), R"(\$1)"); + boost::replace_all(s2, "{", R"(\{)"); + boost::replace_all(s2, "}", R"(\})"); // preserve intended spacing at start and end of lines boost::replace_all(s2, "\n", ZERO_WIDTH_SPACE + R"(\N)" + ZERO_WIDTH_SPACE); return ZERO_WIDTH_SPACE + s2 + ZERO_WIDTH_SPACE; } -int clip_byte(float x) { +uint8_t clip_byte(float x) { if (x > 255) return 255; else if (x < 0) return 0; else return round(x); @@ -143,9 +143,9 @@ string convert_color(int RGB, int width = 1280, int height = 576) { return "000000"; else if (RGB == 0xFFFFFF) return "FFFFFF"; - int R = (RGB >> 16) & 0xFF; - int G = (RGB >> 8) & 0xFF; - int B = RGB & 0xFF; + uint8_t R = RGB >> 16; + uint8_t G = RGB >> 8; + uint8_t B = RGB; if (width < 1280 and height < 576) return fmt::format("{:02X}{:02X}{:02X}", B, G, R); else @@ -164,10 +164,6 @@ string convert_progress(float progress) { return fmt::format("{}:{:02d}:{:02d}.{:02d}", hour, minute, second, centsecond); } -int convert_type2(int row, int height, int reserve_blank) { - return height - reserve_blank - row; -} - void merge_comment(Comment& comment, unsigned int count) { if (count > 1) { string tmp = fmt::format(" *{}", count); @@ -191,8 +187,8 @@ class Ass { bool merge_duplicate; // others vector comments; - vector bili_player_size; - vector zoom_factor; + array bili_player_size; + array zoom_factor; string head; string body = ""; bool need_clear = false; @@ -222,13 +218,14 @@ class Ass { "\n" "[Events]\n" "Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text\n", - width, height, font_face, font_size, int(round(1 - alpha)) * 255, + width, height, font_face, font_size, int(round((1 - alpha) * 255)), max(font_size / 25.0, 1.0)); // bili_player_size = {512, 384} // Bilibili player version 2010 // bili_player_size = {540, 384} // Bilibili player version 2012 // bili_player_size = {672, 438} // Bilibili player version 2014 bili_player_size = {891, 589}; // Bilibili player version 2021 (flex) - vector target_size{width, height}; + array target_size{width, height}; + height -= reserve_blank; zoom_factor = get_zoom_factor(bili_player_size, target_size); if (!flt.empty()) { @@ -297,18 +294,18 @@ class Ass { return a.ctime < b.ctime; }); /// 3. find row - vector> rows(4, vector(height - reserve_blank + 1, nullptr)); - for (size_t idx = 0; idx < comments.size(); ++idx) { - Comment& c = comments[idx]; + array, 4> rows; + rows.fill(vector(height + 1, nullptr)); + for (Comment& c : comments) { if (merge_duplicate) merge_comment(c, comments_map[c.content]); if (c.mode != 4) { // not a bilipos int row = 0; - int row_max = height - reserve_blank - c.part_size; + int row_max = height - c.part_size; bool flag = true; while (row <= row_max) { int free_row = test_free_row(rows, c, row, - width, height, reserve_blank, duration_marquee, duration_still); + width, height, duration_marquee, duration_still); if (free_row >= c.part_size) { mark_comment_row(rows, c, row); flag = false; @@ -317,7 +314,7 @@ class Ass { row += free_row || 1; // todo condition is always true? } if (flag && !reduced) { - row = find_alternative_row(rows, c, height, reserve_blank); + row = find_alternative_row(rows, c, height); if (row == 0) unmark_rows(rows, c.mode); mark_comment_row(rows, c, row); @@ -331,45 +328,45 @@ class Ass { } void write_comment(Comment& c, std::ofstream* out_fp = nullptr) { - vector styles; + string styles; float duration; switch (c.mode) { case 1: { - styles.push_back(fmt::format("\\an8\\pos({}, {})", + styles.append(fmt::format("\\an8\\pos({}, {})", width / 2, c.row)); duration = duration_still; break; } case 2: { - styles.push_back(fmt::format("\\an2\\pos({}, {})", - width / 2, convert_type2(c.row, height, reserve_blank))); + styles.append(fmt::format("\\an2\\pos({}, {})", + width / 2, height - c.row)); duration = duration_still; break; } case 3: { - styles.push_back(fmt::format("\\move({2}, {1}, {0}, {1})", + styles.append(fmt::format("\\move({2}, {1}, {0}, {1})", width, c.row, -ceil(c.max_len))); duration = duration_marquee; break; } default: { - styles.push_back(fmt::format("\\move({0}, {1}, {2}, {1})", + styles.append(fmt::format("\\move({0}, {1}, {2}, {1})", width, c.row, -ceil(c.max_len))); duration = duration_marquee; } } float size = c.size - font_size; if (size <= -1 || size >= 1) - styles.push_back(fmt::format("\\fs{:.0f}", c.size)); + styles.append(fmt::format("\\fs{:.0f}", c.size)); if (c.color != 0xFFFFFF) { - styles.push_back(fmt::format("\\c&H{}&", convert_color(c.color))); + styles.append(fmt::format("\\c&H{}&", convert_color(c.color))); if (c.color == 0x000000) - styles.push_back("\\3c&HFFFFFF&"); + styles.append("\\3c&HFFFFFF&"); } string line = fmt::format("Dialogue: 2,{0},{1},danmakuC,,0000,0000,0000,,{{{2}}}{3}\n", convert_progress(c.progress), convert_progress(c.progress + duration), - boost::algorithm::join(styles, ""), + styles, c.content ); if (out_fp == nullptr)