├── .github
└── workflows
│ ├── build.yml
│ └── deploy.yml
├── .gitignore
├── .idea
├── .gitignore
├── VideoMosaic.iml
├── developer-tools.xml
├── dictionaries
│ └── Administrator.xml
├── encodings.xml
├── inspectionProfiles
│ ├── Project_Default.xml
│ └── profiles_settings.xml
├── misc.xml
├── modules.xml
├── other.xml
├── ruff.xml
├── statistic.xml
└── vcs.xml
├── .python-version
├── CHANGELOG.md
├── LICENSE
├── README.assets
├── 3.jpg
├── banner.png
├── demo.gif
└── logo.png
├── README.md
├── VideoFusion.py
├── assets
├── about.html
├── images
│ ├── add.ico
│ ├── add_256.png
│ ├── logo.ico
│ ├── logo.png
│ ├── start_merge.ico
│ └── tooltip
│ │ └── upscale.png
├── resource.qrc
└── ui
│ ├── concate_page.ui
│ └── home_page.ui
├── bin
├── ESPCN_x2.pb
├── LapSRN_x2.pb
└── cb.rnnn
├── cli_interface.py
├── docs
├── .vitepress
│ ├── cache
│ │ └── deps
│ │ │ ├── _metadata.json
│ │ │ ├── chunk-CAILPCNG.js
│ │ │ ├── chunk-CAILPCNG.js.map
│ │ │ ├── chunk-DQYAFVCV.js
│ │ │ ├── chunk-DQYAFVCV.js.map
│ │ │ ├── package.json
│ │ │ ├── vitepress___@vue_devtools-api.js
│ │ │ ├── vitepress___@vue_devtools-api.js.map
│ │ │ ├── vitepress___@vueuse_core.js
│ │ │ ├── vitepress___@vueuse_core.js.map
│ │ │ ├── vitepress___@vueuse_integrations_useFocusTrap.js
│ │ │ ├── vitepress___@vueuse_integrations_useFocusTrap.js.map
│ │ │ ├── vitepress___mark__js_src_vanilla__js.js
│ │ │ ├── vitepress___mark__js_src_vanilla__js.js.map
│ │ │ ├── vitepress___minisearch.js
│ │ │ ├── vitepress___minisearch.js.map
│ │ │ ├── vue.js
│ │ │ └── vue.js.map
│ ├── config.mts
│ └── theme
│ │ ├── index.ts
│ │ └── style.css
├── CHANGLOG.md
├── about.md
├── advanced_settings.md
├── contact_me.md
├── donated.md
├── index.md
├── normal_settings.md
├── opencv_only_settings.md
├── public
│ ├── VF和达芬奇和PR的区别.png
│ ├── archive_folder.svg
│ ├── audio_wave.svg
│ ├── black_remover.png
│ ├── demo.gif
│ ├── donate.png
│ ├── live_stabilization.gif
│ ├── logo.png
│ ├── process.svg
│ ├── star.svg
│ ├── 不止合并.jpg
│ ├── 亮度自动调整.png
│ ├── 快速合并视频向导图片_1.png
│ ├── 检查更新.png
│ ├── 白平衡.png
│ ├── 简单的设置页面.png
│ ├── 视频去色块.png
│ ├── 视频去色带.png
│ ├── 视频帧率对画面的影响.gif
│ ├── 设置引擎.png
│ ├── 调整视频顺序.gif
│ ├── 超分平滑.png
│ ├── 输出页面.png
│ ├── 重新开始.png
│ ├── 随时暂停.gif
│ └── 预览视频去黑边.png
├── quick-start.md
└── thanks.md
├── libomp140.x86_64.dll
├── openh264-2.4.1-win64.dll
├── package.json
├── pnpm-lock.yaml
├── pyproject.toml
├── requirements-dev.lock
├── requirements.lock
├── requirements.txt
├── resource_rc.py
├── scripts
├── compile_view.py
└── packaged.py
├── src
├── __init__.py
├── common
│ ├── __init__.py
│ ├── black_remove
│ │ ├── __init__.py
│ │ ├── img_black_remover.py
│ │ └── video_remover.py
│ ├── black_remove_algorithm
│ │ ├── __init__.py
│ │ ├── black_remove_algorithm.py
│ │ ├── img_black_remover.py
│ │ └── video_remover.py
│ ├── ffmpeg.py
│ ├── ffmpeg_command.py
│ ├── ffmpeg_handler.py
│ ├── processors
│ │ ├── __init__.py
│ │ ├── audio_processors
│ │ │ ├── __init__.py
│ │ │ ├── audio_ffmpeg_processor.py
│ │ │ └── audio_processor_manager.py
│ │ ├── base_processor.py
│ │ ├── exe_processors
│ │ │ ├── __init__.py
│ │ │ ├── audio_separator_processor.py
│ │ │ ├── auto_editor_processor.py
│ │ │ └── exe_processor_manager.py
│ │ ├── ffmpeg_processors
│ │ │ ├── __init__.py
│ │ │ └── ffmpeg_command_processor.py
│ │ ├── opencv_processors
│ │ │ ├── __init__.py
│ │ │ ├── bilateral_denoise_processor.py
│ │ │ ├── brightness_contrast_processor.py
│ │ │ ├── crop_processor.py
│ │ │ ├── deband_processor.py
│ │ │ ├── deblock_processor.py
│ │ │ ├── deshake_processor.py
│ │ │ ├── means_denoise_processor.py
│ │ │ ├── opencv_processor_manager.py
│ │ │ ├── resize_processor.py
│ │ │ ├── rotate_processor.py
│ │ │ ├── super_resolution_processor.py
│ │ │ └── white_balance_processor.py
│ │ └── processor_global_var.py
│ ├── program_coordinator.py
│ ├── task_resumer
│ │ ├── __init__.py
│ │ ├── task_resumer.py
│ │ └── task_resumer_manager.py
│ ├── utils
│ │ ├── __init__.py
│ │ └── image_utils.py
│ ├── video_engines
│ │ ├── __init__.py
│ │ ├── base_video_engine.py
│ │ ├── ffmpeg_video_engine.py
│ │ └── opencv_video_engine.py
│ ├── video_handler.py
│ ├── video_info.py
│ └── video_info_reader.py
├── components
│ ├── __init__.py
│ ├── cmd_text_edit.py
│ ├── draggable_list_widget.py
│ ├── file_drag_and_drop_lineedit.py
│ ├── file_treeview.py
│ ├── message_dialog.py
│ └── sort_tool_component.py
├── config.py
├── core
│ ├── __init__.py
│ ├── about.py
│ ├── datacls.py
│ ├── dicts.py
│ ├── enums.py
│ ├── paths.py
│ └── version.py
├── interface
│ ├── Ui_concate_page.py
│ ├── Ui_home_page.py
│ └── __init__.py
├── model
│ ├── __init__.py
│ ├── concate_model.py
│ ├── home_model.py
│ └── settings_model.py
├── presenter
│ ├── __init__.py
│ ├── concate_presenter.py
│ ├── home_presenter.py
│ ├── main_presenter.py
│ └── settings_presenter.py
├── settings.py
├── signal_bus.py
├── utils.py
└── view
│ ├── __init__.py
│ ├── concate_view.py
│ ├── home_view.py
│ ├── main_view.py
│ ├── message_base_view.py
│ └── settings_view.py
└── tests
├── __init__.py
├── ffmpeg_handler_test.py
├── test_balck_remove_algorithm
├── __init__.py
├── img_black_remove_algorithm_test.py
└── video_black_remove_algorithm_test.py
├── test_data
└── images
│ ├── has_black_1.png
│ ├── has_black_2.jpg
│ ├── no_black_1.jpg
│ └── no_black_2.jpg
├── test_processors
├── __init__.py
└── test_opencv_processors
│ ├── __init__.py
│ ├── bilateral_denoise_test.py
│ ├── crop_processor_test.py
│ ├── resize_processor_test.py
│ └── rotate_processor_test.py
├── utils_test
├── __init__.py
└── image_utils_test.py
└── video_info_reader_test.py
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Build and Package with Nuitka
2 |
3 | on:
4 | workflow_dispatch:
5 |
6 | jobs:
7 | build-windows:
8 | runs-on: windows-latest
9 |
10 | steps:
11 | - name: Checkout code
12 | uses: actions/checkout@v2
13 |
14 | - name: Set up Python
15 | uses: actions/setup-python@v4
16 | with:
17 | python-version: "3.10"
18 | cache: "pip"
19 |
20 | - name: Verify Python Version
21 | run: python --version
22 |
23 | - name: Install dependencies
24 | run: |
25 | python -m pip install --upgrade pip
26 | pip install nuitka pyside6
27 |
28 | - name: Package with Nuitka
29 | run: |
30 | nuitka --show-progress --remove-output --lto=no --output-dir=./output/windows --main="video_mosaic.py" --standalone --assume-yes-for-downloads --enable-plugin=pyside6 --windows-disable-console
31 |
32 | - name: Upload Windows Artifact
33 | uses: actions/upload-artifact@v2
34 | with:
35 | name: windows-output
36 | path: ./output/windows
37 |
38 | build-linux:
39 | runs-on: ubuntu-latest
40 |
41 | steps:
42 | - name: Checkout code
43 | uses: actions/checkout@v2
44 |
45 | - name: Set up Python
46 | uses: actions/setup-python@v4
47 | with:
48 | python-version: "3.10"
49 | cache: "pip"
50 |
51 | - name: Verify Python Version
52 | run: python --version
53 |
54 | - name: Install dependencies
55 | run: |
56 | python -m pip install --upgrade pip
57 | pip install nuitka PySide6
58 |
59 | - name: Package with Nuitka
60 | run: |
61 | nuitka --output-dir=./output/linux --main="video_mosaic.py" --standalone --assume-yes-for-downloads --enable-plugin=pyside6
62 |
63 | - name: Upload Linux Artifact
64 | uses: actions/upload-artifact@v2
65 | with:
66 | name: linux-output
67 | path: ./output/linux
68 |
69 | build-macos:
70 | runs-on: macos-latest
71 |
72 | steps:
73 | - name: Checkout code
74 | uses: actions/checkout@v2
75 |
76 | - name: Install gettext
77 | run: |
78 | brew install gettext
79 | echo 'export DYLD_LIBRARY_PATH="/usr/local/opt/gettext/lib:$DYLD_LIBRARY_PATH"' >> $GITHUB_ENV
80 |
81 | - name: Set up Python
82 | uses: actions/setup-python@v4
83 | with:
84 | python-version: "3.10"
85 | architecture: "x64"
86 | cache: "pip"
87 |
88 | - name: Verify Python Version
89 | run: python --version
90 |
91 | - name: Install dependencies
92 | run: |
93 | python -m pip install --upgrade pip
94 | pip install nuitka pyside6
95 |
96 | - name: Package with Nuitka
97 | run: |
98 | nuitka --output-dir=./output/macos --main="video_mosaic.py" --standalone --assume-yes-for-downloads --enable-plugin=pyside6
99 |
100 | - name: Upload MacOS Artifact
101 | uses: actions/upload-artifact@v2
102 | with:
103 | name: macos-output
104 | path: ./output/macos
105 |
--------------------------------------------------------------------------------
/.github/workflows/deploy.yml:
--------------------------------------------------------------------------------
1 | # 构建 VitePress 站点并将其部署到 GitHub Pages 的示例工作流程
2 | #
3 | name: Deploy VitePress site to Pages
4 |
5 | on:
6 | # 在针对 `master` 分支的推送上运行。
7 | # push:
8 | # branches: [master]
9 |
10 | # 允许你从 Actions 选项卡手动运行此工作流程
11 | workflow_dispatch:
12 |
13 | # 设置 GITHUB_TOKEN 的权限,以允许部署到 GitHub Pages
14 | permissions:
15 | contents: read
16 | pages: write
17 | id-token: write
18 |
19 | # 只允许同时进行一次部署,跳过正在运行和最新队列之间的运行队列
20 | # 但是,不要取消正在进行的运行,因为我们希望允许这些生产部署完成
21 | concurrency:
22 | group: pages
23 | cancel-in-progress: false
24 |
25 | jobs:
26 | # 构建工作
27 | build:
28 | runs-on: ubuntu-latest
29 | steps:
30 | - name: Checkout
31 | uses: actions/checkout@v4
32 |
33 | - name: Setup pnpm
34 | uses: pnpm/action-setup@v3
35 | with:
36 | version: 9 # 指定您需要的 pnpm 版本
37 |
38 | - name: Setup Node
39 | uses: actions/setup-node@v4
40 | with:
41 | node-version: 20
42 | cache: pnpm
43 |
44 | - name: Install dependencies
45 | run: pnpm install
46 | env:
47 | NODE_ENV: development
48 |
49 | - name: Build with VitePress
50 | run: pnpm docs:build
51 |
52 | - name: Upload artifact
53 | uses: actions/upload-pages-artifact@v3
54 | with:
55 | path: docs/.vitepress/dist
56 |
57 | # 部署工作
58 | deploy:
59 | environment:
60 | name: github-pages
61 | url: ${{ steps.deployment.outputs.page_url }}
62 | needs: build
63 | runs-on: ubuntu-latest
64 | name: Deploy
65 | steps:
66 | - name: Deploy to GitHub Pages
67 | id: deployment
68 | uses: actions/deploy-pages@v4
69 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # 默认忽略的文件
2 | /shelf/
3 | /workspace.xml
4 | # 基于编辑器的 HTTP 客户端请求
5 | /httpRequests/
6 | # Datasource local storage ignored files
7 | /dataSources/
8 | /dataSources.local.xml
9 |
--------------------------------------------------------------------------------
/.idea/VideoMosaic.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/.idea/developer-tools.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/dictionaries/Administrator.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
23 |
24 |
25 |
26 |
41 |
42 |
43 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/other.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/ruff.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/statistic.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.python-version:
--------------------------------------------------------------------------------
1 | 3.10.11
2 |
--------------------------------------------------------------------------------
/README.assets/3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/271374667/VideoFusion/9ba7b8b301481ed148bc4d853a5efe8bbb7188b5/README.assets/3.jpg
--------------------------------------------------------------------------------
/README.assets/banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/271374667/VideoFusion/9ba7b8b301481ed148bc4d853a5efe8bbb7188b5/README.assets/banner.png
--------------------------------------------------------------------------------
/README.assets/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/271374667/VideoFusion/9ba7b8b301481ed148bc4d853a5efe8bbb7188b5/README.assets/demo.gif
--------------------------------------------------------------------------------
/README.assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/271374667/VideoFusion/9ba7b8b301481ed148bc4d853a5efe8bbb7188b5/README.assets/logo.png
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | VideoFusion
3 |
4 | 一站式视频批量处理软件
5 |
6 | 点击即用,自动去黑边,智能拼接,补帧,自动调整分辨率,白平衡,AI 音频降噪
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | 
18 |
19 | ## 10 秒爱上VideoFusion
20 |
21 | 下面这个 GIF 将会带您快速查看两个有不规则黑边的视频是如何完美去除黑边然后旋转成正确的朝向最后合并的
22 |
23 | 
24 |
25 |
26 | ## ✨软件介绍
27 |
28 | VideoFusion 旨在打造一个上手即用,随用随走的轻量化视频批量优化和处理工具,对于无经验的视频创作者您只需要几次点击就能实现视频拼接,AI音频降噪,超分平滑,白平衡,亮度自动调整,补帧等功能,使用 VideoFusion 帮助您预处理您的视频,您无需关心参数和更多细节,VideoFusion 会自动帮您处理好剩下复杂的逻辑
29 |
30 | VideoFusion 最初的目标是制作一个能够快速去除大量视频黑边的软件,其中还需要为对视频进行合成,但是视频的尺寸不一致,分辨率也不一致,视频内横屏还是竖屏的状态也不一致,于是我开发了这个软件,能够批量将视频去除黑边后旋转到统一尺寸最后合并,相比其他的合并软件,VideoFusion 合并视频会计算最佳的分辨率,进行缩放/旋转/剪裁等一系列操作后才会进行合并,确保大量视频哪怕分辨率不一致,合成完毕之后的黑边区域面积最小(其他软件分辨率不一致直接合并会出现大量黑边,严重影响观感)
31 |
32 | 同时 VideoFusion 对去黑边进行了特化,可以去除多种多样的黑边,视频外有 logo,多余的文字都可以进行去除
33 |
34 | 在不断的发展中 VideoFusion 功能越来越强大,经过 VideoFusion 处理过的视频,大小可能仅为原来的一半!但是画面却没有肉眼可见的损失,同时还增加了大量新的功能,例如白平衡,亮度自动调整,响度自动调整,补帧等等功能,功能非常多而且未来还会继续更新,欢迎大家前往文档查看
35 |
36 | ## 🚀功能介绍
37 |
38 | 
39 |
40 |
41 |
42 |
软件详情请前往文档进行查看
43 |
点我前往文档
44 |
45 |
46 |
47 |
48 | ## 如何运行该项目
49 |
50 | ### 推荐运行方法(用户)
51 |
52 | 直接通过 Release 下载最新的版本直接点击其中的 exe 文件即可运行
53 |
54 | ### 编译运行(程序员)
55 |
56 | > 推荐运行环境 Python 3.10
57 | > 备注: 可以选择更高版本,但是不能低于 Python 3.10
58 |
59 | 通过在项目根目录下输入下面的命令安装第三方库
60 |
61 | ```cmd
62 | pip install -r requirements.txt
63 | ```
64 |
65 | 然后运行项目根目录下的 `video_fusion.py` 文件
66 |
67 | ## 额外说明
68 |
69 | - 该软件支持 Window10 64位 以及 Window11 64位,其余 Window 版本未经过测试,不保证稳定运行
70 | - 该软件永久免费,如果您在其他地方付费下载到了该软件请马上退款止损
71 | - 如果您使用出现了问题或者对软件的建议请您在该页面提出 issue
72 | - 如果您有更多的问题请前往文档查看
--------------------------------------------------------------------------------
/VideoFusion.py:
--------------------------------------------------------------------------------
1 | import loguru
2 | from PySide6.QtWidgets import QApplication
3 |
4 | from src.core.paths import LOG_FILE
5 | from src.presenter.main_presenter import MainPresenter
6 |
7 | loguru.logger.add(LOG_FILE, rotation="1 week", retention="1 days", level="DEBUG")
8 |
9 | @loguru.logger.catch(reraise=True)
10 | def main():
11 | app = QApplication([])
12 | main_presenter = MainPresenter()
13 | main_presenter.get_view().show()
14 | app.exec()
15 |
16 |
17 | if __name__ == '__main__':
18 | main()
19 |
--------------------------------------------------------------------------------
/assets/about.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 视频拼接工具
6 |
83 |
84 |
85 |
86 |
87 | VideoFusion
88 | 一站式短视频合成工具
89 |
90 |
91 | 功能特色
92 | 支持多种视频格式,支持多个视频拼接,自研去黑边算法,处理复杂黑边。自动横竖屏切换,自动分辨率,大量音视频增强功能。
93 |
94 | 支持格式
95 | 支持视频格式:mp4, avi, flv, mov, wmv, mkv, rmvb, rm, 3gp, mpeg, asf, ts
96 |
97 |
104 |
112 |
117 |
118 |
119 |
--------------------------------------------------------------------------------
/assets/images/add.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/271374667/VideoFusion/9ba7b8b301481ed148bc4d853a5efe8bbb7188b5/assets/images/add.ico
--------------------------------------------------------------------------------
/assets/images/add_256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/271374667/VideoFusion/9ba7b8b301481ed148bc4d853a5efe8bbb7188b5/assets/images/add_256.png
--------------------------------------------------------------------------------
/assets/images/logo.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/271374667/VideoFusion/9ba7b8b301481ed148bc4d853a5efe8bbb7188b5/assets/images/logo.ico
--------------------------------------------------------------------------------
/assets/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/271374667/VideoFusion/9ba7b8b301481ed148bc4d853a5efe8bbb7188b5/assets/images/logo.png
--------------------------------------------------------------------------------
/assets/images/start_merge.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/271374667/VideoFusion/9ba7b8b301481ed148bc4d853a5efe8bbb7188b5/assets/images/start_merge.ico
--------------------------------------------------------------------------------
/assets/images/tooltip/upscale.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/271374667/VideoFusion/9ba7b8b301481ed148bc4d853a5efe8bbb7188b5/assets/images/tooltip/upscale.png
--------------------------------------------------------------------------------
/assets/resource.qrc:
--------------------------------------------------------------------------------
1 |
2 |
3 | images/cancel.ico
4 | images/logo.ico
5 | images/add.ico
6 | images/start_merge.ico
7 |
8 |
9 | images/tooltip/upscale.png
10 | images/tooltip/black_remover.png
11 | images/tooltip/Deblocking.png
12 | images/tooltip/debanding.png
13 |
14 |
15 |
--------------------------------------------------------------------------------
/bin/ESPCN_x2.pb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/271374667/VideoFusion/9ba7b8b301481ed148bc4d853a5efe8bbb7188b5/bin/ESPCN_x2.pb
--------------------------------------------------------------------------------
/bin/LapSRN_x2.pb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/271374667/VideoFusion/9ba7b8b301481ed148bc4d853a5efe8bbb7188b5/bin/LapSRN_x2.pb
--------------------------------------------------------------------------------
/cli_interface.py:
--------------------------------------------------------------------------------
1 | import argparse
2 | from pathlib import Path
3 |
4 | import loguru
5 |
6 | from src.core.enums import Orientation
7 |
8 | description = """批量视频处理工具
9 | 目前更加细致的设定请前往GUI中进行设置,命令只提供最基础的合并功能
10 | """
11 |
12 | parser = argparse.ArgumentParser(description=description)
13 | parser.add_argument('-i', '--input', type=str, help='包含视频文件地址的txt文件的地址')
14 | parser.add_argument("--video_oritation", type=str, default='horization', choices=['vertical', 'horization'],
15 | help='视频的方向')
16 |
17 |
18 | def parse_args():
19 | # 解析视频文件地址
20 | input_txt_path: Path = Path(parser.parse_args().input)
21 | video_path_list: list[Path] = []
22 | if not input_txt_path.exists():
23 | loguru.logger.error(f'txt文件{input_txt_path}不存在')
24 | raise FileNotFoundError(f'txt文件{input_txt_path}不存在')
25 |
26 | # 判断里面每一行是否都是路径
27 | for line in input_txt_path.read_text().replace('"', '').splitlines():
28 | real_path = Path(line)
29 | if not real_path.exists():
30 | loguru.logger.error(f'视频文件{line}不存在,请检查txt文件中的路径是否正确')
31 | raise FileNotFoundError(f'视频文件{line}不存在,请检查txt文件中的路径是否正确')
32 | video_path_list.append(real_path)
33 |
34 | loguru.logger.debug(f'一共加载了{len(video_path_list)}个视频文件,分别是{video_path_list}')
35 |
36 | # 解析视频朝向
37 | video_orientation = parser.parse_args().video_oritation
38 | if video_orientation == 'vertical':
39 | video_orientation = Orientation.VERTICAL
40 | else:
41 | video_orientation = Orientation.HORIZONTAL
42 | loguru.logger.debug(f'视频的方向为{video_orientation}')
43 |
44 |
45 | if __name__ == '__main__':
46 | parse_args()
47 | print(parser.print_help())
48 | print(parser.parse_args())
49 |
--------------------------------------------------------------------------------
/docs/.vitepress/cache/deps/_metadata.json:
--------------------------------------------------------------------------------
1 | {
2 | "hash": "99249c4c",
3 | "configHash": "aacd56f1",
4 | "lockfileHash": "8b33d3c5",
5 | "browserHash": "e41b3b20",
6 | "optimized": {
7 | "vue": {
8 | "src": "../../../../node_modules/.pnpm/vue@3.4.33/node_modules/vue/dist/vue.runtime.esm-bundler.js",
9 | "file": "vue.js",
10 | "fileHash": "19228ae5",
11 | "needsInterop": false
12 | },
13 | "vitepress > @vue/devtools-api": {
14 | "src": "../../../../node_modules/.pnpm/@vue+devtools-api@7.3.7/node_modules/@vue/devtools-api/dist/index.js",
15 | "file": "vitepress___@vue_devtools-api.js",
16 | "fileHash": "f6cf1ec2",
17 | "needsInterop": false
18 | },
19 | "vitepress > @vueuse/core": {
20 | "src": "../../../../node_modules/.pnpm/@vueuse+core@10.11.0_vue@3.4.33/node_modules/@vueuse/core/index.mjs",
21 | "file": "vitepress___@vueuse_core.js",
22 | "fileHash": "0def523b",
23 | "needsInterop": false
24 | },
25 | "vitepress > @vueuse/integrations/useFocusTrap": {
26 | "src": "../../../../node_modules/.pnpm/@vueuse+integrations@10.11.0_focus-trap@7.5.4_vue@3.4.33/node_modules/@vueuse/integrations/useFocusTrap.mjs",
27 | "file": "vitepress___@vueuse_integrations_useFocusTrap.js",
28 | "fileHash": "46d44b77",
29 | "needsInterop": false
30 | },
31 | "vitepress > mark.js/src/vanilla.js": {
32 | "src": "../../../../node_modules/.pnpm/mark.js@8.11.1/node_modules/mark.js/src/vanilla.js",
33 | "file": "vitepress___mark__js_src_vanilla__js.js",
34 | "fileHash": "272d94a9",
35 | "needsInterop": false
36 | },
37 | "vitepress > minisearch": {
38 | "src": "../../../../node_modules/.pnpm/minisearch@7.1.0/node_modules/minisearch/dist/es/index.js",
39 | "file": "vitepress___minisearch.js",
40 | "fileHash": "4951fcd4",
41 | "needsInterop": false
42 | }
43 | },
44 | "chunks": {
45 | "chunk-CAILPCNG": {
46 | "file": "chunk-CAILPCNG.js"
47 | },
48 | "chunk-DQYAFVCV": {
49 | "file": "chunk-DQYAFVCV.js"
50 | }
51 | }
52 | }
--------------------------------------------------------------------------------
/docs/.vitepress/cache/deps/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "module"
3 | }
4 |
--------------------------------------------------------------------------------
/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js.map:
--------------------------------------------------------------------------------
1 | {
2 | "version": 3,
3 | "sources": [],
4 | "sourcesContent": [],
5 | "mappings": "",
6 | "names": []
7 | }
8 |
--------------------------------------------------------------------------------
/docs/.vitepress/cache/deps/vue.js.map:
--------------------------------------------------------------------------------
1 | {
2 | "version": 3,
3 | "sources": [],
4 | "sourcesContent": [],
5 | "mappings": "",
6 | "names": []
7 | }
8 |
--------------------------------------------------------------------------------
/docs/.vitepress/config.mts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vitepress'
2 |
3 |
4 | // https://vitepress.dev/reference/site-config
5 | export default defineConfig({
6 | title: "VideoFusion",
7 | description: "A Video Tools",
8 | cleanUrls: true,
9 | base: '/VideoFusion/',
10 | themeConfig: {
11 | // https://vitepress.dev/reference/default-theme-config
12 | nav: [
13 | { text: '联系我', link: '/contact_me' },
14 | { text: '更新日志', link: '/CHANGLOG' }
15 | ],
16 | logo: '/logo.png',
17 |
18 | sidebar: [
19 | {
20 | text: '导览',
21 | items: [
22 | { text: '快速开始', link: '/quick-start.md' },
23 | { text: '为什么选择 VideoFusion ?', link: '/about.md' },
24 |
25 | ]
26 | },
27 | {
28 | text: '设置',
29 | items: [
30 | { text: '常规设置', link: '/normal_settings.md' },
31 | { text: '高级设置', link: '/advanced_settings.md' },
32 | { text: 'OpenCV引擎下专属设置', link: '/opencv_only_settings.md' },
33 | ]
34 | },
35 | {
36 | text: '更多',
37 | items: [
38 | { text: '更新日志', link: '/CHANGLOG.md' },
39 | { text: '联系我', link: '/contact_me.md' },
40 | { text: '捐赠', link: '/donated.md' },
41 | { text: '感谢', link: '/thanks.md' },
42 | ]
43 | }
44 | ],
45 |
46 |
47 | socialLinks: [
48 | { icon: 'github', link: 'https://github.com/271374667/VideoFusion' }
49 | ],
50 |
51 | search: {
52 | provider: 'local'
53 | },
54 | outline: {
55 | level: 'deep',
56 | label: '目录'
57 | }
58 |
59 | }
60 | })
61 |
--------------------------------------------------------------------------------
/docs/.vitepress/theme/index.ts:
--------------------------------------------------------------------------------
1 | // https://vitepress.dev/guide/custom-theme
2 | import { h } from 'vue'
3 | import type { Theme } from 'vitepress'
4 | import DefaultTheme from 'vitepress/theme'
5 | import './style.css'
6 |
7 | export default {
8 | extends: DefaultTheme,
9 | Layout: () => {
10 | return h(DefaultTheme.Layout, null, {
11 | // https://vitepress.dev/guide/extending-default-theme#layout-slots
12 | })
13 | },
14 | enhanceApp({ app, router, siteData }) {
15 | // ...
16 | }
17 | } satisfies Theme
18 |
19 |
--------------------------------------------------------------------------------
/docs/.vitepress/theme/style.css:
--------------------------------------------------------------------------------
1 | /**
2 | * Customize default theme styling by overriding CSS variables:
3 | * https://github.com/vuejs/vitepress/blob/main/src/client/theme-default/styles/vars.css
4 | */
5 |
6 | /**
7 | * Colors
8 | *
9 | * Each colors have exact same color scale system with 3 levels of solid
10 | * colors with different brightness, and 1 soft color.
11 | *
12 | * - `XXX-1`: The most solid color used mainly for colored text. It must
13 | * satisfy the contrast ratio against when used on top of `XXX-soft`.
14 | *
15 | * - `XXX-2`: The color used mainly for hover state of the button.
16 | *
17 | * - `XXX-3`: The color for solid background, such as bg color of the button.
18 | * It must satisfy the contrast ratio with pure white (#ffffff) text on
19 | * top of it.
20 | *
21 | * - `XXX-soft`: The color used for subtle background such as custom container
22 | * or badges. It must satisfy the contrast ratio when putting `XXX-1` colors
23 | * on top of it.
24 | *
25 | * The soft color must be semi transparent alpha channel. This is crucial
26 | * because it allows adding multiple "soft" colors on top of each other
27 | * to create a accent, such as when having inline code block inside
28 | * custom containers.
29 | *
30 | * - `default`: The color used purely for subtle indication without any
31 | * special meanings attched to it such as bg color for menu hover state.
32 | *
33 | * - `brand`: Used for primary brand colors, such as link text, button with
34 | * brand theme, etc.
35 | *
36 | * - `tip`: Used to indicate useful information. The default theme uses the
37 | * brand color for this by default.
38 | *
39 | * - `warning`: Used to indicate warning to the users. Used in custom
40 | * container, badges, etc.
41 | *
42 | * - `danger`: Used to show error, or dangerous message to the users. Used
43 | * in custom container, badges, etc.
44 | * -------------------------------------------------------------------------- */
45 |
46 | :root {
47 | --vp-c-default-1: var(--vp-c-gray-1);
48 | --vp-c-default-2: var(--vp-c-gray-2);
49 | --vp-c-default-3: var(--vp-c-gray-3);
50 | --vp-c-default-soft: var(--vp-c-gray-soft);
51 |
52 | --vp-c-brand-1: var(--vp-c-indigo-1);
53 | --vp-c-brand-2: var(--vp-c-indigo-2);
54 | --vp-c-brand-3: var(--vp-c-indigo-3);
55 | --vp-c-brand-soft: var(--vp-c-indigo-soft);
56 |
57 | --vp-c-tip-1: var(--vp-c-brand-1);
58 | --vp-c-tip-2: var(--vp-c-brand-2);
59 | --vp-c-tip-3: var(--vp-c-brand-3);
60 | --vp-c-tip-soft: var(--vp-c-brand-soft);
61 |
62 | --vp-c-warning-1: var(--vp-c-yellow-1);
63 | --vp-c-warning-2: var(--vp-c-yellow-2);
64 | --vp-c-warning-3: var(--vp-c-yellow-3);
65 | --vp-c-warning-soft: var(--vp-c-yellow-soft);
66 |
67 | --vp-c-danger-1: var(--vp-c-red-1);
68 | --vp-c-danger-2: var(--vp-c-red-2);
69 | --vp-c-danger-3: var(--vp-c-red-3);
70 | --vp-c-danger-soft: var(--vp-c-red-soft);
71 | }
72 |
73 | /**
74 | * Component: Button
75 | * -------------------------------------------------------------------------- */
76 |
77 | :root {
78 | --vp-button-brand-border: transparent;
79 | --vp-button-brand-text: var(--vp-c-white);
80 | --vp-button-brand-bg: var(--vp-c-brand-3);
81 | --vp-button-brand-hover-border: transparent;
82 | --vp-button-brand-hover-text: var(--vp-c-white);
83 | --vp-button-brand-hover-bg: var(--vp-c-brand-2);
84 | --vp-button-brand-active-border: transparent;
85 | --vp-button-brand-active-text: var(--vp-c-white);
86 | --vp-button-brand-active-bg: var(--vp-c-brand-1);
87 | }
88 |
89 | /**
90 | * Component: Home
91 | * -------------------------------------------------------------------------- */
92 |
93 | :root {
94 | --vp-home-hero-name-color: transparent;
95 | --vp-home-hero-name-background: -webkit-linear-gradient(
96 | 120deg,
97 | #bd34fe 30%,
98 | #41d1ff
99 | );
100 |
101 | --vp-home-hero-image-background-image: linear-gradient(
102 | -45deg,
103 | #bd34fe 50%,
104 | #47caff 50%
105 | );
106 | --vp-home-hero-image-filter: blur(44px);
107 | }
108 |
109 | @media (min-width: 640px) {
110 | :root {
111 | --vp-home-hero-image-filter: blur(56px);
112 | }
113 | }
114 |
115 | @media (min-width: 960px) {
116 | :root {
117 | --vp-home-hero-image-filter: blur(68px);
118 | }
119 | }
120 |
121 | /**
122 | * Component: Custom Block
123 | * -------------------------------------------------------------------------- */
124 |
125 | :root {
126 | --vp-custom-block-tip-border: transparent;
127 | --vp-custom-block-tip-text: var(--vp-c-text-1);
128 | --vp-custom-block-tip-bg: var(--vp-c-brand-soft);
129 | --vp-custom-block-tip-code-bg: var(--vp-c-brand-soft);
130 | }
131 |
132 | /**
133 | * Component: Algolia
134 | * -------------------------------------------------------------------------- */
135 |
136 | .DocSearch {
137 | --docsearch-primary-color: var(--vp-c-brand-1) !important;
138 | }
139 |
140 |
--------------------------------------------------------------------------------
/docs/CHANGLOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## [v1.12.5] - 2024-08-01
4 |
5 | ### Added
6 | - 新增一个非常直观的gif demo让人10秒内了解该软件 (147ef46)。
7 | - 新增一个全新的banner (147ef46)。
8 |
9 | ### Changed
10 | - 完善了README的介绍,修改了徽章显示不了的问题 (7b49364)。
11 | - 修改README内结构逻辑,为README添加徽章 (567d805)。
12 | - 移动README里面的捐赠到文档当中,让README拥有更加一致的阅读体验 (4f78b76)。
13 | - 修改文档里面github跳转为当前项目(之前是跳到个人仓库) (4e8bfed)。
14 | - 优化了 README 以及文档 (ede6b91, bcbd0ce)。
15 |
16 | ### Fixed
17 | - 修复了任务恢复系统在找不到本地文件的时候会报错的bug (147ef46)。
18 | - 修复了不检测所有任务是否完成而直接检测总任务是否完成的bug (147ef46)。
19 | - 修复了徽章版本不显示的bug (3392e88)。
20 | - 通过固定窗体大小修复了在运行完毕之后窗口会“长高”的bug (14b8cc9)。
21 | - 修复了开始一个空任务导致提示输入视频不能为空的bug (091a9d6)。
22 | - 修复了文档内图片失效的问题以及README文字居中问题 (24fd63f)。
23 |
24 | ### Documentation
25 | - 完善了文档内的重新开始的提示和文档的CHANGLOG (091a9d6)。
26 | - 为README添加了徽章 (567d805)。
27 | - 修复了README徽章显示不了的问题 (7b49364)。
28 |
29 | ### UI
30 | - 现在第一次进入设置页面不会再弹出显示当前引擎的弹窗 (9398b4b)。
31 |
32 | ### Action
33 | - 现在的action将仅使用手动触发 (091a9d6)。
34 |
35 | ## [v1.12.2] - 2024-07-31
36 |
37 | 这一次相比上一个 v0.32.4 版本增加了大量的新内容,整个底层的逻辑完全重写了,于是现在大版本号进化到了 v1,相比起之前这一次多了很多新的内容
38 |
39 | 1. 增加了一个新的处理引擎,使用功能更加强大的 OpenCV 引擎能够处理更加多样化的视频需求,同时也可以切换回 FFmpeg 引擎,以此来保证速度
40 | 2. 新增了备份系统,如果在合成的过程中报错了,程序会记住报错文件,下一次能直接处理失败的文件
41 | 3. 优化了 FFmpeg 引擎模式下除了 H264 编码器外的所有模式
42 | 4. 现在使用终止程序,程序会立即停下,并且马上释放资源
43 | 5. 优化了对没有音频的视频的处理,现在会自动加一个音频(以前会直接报错)
44 | 6. 完整的说明文档,您可以在仓库页面找到他们
45 | 7. 更多全新的处理器(OpenCV 模式下)
46 | 8. 界面细节进一步优化,更加符合操作直觉
47 |
48 | ### Added
49 | - 主页删除视频时预览画面现在会实时刷新 (02f8c2b)。
50 | - 显示了备份恢复,如果上一次任务没有完成将会自动恢复上一次的任务 (05d7d3a)。
51 | - 完整实现了FFmpegEngine (640833d)。
52 | - 现在的ffmpeg报错信息染色会更加全面 (640833d)。
53 | - FFmpegEngine模式下使用了新的填充黑边的方式 (640833d)。
54 | - 使用策略模式分离VideoEngine,现在OpenCV和FFmpeg都有自己的方法 (31ab275)。
55 | - 设置页面的版本更新说明使用了新的messagebox提示框 (4513ed9)。
56 | - 新增一个MessageBox可以使用一个无边框的信息弹框 (4513ed9)。
57 | - 新增处理模式,未来可以选择使用什么引擎处理视频(ffmpeg,opencv) (7f3c11b)。
58 | - 新增了文件树控件,为未来批量视频压缩功能做准备 (7f3c11b)。
59 | - 现在会自动为没有音频轨道的视频添加音频 (82d0b20)。
60 | - 现在软件的使用时间会在日志中输出 (af020ec)。
61 | - 添加了一个真正的中断子线程的方法 (e3022a1)。
62 | - 新增白平衡 (545e05c)。
63 | - 新增亮度调整 (545e05c)。
64 | - 新增视频AI平滑 (545e05c)。
65 | - 新增了音频降噪处理器 (6c76102)。
66 | - 新增一个线程超时自动杀死的函数 (896c94f)。
67 | - 新增一个视频画面调整尺寸处理器 (896c94f)。
68 | - 新增ESPCN视频超分AI模型以及处理器 (c49b4ef)。
69 | - 新增LapSRN视频超分AI模型以及处理器 (c49b4ef)。
70 | - 新增视频亮度和对比度调节处理器 (c49b4ef)。
71 | - 新增白平衡处理器 (c34baae)。
72 | - 新增了命令行调用方式 (e86d5c4)。
73 | - 视频额外增加一层OpenCV处理层,未来能加入更多东西 (e86d5c4)。
74 |
75 | ### Changed
76 | - 修改vitepress的base为VideoFusion (7d036c7)。
77 | - 现在设置页面中仅OpenCV支持的功能被单独移动到了新的分组方便区分 (ec14943)。
78 | - 现在设置页面的提示条消失的更快 (ec14943)。
79 | - 重新修改视频降噪策略枚举类的值为ffmpeg的字符串 (640833d)。
80 | - task_resumer新增一个获取输出路径的属性 (31ab275)。
81 | - ffmpeg_handler中的一些私有方法现在转为公开方法 (31ab275)。
82 | - video_handler的入参从task_resumer转换为更加通用的Path (31ab275)。
83 | - 不再支持自动计算最大化音频采样率,以及移除相关依赖 (6c76102)。
84 | - 修改了crop处理器的处理条件 (545e05c)。
85 | - 修改了设置文件为设置文件夹,现在所有的视频都会输出到输出文件夹中 (bc92ba7)。
86 |
87 | ### Fixed
88 | - 修复了H265格式下过于先进的参数会导致有一些老旧视频无法支持的bug(已经退回到更加兼容的参数) (99ad599)。
89 | - 修复了少量静态视频在合并的时候长时间处于获取最佳分辨率的bug (02f8c2b)。
90 | - 修复因为package.json不存在导致构建失败的bug (f2d284c)。
91 | - 修复了H265格式编码导致的报错 (c5fb666)。
92 | - 修复了在小部分情况下FFmpegEngine因为scale小于pad会报错的问题 (640833d)。
93 | - 修复了设置页面修改临时目录等选项的时候ToolTip不会发生改变,而是需要重启才会发生改变的bug (7f3c11b)。
94 | - 修复了多个视频合并的时候crop_processor剪裁会出现错误 (6696f8c)。
95 | - 修复了获取视频信息时出现的一个逻辑漏洞 (6696f8c)。
96 | - 修复了提取音频的时候出现多个输出结果的bug (6696f8c)。
97 | - 修复了旋转角度为Nothing的时候会导致报错的bug (e3022a1)。
98 | - 修复了横屏报错的bug (e3022a1)。
99 | - 修复了processor_global_var可能全局不唯一的bug (e3022a1)。
100 | - 修复了参数传入的时候会被清除导致没能正确设置的bug (e3022a1)。
101 | - 修复了分析视频的时候主进度条不会发生改变的bug (e3022a1)。
102 | - 修复了结束的时候没有正确的完成信号的bug (e3022a1)。
103 | - 修复了即使不勾选删除临时文件夹也会导致文件夹被删除的bug (1e98c52)。
104 | - 修复了退出后无法删除临时文件夹的bug (fdfb5a0)。
105 | - 修复了video_info_reader模块中黑边逻辑的bug (bc92ba7)。
106 |
107 | ### Removed
108 | - 删除vitepress的base (5c84fa7)。
109 | - 删除了无用了函数 (733aeb2)。
110 |
111 | ### Documentation
112 | - 更新了文档视频顺序调整的部分 (fcd1c39)。
113 | - 调整了文档内导航栏的结构 (fcd1c39)。
114 | - 添加了完整的 VideoFusion 的介绍 (42c5367)。
115 |
116 | ### Optimization
117 | - 现在设置页面如果不是静态去黑边模式则视频采样率滑条会保存不可选状态 (42c5367)。
118 | - 优化了报错页面的报错捕获条件 (c5fb666)。
119 | - 优化了重新编译的参数,现在H265等其他编码器也有更好的效果 (d9dc12f)。
120 | - 视频不需要合并的时候不会修改分辨率 (bc92ba7)。
121 | - 将原本ffmpeg_handler中的compress_video中的职责分给reencode_video使其更加符合单一职责原则 (bc92ba7)。
122 | - 优化了rotate_processor中获取值的方式,让其更加安全 (af020ec)。
123 | - 重构了crop_processor中变量的访问权限 (af020ec)。
124 | - 重构了TempDir在其他模块中的调用 (e3022a1)。
125 | - 使用黑板模式代替DTO传递数据,更加灵活 (896c94f)。
126 | - 使用ffmpeg_handler将所有需要用到ffmpeg的地方都整合到了这里 (896c94f)。
127 | - 使用video_handler将所有对视频的操作整合到了这里 (896c94f)。
128 | - 重构了剪裁处理器 (8cbdcb6)。
129 | - 重构了旋转处理器 (8cbdcb6)。
130 | - 使用OpenCV重构了去色带模块 (c34baae)。
131 | - 使用OpenCV重构了去色块模块 (c34baae)。
132 | - 使用OpenCV重构了去抖动模块 (c34baae)。
133 | - 使用OpenCV重构了视频降噪模块 (c34baae)。
134 | - 使用OpenCV重构了视频均值降噪模块模块 (c34baae)。
135 | - 重构了获取视频信息的方式 (e86d5c4)。
136 | - 重构了图片去黑边的方式,从中提取为单独的工具类,增加复用 (e86d5c4)。
137 | - 重构两个去黑边方式,令他们面向对象 (e86d5c4)。
138 |
139 | ### Dependence
140 | - 更新了nuitka和fluent-widget依赖 (4513ed9)。
141 | - 重新依赖opencv-contrib-python (c34baae)。
142 |
143 | ### Actions
144 | - 修改pnpm大版本号为9 (981ad52)。
145 | - 试图让action能够识别pnpm-lock.yaml (0886911)。
146 | - 修改pnpm的版本为9 (7ee8a1d)。
147 | - 指定了pnpm的版本 (5666ff5)。
148 | - 修改action中npm为pnpm (599feb7)。
149 | - 新增了自动部署 vitepress 的 action (42c5367)。
150 |
151 | ### Tests
152 | - 新增resize_processor的测试 (896c94f)。
153 | - 为剪裁和旋转新增了测试 (8cbdcb6)。
154 | - 新增了更多的测试 (c34baae)。
155 | - 新增了测试4个测试 (e86d5c4)。
156 |
157 | ## [v0.32.4] - 2024-07-06
158 |
159 | ### Added
160 | - 新增失败提示条,程序出错之后会在主页面弹出提示条进行提醒 (cdd175f)。
161 | - 现在会检测ffprobe是否能够成功载入 (a6baa74)。
162 |
163 | ### Changed
164 | - 更新了nuitka和fluent-widget相关依赖 (cc5b21c)。
165 | - 更新了所有的依赖 (a6baa74)。
166 |
167 | ### Fixed
168 | - 修复了自动去黑边因为视频剪裁宽高不一致而导致的小概率出现合成失败的bug (cdd175f)。
169 | - 修复了ffprobe载入失败的bug (a6baa74)。
170 |
171 |
--------------------------------------------------------------------------------
/docs/about.md:
--------------------------------------------------------------------------------
1 | # VideoFusion 快速介绍
2 |
3 | ## VideoFusion 是什么?
4 |
5 | **VideoFusion 被开发出来是为了能让更多没有基础的朋友也能快速批量优化自己的视频**,所以会尽可能的减少普通用户操作的空间,相比起其他的开源视频处理工具,VideoFusion 限制了用户能够调整的参数细节,您无需苦心积虑的考虑这个参数是 0.1 好还是 0.2 好,所有的参数都被提前调优,您能接触到的功能将会仅仅只是一个开关,开启一个开关 VideoFusion 会自动帮您处理好剩下复杂的逻辑,您无需关心参数和更多细节
6 |
7 | 
8 |
9 | VideoFusion 在精细化处理每一个视频的同时依旧不忘初心,之后的功能都会支持批量处理的功能,您可以在设置好参数之后就可以点击开始然后去喝杯咖啡,陪陪孩子,VideoFusion 会处理好您的视频
10 |
11 | ## VideoFusion 不是什么?
12 |
13 | VideoFusion 不是 Pr,达芬奇那样精密的视频剪辑工具,而是一款剑走偏锋的软件,**VideoFusion主要是用来处理大量视频**,同时你有不需要对视频有精细化的裁剪便可以尝试使用 VideoFusion
14 |
15 | 你没有办法精确到每一帧的修改视频,而是使用诸如 AI 降噪,视频补帧之类的抽象概念对视频进行修改视频,所以可能在细节上会和您想象中的有所出入,不过 VideoFusion 依然可以当做您精细化处理视频的预处理操作
16 |
17 | 
18 |
19 | ## VideoFusion 的适用场景有哪些?
20 |
21 | 1. 素材整理
22 | 2. 日常 vlog 压缩
23 | 3. 批量视频合并
24 | 4. 视频音频降噪
25 | 5. 简单的视频增强
26 |
27 | 同学聚会,素材整理,日常 vlog,亦或是从网络上下载的有趣的小片段,他们并不值得我们花费大量时间在 Pr 里面精心剪裁,苦心积虑的添加音乐,后期二次调色等等,我们想要的只是为了取悦自己,或者留下一个美好的回忆,相比起操作各种专业剪辑软件中大量的时间消耗,如果您更加在意只是希望快速的批量调整一下视频的声音,或者觉得几个 G 的超高清影片有点过于占用硬盘空间,那么不要犹豫,请使用 VideoFusion,VideoFusion 能帮您处理好一切
--------------------------------------------------------------------------------
/docs/advanced_settings.md:
--------------------------------------------------------------------------------
1 | # 视频设置
2 |
3 | ## 设置输出文件路径
4 |
5 | 在这里设置输出文件夹的路径,需要注意,该路径内部最好不要存放其他文件,有可能会被输出的同名文件覆盖
6 |
7 | 默认情况下会在本地创建一个 output 文件夹并输出
8 |
9 | ## 视频去色带
10 |
11 | 色带是视频中常见的一种瑕疵,尤其在8位YUV格式的视频中2。它产生的原因是编码精度不足。具体来说,由于8位编码的限制,颜色渐变区域的编码值数量不足,导致显示器上的渐变效果被表现成了明显的色阶。
12 |
13 | 
14 |
15 |
16 | ## 视频去色块
17 |
18 | 块效应通常出现在视频的低比特率压缩中,尤其是在使用块编码(如H.264、MPEG-2等)时,视频中有块状分区
19 |
20 | 
21 |
22 | ## 视频去抖动
23 |
24 | 视频去抖动,参考了这位大佬的代码: https://github.com/lengkujiaai/video_stabilization
25 |
26 | 
27 |
28 | ::: warning 警告
29 | 同时该选项会出现大量黑边
30 | :::
31 |
32 | ## 输出视频帧率
33 |
34 | 视频帧率越高,视频越流畅,但是处理时长越长
35 |
36 | 
37 |
38 | ## 去黑边采样帧率
39 |
40 | 该选项可以控制去黑边的采样率,VideoFusion 会从视频里面获取这些数量的帧,然后对他们每一张进行去黑边处理,最后获取出现次数最高的可能性作为整个视频的去黑边基础,所以如果您发现您的视频被剪裁多了,您可以尝试加大该选项的值
41 |
42 | ::: tip 提示
43 | 仅在使用静态去黑边算法的时候该选项有效,[点我跳转到静态去黑边文档处](#静态去黑边)
44 | :::
45 |
46 | ## 视频黑边去除算法
47 |
48 | ### 静态去黑边
49 |
50 | 通过自研的算法实现单张去黑边,然后将整个视频去黑边的结果进行统计,出现次数最多的作为视频真正的去黑边值,适合在静态图片的时候使用,该选项在视频较短的时候速度比动态去黑边快很多,但是准确率有待提高
51 |
52 | ### 动态去黑边(推荐)
53 |
54 | 使用变动画面叠加的方式来获取到持续发生改变的区域,该区域作为真正的画面(默认黑边不会动),如果黑边是会动的黑边那么则无法实现去黑边,可以尝试使用静态去黑边,或者使用专业软件进行剪裁
55 |
56 | 
57 |
58 | ## 视频音量自动调整
59 |
60 | 用于解决视频音量过大或者过小的问题,响度标准来自中国政府统一标准
61 |
62 | 《国家广播电视总局关于发布《网络视听节目音频响度技术要求和测量方法》等三项广播电视和网络视听行业标准的通知》:https://www.gov.cn/zhengce/zhengceku/202308/content_6900294.htm
63 |
64 | 目前一共有 4 种可以设置的值
65 |
66 | 1. 关闭
67 | 2. 电台(适合嘈杂的环境)
68 | 3. TV(适合普通环境)
69 | 4. 电影(适合安静环境)
70 |
71 | ## 音频降噪
72 |
73 | ### 静态降噪
74 |
75 | 通过削去高频和低频的声音达到降噪的目的,参数经过测试可以实现大部分的降噪效果,速度快,但是对音乐或者特殊场合支持不友好,可能会消去重要声音
76 |
77 | ### AI 降噪(推荐)
78 |
79 | 使用 FFmpeg 的 arnndn 模型对音频进行降噪,速度同样也很快,不过对音乐的高频部分有部分削弱
80 |
81 | ## 视频降噪
82 |
83 | ### bilateral(双边滤波)
84 |
85 | 双边滤波器在平滑图像的同时保留边缘,这对于避免图像模糊非常重要,算法相对简单,计算效率较高
86 |
87 | ### NLMeans Filter(非局部均值滤波)
88 |
89 | 非局部均值滤波可以更好地去除噪声,同时保留更多的细节和纹理。它通过搜索整个图像中的相似块进行降噪,适应性较强,能够处理不同类型的噪声。对于复杂的、空间相关的噪声也有很好的去除效果。对低光视频拍摄中的噪声去除、老旧影片的修复都很有帮助
90 |
91 | :::warning 警告
92 | nlmeans 方法降噪会大幅增加视频处理时间(成倍增长),请谨慎使用
93 | :::
94 |
95 | ## 分辨率缩放算法
96 |
97 | ### 1. NEAREST(最近邻插值)
98 | 优势:
99 | - 计算简单:最近邻插值的计算量最小,因此速度非常快。
100 | - 无额外模糊:不会引入额外的模糊,保持原始像素的硬边缘。
101 |
102 | 适用场所:
103 | - 实时处理:需要快速计算的场合,例如实时视频流处理。
104 | - 低分辨率图像:放大像素艺术(如像素游戏图像)时效果较好。
105 |
106 | 缺点:
107 | - 图像质量低:可能产生锯齿效应和马赛克现象,缩放后图像质量较差。
108 | - 细节丢失:不适合需要高质量图像的场合。
109 |
110 | ### 2. BILINEAR(双线性插值)
111 | 优势:
112 | - 平滑过渡:相比最近邻插值,双线性插值在图像缩放时能产生平滑过渡效果。
113 | - 计算较快:计算复杂度较低,适合快速处理需求。
114 |
115 | 适用场所:
116 | - 一般视频缩放:适用于对质量要求不高的常规视频缩放。
117 | - 实时应用:如网络视频流、视频聊天等需要快速响应的场合。
118 |
119 | 缺点:
120 | - 细节损失:图像边缘会变得模糊,细节部分容易丢失。
121 | - 模糊效果:较大的缩放比例会导致图像明显模糊。
122 |
123 | ### 3. BICUBIC(双三次插值)
124 | 优势:
125 | - 高质量:相比双线性插值,双三次插值在缩放过程中能更好地保留细节。
126 | - 平滑效果好:平滑效果优于双线性插值,缩放后的图像更自然。
127 |
128 | 适用场所:
129 | - 图像处理:适用于需要较高质量图像缩放的场合,如图像编辑和照片处理。
130 | - 视频后期制作:对视频质量要求较高的后期制作过程。
131 |
132 | 缺点:
133 | - 计算复杂:计算量较大,处理时间较长,不适合实时处理。
134 | - 可能产生伪影:某些情况下,可能会产生轻微的伪影或边缘增强现象。
135 |
136 | ### 4. LANCZOS(Lanczos重采样)
137 | 优势:
138 | - 高保真度:Lanczos滤波器能最大限度地保留图像细节,缩放效果非常好。
139 | - 平滑自然:提供比双三次插值更好的平滑效果,减少伪影和模糊。
140 |
141 | 适用场所:
142 | - 高分辨率视频:适合需要保持高分辨率和细节的场合,如4K视频处理。
143 | - 视频转码和放大:对画质有较高要求的场合,如电影制作和视频修复。
144 |
145 | 缺点:
146 | - 计算复杂:计算量大,处理时间长,通常不适用于实时应用。
147 | - 边缘效应:某些情况下,可能会在高对比度边缘处产生轻微的振铃效应。
148 |
149 | ### 5. SINC(Sinc插值)
150 | 优势:
151 | - 理论最佳:在理论上,Sinc插值可以提供最理想的重建效果,最大程度保留原始信息。
152 | - 高保真度:能很好地保留图像细节,减少失真。
153 |
154 | 适用场所:
155 | - 高精度图像处理:适用于需要最高精度的图像和视频处理场合,如科学图像处理和高精度视频修复。
156 | - 专业视频制作:用于需要最高质量的专业视频制作和修复。
157 |
158 | 缺点:
159 | - 计算复杂:计算量非常大,处理时间长,不适用于实时处理。
160 | - 振铃效应:可能会产生振铃效应(ringing artifacts),尤其是在高对比度边缘。
161 |
162 | ### 总结
163 | - NEAREST:适合实时处理和像素艺术,但质量较低。
164 | - BILINEAR:适合一般视频缩放,速度快但模糊。
165 | - BICUBIC:适合需要高质量的图像和视频处理,但计算复杂。
166 | - LANCZOS:适合高分辨率和高质量视频处理,但计算复杂。
167 | - SINC:适合最高精度和专业用途,但计算最复杂。
168 |
169 | ## 视频补帧算法
170 |
171 | ### 普通补帧(Frame Interpolation)
172 |
173 | 普通视频补帧方法(如重复帧、线性插值)通常计算简单,速度较快。
174 |
175 | ### 光流法视频补帧(Optical Flow Interpolation)
176 |
177 | 光流法利用运动矢量进行插值,能够生成高质量的中间帧,减少模糊和伪影。插值帧效果更自然,尤其在处理复杂运动场景时表现优异。能够更好地保留图像细节和边缘,提升视觉体验。
178 |
179 | :::warning 警告
180 | 启用该选项会大幅增加处理时间
181 |
182 | 光流法计算量大,处理时间长,需要高性能的计算资源。
183 | :::
184 |
185 | ## 输出视频编码
186 |
187 | **默认情况下推荐只使用 H264 模式(速度均衡,压缩率高,画面损失肉眼几乎不可见)**,如果您拥有显卡,使用硬件编码速度会非常快,不过您需要确保您的环境支持使用硬件编码
188 |
189 | 一些编码器依赖于特定的硬件加速,所以需要确保你的系统中有相应的硬件。例如:
190 |
191 | *h264_qsv 和 hevc_qsv 需要Intel CPU并启用Quick Sync Video。*
192 |
193 | *h264_amf 和 hevc_amf 需要AMD GPU并启用AMF。*
194 |
195 | *h264_nvenc 和 hevc_nvenc 需要NVIDIA GPU并启用NVENC。*
196 |
197 | 确保你安装了最新的硬件驱动,以及相应的依赖库。例如:
198 |
199 | *Intel: 需要安装Intel Media SDK。*
200 |
201 | *AMD: 需要安装AMF SDK。*
202 |
203 | *NVIDIA: 需要安装NVIDIA驱动和CUDA。*
204 |
205 | ## 音频采样率
206 |
207 | 音频采样率,决定视频的音频质量的好坏,现在有下面的这些值支持选取
208 |
209 | - "8kHz-电话音质",
210 | - "16kHz-低质量音乐录音",
211 | - "22.05kHz-AM广播质量",
212 | - "32kHz-FM广播质量",
213 | - "44.1kHz-CD音质",
214 | - "96kHz-高解析度音频"
--------------------------------------------------------------------------------
/docs/contact_me.md:
--------------------------------------------------------------------------------
1 | # 联系我
2 |
3 | :email: 271374667@qq.com
4 |
5 | :radio: QQ群:557434492
6 |
7 | :computer: B站:https://space.bilibili.com/282527875
8 |
9 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/docs/donated.md:
--------------------------------------------------------------------------------
1 | # 捐赠
2 |
3 | 开源的梦想是一个崇高的事业,它使得社区成长, 代码得以培育。 根据自由开源的理念,VideoFusion致力于Good Labs的原则 。
4 |
5 | 如果我的工作对您的事业产生了帮助,请考虑将一些资金返还给开发团队,您的捐赠会让 VideoFusion更好的发展和更新,让我知道还有人在使用本产品,开源的财务支持字面上意味着开发项目的人有另外的理由坚持下去
6 |
7 | 许多有前途的开源项目变成了“放弃软件”,因为最初的开发人员已经失去了动力。 维护阶段实际上是许多开源项目死亡的地方。 金钱是一种很好的社区反馈,它说“坚持下去并继续维护这个项目!我们需要你!!!”
8 |
9 | ## 你从中获得了什么[¶](https://somethingcool.top/SimpleWMS/donation.html#_2)
10 |
11 | - 如果您支持开源,您将成为社区的一员。 这是一个庞大的社区,但往往紧密结合。 您的支持表明您相信社区,这使您成为家庭的一部分。 如果足够的财务支持汇集在一起,也可能意味着核心开发人员和维护人员能够全职工作而不需要在其他地方工作。 这反过来又促进了一个环境,在这个环境中,他们能够比在“业余爱好”的基础上更快地处理修复和请求。
12 | - 因此,也许您是一家使用开源软件为自己提供竞争优势、提高利润或简单解决其他软件无法解决的问题的企业。 也许你是一个使用开源解决方案的个人,你无法承受昂贵的企业软件。就像这个例子。
13 | - 或者您认识到数万行代码是您不必编写的行,仅仅只是感谢这背后的努力。
14 | - 无论您使用什么样的开源软件,都要想到那些花了很长时间开发它并让它为您运行的人。
15 | - 即使是一笔小额捐款也可能对他们产生巨大的影响,尤其是因为这意味着有人会非常感激他们并让他们知道。
16 |
17 | ## 如何捐赠?[¶](https://somethingcool.top/SimpleWMS/donation.html#_3)
18 |
19 | 您可以通过邮箱联系我进行捐赠,也可以通过下方的二维码进行捐赠
20 |
21 | 
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | # https://vitepress.dev/reference/default-theme-home-page
3 | layout: home
4 |
5 | hero:
6 | name: "VideoFusion"
7 | text: "批量化视频处理工具"
8 | tagline: |
9 | 自动去黑边,视频合并,AI降噪,视频压缩,无需繁杂操作
10 | 轻轻点击,应有尽有
11 | image:
12 | src: /logo.png
13 | alt: VideoFusion图标
14 | actions:
15 | - theme: brand
16 | text: 快速开始
17 | link: /quick-start
18 | - theme: alt
19 | text: 什么是VideoFusion?
20 | link: /about
21 |
22 | features:
23 | - title: 自动去黑边
24 | details: 自动识别视频黑边,可去除复杂彩色黑边,去除不规则黑边,去除黑边水印
25 | icon:
26 | src: /star.svg
27 | - title: 批量压缩
28 | details: 保持画质的情况下,大幅压缩视频大小,节省内存空间
29 | icon:
30 | src: /archive_folder.svg
31 | - title: 音频优化
32 | details: 自动调整音频响度,AI自动对音频降噪等
33 | icon:
34 | src: /audio_wave.svg
35 | - title: 视频快速二次处理
36 | details: 大量视频处理选项,白平衡,亮度自动调整,去色带,去抖动,应有尽有!
37 | icon:
38 | src: /process.svg
39 | ---
40 |
41 | ---
42 |
43 |
44 |
10 秒爱上VideoFusion
45 | 简单 3 步,去除视频黑边,同时合并视频,一切都是如此简单
46 |
47 | 
48 |
49 |
50 |
--------------------------------------------------------------------------------
/docs/normal_settings.md:
--------------------------------------------------------------------------------
1 | # 设置
2 |
3 | 点击左侧侧边栏最下面的一个按钮进入设置页面,接下来所有的设置都会在这个页面进行
4 |
5 | ## 引擎选择
6 |
7 | VideoFusion 一共提供了 2 种底层引擎供您处理视频
8 |
9 | 1. FFmpeg (更快)
10 | 2. OpenCV (功能更多)
11 |
12 | 您可以在设置页面里面选择不同的引擎,相比起 FFmpeg,OpenCV 能实现更加强大的功能(比如自动白平衡,视频亮度自动调整等),有一些功能仅仅在引擎为 OpenCV 模式下才能正常使用
13 |
14 | 
15 |
16 | ## FFmpeg 路径设置
17 |
18 | 软件的一大半功能都需要 FFmpeg 的支持,**VideoFusion 内置一个完整编译版本的 FFmpeg,所以您无需在电脑上额外安装 FFmpeg**,不过您也可以自行前往官网下载最新的 ffmpeg.exe 将它放入并替换根目录下的 bin 目录里面的 ffmpeg.exe
19 |
20 | ffmpeg.exe 下载地址:
21 | https://ffmpeg.org/download.html
22 |
23 | ## 合并视频(重要)
24 |
25 | 如果您不需要视频合并在一起,只是需要视频经过简单的处理,您可以取消勾选该选项,最终将会将视频分别输出,同时不会再二次缩放视频的分辨率
26 |
27 | 默认情况下为勾选状态
28 |
29 | ## 临时目录
30 |
31 | 视频处理的过程中会生成中间过程文件,这些文件通常和视频同等大小,不过不用担心,他们通常会在软件关闭的时候自动被清理,所以您不用担心硬盘会被过度占用,不过在运行的过程中依然建议使用一个较大的空间的盘符来存储视频临时文件
32 |
33 | 默认情况下会在项目根目录下创建一个 Temp 文件夹,在运行完毕之后会被释放
34 |
35 | ## 是否删除临时文件
36 |
37 | 您可以选择不删除临时文件,从里面找到一些有用的东西,或者分析视频为什么没有被成功的输出,不过需要注意,如果您勾选了删除临时文件,那么他们无法从回收站被找回
38 |
39 | ## 预览视频去黑边
40 |
41 | 将主页中的预览图片去除黑边
42 |
43 | 
44 |
45 | ::: info 提示
46 | 视频预览不代表最终处理结果,仅仅是一个预览,视频真正处理时会采用更加复杂的判断方法,单张图片的去黑边并不准确
47 | :::
48 |
49 | 默认情况下,该选项是关闭的
50 |
51 | ## 预览视频帧
52 |
53 | 通过这个选项可以改变在主页预览视频的时候是哪一帧的画面,因为有一些视频的开头或者结尾可能是黑屏,所以支持随机帧预览
54 |
55 | 默认情况下为视频的第一帧
56 |
57 | ## 检查更新
58 |
59 | 点击检查更新,软件会自动检测当然是否有更新的版本,不过软件不会下载,您需要自行前往项目地址进行下载,项目在国内和国外都有分流地址,检测地址使用的是 GitHub,所以有可能会检测失败
60 |
61 | 
62 |
63 |
64 | GitHub下载地址: https://github.com/271374667/VideoFusion
65 |
66 | Gitee下载地址(国内): https://gitee.com/sun-programing/VideoFusion
67 |
68 | Gitcode(国内): https://gitcode.com/PythonImporter/VideoFusion
69 |
70 |
71 |
--------------------------------------------------------------------------------
/docs/opencv_only_settings.md:
--------------------------------------------------------------------------------
1 | # OpenCV 专属设置
2 |
3 | 使用这些命令之前您需要先将底层引擎调整至 OpenCV 模式
4 |
5 | [如何设置?](/normal_settings.md#引擎选择)
6 |
7 | ## 视频白平衡
8 |
9 | 自动白平衡用于调整视频帧中的颜色,使其看起来更加自然和真实。白平衡的主要作用是消除由于不同光源(如日光、荧光灯、白炽灯等)引起的色偏,使得图像中的白色物体在不同光照条件下都能呈现出真实的白色
10 |
11 | 
12 |
13 | ## 自动调整视频亮度对比度
14 |
15 | 自动将视频亮度和对比度调整到一个合适的值确保画面不会过暗或者过亮,VideoFusion 会自动提高或者降低每一帧的亮度
16 |
17 | 
18 |
19 | ## 超分辨率算法平滑画面
20 |
21 | 该方法不是传统意义上的超分辨率,他更像是一个超级降噪,使用了这个功能之后视频的噪点几乎会全部消失
22 |
23 | 
24 |
25 | :::warning 警告
26 | 该选项会大幅增加运算时间
27 | :::
--------------------------------------------------------------------------------
/docs/public/VF和达芬奇和PR的区别.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/271374667/VideoFusion/9ba7b8b301481ed148bc4d853a5efe8bbb7188b5/docs/public/VF和达芬奇和PR的区别.png
--------------------------------------------------------------------------------
/docs/public/archive_folder.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/public/audio_wave.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/public/black_remover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/271374667/VideoFusion/9ba7b8b301481ed148bc4d853a5efe8bbb7188b5/docs/public/black_remover.png
--------------------------------------------------------------------------------
/docs/public/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/271374667/VideoFusion/9ba7b8b301481ed148bc4d853a5efe8bbb7188b5/docs/public/demo.gif
--------------------------------------------------------------------------------
/docs/public/donate.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/271374667/VideoFusion/9ba7b8b301481ed148bc4d853a5efe8bbb7188b5/docs/public/donate.png
--------------------------------------------------------------------------------
/docs/public/live_stabilization.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/271374667/VideoFusion/9ba7b8b301481ed148bc4d853a5efe8bbb7188b5/docs/public/live_stabilization.gif
--------------------------------------------------------------------------------
/docs/public/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/271374667/VideoFusion/9ba7b8b301481ed148bc4d853a5efe8bbb7188b5/docs/public/logo.png
--------------------------------------------------------------------------------
/docs/public/process.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/public/star.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/public/不止合并.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/271374667/VideoFusion/9ba7b8b301481ed148bc4d853a5efe8bbb7188b5/docs/public/不止合并.jpg
--------------------------------------------------------------------------------
/docs/public/亮度自动调整.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/271374667/VideoFusion/9ba7b8b301481ed148bc4d853a5efe8bbb7188b5/docs/public/亮度自动调整.png
--------------------------------------------------------------------------------
/docs/public/快速合并视频向导图片_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/271374667/VideoFusion/9ba7b8b301481ed148bc4d853a5efe8bbb7188b5/docs/public/快速合并视频向导图片_1.png
--------------------------------------------------------------------------------
/docs/public/检查更新.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/271374667/VideoFusion/9ba7b8b301481ed148bc4d853a5efe8bbb7188b5/docs/public/检查更新.png
--------------------------------------------------------------------------------
/docs/public/白平衡.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/271374667/VideoFusion/9ba7b8b301481ed148bc4d853a5efe8bbb7188b5/docs/public/白平衡.png
--------------------------------------------------------------------------------
/docs/public/简单的设置页面.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/271374667/VideoFusion/9ba7b8b301481ed148bc4d853a5efe8bbb7188b5/docs/public/简单的设置页面.png
--------------------------------------------------------------------------------
/docs/public/视频去色块.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/271374667/VideoFusion/9ba7b8b301481ed148bc4d853a5efe8bbb7188b5/docs/public/视频去色块.png
--------------------------------------------------------------------------------
/docs/public/视频去色带.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/271374667/VideoFusion/9ba7b8b301481ed148bc4d853a5efe8bbb7188b5/docs/public/视频去色带.png
--------------------------------------------------------------------------------
/docs/public/视频帧率对画面的影响.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/271374667/VideoFusion/9ba7b8b301481ed148bc4d853a5efe8bbb7188b5/docs/public/视频帧率对画面的影响.gif
--------------------------------------------------------------------------------
/docs/public/设置引擎.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/271374667/VideoFusion/9ba7b8b301481ed148bc4d853a5efe8bbb7188b5/docs/public/设置引擎.png
--------------------------------------------------------------------------------
/docs/public/调整视频顺序.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/271374667/VideoFusion/9ba7b8b301481ed148bc4d853a5efe8bbb7188b5/docs/public/调整视频顺序.gif
--------------------------------------------------------------------------------
/docs/public/超分平滑.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/271374667/VideoFusion/9ba7b8b301481ed148bc4d853a5efe8bbb7188b5/docs/public/超分平滑.png
--------------------------------------------------------------------------------
/docs/public/输出页面.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/271374667/VideoFusion/9ba7b8b301481ed148bc4d853a5efe8bbb7188b5/docs/public/输出页面.png
--------------------------------------------------------------------------------
/docs/public/重新开始.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/271374667/VideoFusion/9ba7b8b301481ed148bc4d853a5efe8bbb7188b5/docs/public/重新开始.png
--------------------------------------------------------------------------------
/docs/public/随时暂停.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/271374667/VideoFusion/9ba7b8b301481ed148bc4d853a5efe8bbb7188b5/docs/public/随时暂停.gif
--------------------------------------------------------------------------------
/docs/public/预览视频去黑边.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/271374667/VideoFusion/9ba7b8b301481ed148bc4d853a5efe8bbb7188b5/docs/public/预览视频去黑边.png
--------------------------------------------------------------------------------
/docs/quick-start.md:
--------------------------------------------------------------------------------
1 | # 快速开始
2 |
3 | ## 10 秒爱上VideoFusion
4 |
5 | 下面这个 GIF 将会带您快速查看两个有不规则黑边的视频是如何完美去除黑边然后旋转成正确的朝向最后合并的
6 |
7 | 
8 |
9 | ## 如何快速合并一堆视频?
10 |
11 | VideoFuison 已经帮您简化了大量的操作,**您只需要 2 次点击就能实现将视频批量合并/处理**
12 |
13 | 
14 |
15 | 
16 |
17 | **在处理完成之后,VideoFusion 会自动弹出合并完成之后的文件夹**,您可以在这里找到您的输出文件,当然您也可以自定义输出文件夹的位置,VideoFusion 拥有大量可自定义内容和功能
18 |
19 | ### 横屏还是竖屏,由您做决定
20 |
21 | 视频的输出需要您人工进行选择,您可以自由的选择输出的视频是一个横屏的视频还是竖屏的视频,VideoFusion 会智能地帮您旋转不符合规则的视频,让所有的视频都如您所愿
22 |
23 | ::: info 提示
24 | 自动旋转是根据视频的长宽进行判断是否旋转,无法识别画面内容,请自行确保旋转之后的画面内容一致,否则有可能出现上下颠倒或者左右颠倒的情况(不过在代码眼里这依旧是正确的结果)
25 | :::
26 |
27 | ## 智能调整视频顺序
28 |
29 | 如果您需要将多个视频合并成一个视频,那么您可以简单的在主页的视屏文件列表里通过拖拽的方式调整他们的顺序,同时 VideoFusion 还内置了一些排序策略,可以智能对视频进行自动排序
30 |
31 | 
32 |
33 | 目前支持自动排序的文件名如下:
34 |
35 | ::: details 点我查看所有支持智能排序的文件名类型
36 |
37 | - **系统重命名文件**
38 | - 例如 `重命名(1), 重命名(2)`,
39 | - 自动根据括号内的数字进行排序
40 | - **包含时间的文件**
41 | - 例如 `2023-6-25毕业旅游.mp4` 或者 `工作需要2018/7/11.mkv`
42 | - 通过识别时间进行排序,注意时间必须得是(年份/月份/日期)这样的组合,中间的连接符随意
43 | - **纯数字**
44 | - 例如 `1.mp4` 或者是 `0006.mp4`
45 | - 通过获取其中的数字进行排序
46 | - **字符串排序**
47 | - 如果上面的需求均不符合则使用字符串排序
48 |
49 | :::
50 |
51 | ::: tip 注意
52 | 必须所有的文件都满足需求才能使用智能排序,例如所有的文件都需要是纯数字才能自动使用纯数字排序,如果有一个文件不是纯数字则使用默认的字符串排序,其他排序同理,所以请确保所有文件名字类型相同
53 | :::
54 |
55 | ## 随时终止您的任务
56 |
57 | 任务太长?配置出现错误?没有关系,随时终止您的任务,释放您的 CPU 和内存,告别 FFmpeg 的后台残留
58 |
59 | 
60 |
61 | :::info 提示
62 | 当所有的视频被分析完成之后进入处理阶段才能保存到本地,如果在分析视频阶段就终止任务则下一次需要重新开始
63 | :::
64 |
65 | ## 重新开始
66 |
67 | VideoFusion 闪退了?任务中断之后后悔了想要重新开始?没有关系,VideoFusion 支持重新开始,轻点 OK 按键让您回到上一次的进度,您甚至可以在修改设置之后再重新开始,VideoFusion 会帮你处理好一切
68 |
69 | 
70 |
71 | ## 观察输出
72 |
73 | 如果您的程序卡主了或者很长时间都没有反应,您可以前往输出页面查看报错,将报错提交为 issue 整理好您的报错信息,附上报错原因和环境,作者将很快与您取得联系
74 |
75 | 
76 |
77 | ## 更多
78 |
79 | 恭喜您,您已经能够成功使用 VideoFusion 了,不过 VideoFusion 依旧有非常多的特性,您可以在设置页面找到他们,同时您也可以在这个文档里面找到相关的说明和帮助
80 |
81 | [前往查看更多设置](/normal_settings.md)
--------------------------------------------------------------------------------
/docs/thanks.md:
--------------------------------------------------------------------------------
1 | # 感谢
2 |
3 | 感谢为本项目提供技术支持的所有项目。没有你们的帮助,本项目不可能得以实现。
4 | 以下排名不分先后:
5 |
6 | - [PyQt-Fluent-Widgets](https://github.com/zhiyiYo/PyQt-Fluent-Widgets):提供了 Qt 的美化
7 | - [PySide6](https://wiki.qt.io/Qt_for_Python):提供了 Python 与 Qt 的接口
8 | - [ffmpeg](https://ffmpeg.org/):提供了视频处理的功能
9 | - [opencv](https://opencv.org/):提供了图像处理的功能
10 | - [auto-editor](https://github.com/WyattBlue/auto-editor):提供了视频自动剪辑的功能
11 | - [python-audio-separator](https://github.com/nomadkaraoke/python-audio-separator):提供了音频分离的功能
--------------------------------------------------------------------------------
/libomp140.x86_64.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/271374667/VideoFusion/9ba7b8b301481ed148bc4d853a5efe8bbb7188b5/libomp140.x86_64.dll
--------------------------------------------------------------------------------
/openh264-2.4.1-win64.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/271374667/VideoFusion/9ba7b8b301481ed148bc4d853a5efe8bbb7188b5/openh264-2.4.1-win64.dll
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "devDependencies": {
3 | "vitepress": "^1.3.1"
4 | },
5 | "scripts": {
6 | "docs:dev": "vitepress dev docs",
7 | "docs:build": "vitepress build docs",
8 | "docs:preview": "vitepress preview docs"
9 | }
10 | }
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [project]
2 | name = "videomosaic"
3 | version = "0.1.0"
4 | description = "一站式短视频拼接软件 无依赖,点击即用,自动去黑边,自动帧同步,自动调整分辨率,批量变更视频为横屏/竖屏"
5 | authors = [
6 | { name = "PythonImporter", email = "271374667@qq.com" }
7 | ]
8 | dependencies = [
9 | "pyside6-fluent-widgets>=1.5.6",
10 | "ansi2html>=1.9.1",
11 | "loguru>=0.7.2",
12 | "opencv-contrib-python>=4.10.0.84",
13 | "typing-extensions>=4.12.2",
14 | "auto-editor>=24.31.1",
15 | "audio-separator[cpu]>=0.18.3",
16 | "noisereduce>=3.0.2",
17 | "onnx==1.16.1",
18 | ]
19 | readme = "README.md"
20 | requires-python = ">= 3.10"
21 |
22 | [build-system]
23 | requires = ["hatchling"]
24 | build-backend = "hatchling.build"
25 |
26 | [tool.rye]
27 | managed = true
28 | dev-dependencies = [
29 | "nuitka>=2.2.3",
30 | "pyinstaller>=6.10.0",
31 | ]
32 |
33 | [[tool.rye.sources]]
34 | name = "default"
35 | url = "https://pypi.tuna.tsinghua.edu.cn/simple"
36 |
37 | [[tool.rye.sources]]
38 | name = "pypi"
39 | url = "https://pypi.org/simple"
40 |
41 | [[tool.rye.sources]]
42 | name = "pytorch"
43 | url = "https://download.pytorch.org/whl/cu121"
44 |
45 | [tool.hatch.metadata]
46 | allow-direct-references = true
47 |
48 | [tool.hatch.build.targets.wheel]
49 | packages = ["src/videomosaic"]
50 |
--------------------------------------------------------------------------------
/requirements-dev.lock:
--------------------------------------------------------------------------------
1 | # generated by rye
2 | # use `rye lock` or `rye sync` to update this lockfile
3 | #
4 | # last locked with the following flags:
5 | # pre: false
6 | # features: []
7 | # all-features: false
8 | # with-sources: false
9 | # generate-hashes: false
10 | # universal: false
11 |
12 | -e file:.
13 | absl-py==2.1.0
14 | # via ml-collections
15 | ae-ffmpeg==1.2.0
16 | # via auto-editor
17 | altgraph==0.17.4
18 | # via pyinstaller
19 | ansi2html==1.9.2
20 | # via videomosaic
21 | audio-separator==0.18.3
22 | # via videomosaic
23 | audioread==3.0.1
24 | # via librosa
25 | auto-editor==24.31.1
26 | # via videomosaic
27 | beartype==0.18.5
28 | # via audio-separator
29 | certifi==2024.7.4
30 | # via requests
31 | cffi==1.17.0
32 | # via samplerate
33 | # via soundfile
34 | charset-normalizer==3.3.2
35 | # via requests
36 | colorama==0.4.6
37 | # via loguru
38 | # via tqdm
39 | coloredlogs==15.0.1
40 | # via onnxruntime
41 | contextlib2==21.6.0
42 | # via ml-collections
43 | contourpy==1.2.1
44 | # via matplotlib
45 | cycler==0.12.1
46 | # via matplotlib
47 | cython==3.0.11
48 | # via diffq
49 | darkdetect==0.8.0
50 | # via pyside6-fluent-widgets
51 | decorator==5.1.1
52 | # via librosa
53 | diffq==0.2.4
54 | # via audio-separator
55 | einops==0.8.0
56 | # via audio-separator
57 | # via rotary-embedding-torch
58 | filelock==3.15.4
59 | # via torch
60 | flatbuffers==24.3.25
61 | # via onnxruntime
62 | fonttools==4.53.1
63 | # via matplotlib
64 | fsspec==2024.6.1
65 | # via torch
66 | humanfriendly==10.0
67 | # via coloredlogs
68 | idna==3.7
69 | # via requests
70 | jinja2==3.1.4
71 | # via torch
72 | joblib==1.4.2
73 | # via librosa
74 | # via scikit-learn
75 | julius==0.2.7
76 | # via audio-separator
77 | kiwisolver==1.4.5
78 | # via matplotlib
79 | lazy-loader==0.4
80 | # via librosa
81 | librosa==0.10.2.post1
82 | # via audio-separator
83 | # via noisereduce
84 | llvmlite==0.43.0
85 | # via numba
86 | loguru==0.7.2
87 | # via videomosaic
88 | markupsafe==2.1.5
89 | # via jinja2
90 | matplotlib==3.9.2
91 | # via noisereduce
92 | ml-collections==0.1.1
93 | # via audio-separator
94 | mpmath==1.3.0
95 | # via sympy
96 | msgpack==1.0.8
97 | # via librosa
98 | networkx==3.3
99 | # via torch
100 | noisereduce==3.0.2
101 | # via videomosaic
102 | nuitka==2.4.2
103 | numba==0.60.0
104 | # via librosa
105 | # via resampy
106 | numpy==1.26.4
107 | # via audio-separator
108 | # via auto-editor
109 | # via contourpy
110 | # via diffq
111 | # via librosa
112 | # via matplotlib
113 | # via noisereduce
114 | # via numba
115 | # via onnx
116 | # via onnx2torch
117 | # via onnxruntime
118 | # via opencv-contrib-python
119 | # via resampy
120 | # via samplerate
121 | # via scikit-learn
122 | # via scipy
123 | # via soxr
124 | # via torchvision
125 | onnx==1.16.1
126 | # via audio-separator
127 | # via onnx2torch
128 | # via videomosaic
129 | onnx2torch==1.5.15
130 | # via audio-separator
131 | onnxruntime==1.18.1
132 | # via audio-separator
133 | opencv-contrib-python==4.10.0.84
134 | # via videomosaic
135 | ordered-set==4.1.0
136 | # via nuitka
137 | packaging==23.2
138 | # via lazy-loader
139 | # via matplotlib
140 | # via onnxruntime
141 | # via pooch
142 | # via pyinstaller
143 | # via pyinstaller-hooks-contrib
144 | pefile==2023.2.7
145 | # via pyinstaller
146 | pillow==10.4.0
147 | # via matplotlib
148 | # via torchvision
149 | platformdirs==4.2.2
150 | # via pooch
151 | pooch==1.8.2
152 | # via librosa
153 | protobuf==5.27.3
154 | # via onnx
155 | # via onnxruntime
156 | pyav==12.3.0
157 | # via auto-editor
158 | pycparser==2.22
159 | # via cffi
160 | pydub==0.25.1
161 | # via audio-separator
162 | pyinstaller==6.10.0
163 | pyinstaller-hooks-contrib==2024.8
164 | # via pyinstaller
165 | pyparsing==3.1.2
166 | # via matplotlib
167 | pyreadline3==3.4.1
168 | # via humanfriendly
169 | pyside6==6.7.2
170 | # via pyside6-fluent-widgets
171 | pyside6-addons==6.7.2
172 | # via pyside6
173 | pyside6-essentials==6.7.2
174 | # via pyside6
175 | # via pyside6-addons
176 | pyside6-fluent-widgets==1.6.0
177 | # via videomosaic
178 | pysidesix-frameless-window==0.3.12
179 | # via pyside6-fluent-widgets
180 | python-dateutil==2.9.0.post0
181 | # via matplotlib
182 | pywin32==306
183 | # via pysidesix-frameless-window
184 | pywin32-ctypes==0.2.3
185 | # via pyinstaller
186 | pyyaml==6.0.2
187 | # via audio-separator
188 | # via ml-collections
189 | requests==2.32.3
190 | # via audio-separator
191 | # via pooch
192 | resampy==0.4.3
193 | # via audio-separator
194 | rotary-embedding-torch==0.6.4
195 | # via audio-separator
196 | samplerate==0.1.0
197 | # via audio-separator
198 | scikit-learn==1.5.1
199 | # via librosa
200 | scipy==1.14.0
201 | # via audio-separator
202 | # via librosa
203 | # via noisereduce
204 | # via scikit-learn
205 | setuptools==72.2.0
206 | # via pyinstaller
207 | # via pyinstaller-hooks-contrib
208 | shiboken6==6.7.2
209 | # via pyside6
210 | # via pyside6-addons
211 | # via pyside6-essentials
212 | six==1.16.0
213 | # via audio-separator
214 | # via ml-collections
215 | # via python-dateutil
216 | soundfile==0.12.1
217 | # via librosa
218 | soxr==0.4.0
219 | # via librosa
220 | sympy==1.13.1
221 | # via onnxruntime
222 | # via torch
223 | threadpoolctl==3.5.0
224 | # via scikit-learn
225 | torch==2.4.0
226 | # via audio-separator
227 | # via diffq
228 | # via julius
229 | # via onnx2torch
230 | # via rotary-embedding-torch
231 | # via torchvision
232 | torchvision==0.19.0
233 | # via onnx2torch
234 | tqdm==4.66.5
235 | # via audio-separator
236 | # via noisereduce
237 | typing-extensions==4.12.2
238 | # via librosa
239 | # via torch
240 | # via videomosaic
241 | urllib3==2.2.2
242 | # via requests
243 | win32-setctime==1.1.0
244 | # via loguru
245 | zstandard==0.23.0
246 | # via nuitka
247 |
--------------------------------------------------------------------------------
/requirements.lock:
--------------------------------------------------------------------------------
1 | # generated by rye
2 | # use `rye lock` or `rye sync` to update this lockfile
3 | #
4 | # last locked with the following flags:
5 | # pre: false
6 | # features: []
7 | # all-features: false
8 | # with-sources: false
9 | # generate-hashes: false
10 | # universal: false
11 |
12 | -e file:.
13 | absl-py==2.1.0
14 | # via ml-collections
15 | ae-ffmpeg==1.2.0
16 | # via auto-editor
17 | ansi2html==1.9.2
18 | # via videomosaic
19 | audio-separator==0.18.3
20 | # via videomosaic
21 | audioread==3.0.1
22 | # via librosa
23 | auto-editor==24.31.1
24 | # via videomosaic
25 | beartype==0.18.5
26 | # via audio-separator
27 | certifi==2024.7.4
28 | # via requests
29 | cffi==1.17.0
30 | # via samplerate
31 | # via soundfile
32 | charset-normalizer==3.3.2
33 | # via requests
34 | colorama==0.4.6
35 | # via loguru
36 | # via tqdm
37 | coloredlogs==15.0.1
38 | # via onnxruntime
39 | contextlib2==21.6.0
40 | # via ml-collections
41 | contourpy==1.2.1
42 | # via matplotlib
43 | cycler==0.12.1
44 | # via matplotlib
45 | cython==3.0.11
46 | # via diffq
47 | darkdetect==0.8.0
48 | # via pyside6-fluent-widgets
49 | decorator==5.1.1
50 | # via librosa
51 | diffq==0.2.4
52 | # via audio-separator
53 | einops==0.8.0
54 | # via audio-separator
55 | # via rotary-embedding-torch
56 | filelock==3.15.4
57 | # via torch
58 | flatbuffers==24.3.25
59 | # via onnxruntime
60 | fonttools==4.53.1
61 | # via matplotlib
62 | fsspec==2024.6.1
63 | # via torch
64 | humanfriendly==10.0
65 | # via coloredlogs
66 | idna==3.7
67 | # via requests
68 | jinja2==3.1.4
69 | # via torch
70 | joblib==1.4.2
71 | # via librosa
72 | # via scikit-learn
73 | julius==0.2.7
74 | # via audio-separator
75 | kiwisolver==1.4.5
76 | # via matplotlib
77 | lazy-loader==0.4
78 | # via librosa
79 | librosa==0.10.2.post1
80 | # via audio-separator
81 | # via noisereduce
82 | llvmlite==0.43.0
83 | # via numba
84 | loguru==0.7.2
85 | # via videomosaic
86 | markupsafe==2.1.5
87 | # via jinja2
88 | matplotlib==3.9.2
89 | # via noisereduce
90 | ml-collections==0.1.1
91 | # via audio-separator
92 | mpmath==1.3.0
93 | # via sympy
94 | msgpack==1.0.8
95 | # via librosa
96 | networkx==3.3
97 | # via torch
98 | noisereduce==3.0.2
99 | # via videomosaic
100 | numba==0.60.0
101 | # via librosa
102 | # via resampy
103 | numpy==1.26.4
104 | # via audio-separator
105 | # via auto-editor
106 | # via contourpy
107 | # via diffq
108 | # via librosa
109 | # via matplotlib
110 | # via noisereduce
111 | # via numba
112 | # via onnx
113 | # via onnx2torch
114 | # via onnxruntime
115 | # via opencv-contrib-python
116 | # via resampy
117 | # via samplerate
118 | # via scikit-learn
119 | # via scipy
120 | # via soxr
121 | # via torchvision
122 | onnx==1.16.1
123 | # via audio-separator
124 | # via onnx2torch
125 | # via videomosaic
126 | onnx2torch==1.5.15
127 | # via audio-separator
128 | onnxruntime==1.18.1
129 | # via audio-separator
130 | opencv-contrib-python==4.10.0.84
131 | # via videomosaic
132 | packaging==23.2
133 | # via lazy-loader
134 | # via matplotlib
135 | # via onnxruntime
136 | # via pooch
137 | pillow==10.4.0
138 | # via matplotlib
139 | # via torchvision
140 | platformdirs==4.2.2
141 | # via pooch
142 | pooch==1.8.2
143 | # via librosa
144 | protobuf==5.27.3
145 | # via onnx
146 | # via onnxruntime
147 | pyav==12.3.0
148 | # via auto-editor
149 | pycparser==2.22
150 | # via cffi
151 | pydub==0.25.1
152 | # via audio-separator
153 | pyparsing==3.1.2
154 | # via matplotlib
155 | pyreadline3==3.4.1
156 | # via humanfriendly
157 | pyside6==6.7.2
158 | # via pyside6-fluent-widgets
159 | pyside6-addons==6.7.2
160 | # via pyside6
161 | pyside6-essentials==6.7.2
162 | # via pyside6
163 | # via pyside6-addons
164 | pyside6-fluent-widgets==1.6.0
165 | # via videomosaic
166 | pysidesix-frameless-window==0.3.12
167 | # via pyside6-fluent-widgets
168 | python-dateutil==2.9.0.post0
169 | # via matplotlib
170 | pywin32==306
171 | # via pysidesix-frameless-window
172 | pyyaml==6.0.2
173 | # via audio-separator
174 | # via ml-collections
175 | requests==2.32.3
176 | # via audio-separator
177 | # via pooch
178 | resampy==0.4.3
179 | # via audio-separator
180 | rotary-embedding-torch==0.6.4
181 | # via audio-separator
182 | samplerate==0.1.0
183 | # via audio-separator
184 | scikit-learn==1.5.1
185 | # via librosa
186 | scipy==1.14.0
187 | # via audio-separator
188 | # via librosa
189 | # via noisereduce
190 | # via scikit-learn
191 | shiboken6==6.7.2
192 | # via pyside6
193 | # via pyside6-addons
194 | # via pyside6-essentials
195 | six==1.16.0
196 | # via audio-separator
197 | # via ml-collections
198 | # via python-dateutil
199 | soundfile==0.12.1
200 | # via librosa
201 | soxr==0.4.0
202 | # via librosa
203 | sympy==1.13.1
204 | # via onnxruntime
205 | # via torch
206 | threadpoolctl==3.5.0
207 | # via scikit-learn
208 | torch==2.4.0
209 | # via audio-separator
210 | # via diffq
211 | # via julius
212 | # via onnx2torch
213 | # via rotary-embedding-torch
214 | # via torchvision
215 | torchvision==0.19.0
216 | # via onnx2torch
217 | tqdm==4.66.5
218 | # via audio-separator
219 | # via noisereduce
220 | typing-extensions==4.12.2
221 | # via librosa
222 | # via torch
223 | # via videomosaic
224 | urllib3==2.2.2
225 | # via requests
226 | win32-setctime==1.1.0
227 | # via loguru
228 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | pyside6-fluent-widgets>=1.5.6
2 | ansi2html>=1.9.1
3 | loguru>=0.7.2
4 | opencv-contrib-python>=4.10.0.84
5 | typing-extensions>=4.12.2
6 | auto-editor>=24.31.1
7 | audio-separator[cpu]>=0.18.3
8 | noisereduce>=3.0.2
--------------------------------------------------------------------------------
/scripts/compile_view.py:
--------------------------------------------------------------------------------
1 | import subprocess
2 | from concurrent.futures import ThreadPoolExecutor
3 |
4 | from src.core.paths import ASSETS_DIR, SRC_DIR, QRC_FILE, QRC_PY_FILE
5 |
6 | UI_DIR = ASSETS_DIR / "ui"
7 | PY_DIR = SRC_DIR / "interface"
8 |
9 | count = 0
10 | thread_pool = ThreadPoolExecutor(10)
11 |
12 | print("正在清空Ui_*.py文件夹下的所有内容")
13 | # 清空Ui_*.py文件夹下的所有内容
14 | for each in PY_DIR.glob("**/*.*"):
15 | if each.stem.startswith("Ui_"):
16 | print(f"正在删除{each}……")
17 | each.unlink()
18 |
19 |
20 | def trans_ui_to_py(ui_file):
21 | print(f"当前正在转换{ui_file}……")
22 | py_file = PY_DIR / f"Ui_{ui_file.stem}.py"
23 | subprocess.run(["pyside6-uic", ui_file, "-o", py_file])
24 |
25 |
26 | for ui_file in UI_DIR.glob("**/*.ui"):
27 | count += 1
28 | thread_pool.submit(trans_ui_to_py, ui_file)
29 |
30 | thread_pool.shutdown(wait=True)
31 |
32 | print(f"Done! {count} files have been converted.")
33 |
34 | print("正在替换资源文件")
35 | for each in PY_DIR.glob("**/*.py"):
36 | if each.stem == "__init__":
37 | continue
38 |
39 | with open(each, "r", encoding="utf-8") as f:
40 | content = f.read()
41 | content = content.replace("import res_rc", "from src.resource import rc_res")
42 | with open(each, "w", encoding="utf-8") as f:
43 | f.write(content)
44 | print(f"替换{each}的 res 路径成功!")
45 |
46 | print(f"Done! {count} files have been converted.")
47 |
48 | # 替换qrc文件
49 | print("正在编译资源文件")
50 | # 使用 pyside6-rcc 命令将 qrc 文件编译成 py 文件
51 | subprocess.run(["pyside6-rcc", str(QRC_FILE), "-o", str(QRC_PY_FILE)])
52 | print("编译完成")
53 |
--------------------------------------------------------------------------------
/scripts/packaged.py:
--------------------------------------------------------------------------------
1 | from abc import ABC, abstractmethod
2 | from enum import Enum
3 |
4 |
5 | class PackageType(Enum):
6 | Pyinstaller = 0
7 | Nuitka = 1
8 |
9 |
10 | class Packaged(ABC):
11 | @abstractmethod
12 | def package(self):
13 | pass
14 |
15 |
16 | class PyinstallerPackaged(Packaged):
17 | def package(self):
18 | print("PyinstallerPackaged")
19 |
20 |
21 | class NuitkaPackaged(Packaged):
22 | def package(self):
23 | print("NuitkaPackaged")
24 |
25 |
26 | class PackagedFactory:
27 | @staticmethod
28 | def create_packaged(packaged_type: PackageType) -> Packaged:
29 | if packaged_type == PackageType.Pyinstaller:
30 | return PyinstallerPackaged()
31 | elif packaged_type == PackageType.Nuitka:
32 | return NuitkaPackaged()
33 | else:
34 | raise ValueError(f"Unknown packaged type {packaged_type}")
35 |
36 |
37 | if __name__ == '__main__':
38 | pf = PackagedFactory()
39 | packaged = pf.create_packaged(PackageType.Pyinstaller)
40 | packaged.package()
41 |
--------------------------------------------------------------------------------
/src/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/271374667/VideoFusion/9ba7b8b301481ed148bc4d853a5efe8bbb7188b5/src/__init__.py
--------------------------------------------------------------------------------
/src/common/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/271374667/VideoFusion/9ba7b8b301481ed148bc4d853a5efe8bbb7188b5/src/common/__init__.py
--------------------------------------------------------------------------------
/src/common/black_remove/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/271374667/VideoFusion/9ba7b8b301481ed148bc4d853a5efe8bbb7188b5/src/common/black_remove/__init__.py
--------------------------------------------------------------------------------
/src/common/black_remove/img_black_remover.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 | from typing import Optional, Union
3 |
4 | import cv2
5 | import numpy as np
6 |
7 |
8 | class BlackRemover:
9 | def __init__(self, threshold: int = 30, border_width: int = 5):
10 | self.threshold: int = threshold
11 | self.border_width: int = border_width
12 |
13 | def start(self, img_path: Optional[Union[str, Path]] = None,
14 | img_array: Optional[np.ndarray] = None) -> tuple[int, int, int, int]:
15 | """
16 | 去除图像黑边,可以传入图像路径或者图像数组
17 |
18 | Args:
19 | img_path: 图像路径
20 | img_array: 图像数组
21 |
22 | Returns:
23 | tuple[int, int, int, int]: 左上角和右下角坐标
24 | """
25 | if img_path is None and img_array is None:
26 | raise ValueError('img_path and img_array cannot be None at the same time')
27 | elif img_path is not None and img_array is not None:
28 | raise ValueError('img_path and img_array cannot be set at the same time')
29 | elif img_path is not None:
30 | if isinstance(img_path, Path):
31 | img_path = str(img_path)
32 | # 读取图像
33 | img = cv2.imread(img_path)
34 | else:
35 | img = img_array
36 | # 获取图片的长和宽
37 | img_height: int = img.shape[0]
38 | img_width: int = img.shape[1]
39 | # 图片裁剪的左上角和右下角坐标
40 | left_top_x: int = 0
41 | left_top_y: int = 0
42 | right_bottom_x: int = img_width
43 | right_bottom_y: int = img_height
44 |
45 | # 如果图像有黑边,则直接返回
46 | if not self.has_black_border(img):
47 | # loguru.logger.debug(f'{img_path} dont have black border, skip it')
48 | return left_top_x, left_top_y, right_bottom_x, right_bottom_y
49 |
50 | # 转换为灰度图像
51 | gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
52 |
53 | # 计算平均亮度阈值
54 | # mean_threshold = np.mean(gray)
55 |
56 | _, binary = cv2.threshold(gray, self.threshold, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
57 | # binary = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 11, 2)
58 | kernel = np.ones((5, 5), np.uint8)
59 |
60 | binary = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel)
61 |
62 | binary = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel)
63 |
64 | # 找出图像中的连通区域
65 | num_labels, labels, stats, _ = cv2.connectedComponentsWithStats(binary, connectivity=8)
66 |
67 | # 创建一个新的二值图像,用于保存去除孤立区域后的结果
68 | new_binary = np.zeros_like(binary)
69 |
70 | # 遍历所有连通区域
71 | for i in range(1, num_labels):
72 | # 如果该连通区域的大小大于阈值,则保留该区域
73 | if stats[i, cv2.CC_STAT_AREA] > 1500: # 500是阈值,可以根据实际情况调整
74 | new_binary[labels == i] = 255
75 |
76 | # 找出图像中的轮廓
77 | contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
78 |
79 | # 找出最大的矩形轮廓
80 | max_area = 0
81 | max_rect = None
82 | for contour in contours:
83 | rect = cv2.boundingRect(contour)
84 | x, y, w, h = rect
85 | area = w * h
86 | if area > max_area:
87 | max_area = area
88 | max_rect = rect
89 |
90 | # 在原图像上画出该矩形轮廓
91 | if max_rect is not None:
92 | x, y, w, h = max_rect
93 | left_top_x = x
94 | left_top_y = y
95 | right_bottom_x = x + w
96 | right_bottom_y = y + h
97 |
98 | # # 绘图显示
99 | # cv2.rectangle(img, (left_top_x, left_top_y), (right_bottom_x, right_bottom_y), (0, 0, 255), 15)
100 | # # 保持横纵比的情况下将图片缩放到720p
101 | # img = cv2.resize(img, (480, 720))
102 | # cv2.imshow('new_binary', new_binary)
103 | # cv2.imshow('img', img)
104 | # # cv2.imwrite(r"E:\load\python\Project\VideoMosaic\temp\dy\temp.png", img)
105 | # cv2.waitKey(0)
106 | return left_top_x, left_top_y, right_bottom_x, right_bottom_y
107 |
108 | def has_black_border(self, img: np.ndarray) -> bool:
109 | """
110 | 判断图像是否有黑边
111 |
112 | Args:
113 | img: 图像
114 |
115 | Returns:
116 | bool: 是否有黑边
117 | """
118 | # 读取图像
119 | img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
120 | threshold = self.threshold
121 | border_width = self.border_width
122 |
123 | # 检查上下左右四个边缘的像素值
124 | top_edge = img[:border_width, :]
125 | bottom_edge = img[-border_width:, :]
126 | left_edge = img[:, :border_width]
127 | right_edge = img[:, -border_width:]
128 |
129 | # 如果这些像素值的平均值都小于给定的阈值(默认为50),那么函数返回True,表示图像有黑边
130 | if np.mean(top_edge) < threshold or np.mean(bottom_edge) < threshold or np.mean(
131 | left_edge) < threshold or np.mean(right_edge) < threshold:
132 | return True
133 | else:
134 | return False
135 |
136 | def is_black(self, img: np.ndarray) -> bool:
137 | """
138 | 判断图像是否为黑色
139 |
140 | Args:
141 | img: 图像
142 |
143 | Returns:
144 | bool: 是否为黑色
145 | """
146 | img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
147 | threshold = self.threshold
148 | return np.mean(img) < threshold
149 |
150 |
151 | if __name__ == '__main__':
152 | b = BlackRemover()
153 | img = r"E:\load\python\Project\VideoFusion\TempAndTest\dy\Clip_2024-06-03_15-23-02.png"
154 | b.start(img_path=img)
155 |
--------------------------------------------------------------------------------
/src/common/black_remove/video_remover.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 |
3 | import cv2
4 | import loguru
5 | import numpy as np
6 |
7 | from src.signal_bus import SignalBus
8 |
9 | signal_bus = SignalBus()
10 |
11 |
12 | class VideoRemover:
13 | def start(self, video_path: Path | str) -> tuple[int, int, int, int]:
14 | video_path: Path = Path(video_path)
15 | loguru.logger.info(f'正在使用差值法检测视频变化区域: {video_path.name}')
16 |
17 | # 打开视频文件
18 | cap = cv2.VideoCapture(str(video_path))
19 | total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
20 | signal_bus.set_detail_progress_max.emit(total_frames)
21 | ret, frame1 = cap.read()
22 | ret, frame2 = cap.read()
23 |
24 | # 初始化累计变化图像
25 | height, width = frame1.shape[:2]
26 | accumulated_changes = np.zeros((height, width), dtype=np.uint8)
27 |
28 | for frame_index in range(total_frames - 2):
29 | # 计算帧差异
30 | diff = cv2.absdiff(frame1, frame2)
31 | gray = cv2.cvtColor(diff, cv2.COLOR_BGR2GRAY)
32 | blur = cv2.GaussianBlur(gray, (5, 5), 0)
33 | _, thresh = cv2.threshold(blur, 20, 255, cv2.THRESH_BINARY)
34 |
35 | kernel = np.ones((5, 5), np.uint8)
36 |
37 | binary = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel)
38 |
39 | binary = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel)
40 |
41 | # 找出图像中的连通区域
42 | num_labels, labels, stats, _ = cv2.connectedComponentsWithStats(binary, connectivity=8)
43 |
44 | # 创建一个新的二值图像,用于保存去除孤立区域后的结果
45 | new_binary = np.zeros_like(binary)
46 |
47 | # 遍历所有连通区域
48 | for i in range(1, num_labels):
49 | # 如果该连通区域的大小大于阈值,则保留该区域
50 | if stats[i, cv2.CC_STAT_AREA] > 1500: # 500是阈值,可以根据实际情况调整
51 | new_binary[labels == i] = 255
52 |
53 | # 找到轮廓
54 | contours, _ = cv2.findContours(new_binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
55 |
56 | for contour in contours:
57 | if cv2.contourArea(contour) < 500:
58 | continue
59 | x, y, w, h = cv2.boundingRect(contour)
60 | cv2.rectangle(frame1, (x, y), (x + w, y + h), (255, 255, 255), -1)
61 | cv2.rectangle(accumulated_changes, (x, y), (x + w, y + h), 255, -1)
62 |
63 | frame1 = frame2
64 | ret, frame2 = cap.read()
65 | signal_bus.set_detail_progress_current.emit(frame_index)
66 |
67 | cap.release()
68 | loguru.logger.debug(f'检测视频变化区域完成: {video_path.name}')
69 |
70 | # 找到累计变化图像中的轮廓以确定最大变化区域
71 | contours, _ = cv2.findContours(accumulated_changes, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
72 |
73 | max_area = 0
74 | max_rect = (0, 0, 0, 0)
75 | for contour in contours:
76 | x, y, w, h = cv2.boundingRect(contour)
77 | area = w * h
78 | if area > max_area:
79 | max_area = area
80 | max_rect = (x, y, w, h)
81 |
82 | signal_bus.set_detail_progress_finish.emit()
83 |
84 | # 返回变化区域图像和最大矩形区域
85 | loguru.logger.debug(f'最大变化区域: x={max_rect[0]}, y={max_rect[1]}, w={max_rect[2]}, h={max_rect[3]}')
86 |
87 | return max_rect
88 |
89 |
90 | if __name__ == '__main__':
91 | # 使用示例
92 | video_path = r"E:\load\python\Project\VideoFusion\TempAndTest\dy\b7bb97e21600b07f66c21e7932cb7550.mp4"
93 | video_remover = VideoRemover()
94 | max_rectangle = video_remover.start(video_path)
95 |
96 | print(f"Largest rectangle: x={max_rectangle[0]}, y={max_rectangle[1]}, w={max_rectangle[2]}, h={max_rectangle[3]}")
97 |
--------------------------------------------------------------------------------
/src/common/black_remove_algorithm/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/271374667/VideoFusion/9ba7b8b301481ed148bc4d853a5efe8bbb7188b5/src/common/black_remove_algorithm/__init__.py
--------------------------------------------------------------------------------
/src/common/black_remove_algorithm/black_remove_algorithm.py:
--------------------------------------------------------------------------------
1 | from abc import ABC, abstractmethod
2 | from pathlib import Path
3 |
4 |
5 | class BlackRemoveAlgorithm(ABC):
6 | @abstractmethod
7 | def remove_black(self, input_file_path: str | Path) -> tuple[int, int, int, int]:
8 | pass
9 |
--------------------------------------------------------------------------------
/src/common/black_remove_algorithm/video_remover.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 |
3 | import cv2
4 | import loguru
5 | import numpy as np
6 |
7 | from src.common.black_remove_algorithm.black_remove_algorithm import BlackRemoveAlgorithm
8 | from src.signal_bus import SignalBus
9 |
10 | signal_bus = SignalBus()
11 |
12 |
13 | class VideoRemover(BlackRemoveAlgorithm):
14 | def remove_black(self, input_file_path: str | Path) -> tuple[int, int, int, int]:
15 | video_path: Path = Path(input_file_path)
16 | loguru.logger.info(f'正在使用差值法检测视频变化区域: {video_path.name}')
17 |
18 | # 如果不是视频则报错
19 | if video_path.suffix not in ['.mp4', '.avi', '.flv', '.mov', '.mkv']:
20 | raise ValueError(f"文件不是视频: {video_path}")
21 |
22 | # 如果不存在则报错
23 | if not video_path.exists():
24 | raise FileNotFoundError(f"文件不存在: {video_path}")
25 |
26 | # 打开视频文件
27 | cap = cv2.VideoCapture(str(video_path))
28 | total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
29 | signal_bus.set_detail_progress_max.emit(total_frames)
30 | ret, frame1 = cap.read()
31 | ret, frame2 = cap.read()
32 |
33 | # 初始化累计变化图像
34 | height, width = frame1.shape[:2]
35 | accumulated_changes = np.zeros((height, width), dtype=np.uint8)
36 |
37 | for frame_index in range(total_frames - 2):
38 | # 计算帧差异
39 | diff = cv2.absdiff(frame1, frame2)
40 | gray = cv2.cvtColor(diff, cv2.COLOR_BGR2GRAY)
41 | blur = cv2.GaussianBlur(gray, (5, 5), 0)
42 | _, thresh = cv2.threshold(blur, 20, 255, cv2.THRESH_BINARY)
43 |
44 | kernel = np.ones((5, 5), np.uint8)
45 |
46 | binary = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel)
47 |
48 | binary = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel)
49 |
50 | # 找出图像中的连通区域
51 | num_labels, labels, stats, _ = cv2.connectedComponentsWithStats(binary, connectivity=8)
52 |
53 | # 创建一个新的二值图像,用于保存去除孤立区域后的结果
54 | new_binary = np.zeros_like(binary)
55 |
56 | # 遍历所有连通区域
57 | for i in range(1, num_labels):
58 | # 如果该连通区域的大小大于阈值,则保留该区域
59 | if stats[i, cv2.CC_STAT_AREA] > 1500: # 500是阈值,可以根据实际情况调整
60 | new_binary[labels == i] = 255
61 |
62 | # 找到轮廓
63 | contours, _ = cv2.findContours(new_binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
64 |
65 | for contour in contours:
66 | if cv2.contourArea(contour) < 500:
67 | continue
68 | x, y, w, h = cv2.boundingRect(contour)
69 | cv2.rectangle(frame1, (x, y), (x + w, y + h), (255, 255, 255), -1)
70 | cv2.rectangle(accumulated_changes, (x, y), (x + w, y + h), 255, -1)
71 |
72 | frame1 = frame2
73 | ret, frame2 = cap.read()
74 | signal_bus.set_detail_progress_current.emit(frame_index)
75 |
76 | cap.release()
77 | loguru.logger.debug(f'检测视频变化区域完成: {video_path.name}')
78 |
79 | # 找到累计变化图像中的轮廓以确定最大变化区域
80 | contours, _ = cv2.findContours(accumulated_changes, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
81 |
82 | max_area = 0
83 | max_rect = (0, 0, 0, 0)
84 | for contour in contours:
85 | x, y, w, h = cv2.boundingRect(contour)
86 | area = w * h
87 | if area > max_area:
88 | max_area = area
89 | max_rect = (x, y, w, h)
90 |
91 | signal_bus.set_detail_progress_finish.emit()
92 |
93 | # 返回变化区域图像和最大矩形区域
94 | loguru.logger.debug(f'最大变化区域: x={max_rect[0]}, y={max_rect[1]}, w={max_rect[2]}, h={max_rect[3]}')
95 |
96 | return max_rect
97 |
98 |
99 | if __name__ == '__main__':
100 | # 使用示例
101 | video_path = r"E:\load\python\Project\VideoFusion\tests\test_data\videos\001.mp4"
102 | video_remover = VideoRemover()
103 | max_rectangle = video_remover.remove_black(video_path)
104 |
105 | print(f"Largest rectangle: x={max_rectangle[0]}, y={max_rectangle[1]}, w={max_rectangle[2]}, h={max_rectangle[3]}")
106 |
--------------------------------------------------------------------------------
/src/common/processors/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/271374667/VideoFusion/9ba7b8b301481ed148bc4d853a5efe8bbb7188b5/src/common/processors/__init__.py
--------------------------------------------------------------------------------
/src/common/processors/audio_processors/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/271374667/VideoFusion/9ba7b8b301481ed148bc4d853a5efe8bbb7188b5/src/common/processors/audio_processors/__init__.py
--------------------------------------------------------------------------------
/src/common/processors/audio_processors/audio_ffmpeg_processor.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 |
3 | from src.common.ffmpeg_handler import FFmpegHandler
4 | from src.common.processors.base_processor import AudioProcessor
5 | from src.config import AudioNoiseReduction, AudioNormalization, AudioSampleRate, cfg
6 |
7 |
8 | class AudioFFmpegProcessor(AudioProcessor):
9 | def __init__(self, **kwargs):
10 | super().__init__(**kwargs)
11 | self._ffmpeg_handler = FFmpegHandler()
12 |
13 | def process(self, input_wav_path: Path) -> Path:
14 | audio_filters: list[str] = []
15 |
16 | # 音频降噪
17 | audio_noise_reduction_mode: AudioNoiseReduction = cfg.get(cfg.audio_noise_reduction)
18 | if audio_noise_reduction_mode == AudioNoiseReduction.AI:
19 | audio_filters.append(f"{audio_noise_reduction_mode.value}".replace('\\', '/'))
20 | elif audio_noise_reduction_mode == AudioNoiseReduction.Static:
21 | audio_filters.append(audio_noise_reduction_mode.value)
22 |
23 | # 音频标准化
24 | audio_normalization: AudioNormalization = cfg.get(cfg.audio_normalization)
25 | if audio_normalization != AudioNormalization.Disable:
26 | audio_filters.append(audio_normalization.value)
27 |
28 | # 音频重新采样
29 | audio_sample_rate: AudioSampleRate = cfg.get(cfg.audio_sample_rate)
30 | audio_filters.append(
31 | f"aresample={audio_sample_rate.value}:resampler=soxr:precision=28:osf=s16:dither_method=triangular")
32 |
33 | return self._ffmpeg_handler.audio_process(input_wav_path, audio_filter=audio_filters)
34 |
35 |
36 | if __name__ == '__main__':
37 | processor = AudioFFmpegProcessor()
38 | print(processor.process(Path(r"E:\load\python\Project\VideoFusion\TempAndTest\dy\v\1\视频(1)_out.wav")))
39 | print("降噪完成")
40 |
--------------------------------------------------------------------------------
/src/common/processors/audio_processors/audio_processor_manager.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 |
3 | from src.common.ffmpeg_handler import FFmpegHandler
4 | from src.common.processors.audio_processors.audio_ffmpeg_processor import AudioFFmpegProcessor
5 | from src.common.processors.base_processor import AudioProcessor, AudioProcessorManager as APM
6 |
7 |
8 | class AudioProcessorManager(APM):
9 | def __init__(self):
10 | super().__init__()
11 | self._ffmpeg_handler = FFmpegHandler()
12 | self._audio_ffmpeg_processor = AudioFFmpegProcessor()
13 |
14 | self._processors: list[AudioProcessor] = [
15 | self._audio_ffmpeg_processor
16 | ]
17 |
18 | def get_processors(self) -> list[AudioProcessor]:
19 | return self._processors
20 |
21 | def add_processor(self, processor: AudioProcessor):
22 | self._processors.append(processor)
23 |
24 | def process(self, x: Path) -> Path:
25 | for processor in self._processors:
26 | x = processor.process(x)
27 | return x
28 |
--------------------------------------------------------------------------------
/src/common/processors/base_processor.py:
--------------------------------------------------------------------------------
1 | from abc import ABC, abstractmethod
2 | from pathlib import Path
3 | from typing import Generic, TypeVar
4 |
5 | import numpy as np
6 |
7 | from src.core.datacls import FFmpegDTO
8 |
9 | T = TypeVar("T")
10 |
11 |
12 | class BaseProcessor(ABC, Generic[T]):
13 | @abstractmethod
14 | def process(self, x: T) -> T:
15 | pass
16 |
17 |
18 | class OpenCVProcessor(BaseProcessor):
19 | is_enable: bool = True
20 |
21 | @abstractmethod
22 | def process(self, frame: np.ndarray) -> np.ndarray:
23 | """
24 | 将输入的帧进行处理并返回处理后的帧
25 |
26 | Args:
27 | frame: 输入的帧
28 |
29 | Returns:
30 | 处理后的帧
31 | """
32 | raise NotImplementedError("Not implemented yet")
33 |
34 |
35 | class FFmpegProcessor(BaseProcessor):
36 | is_enable: bool = True
37 |
38 | @abstractmethod
39 | def process(self, ffmpeg_dto: list[FFmpegDTO]) -> list[FFmpegDTO]:
40 | """
41 | 将输入的文件进行处理并返回处理后的文件路径
42 |
43 | Args:
44 | ffmpeg_dto: 输入文件路径
45 |
46 | Returns:
47 | 处理后的文件路径
48 | """
49 | raise NotImplementedError("Not implemented yet")
50 |
51 |
52 | class EXEProcessor(BaseProcessor):
53 | is_enable: bool = True
54 |
55 | @abstractmethod
56 | def process(self, input_file_path: Path) -> Path:
57 | """
58 | 将输入的文件进行处理并返回处理后的文件路径
59 |
60 | Args:
61 | input_file_path: 输入文件路径
62 |
63 | Returns:
64 | 处理后的文件路径
65 | """
66 | raise NotImplementedError("Not implemented yet")
67 |
68 |
69 | class AudioProcessor(BaseProcessor):
70 | @abstractmethod
71 | def process(self, input_wav_path: Path) -> Path:
72 | """
73 | 将输入的音频文件进行处理并返回处理后的音频文件路径
74 |
75 | Args:
76 | input_wav_path: 输入音频文件路径
77 |
78 | Returns:
79 | 处理后的音频文件路径
80 | """
81 | raise NotImplementedError("Not implemented yet")
82 |
83 |
84 | class BaseProcessorManager(ABC, Generic[T]):
85 | def __init__(self):
86 | self._processors: list[BaseProcessor] = []
87 |
88 | def get_processors(self) -> list[BaseProcessor]:
89 | return self._processors
90 |
91 | def add_processor(self, processor: BaseProcessor):
92 | self._processors.append(processor)
93 |
94 | def process(self, x: T) -> T:
95 | for processor in self._processors:
96 | x = processor.process(x)
97 | return x
98 |
99 |
100 | class OpenCVProcessorManager(BaseProcessorManager[np.ndarray]):
101 | def __init__(self):
102 | super().__init__()
103 | self._processors: list[OpenCVProcessor] = []
104 |
105 | def get_processors(self) -> list[OpenCVProcessor]:
106 | return self._processors
107 |
108 | def add_processor(self, processor: OpenCVProcessor):
109 | self._processors.append(processor)
110 |
111 | def process(self, frame: np.ndarray) -> np.ndarray:
112 | for processor in self._processors:
113 | frame = processor.process(frame)
114 | return frame
115 |
116 |
117 | class FFmpegProcessorManager(BaseProcessorManager[FFmpegDTO]):
118 | def __init__(self):
119 | super().__init__()
120 | self._processors: list[FFmpegProcessor] = []
121 |
122 | def get_processors(self) -> list[FFmpegProcessor]:
123 | return self._processors
124 |
125 | def add_processor(self, processor: FFmpegProcessor):
126 | self._processors.append(processor)
127 |
128 | def process(self, x: T) -> T:
129 | for processor in self._processors:
130 | x = processor.process(x)
131 | return x
132 |
133 |
134 | class EXEProcessorManager(BaseProcessorManager[Path]):
135 | def __init__(self):
136 | super().__init__()
137 | self._processors: list[EXEProcessor] = []
138 |
139 | def get_processors(self) -> list[EXEProcessor]:
140 | return self._processors
141 |
142 | def add_processor(self, processor: EXEProcessor):
143 | self._processors.append(processor)
144 |
145 | def process(self, x: T) -> T:
146 | for processor in self._processors:
147 | x = processor.process(x)
148 | return x
149 |
150 |
151 | class AudioProcessorManager(BaseProcessorManager[Path]):
152 | def __init__(self):
153 | super().__init__()
154 | self._processors: list[AudioProcessor] = []
155 |
156 | def get_processors(self) -> list[AudioProcessor]:
157 | return self._processors
158 |
159 | def add_processor(self, processor: AudioProcessor):
160 | self._processors.append(processor)
161 |
162 | def process(self, x: T) -> T:
163 | for processor in self._processors:
164 | x = processor.process(x)
165 | return x
166 |
--------------------------------------------------------------------------------
/src/common/processors/exe_processors/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/271374667/VideoFusion/9ba7b8b301481ed148bc4d853a5efe8bbb7188b5/src/common/processors/exe_processors/__init__.py
--------------------------------------------------------------------------------
/src/common/processors/exe_processors/audio_separator_processor.py:
--------------------------------------------------------------------------------
1 | import re
2 | from enum import Enum
3 | from pathlib import Path
4 |
5 | import loguru
6 | from PySide6.QtCore import QObject, Signal
7 | from audio_separator.separator import Separator
8 |
9 | from src.common.ffmpeg_handler import FFmpegHandler
10 | from src.common.processors.base_processor import EXEProcessor
11 | from src.config import cfg, AudioSeparationAlgorithm
12 | from src.core.paths import MODELS_DIR, OUTPUT_DIR, AUDIO_SEPARATOR_EXE_FILE
13 | from src.signal_bus import SignalBus
14 | from src.utils import TempDir
15 | import subprocess
16 |
17 |
18 | class AudioSeparationType(Enum):
19 | Instructment = 0
20 | Vocal = 1
21 |
22 |
23 | class AudioSeparator:
24 | def __init__(self, model_name: str, model_file_dir: Path = MODELS_DIR, output_dir: Path = OUTPUT_DIR):
25 | self._output_dir: Path = output_dir
26 | self._model_name: str = model_name
27 | self.separator = Separator(
28 | model_file_dir=model_file_dir,
29 | output_dir=output_dir
30 | )
31 | self.separator.load_model(model_name)
32 |
33 | @property
34 | def model_name(self) -> str:
35 | return self._model_name
36 |
37 | def separate_audio(self, input_file: str) -> tuple[Path, Path]:
38 | """分离音频
39 | Args:
40 | input_file: 输入音频文件路径(wav格式)
41 |
42 | Returns:
43 | tuple[Path, Path]: 分离后的音频文件路径,同样为wav格式(乐器,人声)
44 | """
45 | output_files = self.separator.separate(input_file)
46 | instrument_file_path: Path = self._output_dir / output_files[0]
47 | vocal_file_path: Path = self._output_dir / output_files[1]
48 | loguru.logger.debug(f"音频分离完成,分离路径如下: {instrument_file_path} {vocal_file_path}")
49 | return instrument_file_path, vocal_file_path
50 |
51 |
52 | class AudioSeparatorRedirect(QObject):
53 | progress_signal = Signal(float)
54 |
55 | def __init__(self):
56 | super().__init__()
57 | self._pre_progress: int = 0
58 | self._process_pattern = re.compile(r"^\s*(\d+)%\|.*?")
59 |
60 | self._signal_bus = SignalBus()
61 |
62 | def write(self, message):
63 | if match := self._process_pattern.match(message):
64 | progress = float(match[1])
65 | else:
66 | progress = 0.0
67 |
68 | progress = int(progress)
69 |
70 | if progress != self._pre_progress:
71 | self._pre_progress = min(progress, 100)
72 | self._signal_bus.set_detail_progress_current.emit(int(self._pre_progress))
73 |
74 | def flush(self):
75 | pass
76 |
77 |
78 | class AudioSeparatorProcessor(EXEProcessor):
79 | def __init__(self):
80 | super().__init__()
81 |
82 | self._signal_bus: SignalBus = SignalBus()
83 | self._temp_dir: TempDir = TempDir()
84 | self._audio_separator_redirect: AudioSeparatorRedirect = AudioSeparatorRedirect()
85 | self._ffmpeg_handler: FFmpegHandler = FFmpegHandler()
86 |
87 | self._signal_bus.system_message.connect(self._audio_separator_redirect.write)
88 |
89 | def process(self, input_file_path: Path) -> Path:
90 | audio_separator_algorithm: AudioSeparationAlgorithm = cfg.get(cfg.audio_separation_algorithm)
91 | match audio_separator_algorithm:
92 | case AudioSeparationAlgorithm.UVRMDXNETVocFTVocal:
93 | model_name = "UVR-MDX-NET-Voc_FT.onnx"
94 | audio_separator_type = AudioSeparationType.Vocal
95 | case AudioSeparationAlgorithm.UVRMDXNETVocFTInstructment:
96 | model_name = "UVR-MDX-NET-Voc_FT.onnx"
97 | audio_separator_type = AudioSeparationType.Instructment
98 | case AudioSeparationAlgorithm.MDX23CVocal:
99 | model_name = "MDX23C_D1581.ckpt"
100 | audio_separator_type = AudioSeparationType.Vocal
101 | case AudioSeparationAlgorithm.MDX23CInstructment:
102 | model_name = "MDX23C_D1581.ckpt"
103 | audio_separator_type = AudioSeparationType.Instructment
104 | case AudioSeparationAlgorithm.BsRoformerVocal:
105 | model_name = "model_bs_roformer_ep_317_sdr_12.9755.ckpt"
106 | audio_separator_type = AudioSeparationType.Vocal
107 | case AudioSeparationAlgorithm.BsRoformerInstructment:
108 | model_name = "model_bs_roformer_ep_317_sdr_12.9755.ckpt"
109 | audio_separator_type = AudioSeparationType.Instructment
110 | case _:
111 | loguru.logger.error(f"未知的音频分离算法: {audio_separator_algorithm}")
112 | raise ValueError(f"未知的音频分离算法: {audio_separator_algorithm}")
113 |
114 | input_audio_path: Path = self._ffmpeg_handler.extract_audio_from_video(input_file_path)
115 |
116 | self._signal_bus.set_detail_progress_max.emit(100)
117 | audio_separator = AudioSeparator(model_name, output_dir=self._temp_dir.get_temp_dir())
118 | instrument_file_path, vocal_file_path = audio_separator.separate_audio(str(input_audio_path))
119 |
120 | result_audio_path: Path = instrument_file_path if audio_separator_type == AudioSeparationType.Instructment else vocal_file_path
121 | final_file_path: Path = self._ffmpeg_handler.replace_video_audio(input_file_path, result_audio_path)
122 | self._signal_bus.set_detail_progress_finish.emit()
123 | return final_file_path
124 |
125 |
126 | if __name__ == '__main__':
127 | from PySide6.QtWidgets import QApplication
128 | from src.components.cmd_text_edit import CMDTextEdit
129 | import threading
130 |
131 |
132 | def main():
133 | audio_separator_processor = AudioSeparatorProcessor()
134 | print(audio_separator_processor.process(
135 | Path(
136 | r"E:\load\python\Project\VideoFusion\TempAndTest\dy\v\测试\去黑边\d41a71f1c171b148cb41006193d3bc70.mp4")))
137 |
138 |
139 | app = QApplication([])
140 | cmd_text_edit = CMDTextEdit()
141 | cmd_text_edit.show()
142 | threading.Thread(target=main).start()
143 | app.exec()
144 |
--------------------------------------------------------------------------------
/src/common/processors/exe_processors/auto_editor_processor.py:
--------------------------------------------------------------------------------
1 | import re
2 | import sys
3 | from os import environ
4 | from pathlib import Path
5 |
6 | import loguru
7 | from PySide6.QtCore import QObject, Signal
8 | from auto_editor.edit import edit_media
9 | from auto_editor.ffwrapper import FFmpeg
10 | from auto_editor.utils.log import Log
11 | from auto_editor.utils.types import Args
12 |
13 | from src.components.cmd_text_edit import CMDTextEdit
14 | from src.core.paths import FFMPEG_FILE
15 | from src.signal_bus import SignalBus
16 | from src.utils import TempDir, get_output_file_path
17 | from src.common.processors.base_processor import EXEProcessor
18 |
19 |
20 | class AutoEditRedirect(QObject):
21 | title_signal = Signal(str)
22 | progress_signal = Signal(float)
23 |
24 | def __init__(self):
25 | super().__init__()
26 | self._signal_bus = SignalBus()
27 | self._pre_title: str = ''
28 | self._pre_progress: int = 0
29 | self._process_pattern = re.compile(r".*?&\s*(?P[\w\s]+)\s*\[.*]\s*(?P