├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── docs-feedback.md │ ├── feature_request.md │ └── help-wanted.md └── pull_request_template.md ├── .gitignore ├── LICENSE ├── README-en.md ├── README.md ├── assets ├── headerDark.svg └── headerLight.svg ├── autosv ├── __init__.py ├── autosv.py ├── calculate │ ├── __init__.py │ ├── selection.py │ ├── sliding_cpu.py │ └── sliding_gpu.py ├── cli.py ├── log │ ├── __init__.py │ └── logger.py └── slice │ ├── __init__.py │ └── slice_video.py ├── pyproject.toml ├── requirements.txt └── test ├── sample.ass ├── sample.mp4 └── sample2.ass /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve this project. 4 | title: "[BUG] " 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Describe the bug 11 | (A clear and concise description of what the bug is.) 12 | 13 | ## How To Reproduce 14 | Steps to reproduce the behavior: 15 | 1. Config/File changes: ... 16 | 2. Run command: ... 17 | 3. See error: ... 18 | 19 | ## Expected behavior 20 | (A clear and concise description of what you expected to happen.) 21 | 22 | ## Screenshots 23 | (If applicable, add screenshots to help explain your problem.) 24 | 25 | ## Environment Information 26 | - Operating System: [e.g. Ubuntu 20.04.5 LTS] 27 | - Python Version: [e.g. Python 3.9.15] 28 | - Driver & CUDA Version: [e.g. Driver 470.103.01 & CUDA 11.4] 29 | - Error Messages and Logs: [If applicable, provide any error messages or relevant log outputs] 30 | 31 | ## Additional context 32 | (Add any other context about the problem here.) 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/docs-feedback.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Docs feedback 3 | about: Improve documentation about this project. 4 | title: "[Docs] " 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Documentation Reference 11 | (Path/Link to the documentation file) 12 | 13 | ## Feedback on documentation 14 | (Your suggestions to the documentation. e.g., accuracy, complex explanations, structural organization, practical examples, technical reliability, and consistency) 15 | 16 | ## Additional context 17 | (Add any other context or screenshots about the documentation here.) 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project. 4 | title: "[Feature] " 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Is your feature request related to a problem? Please describe. 11 | (A clear and concise description of what the problem is.) 12 | 13 | ## Describe the solution you'd like 14 | (A clear and concise description of what you want to happen.) 15 | 16 | ## Describe alternatives you've considered 17 | (A clear and concise description of any alternative solutions or features you've considered.) 18 | 19 | ## Additional context 20 | (Add any other context or screenshots about the feature request here.) 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/help-wanted.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Help wanted 3 | about: Want help from this project team. 4 | title: "[Help] " 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Problem Overview 11 | (Briefly and clearly describe the issue you're facing and seeking help with.) 12 | 13 | ## Steps Taken 14 | (Detail your attempts to resolve the issue, including any relevant steps or processes.) 15 | 1. Config/File changes: ... 16 | 2. Run command: ... 17 | 3. See errors: ... 18 | 19 | ## Expected Outcome 20 | (A clear and concise description of what you expected to happen.) 21 | 22 | ## Screenshots 23 | (If applicable, add screenshots to help explain your problem.) 24 | 25 | ## Environment Information 26 | - Operating System: [e.g. Ubuntu 20.04.5 LTS] 27 | - Python Version: [e.g. Python 3.9.15] 28 | - Driver & CUDA Version: [e.g. Driver 470.103.01 & CUDA 11.4] 29 | - Error Messages and Logs: [If applicable, provide any error messages or relevant log outputs] 30 | 31 | ## Additional context 32 | (Add any other context about the problem here.) 33 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 2 | ## Description 3 | 4 | [Please describe the background, purpose, changes made, and how to test this PR] 5 | 6 | ## Related Issues 7 | 8 | [List the issue numbers related to this PR] 9 | 10 | ## Changes Proposed 11 | 12 | - [ ] change1 13 | - [ ] ... 14 | 15 | ## Who Can Review? 16 | 17 | [Please use the '@' symbol to mention any community member who is free to review the PR once the tests have passed. Feel free to tag members or contributors who might be interested in your PR.] 18 | 19 | ## TODO 20 | 21 | - [ ] task1 22 | - [ ] ... 23 | 24 | ## Checklist 25 | 26 | - [ ] Code has been reviewed 27 | - [ ] Code complies with the project's code standards and best practices 28 | - [ ] Code has passed all tests 29 | - [ ] Code does not affect the normal use of existing features 30 | - [ ] Code has been commented properly 31 | - [ ] Documentation has been updated (if applicable) 32 | - [ ] Demo/checkpoint has been attached (if applicable) 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/intellij+all,python,pycharm+all,macos,windows 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=intellij+all,python,pycharm+all,macos,windows 3 | 4 | ### Intellij+all ### 5 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 6 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 7 | 8 | # User-specific stuff 9 | .idea/**/workspace.xml 10 | .idea/**/tasks.xml 11 | .idea/**/usage.statistics.xml 12 | .idea/**/dictionaries 13 | .idea/**/shelf 14 | 15 | # Generated files 16 | .idea/**/contentModel.xml 17 | 18 | # Sensitive or high-churn files 19 | .idea/**/dataSources/ 20 | .idea/**/dataSources.ids 21 | .idea/**/dataSources.local.xml 22 | .idea/**/sqlDataSources.xml 23 | .idea/**/dynamic.xml 24 | .idea/**/uiDesigner.xml 25 | .idea/**/dbnavigator.xml 26 | 27 | # Gradle 28 | .idea/**/gradle.xml 29 | .idea/**/libraries 30 | 31 | # Gradle and Maven with auto-import 32 | # When using Gradle or Maven with auto-import, you should exclude module files, 33 | # since they will be recreated, and may cause churn. Uncomment if using 34 | # auto-import. 35 | # .idea/artifacts 36 | # .idea/compiler.xml 37 | # .idea/jarRepositories.xml 38 | # .idea/modules.xml 39 | # .idea/*.iml 40 | # .idea/modules 41 | # *.iml 42 | # *.ipr 43 | 44 | # CMake 45 | cmake-build-*/ 46 | 47 | # Mongo Explorer plugin 48 | .idea/**/mongoSettings.xml 49 | 50 | # File-based project format 51 | *.iws 52 | 53 | # IntelliJ 54 | out/ 55 | 56 | # mpeltonen/sbt-idea plugin 57 | .idea_modules/ 58 | 59 | # JIRA plugin 60 | atlassian-ide-plugin.xml 61 | 62 | # Cursive Clojure plugin 63 | .idea/replstate.xml 64 | 65 | # Crashlytics plugin (for Android Studio and IntelliJ) 66 | com_crashlytics_export_strings.xml 67 | crashlytics.properties 68 | crashlytics-build.properties 69 | fabric.properties 70 | 71 | # Editor-based Rest Client 72 | .idea/httpRequests 73 | 74 | # Android studio 3.1+ serialized cache file 75 | .idea/caches/build_file_checksums.ser 76 | 77 | ### Intellij+all Patch ### 78 | # Ignores the whole .idea folder and all .iml files 79 | # See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360 80 | 81 | .idea/ 82 | 83 | # Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 84 | 85 | *.iml 86 | modules.xml 87 | .idea/misc.xml 88 | *.ipr 89 | 90 | # Sonarlint plugin 91 | .idea/sonarlint 92 | 93 | ### macOS ### 94 | # General 95 | .DS_Store 96 | .AppleDouble 97 | .LSOverride 98 | 99 | # Icon must end with two \r 100 | Icon 101 | 102 | 103 | # Thumbnails 104 | ._* 105 | 106 | # Files that might appear in the root of a volume 107 | .DocumentRevisions-V100 108 | .fseventsd 109 | .Spotlight-V100 110 | .TemporaryItems 111 | .Trashes 112 | .VolumeIcon.icns 113 | .com.apple.timemachine.donotpresent 114 | 115 | # Directories potentially created on remote AFP share 116 | .AppleDB 117 | .AppleDesktop 118 | Network Trash Folder 119 | Temporary Items 120 | .apdisk 121 | 122 | ### PyCharm+all ### 123 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 124 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 125 | 126 | # User-specific stuff 127 | 128 | # Generated files 129 | 130 | # Sensitive or high-churn files 131 | 132 | # Gradle 133 | 134 | # Gradle and Maven with auto-import 135 | # When using Gradle or Maven with auto-import, you should exclude module files, 136 | # since they will be recreated, and may cause churn. Uncomment if using 137 | # auto-import. 138 | # .idea/artifacts 139 | # .idea/compiler.xml 140 | # .idea/jarRepositories.xml 141 | # .idea/modules.xml 142 | # .idea/*.iml 143 | # .idea/modules 144 | # *.iml 145 | # *.ipr 146 | 147 | # CMake 148 | 149 | # Mongo Explorer plugin 150 | 151 | # File-based project format 152 | 153 | # IntelliJ 154 | 155 | # mpeltonen/sbt-idea plugin 156 | 157 | # JIRA plugin 158 | 159 | # Cursive Clojure plugin 160 | 161 | # Crashlytics plugin (for Android Studio and IntelliJ) 162 | 163 | # Editor-based Rest Client 164 | 165 | # Android studio 3.1+ serialized cache file 166 | 167 | ### PyCharm+all Patch ### 168 | # Ignores the whole .idea folder and all .iml files 169 | # See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360 170 | 171 | 172 | # Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 173 | 174 | 175 | # Sonarlint plugin 176 | 177 | ### Python ### 178 | # Byte-compiled / optimized / DLL files 179 | __pycache__/ 180 | *.py[cod] 181 | *$py.class 182 | 183 | # C extensions 184 | *.so 185 | 186 | # Distribution / packaging 187 | .Python 188 | build/ 189 | develop-eggs/ 190 | dist/ 191 | downloads/ 192 | eggs/ 193 | .eggs/ 194 | parts/ 195 | sdist/ 196 | var/ 197 | wheels/ 198 | pip-wheel-metadata/ 199 | share/python-wheels/ 200 | *.egg-info/ 201 | .installed.cfg 202 | *.egg 203 | MANIFEST 204 | 205 | # PyInstaller 206 | # Usually these files are written by a python script from a template 207 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 208 | *.manifest 209 | *.spec 210 | 211 | # Installer logs 212 | pip-log.txt 213 | pip-delete-this-directory.txt 214 | 215 | # Unit test / coverage reports 216 | htmlcov/ 217 | .tox/ 218 | .nox/ 219 | .coverage 220 | .coverage.* 221 | .cache 222 | nosetests.xml 223 | coverage.xml 224 | *.cover 225 | *.py,cover 226 | .hypothesis/ 227 | .pytest_cache/ 228 | pytestdebug.log 229 | 230 | # Translations 231 | *.mo 232 | *.pot 233 | 234 | # Django stuff: 235 | *.log 236 | local_settings.py 237 | db.sqlite3 238 | db.sqlite3-journal 239 | 240 | # Flask stuff: 241 | instance/ 242 | .webassets-cache 243 | 244 | # Scrapy stuff: 245 | .scrapy 246 | 247 | # Sphinx documentation 248 | docs/_build/ 249 | doc/_build/ 250 | 251 | # PyBuilder 252 | target/ 253 | 254 | # Jupyter Notebook 255 | .ipynb_checkpoints 256 | 257 | # IPython 258 | profile_default/ 259 | ipython_config.py 260 | 261 | # pyenv 262 | .python-version 263 | 264 | # pipenv 265 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 266 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 267 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 268 | # install all needed dependencies. 269 | #Pipfile.lock 270 | 271 | # poetry 272 | #poetry.lock 273 | 274 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 275 | __pypackages__/ 276 | 277 | # Celery stuff 278 | celerybeat-schedule 279 | celerybeat.pid 280 | 281 | # SageMath parsed files 282 | *.sage.py 283 | 284 | # Environments 285 | # .env 286 | .env/ 287 | .venv/ 288 | env/ 289 | venv/ 290 | ENV/ 291 | env.bak/ 292 | venv.bak/ 293 | pythonenv* 294 | 295 | # Spyder project settings 296 | .spyderproject 297 | .spyproject 298 | 299 | # Rope project settings 300 | .ropeproject 301 | 302 | # mkdocs documentation 303 | /site 304 | 305 | # mypy 306 | .mypy_cache/ 307 | .dmypy.json 308 | dmypy.json 309 | 310 | # Pyre type checker 311 | .pyre/ 312 | 313 | # pytype static type analyzer 314 | .pytype/ 315 | 316 | # operating system-related files 317 | *.DS_Store #file properties cache/storage on macOS 318 | Thumbs.db #thumbnail cache on Windows 319 | 320 | # profiling data 321 | .prof 322 | 323 | 324 | ### Windows ### 325 | # Windows thumbnail cache files 326 | Thumbs.db 327 | Thumbs.db:encryptable 328 | ehthumbs.db 329 | ehthumbs_vista.db 330 | 331 | # Dump file 332 | *.stackdump 333 | 334 | # Folder config file 335 | [Dd]esktop.ini 336 | 337 | # Recycle Bin used on file shares 338 | $RECYCLE.BIN/ 339 | 340 | # Windows Installer files 341 | *.cab 342 | *.msi 343 | *.msix 344 | *.msm 345 | *.msp 346 | 347 | # Windows shortcuts 348 | *.lnk 349 | 350 | # End of https://www.toptal.com/developers/gitignore/api/intellij+all,python,pycharm+all,macos,windows 351 | 352 | printdens.py -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 John Howe 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README-en.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | auto-slice-video 5 | 6 | 7 | **Auto slice the highlight shorts** based on the density of danmaku. 8 | 9 | English | [简体中文](./README-zh.md) 10 | 11 |
12 | 13 | ## Introduction 14 | 15 | > If you think the project is good, welcome ⭐ also welcome PR cooperation, if you have any questions, please raise an issue for discussion. 16 | 17 | A video automatic slicing tool that supports GPU acceleration calculation, command line usage, and API usage. 18 | 19 | ## Features 20 | 21 | - Detect the dense period of danmaku based on the sliding window algorithm. 22 | - Slice the video based on the density of danmaku. 23 | - Support GPU accelerated calculation.([Automatically choose whether to use GPU acceleration](#why-i-cannot-use-the-gpu-acceleration)) 24 | - Support custom quantity slicing videos. 25 | - Support custom slice duration. 26 | - Add detailed log information. 27 | - Support cli usage and api usage. 28 | 29 | ## Demo 30 | 31 | ![](https://cdn.jsdelivr.net/gh/timerring/scratchpad2023/2024/2025-03-25-18-27-58.gif) 32 | 33 | As shown above, extract 3 highlight videos from a video, each video is 300 seconds long, and the maximum overlap is 60 seconds. Then calculate and slice out 3 highlight videos. The format is `xxxs_original video name`, `xxx` represents the starting time of the slice in the original video, which is convenient for the user to locate. 34 | 35 | ## Installation 36 | 37 | To use this tool, you need to install ffmpeg first. 38 | 39 | - Windows: `choco install ffmpeg` (via [Chocolatey](https://chocolatey.org/)) or other methods. 40 | - macOS: `brew install ffmpeg` (via [Homebrew](https://brew.sh/)). 41 | - Linux: `sudo apt install ffmpeg` (Debian/Ubuntu). 42 | 43 | More OS please refer to the [official website](https://ffmpeg.org/download.html). 44 | 45 | Then install the `autosv` package. 46 | 47 | ```bash 48 | pip install autosv 49 | ``` 50 | 51 | ## Usage 52 | 53 | ### cli usage 54 | 55 | ```bash 56 | # eg. The default parameters are shown in autosv -h 57 | autosv -a sample.ass -v sample.mp4 58 | autosv -a sample.ass -v sample.mp4 -d 300 -n 3 --overlap 60 --step 1 59 | autosv -h 60 | # optional arguments: 61 | # -h, --help show this help message and exit 62 | # -V, --version Print version information 63 | # -a ASS, --ass ASS The input ass file of the danmaku 64 | # -v VIDEO, --video VIDEO 65 | # The input video file 66 | # -d DURATION, --duration DURATION 67 | # The duration(seconds) of the sliced highlight video, default is 60 68 | # -n TOP_N, --top_n TOP_N 69 | # The number of the top dense periods to return, default is 1 70 | # --overlap OVERLAP The overlapped(seconds) between the sliced highlight videos, default is 30 71 | # --step STEP The step(seconds) of the sliding window, default is 1 72 | ``` 73 | 74 | ### api usage 75 | 76 | ```python 77 | from autosv import slice_video_by_danmaku 78 | # The default parameters are the same as the cli usage 79 | slice_video_by_danmaku(ass_path, video_path, duration=300, top_n=3, max_overlap=60, step=1) 80 | ``` 81 | 82 | ## common issues 83 | 84 | ### What is the difference between cpu and gpu implementation? 85 | 86 | Generally speaking, the gpu implementation is faster and more efficient than the cpu implementation due to the parallel computing. In my practice, when the input data is around 30k(Try `test/sample2.ass`), the gpu implementation only takes 2 seconds, while the cpu implementation takes 33 seconds, which is 16.5 times faster with only 55 MB VRAM occupied. 87 | 88 | ### Why I cannot use the gpu acceleration? 89 | 90 | The autosv will detect whether the cuda is available on the machine via `nvcc -V`, if your machine has nvidia gpu, please make sure your driver is installed and the cuda is available. Meanwhile, make sure you have installed the `numba` and `numpy` with the right version. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | auto-slice-video 5 | 6 | 7 | 基于弹幕密度的高能片段自动切片机 8 | 9 | [English](./README-en.md) | 简体中文 10 | 11 |
12 | 13 | ## 简介 14 | 15 | > 如果您觉得项目不错,欢迎 ⭐ 也欢迎 PR 合作,如果有任何疑问,欢迎提 issue 交流。 16 | 17 | 可自定义切片数量和时长的视频自动切片工具,支持 GPU 加速计算,支持命令行使用和 API 使用。 18 | 19 | ## 功能 20 | 21 | - 基于滑动窗口算法检测弹幕密集的时间段 22 | - 根据弹幕密度自动切片视频 23 | - 支持 Nvidia GPU 加速计算([自动选择是否使用GPU加速](#为什么我不能使用gpu加速)) 24 | - 支持自定义数量的视频切片 25 | - 支持自定义切片时长 26 | - 添加详细的日志信息 27 | - 支持命令行使用和API使用 28 | 29 | ## 效果展示 30 | 31 | ![](https://cdn.jsdelivr.net/gh/timerring/scratchpad2023/2024/2025-03-25-18-27-58.gif) 32 | 33 | 如上,对一段视频提取 3 条高能片段,每个片段 300 秒,允许最大重叠 60 秒。然后计算并且切分出了 3 条高能片段。形式为 `xxxs_原始视频名称`,`xxx` 代表该切片在原始视频中的起始时间(秒),方便用户定位。 34 | 35 | ## 安装 36 | 37 | 使用此工具前,您需要先安装ffmpeg: 38 | 39 | - Windows: `choco install ffmpeg`(通过[Chocolatey](https://chocolatey.org/))或其他方法 40 | - macOS: `brew install ffmpeg`(通过[Homebrew](https://brew.sh/)) 41 | - Linux: `sudo apt install ffmpeg`(Debian/Ubuntu) 42 | 43 | 更多操作系统安装 ffmpeg 请参考[官方网站](https://ffmpeg.org/download.html)。 44 | 45 | 然后安装 `autosv`: 46 | 47 | ```bash 48 | pip install autosv 49 | ``` 50 | 51 | ## 使用方法 52 | 53 | ### 命令行使用 54 | 55 | ```bash 56 | # eg. 默认参数见 autosv -h 57 | autosv -a sample.ass -v sample.mp4 58 | autosv -a sample.ass -v sample.mp4 -d 300 -n 3 --overlap 60 --step 1 59 | autosv -h 60 | # optional arguments: 61 | # -h, --help show this help message and exit 62 | # -V, --version Print version information 63 | # -a ASS, --ass ASS The input ass file of the danmaku 64 | # -v VIDEO, --video VIDEO 65 | # The input video file 66 | # -d DURATION, --duration DURATION 67 | # The duration(seconds) of the sliced highlight video, default is 60 68 | # -n TOP_N, --top_n TOP_N 69 | # The number of the top dense periods to return, default is 1 70 | # --overlap OVERLAP The overlapped(seconds) between the sliced highlight videos, default is 30 71 | # --step STEP The step(seconds) of the sliding window, default is 1 72 | ``` 73 | 74 | ### API使用 75 | 76 | ```python 77 | from autosv import slice_video_by_danmaku 78 | # 基本参数同上 79 | slice_video_by_danmaku(ass_path, video_path, duration=300, top_n=3, max_overlap=60, step=1) 80 | ``` 81 | 82 | ## 常见问题 83 | 84 | ### GPU 和 CPU 实现有什么区别? 85 | 86 | 一般来说,当输入计算数据较大时,由于 GPU 是并行计算,因此GPU 计算比 CPU 计算更快且更高效。在我的实测中,当输入数据规模达到3万多条时 (见`test/sample2.ass`),GPU 计算仅用 2 秒,而 CPU 计算用 33 秒,GPU 实现速度是 CPU 实现的 16.5 倍,并且仅占用 55 MB 的显存。 87 | 88 | ### 为什么我不能使用 GPU 加速? 89 | 90 | `autosv` 会通过 `nvcc -V` 检测机器上是否可用cuda,如果您的机器有NVIDIA GPU,请确保您的驱动已安装且cuda可用。同时,确保您已安装正确版本的`numba`和`numpy`。 -------------------------------------------------------------------------------- /autosv/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2025 auto-slice-video 2 | 3 | from .autosv import slice_video_by_danmaku 4 | 5 | __all__ = ["slice_video_by_danmaku"] 6 | -------------------------------------------------------------------------------- /autosv/autosv.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2025 auto-slice-video 2 | 3 | import os 4 | from .calculate.selection import find_dense_periods 5 | from .slice.slice_video import slice_video 6 | from .log.logger import Log 7 | 8 | 9 | def parse_time(time_str): 10 | """Convert ASS time format to seconds with milliseconds.""" 11 | h, m, s = time_str.split(":") 12 | s, ms = s.split(".") 13 | return int(h) * 3600 + int(m) * 60 + int(s) + int(ms) / 1000 14 | 15 | 16 | def extract_timestamps(file_path): 17 | """Extract dialogue start times from the ASS file.""" 18 | timestamps = [] 19 | with open(file_path, "r", encoding="utf-8") as file: 20 | for line in file: 21 | if line.startswith("Dialogue:"): 22 | parts = line.split(",") 23 | start_time = parse_time(parts[1].strip()) 24 | timestamps.append(start_time) 25 | return timestamps 26 | 27 | 28 | def slice_video_by_danmaku( 29 | ass_path, video_path, duration=60, top_n=1, max_overlap=30, step=1 30 | ): 31 | """ 32 | Slice the video by the dense periods of danmaku. 33 | 34 | Args: 35 | ass_path: The path to the ASS file. 36 | video_path: The path to the video file. 37 | duration: The duration of the slice. 38 | top_n: The number of top dense periods to return. 39 | max_overlap: The maximum allowed overlap between periods (in seconds). 40 | step: The step size for sliding window (in seconds). 41 | """ 42 | autosv_log = Log("autosv") 43 | autosv_log.info("autosv v0.0.3") 44 | autosv_log.info("https://github.com/timerring/auto-slice-video") 45 | output_folder = os.path.abspath(os.path.dirname(video_path)) 46 | video_name = os.path.basename(video_path) 47 | timestamps = extract_timestamps(ass_path) 48 | dense_periods = find_dense_periods( 49 | autosv_log, timestamps, duration, top_n, max_overlap, step 50 | ) 51 | autosv_log.info("The dense periods and their count are:") 52 | slices_path = [] 53 | for period in dense_periods: 54 | autosv_log.info( 55 | f"Start from {period[0]} to {period[0] + duration} seconds with the count is {period[1]}" 56 | ) 57 | slice_video( 58 | video_path, 59 | f"{output_folder}/{period[0]}s_{video_name}", 60 | period[0], 61 | duration, 62 | ) 63 | autosv_log.info(f"Slice the {output_folder}/{period[0]}s_{video_name} done.") 64 | slices_path.append(f"{output_folder}/{period[0]}s_{video_name}") 65 | return slices_path 66 | 67 | 68 | if __name__ == "__main__": 69 | slice_video_by_danmaku("./test/sample.ass", "./test/sample.mp4", 300, 3, 60, 1) 70 | -------------------------------------------------------------------------------- /autosv/calculate/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timerring/auto-slice-video/57f4b59d58f827b78d6aa3ef4955c4f42f21b792/autosv/calculate/__init__.py -------------------------------------------------------------------------------- /autosv/calculate/selection.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2025 auto-slice-video 2 | 3 | import subprocess 4 | from .sliding_cpu import find_dense_periods_cpu 5 | 6 | 7 | def check_cuda_available(): 8 | """Check if CUDA is available by testing nvcc command.""" 9 | try: 10 | subprocess.run( 11 | ["nvcc", "-V"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE 12 | ) 13 | return True 14 | except (subprocess.SubprocessError, FileNotFoundError): 15 | return False 16 | 17 | 18 | USE_GPU = check_cuda_available() 19 | if USE_GPU: 20 | try: 21 | from .sliding_gpu import find_dense_periods_gpu 22 | except ImportError: 23 | USE_GPU = False 24 | 25 | 26 | def find_dense_periods(log, timestamps, window_size, top_n, max_overlap, step): 27 | """Find dense periods using either GPU or CPU implementation based on GPU availability.""" 28 | if USE_GPU: 29 | log.info("Using GPU implementation") 30 | return find_dense_periods_gpu(timestamps, window_size, top_n, max_overlap, step) 31 | 32 | log.info("Using CPU implementation") 33 | return find_dense_periods_cpu(timestamps, window_size, top_n, max_overlap, step) 34 | -------------------------------------------------------------------------------- /autosv/calculate/sliding_cpu.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2025 auto-slice-video 2 | 3 | from collections import defaultdict 4 | 5 | 6 | def find_dense_periods_cpu(timestamps, window_size, top_n, max_overlap, step): 7 | """Calculate the top N maximum density periods of timestamps in a given window size. 8 | 9 | Args: 10 | timestamps: List of dialogue timestamps 11 | window_size: Size of the sliding window 12 | top_n: Number of top density periods to return 13 | max_overlap: Maximum allowed overlap between periods (in seconds) 14 | step: Step size for sliding window (in seconds) 15 | 16 | Returns: 17 | List of tuples (start_time, density) sorted by density in descending order 18 | """ 19 | time_counts = defaultdict(int) 20 | for time in timestamps: 21 | time_counts[time] += 1 22 | 23 | # Store (start_time, density) pairs 24 | density_periods = [] 25 | 26 | # Use a sliding window to calculate density 27 | sorted_times = sorted(time_counts.keys()) 28 | for i in range(0, len(sorted_times), step): 29 | start_time = sorted_times[i] 30 | end_time = start_time + window_size 31 | current_density = sum( 32 | count 33 | for time, count in time_counts.items() 34 | if start_time <= time < end_time 35 | ) 36 | density_periods.append((start_time, current_density)) 37 | 38 | # Sort by density in descending order and return top N results 39 | density_periods.sort(key=lambda x: x[1], reverse=True) 40 | # If max_overlap is not specified, return top N results directly 41 | if max_overlap is None: 42 | return density_periods[:top_n] 43 | 44 | # Filter periods with overlap constraint 45 | filtered_periods = [] 46 | for start_time, density in density_periods: 47 | # Check if current period overlaps too much with any selected period 48 | valid_period = True 49 | for selected_start, _ in filtered_periods: 50 | overlap = min(selected_start + window_size, start_time + window_size) - max( 51 | selected_start, start_time 52 | ) 53 | if overlap > max_overlap: 54 | valid_period = False 55 | break 56 | 57 | if valid_period: 58 | filtered_periods.append((int(start_time), density)) 59 | if len(filtered_periods) == top_n: 60 | break 61 | 62 | return filtered_periods 63 | -------------------------------------------------------------------------------- /autosv/calculate/sliding_gpu.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2025 auto-slice-video 2 | 3 | import numpy as np 4 | from numba import cuda 5 | import math 6 | import warnings 7 | 8 | warnings.filterwarnings("ignore") 9 | 10 | 11 | @cuda.jit 12 | def calculate_window_density(timestamps, window_size, step, results): 13 | """ 14 | Calculate the density of timestamps in a given window size. 15 | 16 | Args: 17 | timestamps: List of timestamps 18 | window_size: Size of the window 19 | step: Step size 20 | results: Array to store the density of each window 21 | """ 22 | # get the index of the current thread 23 | idx = cuda.grid(1) 24 | 25 | if idx < len(results): 26 | window_start = timestamps[0] + idx * step 27 | window_end = window_start + window_size 28 | 29 | count = 0 30 | # calculate the number of timestamps in the window 31 | for t in timestamps: 32 | if window_start <= t <= window_end: 33 | count += 1 34 | 35 | results[idx] = count / window_size 36 | 37 | 38 | def find_dense_periods_gpu(timestamps, window_size, top_n, max_overlap, step): 39 | """ 40 | Find the dense periods of timestamps in a given window size. 41 | 42 | Args: 43 | timestamps: List of timestamps 44 | window_size: Size of the window 45 | step: Step size 46 | top_n: Number of top density periods to return 47 | max_overlap: Maximum allowed overlap between periods (in seconds) 48 | 49 | Returns: 50 | List of tuples (start_time, density) sorted by density in descending order 51 | """ 52 | timestamps = np.array(timestamps, dtype=np.float32) 53 | 54 | # calculate the number of needed windows 55 | start_time = timestamps.min() 56 | end_time = timestamps.max() 57 | num_windows = math.ceil((end_time - start_time) / step) 58 | 59 | # prepare the GPU arrays 60 | results = np.zeros(num_windows, dtype=np.float32) 61 | d_timestamps = cuda.to_device(timestamps) 62 | d_results = cuda.to_device(results) 63 | 64 | # configure the CUDA grid 65 | threads_per_block = 256 66 | blocks = (num_windows + threads_per_block - 1) // threads_per_block 67 | 68 | # start the CUDA cores 69 | calculate_window_density[blocks, threads_per_block]( 70 | d_timestamps, window_size, step, d_results 71 | ) 72 | 73 | # copy the results back to the host 74 | results = d_results.copy_to_host() 75 | 76 | # find all the density values and their indices 77 | density_periods = [(i, results[i]) for i in range(len(results))] 78 | density_periods.sort(key=lambda x: x[1], reverse=True) 79 | 80 | # if no need to control the overlap, return the top n results 81 | if max_overlap is None: 82 | selected_indices = [i for i, _ in density_periods[:top_n]] 83 | else: 84 | # filter the intervals with too much overlap 85 | selected_indices = [] 86 | for idx, density in density_periods: 87 | current_start = start_time + idx * step 88 | 89 | # check if the interval overlaps too much with the already selected intervals 90 | valid_period = True 91 | for selected_idx in selected_indices: 92 | selected_start = start_time + selected_idx * step 93 | overlap = min( 94 | selected_start + window_size, current_start + window_size 95 | ) - max(selected_start, current_start) 96 | if overlap > max_overlap: 97 | valid_period = False 98 | break 99 | 100 | if valid_period: 101 | selected_indices.append(idx) 102 | if len(selected_indices) == top_n: 103 | break 104 | 105 | densest_periods = [ 106 | (int(start_time + i * step), int(results[i] * window_size)) 107 | for i in selected_indices 108 | ] 109 | 110 | return densest_periods 111 | -------------------------------------------------------------------------------- /autosv/cli.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2025 auto-slice-video 2 | 3 | import argparse 4 | import sys 5 | import os 6 | import textwrap 7 | from autosv import slice_video_by_danmaku 8 | 9 | 10 | def cli(): 11 | parser = argparse.ArgumentParser( 12 | prog="autosv", 13 | formatter_class=argparse.RawDescriptionHelpFormatter, 14 | description=textwrap.dedent( 15 | """ 16 | Auto slice the highlight shorts based on the density of danmaku. 17 | Source code at https://github.com/timerring/auto-slice-video 18 | """ 19 | ), 20 | epilog=textwrap.dedent( 21 | """ 22 | Example: 23 | autosv -a input.ass -v input.mp4 24 | autosv -a input.ass -v input.mp4 -d 60 -n 1 --overlap 30 --step 1 25 | """ 26 | ), 27 | ) 28 | parser.add_argument( 29 | "-V", 30 | "--version", 31 | action="version", 32 | version="autosv 0.0.3 and source code at https://github.com/timerring/auto-slice-video", 33 | help="Print version information", 34 | ) 35 | parser.add_argument( 36 | "-a", 37 | "--ass", 38 | required=True, 39 | type=str, 40 | help="The input ass file of the danmaku", 41 | ) 42 | parser.add_argument( 43 | "-v", 44 | "--video", 45 | required=True, 46 | type=str, 47 | help="The input video file", 48 | ) 49 | parser.add_argument( 50 | "-d", 51 | "--duration", 52 | default=60, 53 | type=int, 54 | help="The duration(seconds) of the sliced highlight video, default is 60", 55 | ) 56 | parser.add_argument( 57 | "-n", 58 | "--top_n", 59 | default=1, 60 | type=int, 61 | help="The number of the top dense periods to return, default is 1", 62 | ) 63 | parser.add_argument( 64 | "--overlap", 65 | default=30, 66 | type=int, 67 | help="The overlapped(seconds) between the sliced highlight videos, default is 30", 68 | ) 69 | parser.add_argument( 70 | "--step", 71 | default=1, 72 | type=int, 73 | help="The step(seconds) of the sliding window, default is 1", 74 | ) 75 | 76 | args = parser.parse_args() 77 | if os.path.splitext(args.ass)[1] == ".ass": 78 | slice_video_by_danmaku( 79 | args.ass, args.video, args.duration, args.top_n, args.overlap, args.step 80 | ) 81 | else: 82 | print("Please assign the correct input the file in ass format!", flush=True) 83 | 84 | 85 | if __name__ == "__main__": 86 | cli() 87 | -------------------------------------------------------------------------------- /autosv/log/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timerring/auto-slice-video/57f4b59d58f827b78d6aa3ef4955c4f42f21b792/autosv/log/__init__.py -------------------------------------------------------------------------------- /autosv/log/logger.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2025 auto-slice-video 2 | 3 | import logging 4 | import time 5 | import os 6 | from typing import Optional 7 | from functools import partial 8 | 9 | 10 | class Logger: 11 | def __init__(self, log_file_prefix: Optional[str] = None): 12 | self.log_file_prefix = log_file_prefix 13 | self._logger = None 14 | 15 | def __get__(self, instance, owner): 16 | if self._logger is None: 17 | self._logger = self._create_logger() 18 | return self._logger 19 | 20 | def _create_logger(self): 21 | logger = logging.getLogger(f"{self.log_file_prefix}") 22 | if not logger.handlers: 23 | logger.setLevel("DEBUG") 24 | formatter = logging.Formatter( 25 | "[%(levelname)s] - [%(asctime)s %(name)s] - %(message)s" 26 | ) 27 | 28 | # console output 29 | console_handler = logging.StreamHandler() 30 | console_handler.setLevel("INFO") 31 | console_handler.setFormatter(formatter) 32 | logger.addHandler(console_handler) 33 | 34 | return logger 35 | 36 | 37 | class Log: 38 | def __init__(self, log_file_prefix: Optional[str] = None): 39 | self.logger = Logger(log_file_prefix) 40 | 41 | @property 42 | def debug(self): 43 | return partial(self.logger.__get__(None, None).debug) 44 | 45 | @property 46 | def info(self): 47 | return partial(self.logger.__get__(None, None).info) 48 | 49 | @property 50 | def warning(self): 51 | return partial(self.logger.__get__(None, None).warning) 52 | 53 | @property 54 | def error(self): 55 | return partial(self.logger.__get__(None, None).error) 56 | 57 | @property 58 | def critical(self): 59 | return partial(self.logger.__get__(None, None).critical) 60 | -------------------------------------------------------------------------------- /autosv/slice/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timerring/auto-slice-video/57f4b59d58f827b78d6aa3ef4955c4f42f21b792/autosv/slice/__init__.py -------------------------------------------------------------------------------- /autosv/slice/slice_video.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024 bilive. 2 | # Copyright (c) 2025 auto-slice-video 3 | 4 | import subprocess 5 | 6 | 7 | def format_time(seconds): 8 | """Format seconds to hh:mm:ss.""" 9 | h = int(seconds // 3600) 10 | m = int((seconds % 3600) // 60) 11 | s = int(seconds % 60) 12 | return f"{h:02}:{m:02}:{s:02}" 13 | 14 | 15 | def slice_video(video_path, output_path, start_time, duration): 16 | """Slice the video using ffmpeg.""" 17 | duration = format_time(duration) 18 | command = [ 19 | "ffmpeg", 20 | "-y", 21 | "-ss", 22 | format_time(start_time), 23 | "-i", 24 | video_path, 25 | "-t", 26 | duration, 27 | "-map_metadata", 28 | "-1", 29 | "-c:v", 30 | "copy", 31 | "-c:a", 32 | "copy", 33 | output_path, 34 | ] 35 | try: 36 | result = subprocess.run(command, check=True, capture_output=True, text=True) 37 | except subprocess.CalledProcessError as e: 38 | print(f"Error: {e.stderr}") 39 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["hatchling"] 3 | build-backend = "hatchling.build" 4 | 5 | [project] 6 | name = "autosv" 7 | version = "0.0.3" 8 | authors = [ 9 | { name="timerring"}, 10 | ] 11 | description = "Auto slice the highlight shorts based on the density of danmaku." 12 | readme = "README.md" 13 | license = { file="LICENSE" } 14 | requires-python = ">=3.7" 15 | classifiers = [ 16 | "Programming Language :: Python :: 3", 17 | "License :: OSI Approved :: MIT License", 18 | "Operating System :: OS Independent", 19 | ] 20 | dependencies = [ 21 | "numba>=0.55.1", 22 | "numpy>=1.21.6", 23 | ] 24 | 25 | [project.scripts] 26 | autosv = "autosv.cli:cli" 27 | 28 | [project.urls] 29 | "Homepage" = "https://github.com/timerring/auto-slice-video" 30 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | numpy>=1.21.6 2 | numba>=0.55.1 3 | -------------------------------------------------------------------------------- /test/sample.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timerring/auto-slice-video/57f4b59d58f827b78d6aa3ef4955c4f42f21b792/test/sample.mp4 --------------------------------------------------------------------------------