├── .browserslistrc
├── .env.development
├── .env.production
├── .gitignore
├── README.md
├── babel.config.js
├── build
├── icons
│ ├── 256x256.png
│ ├── icon.icns
│ ├── icon.ico
│ └── icon.png
└── image
├── package.json
├── public
└── index.html
├── src
├── App.vue
├── assets
│ ├── css
│ │ ├── element-variables.scss
│ │ ├── global.scss
│ │ ├── mixin.scss
│ │ └── variables.scss
│ ├── iconsvg
│ │ ├── chart.svg
│ │ ├── components.svg
│ │ ├── control.svg
│ │ ├── detail.svg
│ │ ├── edit.svg
│ │ ├── editor.svg
│ │ ├── home.svg
│ │ ├── icon.svg
│ │ ├── list.svg
│ │ ├── page.svg
│ │ ├── permissions.svg
│ │ ├── set.svg
│ │ └── svgo.yml
│ └── images
│ │ ├── .gitkeep
│ │ ├── 1.png
│ │ ├── 2.png
│ │ ├── 3.png
│ │ ├── 4.png
│ │ ├── 5.png
│ │ ├── 6.png
│ │ ├── 7.png
│ │ ├── 8.png
│ │ ├── bg-smooth.jpg
│ │ ├── icon.ico
│ │ ├── logo.png
│ │ └── qrcode.png
├── background.ts
├── background
│ ├── audio
│ │ └── index.ts
│ ├── autoUpdate.ts
│ ├── clearwater
│ │ └── index.ts
│ ├── database
│ │ └── index.ts
│ ├── edit-video
│ │ └── index.ts
│ ├── ffmpeg
│ │ ├── audio
│ │ │ ├── getAudioInfo.ts
│ │ │ └── saveAudio.ts
│ │ ├── ffmpeg.ts
│ │ ├── helper.ts
│ │ ├── utils
│ │ │ ├── formatBytes.ts
│ │ │ └── formatSeconds.ts
│ │ └── video
│ │ │ ├── concatVideo.ts
│ │ │ ├── cutVideo.ts
│ │ │ ├── getScreenshots.ts
│ │ │ ├── getVideoInfo.ts
│ │ │ └── saveVideo.ts
│ ├── index.ts
│ ├── terminal
│ │ └── index.ts
│ └── utils.ts
├── components
│ ├── Permission
│ │ └── index.vue
│ └── Result
│ │ ├── index.vue
│ │ └── svg
│ │ ├── error.vue
│ │ ├── info.vue
│ │ ├── noFound.vue
│ │ ├── serverError.vue
│ │ ├── success.vue
│ │ ├── unauthorized.vue
│ │ └── warning.vue
├── config
│ ├── clearwater.ts
│ ├── languageList.ts
│ ├── settings.ts
│ └── videoConfig.ts
├── main.ts
├── router
│ └── index.ts
├── services
│ ├── audio.ts
│ ├── clearwater.ts
│ ├── discern.ts
│ ├── indexData.ts
│ ├── request.ts
│ ├── user.ts
│ └── weight.ts
├── shims-vue.d.ts
├── store
│ ├── global.ts
│ ├── index.ts
│ └── user.ts
├── utils
│ ├── localToken.ts
│ └── validate.ts
└── views
│ ├── 404
│ └── index.vue
│ ├── audio
│ ├── config.vue
│ ├── history.vue
│ ├── index.vue
│ ├── play.vue
│ └── textarea.vue
│ ├── clearwater
│ ├── config.ts
│ ├── index.vue
│ ├── table.vue
│ └── utils.ts
│ ├── discern
│ └── index.vue
│ ├── edit-video
│ ├── config.vue
│ ├── index.vue
│ └── textarea.vue
│ ├── home
│ ├── PageLoading
│ │ └── index.vue
│ └── index.vue
│ ├── index.vue
│ ├── login
│ ├── index.vue
│ └── qrcode.png
│ ├── terminal
│ └── index.vue
│ └── weight
│ └── index.vue
├── tsconfig.json
└── vue.config.js
/.browserslistrc:
--------------------------------------------------------------------------------
1 | > 1%
2 | last 2 versions
3 | not dead
4 |
--------------------------------------------------------------------------------
/.env.development:
--------------------------------------------------------------------------------
1 | #运行环境
2 | NODE_ENV = 'development'
3 | VUE_APP_APIHOST=http://localhost:8080
4 |
5 |
--------------------------------------------------------------------------------
/.env.production:
--------------------------------------------------------------------------------
1 | #运行环境
2 | NODE_ENV = 'production'
3 |
4 | VUE_APP_APIHOST=https://zimeitizizhu.com/
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | data
3 | dist_electron
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | > **此版本暂时不维护,请进入一下链接 ,下载相关软件资源** ,
2 | > [https://github.com/linqian02/axvideo/](https://github.com/lqomg/wvovw/)
3 |
4 |
5 | > **AX Video 爱写视频**,是一个桌面软件,自动将 Markdown 文档转成一段视频的短视频编辑器,支持操作界面
6 | 文档里面的视频、音频、图片网址,都会抓取后插入视频,还可以根据文字生成人工语音的旁白朗读。
7 |
8 |
9 | > 问题反馈请提issues 或者 , 进入QQ群 `576301295 ` 反馈。
10 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/cli-plugin-babel/preset'
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/build/icons/256x256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lqomg/zimeiti/3c6d3c188d61ea1696855883eaf71311ab0a8e9a/build/icons/256x256.png
--------------------------------------------------------------------------------
/build/icons/icon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lqomg/zimeiti/3c6d3c188d61ea1696855883eaf71311ab0a8e9a/build/icons/icon.icns
--------------------------------------------------------------------------------
/build/icons/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lqomg/zimeiti/3c6d3c188d61ea1696855883eaf71311ab0a8e9a/build/icons/icon.ico
--------------------------------------------------------------------------------
/build/icons/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lqomg/zimeiti/3c6d3c188d61ea1696855883eaf71311ab0a8e9a/build/icons/icon.png
--------------------------------------------------------------------------------
/build/image:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "zimeitizizhu",
3 | "version": "0.1.3",
4 | "private": true,
5 | "description": "自媒体自助桌面版本",
6 | "scripts": {
7 | "serve": "vue-cli-service electron:serve",
8 | "build": "vue-cli-service electron:build",
9 | "rebuild": "./node_modules/.bin/electron-rebuild.cmd",
10 | "web:build": "vue-cli-service build",
11 | "web:serve": "vue-cli-service serve",
12 | "postinstall": "electron-builder install-app-deps",
13 | "postuninstall": "electron-builder install-app-deps"
14 | },
15 | "main": "background.js",
16 | "dependencies": {
17 | "@ffmpeg-installer/ffmpeg": "^1.0.20",
18 | "@ffprobe-installer/ffprobe": "^1.1.0",
19 | "axios": "^0.21.1",
20 | "child-process-promise": "^2.2.1",
21 | "core-js": "^3.8.3",
22 | "electron-updater": "^4.3.8",
23 | "element-plus": "^1.0.1-beta.27",
24 | "localforage": "^1.9.0",
25 | "md5-node": "^1.0.1",
26 | "nedb": "^1.8.0",
27 | "normalize.css": "^8.0.1",
28 | "silly-datetime": "^0.1.2",
29 | "vue": "3.0.7",
30 | "vue-router": "^4.0.3",
31 | "vuex": "^4.0.0-rc.2",
32 | "vuex-persistedstate": "^4.0.0-beta.3"
33 | },
34 | "devDependencies": {
35 | "@types/electron-devtools-installer": "^2.2.0",
36 | "@vue/cli-plugin-babel": "~4.5.10",
37 | "@vue/cli-plugin-router": "~4.5.10",
38 | "@vue/cli-plugin-typescript": "~4.5.10",
39 | "@vue/cli-plugin-vuex": "~4.5.10",
40 | "@vue/cli-service": "~4.5.10",
41 | "@vue/compiler-sfc": "3.0.7",
42 | "electron": "6.0.9",
43 | "electron-debug": "^3.2.0",
44 | "electron-devtools-installer": "^3.1.1",
45 | "electron-rebuild": "2.3.4",
46 | "node-sass": "^4.14.1",
47 | "ffmpeg-concat": "^1.3.3",
48 | "fluent-ffmpeg": "^2.1.2",
49 | "sass-loader": "^8.0.2",
50 | "typescript": "~3.9.7",
51 | "vue-cli-plugin-electron-builder": "~2.0.0-rc.5"
52 | },
53 | "__npminstall_done": false
54 | }
55 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | <%= htmlWebpackPlugin.options.title %>
9 |
10 |
11 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
正在下载应用最新版本: {{ appName }}
6 |
7 |
8 | {{ percentage }}%
9 | 当前更新进度
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
98 |
127 |
--------------------------------------------------------------------------------
/src/assets/css/element-variables.scss:
--------------------------------------------------------------------------------
1 | /* 改变主题色变量 */
2 | $--color-primary: #009688;
3 |
4 | /// color|1|Font Color|2
5 | $--color-text-primary: #303133 !default;
6 |
7 | /* 改变 icon 字体路径变量,必需 */
8 | $--font-path: '~element-plus/lib/theme-chalk/fonts';
9 |
10 | @import '~element-plus/packages/theme-chalk/src/index';
11 |
12 | $heading-color: rgba(0, 0, 0, 0.85); // 标题色
13 |
14 | // Menu
15 | $menu-collapsed-width: 54px;
16 |
17 | // dark theme
18 | $menu-dark-bg: #222834;
19 | $menu-dark-submenu-bg: #000c17;
20 | $menu-dark-color: rgba(#FFF, 0.65);
21 | $menu-dark-highlight-color: #fff;
22 |
23 |
24 | /* 重置 NavMenu */
25 | .index-layout-leftmenu {
26 | background-color: $menu-dark-bg;
27 | border: none;
28 |
29 |
30 | .el-submenu .el-menu {
31 | background-color: $menu-dark-submenu-bg;
32 | }
33 |
34 | .el-submenu__title,
35 | .el-menu-item {
36 | height: 40px;
37 | line-height: 40px;
38 | color: $menu-dark-color;
39 | .icon {
40 | margin-right: 5px;
41 | vertical-align: middle;
42 | width: 24px;
43 | text-align: center;
44 | color: $menu-dark-color;
45 | }
46 |
47 | &:focus,
48 | &:hover {
49 | color: $menu-dark-highlight-color !important;
50 | background:none;
51 | .icon {
52 | color: $menu-dark-highlight-color !important;
53 | }
54 | }
55 | }
56 |
57 | .is-active>.el-submenu__title {
58 | color: $menu-dark-highlight-color !important;
59 | .icon {
60 | color: $menu-dark-highlight-color !important;
61 | }
62 | }
63 | .el-submenu .el-menu-item.is-active,
64 | .el-menu-item.is-active{
65 | color: $menu-dark-highlight-color !important;
66 | background-color: $--color-primary !important;
67 | }
68 | .el-submenu__title {
69 | margin-top: 4px;
70 | margin-bottom: 4px;
71 | }
72 | .el-menu-item:not(:last-child) {
73 | margin-bottom: 8px;
74 | }
75 |
76 |
77 | /* 收起 */
78 | &.el-menu--collapse {
79 | width: $menu-collapsed-width;
80 | .el-submenu__title,
81 | .el-menu-item {
82 | .icon {
83 | position: absolute;
84 | left: 15px;
85 | top: 10px;
86 | margin: 0;
87 | }
88 | }
89 | }
90 |
91 |
92 | }
93 | .index-layout-leftmenu-popper {
94 |
95 | .el-menu {
96 | background-color: $menu-dark-submenu-bg;
97 | }
98 | .el-submenu__title,
99 | .el-menu-item {
100 | height: 40px;
101 | line-height: 40px;
102 | color: $menu-dark-color;
103 | .icon {
104 | margin-right: 5px;
105 | vertical-align: middle;
106 | width: 24px;
107 | text-align: center;
108 | color: $menu-dark-color;
109 | }
110 |
111 | &:focus,
112 | &:hover {
113 | color: $menu-dark-highlight-color !important;
114 | background:none;
115 | .icon {
116 | color: $menu-dark-highlight-color !important;
117 | }
118 | }
119 | }
120 |
121 | .is-active>.el-submenu__title {
122 | color: $menu-dark-highlight-color !important;
123 | .icon {
124 | color: $menu-dark-highlight-color !important;
125 | }
126 | }
127 | .el-submenu .el-menu-item.is-active,
128 | .el-menu-item.is-active{
129 | color: $menu-dark-highlight-color !important;
130 | background-color: $--color-primary !important;
131 | }
132 | .el-submenu__title {
133 | margin-top: 4px;
134 | margin-bottom: 4px;
135 | }
136 | .el-menu-item:not(:last-child) {
137 | margin-bottom: 8px;
138 | }
139 |
140 |
141 | }
142 |
143 |
144 | /* 重置 Drawer */
145 | .el-drawer {
146 | .el-drawer__header {
147 | padding: 15px 20px;
148 | margin-bottom: 0;
149 | border-bottom: 1px solid #f0f0f0;
150 | span,
151 | button {
152 | outline: none;
153 | }
154 | }
155 | }
156 |
157 | /* 重置 card */
158 | .cus-card {
159 | .el-card {
160 | border: 0;
161 | }
162 | }
163 |
164 | /*自定义 table 样式*/
165 | .custom-table {
166 | .el-table__header {
167 | background-color: #F5F7FA;
168 | }
169 | .custom-td-header-one,
170 | .el-table__body tr:hover .custom-td-header-one {
171 | color: #303133;
172 | font-weight: bold;
173 | background-color: #F5F7FA;
174 | }
175 | }
176 | .custom-table-header {
177 | color: #303133;
178 | th {
179 | background-color: #F5F7FA;
180 | }
181 | }
182 |
183 |
184 |
--------------------------------------------------------------------------------
/src/assets/css/global.scss:
--------------------------------------------------------------------------------
1 | @import './element-variables.scss';
2 | @import './variables.scss';
3 | @import './mixin.scss';
4 |
5 | .flex-wrap-wrap {
6 | flex-wrap: wrap;
7 | }
8 |
9 | a {
10 | color: #1890ff;
11 | text-decoration: none;
12 | background-color: transparent;
13 | outline: none;
14 | cursor: pointer;
15 | transition: color .3s;
16 | }
17 | a:active,
18 | a:hover {
19 | text-decoration: none;
20 | outline: 0;
21 | }
22 | a:active {
23 | color: #096dd9;
24 | }
25 | a:hover {
26 | color: #40a9ff;
27 | }
28 |
29 | .border-solid-transparent{
30 | border: solid 1px transparent;
31 | }
32 |
33 | .text-align-right {
34 | text-align: right;
35 | }
36 |
37 | .float-right {
38 | float: right;
39 | }
40 |
41 | .padding-t10 {
42 | padding-top: 10px;
43 | }
44 |
45 | .cursor-pointer {
46 | cursor: pointer;
47 | }
--------------------------------------------------------------------------------
/src/assets/css/mixin.scss:
--------------------------------------------------------------------------------
1 | @mixin scrollbar {
2 | ::-webkit-scrollbar {
3 | width: 6px;
4 | height: 6px;
5 | }
6 | ::-webkit-scrollbar-thumb {
7 | background: hsla(0,0%,100%,.2);
8 | border-radius: 3px;
9 | box-shadow: inset 0 0 5px hsla(0,0%,100%,.05);
10 | }
11 | ::-webkit-scrollbar-track {
12 | background: hsla(0,0%,100%,.15);
13 | border-radius: 3px;
14 | box-shadow: inset 0 0 5px rgba(37,37,37,.05);
15 | }
16 | }
--------------------------------------------------------------------------------
/src/assets/css/variables.scss:
--------------------------------------------------------------------------------
1 | // 主窗口背景色
2 | $mainBgColor: #f0f3f4;
3 | // 左边宽度
4 | $leftSideBarWidth: 200px;
5 | // 头部高度
6 | $headerHeight: 50px;
7 | // 头部下方面包屑高度
8 | $headerBreadcrumbHeight: 34px;
9 |
--------------------------------------------------------------------------------
/src/assets/iconsvg/chart.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/iconsvg/components.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/iconsvg/control.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/iconsvg/detail.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/iconsvg/edit.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/iconsvg/editor.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/iconsvg/home.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/iconsvg/icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/iconsvg/list.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/iconsvg/page.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/iconsvg/permissions.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/iconsvg/set.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/iconsvg/svgo.yml:
--------------------------------------------------------------------------------
1 | # replace default config
2 |
3 | # multipass: true
4 | # full: true
5 |
6 | plugins:
7 |
8 | # - name
9 | #
10 | # or:
11 | # - name: false
12 | # - name: true
13 | #
14 | # or:
15 | # - name:
16 | # param1: 1
17 | # param2: 2
18 |
19 | - removeAttrs:
20 | attrs:
21 | - 'fill'
22 | - 'fill-rule'
23 |
--------------------------------------------------------------------------------
/src/assets/images/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lqomg/zimeiti/3c6d3c188d61ea1696855883eaf71311ab0a8e9a/src/assets/images/.gitkeep
--------------------------------------------------------------------------------
/src/assets/images/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lqomg/zimeiti/3c6d3c188d61ea1696855883eaf71311ab0a8e9a/src/assets/images/1.png
--------------------------------------------------------------------------------
/src/assets/images/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lqomg/zimeiti/3c6d3c188d61ea1696855883eaf71311ab0a8e9a/src/assets/images/2.png
--------------------------------------------------------------------------------
/src/assets/images/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lqomg/zimeiti/3c6d3c188d61ea1696855883eaf71311ab0a8e9a/src/assets/images/3.png
--------------------------------------------------------------------------------
/src/assets/images/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lqomg/zimeiti/3c6d3c188d61ea1696855883eaf71311ab0a8e9a/src/assets/images/4.png
--------------------------------------------------------------------------------
/src/assets/images/5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lqomg/zimeiti/3c6d3c188d61ea1696855883eaf71311ab0a8e9a/src/assets/images/5.png
--------------------------------------------------------------------------------
/src/assets/images/6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lqomg/zimeiti/3c6d3c188d61ea1696855883eaf71311ab0a8e9a/src/assets/images/6.png
--------------------------------------------------------------------------------
/src/assets/images/7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lqomg/zimeiti/3c6d3c188d61ea1696855883eaf71311ab0a8e9a/src/assets/images/7.png
--------------------------------------------------------------------------------
/src/assets/images/8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lqomg/zimeiti/3c6d3c188d61ea1696855883eaf71311ab0a8e9a/src/assets/images/8.png
--------------------------------------------------------------------------------
/src/assets/images/bg-smooth.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lqomg/zimeiti/3c6d3c188d61ea1696855883eaf71311ab0a8e9a/src/assets/images/bg-smooth.jpg
--------------------------------------------------------------------------------
/src/assets/images/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lqomg/zimeiti/3c6d3c188d61ea1696855883eaf71311ab0a8e9a/src/assets/images/icon.ico
--------------------------------------------------------------------------------
/src/assets/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lqomg/zimeiti/3c6d3c188d61ea1696855883eaf71311ab0a8e9a/src/assets/images/logo.png
--------------------------------------------------------------------------------
/src/assets/images/qrcode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lqomg/zimeiti/3c6d3c188d61ea1696855883eaf71311ab0a8e9a/src/assets/images/qrcode.png
--------------------------------------------------------------------------------
/src/background.ts:
--------------------------------------------------------------------------------
1 |
2 |
3 | import { app, globalShortcut, protocol, BrowserWindow, Menu } from 'electron'
4 | import { createProtocol } from 'vue-cli-plugin-electron-builder/lib'
5 | import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer'
6 | const isDevelopment = process.env.NODE_ENV !== 'production'
7 | import './background/clearwater';
8 | import './background/edit-video';
9 | import './background/audio';
10 | import './background/terminal'
11 | import updateHandel from './background/autoUpdate';
12 | // Scheme must be registered before the app is ready
13 | protocol.registerSchemesAsPrivileged([
14 | { scheme: 'app', privileges: { secure: true, standard: true } }
15 | ])
16 |
17 | async function createWindow() {
18 | // Create the browser window.
19 | const win = new BrowserWindow({
20 | width: 1600,
21 | height: 800,
22 | icon: require('path').join(__dirname, '../build/icons/icon.ico'),
23 | webPreferences: {
24 | webSecurity: false,
25 | enableRemoteModule: true,
26 | // Use pluginOptions.nodeIntegration, leave this alone
27 | // See nklayman.github.io/vue-cli-plugin-electron-builder/guide/security.html#node-integration for more info
28 | nodeIntegration: (process.env
29 | .ELECTRON_NODE_INTEGRATION as unknown) as boolean
30 | }
31 | })
32 | globalShortcut.register('CommandOrControl+Shift+x', function () {
33 | win.webContents.openDevTools()
34 | })
35 |
36 | if (process.env.WEBPACK_DEV_SERVER_URL) {
37 | // Load the url of the dev server if in development mode
38 | await win.loadURL(process.env.WEBPACK_DEV_SERVER_URL as string)
39 | if (!process.env.IS_TEST) win.webContents.openDevTools()
40 | } else {
41 |
42 | // The production environment hides menus
43 | Menu.setApplicationMenu(null);
44 |
45 | createProtocol('app')
46 | // Load the index.html when not in development
47 | win.loadURL('app://./index.html')
48 | }
49 | win.setMenu(null);
50 | updateHandel(win);
51 | }
52 |
53 | // Quit when all windows are closed.
54 | app.on('window-all-closed', () => {
55 | // On macOS it is common for applications and their menu bar
56 | // to stay active until the user quits explicitly with Cmd + Q
57 | if (process.platform !== 'darwin') {
58 | app.quit()
59 | }
60 | })
61 |
62 | app.on('activate', () => {
63 | // On macOS it's common to re-create a window in the app when the
64 | // dock icon is clicked and there are no other windows open.
65 | if (BrowserWindow.getAllWindows().length === 0) createWindow()
66 | })
67 |
68 | // This method will be called when Electron has finished
69 | // initialization and is ready to create browser windows.
70 | // Some APIs can only be used after this event occurs.
71 | app.on('ready', async () => {
72 | if (isDevelopment && !process.env.IS_TEST) {
73 | // Install Vue Devtools
74 | try {
75 | await installExtension(VUEJS_DEVTOOLS)
76 | } catch (e) {
77 | console.error('Vue Devtools failed to install:', e.toString())
78 | }
79 | }
80 | createWindow()
81 | })
82 |
83 | // Exit cleanly on request from parent process in development mode.
84 | if (isDevelopment) {
85 | if (process.platform === 'win32') {
86 | process.on('message', (data) => {
87 | if (data === 'graceful-exit') {
88 | app.quit()
89 | }
90 | })
91 | } else {
92 | process.on('SIGTERM', () => {
93 | app.quit()
94 | })
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/src/background/audio/index.ts:
--------------------------------------------------------------------------------
1 | import { ipcMain } from 'electron';
2 | import db from '../database/index';
3 | import { saveAudio, getAudioInfo } from '../ffmpeg/helper';
4 | const date = require("silly-datetime");
5 | const { audioList } = db;
6 | ipcMain.on('save-audio', async (event: any, arg: any) => {
7 | const { url, type, fileName } = arg;
8 | saveAudio(arg, {
9 | error({ filePath, err }) {
10 | event.returnValue = {
11 | code: 404,
12 | filePath,
13 | error: err
14 | }
15 | },
16 | async end({ filePath, fileName }) {
17 | const data: any = await getAudioInfo(filePath);
18 | const { result, dbData } = data;
19 | if (result.status) {
20 | audioList.insert({
21 | filePath,
22 | fileName,
23 | type: type || 'mp3',
24 | ...dbData
25 | })
26 | }
27 | event.returnValue = {
28 | code: 200,
29 | filePath
30 | };
31 | }
32 | })
33 | });
34 | ipcMain.on('delete-audio', (event, id) => {
35 | audioList.remove({ _id: id }, (err) => {
36 | if (err) {
37 | event.returnValue = false;
38 | } else {
39 | event.returnValue = true;
40 | }
41 | })
42 | })
43 | ipcMain.on('get-audio-list', async (event, arg) => {
44 | let { pageSize, pageNumber } = arg;
45 | const total = await new Promise((resole, reject) => {
46 | audioList.count({}, (err, n) => {
47 | if (err) {
48 | resole(0)
49 | } else {
50 | resole(n);
51 | }
52 | })
53 | })
54 | if (total < pageSize) {
55 | pageNumber = 1;
56 | }
57 | const skip = (pageNumber - 1) * pageSize;
58 | audioList.find({}).sort({ updatedAt: -1 }).skip(skip).limit(pageSize).exec((err, docs) => {
59 | if (err) {
60 | event.returnValue = [];
61 | }
62 | event.returnValue = {
63 | list: docs,
64 | total,
65 | };
66 | });
67 | })
68 |
--------------------------------------------------------------------------------
/src/background/autoUpdate.ts:
--------------------------------------------------------------------------------
1 | const autoUpdater = require('electron-updater').autoUpdater
2 | const ipcMain = require('electron').ipcMain
3 | const uploadUrl = process.env.VUE_APP_APIHOST + '/public/app/';
4 | // 检测更新,在你想要检查更新的时候执行,renderer事件触发后的操作自行编写
5 | const UPDATE_MESSAGE_STATUS = {
6 | ERROR: 0,
7 | CHECKING: 1,
8 | UPDATE: 2,
9 | NOT_UPDATE: 3,
10 | SUCCESS: 4,
11 | DOWNLOAD: 5
12 | }
13 |
14 | // 通过main进程发送事件给renderer进程,提示更新信息
15 | function sendUpdateMessage(text: any, mainWindow: any) {
16 | mainWindow.webContents.send('message', text)
17 | }
18 |
19 | function updateHandle(mainWindow: any) {
20 | autoUpdater.setFeedURL(uploadUrl);
21 | autoUpdater.on('error', function (error: any) {
22 | sendUpdateMessage({
23 | code: UPDATE_MESSAGE_STATUS.ERROR,
24 | message: error
25 | }, mainWindow)
26 | });
27 | autoUpdater.on('checking-for-update', function () {
28 | sendUpdateMessage({
29 | code: UPDATE_MESSAGE_STATUS.CHECKING
30 | }, mainWindow)
31 | });
32 | autoUpdater.on('update-available', function (info: any) {
33 | sendUpdateMessage({
34 | code: UPDATE_MESSAGE_STATUS.UPDATE,
35 | message: info
36 | }, mainWindow)
37 | });
38 | autoUpdater.on('update-not-available', function (info: any) {
39 | sendUpdateMessage({
40 | code: UPDATE_MESSAGE_STATUS.NOT_UPDATE,
41 | message: info
42 | }, mainWindow)
43 | });
44 |
45 | autoUpdater.on('download-progress', function (progressObj: any) {
46 | mainWindow.webContents.send('downloadProgress', progressObj)
47 | })
48 | autoUpdater.on('update-downloaded', function (...args: any) {
49 | sendUpdateMessage({
50 | code: UPDATE_MESSAGE_STATUS.DOWNLOAD,
51 | message: args
52 | }, mainWindow);
53 | });
54 | ipcMain.on('isUpdateNow', (args: any) => {
55 | sendUpdateMessage({
56 | code: UPDATE_MESSAGE_STATUS.SUCCESS,
57 | message: args
58 | }, mainWindow);
59 | setTimeout(() => {
60 | autoUpdater.quitAndInstall();
61 | }, 1000)
62 | });
63 |
64 | ipcMain.on("checkForUpdate", () => {
65 | autoUpdater.checkForUpdates();
66 | });
67 | }
68 |
69 |
70 | export default updateHandle
--------------------------------------------------------------------------------
/src/background/clearwater/index.ts:
--------------------------------------------------------------------------------
1 | import { ipcMain } from 'electron';
2 | import db from '../database/index';
3 | const date = require("silly-datetime");
4 | const { videoList } = db;
5 | import { getVideoInfo, getScreenshots, saveVideo } from '../ffmpeg/helper';
6 | ipcMain.on('get-video-info', async (event, { originUrl, url, title }) => {
7 | try {
8 | let videoData: any = await getVideoInfo(url);
9 | let { status, data: dbData, msg } = videoData || {};
10 | if (!status) throw new Error(msg)
11 | let screenShots = await getScreenshots(url);
12 | dbData.screenShots = screenShots;
13 | dbData.status = 'start';
14 | dbData.errorMsg = '';
15 | dbData.originUrl = originUrl;
16 | dbData.originTitle = title || '---';
17 | console.log(originUrl)
18 | videoList.insert(dbData, (err: any, doc: any) => {
19 | if (err) {
20 | event.sender.send('reply-video-info', {
21 | msg: err,
22 | });
23 | } else {
24 | dbData.screenShots = screenShots;
25 | dbData.id = doc._id;
26 | dbData.progress = 0;
27 | event.sender.send('reply-video-info', {
28 | msg: '',
29 | videoInfo: dbData
30 | }); // 异步发送
31 | }
32 | })
33 | } catch (error) {
34 | event.sender.send('reply-video-info', {
35 | msg: error.message,
36 | videoInfo: null
37 | });
38 | }
39 |
40 | });
41 |
42 | ipcMain.on('get-video-list', async (event, arg) => {
43 | let { pageSize, pageNumber } = arg;
44 | const total: number = await new Promise((resole, reject) => {
45 | videoList.count({}, (err: any, n: number) => {
46 | if (err) {
47 | resole(0)
48 | } else {
49 | resole(n);
50 | }
51 | })
52 | })
53 | if (total < pageSize) {
54 | pageNumber = 1;
55 | }
56 | const skip = (pageNumber - 1) * pageSize;
57 | videoList.find({}).sort({ updatedAt: -1 }).skip(skip).limit(pageSize).exec((err: any, docs: any) => {
58 | if (err) {
59 | event.returnValue = [];
60 | }
61 | event.returnValue = {
62 | list: docs,
63 | total,
64 | };
65 | });
66 | })
67 |
68 | ipcMain.on('delete-video', (event, id) => {
69 | videoList.remove({ _id: id }, (err: any) => {
70 | if (err) {
71 | event.returnValue = false;
72 | } else {
73 | event.returnValue = true;
74 | }
75 | })
76 | })
77 | ipcMain.on('save-video', (event, arg) => {
78 | const { id, videosize = '', videotype } = arg;
79 | const size = videosize.split('|')
80 | const convertObj = {
81 | convertType: videotype,
82 | convertWidth: Number(size && size[0]),
83 | convertHeight: Number(size && size[1]),
84 | }
85 | saveVideo(arg, {
86 | start(data: any) {
87 | event.sender.send('reply-save-video', {
88 | tag: "start",
89 | msg: '视频转换开始....',
90 | data: {
91 | filePath: data.filePath,
92 | idx: id
93 | }
94 | });
95 | },
96 | end(data: any) {
97 | videoList.update({ _id: id }, {
98 | $set: {
99 | ...convertObj,
100 | updateDate: date.format(new Date(), 'YYYY-MM-DD HH:mm:ss'),
101 | filePath: data.filePath,
102 | status: 'success',
103 | progress: 100
104 | }
105 | });
106 | event.sender.send('reply-save-video', {
107 | tag: "end",
108 | msg: '视频转换完成',
109 | data: {
110 | ...convertObj,
111 | filePath: data.filePath,
112 | idx: id
113 | }
114 | });
115 | },
116 | progress(data: any) {
117 | event.sender.send('reply-save-video', {
118 | tag: "progress", data: {
119 | ...convertObj,
120 | percent: data.percent,
121 | filePath: data.filePath,
122 | idx: id
123 | }
124 | });
125 | },
126 | error(data: any) {
127 |
128 | videoList.update({ _id: id }, { $set: { filePath: data.filePath, errorMsg: data.err.message, status: 'error', progress: 0 } });
129 | event.sender.send('reply-save-video', {
130 | tag: "error",
131 | msg: data.err.message,
132 | data: {
133 | ...convertObj,
134 | err: data.err,
135 | filePath: data.filePath,
136 | idx: id
137 | }
138 | });
139 | }
140 | });
141 | });
142 |
--------------------------------------------------------------------------------
/src/background/database/index.ts:
--------------------------------------------------------------------------------
1 | import Datastore from 'nedb'
2 | import path from 'path'
3 | import { app } from 'electron'
4 | const filePath = app.getPath('userData');
5 | const timestampData = true;
6 | const db = {
7 | audioList: new Datastore({ filename: path.join(filePath, '/audio.db'), timestampData, autoload: true }),
8 | videoList: new Datastore({ filename: path.join(filePath, '/videoList.db'), timestampData, autoload: true }),
9 | editVideoList: new Datastore({ filename: path.join(filePath, '/editVideoList.db'), timestampData, autoload: true })
10 | }
11 |
12 | export default db;
--------------------------------------------------------------------------------
/src/background/edit-video/index.ts:
--------------------------------------------------------------------------------
1 | import { ipcMain } from 'electron';
2 | import db from '../database/index';
3 | const date = require("silly-datetime");
4 | const { editVideoList } = db;
5 | import { cutVideo, concatVideo } from '../ffmpeg/helper';
6 |
7 | ipcMain.on('cut-video', async (event, arg) => {
8 | const { url, times } = arg;
9 | const filePaths = await Promise.all(times.map(async ({ start, end }, index) => {
10 | return new Promise((resolve, reject) => {
11 | cutVideo({ url, start, end }, event, {
12 | start({ filePath }) {
13 | event.sender.send('reply-cut-video', { status: 'start', filePath })
14 | },
15 | end({ filePath }) {
16 | event.sender.send('reply-cut-video', { status: 'end', filePath });
17 | resolve(filePath)
18 | },
19 | progress({ filePath, percent }) {
20 | event.sender.send('reply-cut-video', { status: 'ing', filePath, percent })
21 | },
22 | error({ err, filePath }) {
23 | event.sender.send('reply-cut-video', { status: 'error', error: err, filePath })
24 | }
25 | });
26 | })
27 | }));
28 | console.log(2222)
29 | concatVideo(filePaths, percent => {
30 | event.sender.send('reply-cut-video', { status: 'concat-ing', filePath: '', percent: percent * 100 })
31 | }).then(path => {
32 | event.sender.send('reply-cut-video', { status: 'concat-end', filePath: path });
33 | }, err => {
34 | event.sender.send('reply-cut-video', { status: 'concat-error', error: err, filePath: null })
35 | })
36 | });
37 |
--------------------------------------------------------------------------------
/src/background/ffmpeg/audio/getAudioInfo.ts:
--------------------------------------------------------------------------------
1 | import ffmpeg from '../ffmpeg';
2 | import formatBytes from '../utils/formatBytes';
3 | import formatSeconds from '../utils/formatSeconds';
4 | const date = require("silly-datetime");
5 |
6 |
7 | export default function (videoUrl: string) {
8 | let result = { status: false, msg: '获取失败', data: {} };
9 | let da = {};
10 | return new Promise((resolve, reject) => {
11 | ffmpeg(videoUrl).ffprobe((err, data) => {
12 | if (err) {
13 | reject(new Error('获取视频信息失败,请检查视频链接是否可访问'));
14 | } else {
15 | const { format } = data;
16 | let { format_long_name, duration, size } = format;
17 | duration = formatSeconds(duration);
18 | size = formatBytes(size);
19 | let adddate = date.format(new Date(), 'YYYY-MM-DD HH:mm:ss');
20 | da = { format_long_name, duration, size, date: adddate, url: videoUrl };
21 | result = { status: true, msg: '处理成功', data: da };
22 | }
23 | resolve({
24 | result,
25 | dbData: da
26 | })
27 | });
28 | })
29 | }
--------------------------------------------------------------------------------
/src/background/ffmpeg/audio/saveAudio.ts:
--------------------------------------------------------------------------------
1 | import ffmpeg, { fileDataDir } from '../ffmpeg';
2 | import { noop } from 'lodash'
3 | const md5 = require('md5-node');
4 | const fs = require('fs')
5 | function fileExist(path) {
6 | try {
7 | fs.statSync(path);
8 | return true;
9 | } catch (e) {
10 | //捕获异常
11 | return false;
12 | }
13 | }
14 | export default function ({ url, type, fileName: name }, {
15 | start = noop,
16 | end = noop,
17 | progress = noop,
18 | error = noop
19 | }) {
20 | let fileName = name || md5(url);
21 | const ext = type || 'mp3';
22 | let file = `${fileDataDir}/${fileName}.${ext}`;
23 | if (fileExist(file)) {
24 | fileName = `${fileName}-${(new Date().getTime()) % 1000000}`;
25 | file = `${fileDataDir}/${fileName}.${ext}`;
26 | }
27 | ffmpeg(url).output(file).on('start', (commandLine) => {
28 | start({
29 | fileName,
30 | filePath: file
31 | })
32 | }).on('end', () => {
33 | end({
34 | fileName,
35 | filePath: file
36 | }); // 异步发送
37 | }).on('progress', (p) => {
38 | progress({
39 | filePath: file,
40 | percent: p.percent
41 | }); // 异步发送
42 | }).on('error', (err, stdout, stderr) => {
43 | error({
44 | err,
45 | fileName,
46 | filePath: file
47 | }); // 异步发送
48 | })
49 | .run();
50 | }
--------------------------------------------------------------------------------
/src/background/ffmpeg/ffmpeg.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable func-names */
2 |
3 |
4 | const ffmpegInstaller = require('@ffmpeg-installer/ffmpeg');
5 | // const ffprobeInstaller = require('@ffprobe-installer/ffprobe');
6 | const ffmpeg = require('fluent-ffmpeg');
7 | const path = require('path');
8 | const fs = require('fs');
9 | const date = require("silly-datetime");
10 | let ffmpegPath = ffmpegInstaller.path;
11 | ffmpeg.setFfmpegPath(ffmpegPath);
12 | const curDate = date.format(new Date(), 'YYYYMMDD');
13 | // 设置目录
14 | const fileTmpDir = path.resolve(path.basename(__dirname), '../', 'tmp');
15 | const fileBaseDataDir = path.resolve(path.basename(__dirname), '../', 'data');
16 | const fileDataDir = path.resolve(fileBaseDataDir, curDate);
17 |
18 |
19 | function fsExistsSync(paths: string[]) {
20 | try {
21 | fs.statSync(paths);
22 | } catch (error) {
23 | fs.mkdir(paths, (err, data) => {
24 | });
25 | }
26 | }
27 | fsExistsSync(fileBaseDataDir);
28 | fsExistsSync(fileTmpDir); // 创建临时目录
29 | fsExistsSync(fileDataDir); // 创建文件目录
30 |
31 |
32 | export default ffmpeg;
33 |
34 | export {
35 | fileTmpDir,
36 | fileBaseDataDir,
37 | fileDataDir
38 | }
--------------------------------------------------------------------------------
/src/background/ffmpeg/helper.ts:
--------------------------------------------------------------------------------
1 |
2 | import getVideoInfo from './video/getVideoInfo';
3 | import getScreenshots from './video/getScreenshots';
4 | import saveVideo from './video/saveVideo';
5 | import cutVideo from './video/cutVideo';
6 | import concatVideo from './video/concatVideo'
7 | // audio
8 | import getAudioInfo from './audio/getAudioInfo';
9 | import saveAudio from './audio/saveAudio';
10 |
11 |
12 | export {
13 | getVideoInfo,
14 | getScreenshots,
15 | saveVideo,
16 | concatVideo,
17 | cutVideo,
18 | saveAudio,
19 | getAudioInfo
20 | }
21 |
--------------------------------------------------------------------------------
/src/background/ffmpeg/utils/formatBytes.ts:
--------------------------------------------------------------------------------
1 | export default function formatBytes(bytes, decimals) {
2 | console.log("bytes", bytes);
3 | if (bytes === 0) return '0 Bytes';
4 | if (bytes === NaN || bytes === undefined) return '0 Bytes';
5 | if (bytes === "N/A") return '* Bytes';
6 | const k = 1000,
7 | dm = decimals + 1 || 3,
8 | sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
9 | i = Math.floor(Math.log(bytes) / Math.log(k));
10 | return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`;
11 | }
12 |
--------------------------------------------------------------------------------
/src/background/ffmpeg/utils/formatSeconds.ts:
--------------------------------------------------------------------------------
1 | export default function formatSeconds(value) {
2 | let theTime = parseInt(value);
3 | let theTime1 = 0;
4 | let theTime2 = 0;
5 |
6 | if (theTime >= 60) {
7 | theTime1 = parseInt(theTime / 60);
8 | theTime = parseInt(theTime % 60);
9 | if (theTime1 >= 60) {
10 | theTime2 = parseInt(theTime1 / 60);
11 | theTime1 = parseInt(theTime1 % 60);
12 | }
13 | }
14 | if (theTime < 10) {
15 | theTime = `0${parseInt(theTime)}`;
16 | }
17 | let result = `${theTime}`;
18 | if (theTime1 >= 0) {
19 | if (theTime1 < 10) {
20 | theTime1 = `0${parseInt(theTime1)}`;
21 | }
22 | result = `${theTime1}:${result}`;
23 | }
24 | if (theTime2 >= 0) {
25 | if (theTime2 < 10) {
26 | theTime2 = `0${parseInt(theTime2)}`;
27 | }
28 | result = `${theTime2}:${result}`;
29 | }
30 | return result;
31 | }
--------------------------------------------------------------------------------
/src/background/ffmpeg/video/concatVideo.ts:
--------------------------------------------------------------------------------
1 | import ffmpeg, { fileDataDir } from '../ffmpeg';
2 | import { isArray } from 'lodash';
3 | const concat = require('ffmpeg-concat')
4 | const md5 = require('md5-node');
5 |
6 | export default function (filePaths,onProgress) {
7 | if (!isArray(filePaths)) return [];
8 | const fileName = md5(new Date().getTime());
9 | const outputPath = `${fileDataDir}/${fileName}.mp4`;
10 | return new Promise(async (resolve, reject) => {
11 | try {
12 | concat({
13 | output: outputPath,
14 | videos: filePaths,
15 | transition: {
16 | name: 'directionalWipe',
17 | duration: 500
18 | },
19 | onProgress: onProgress || function(p){
20 | console.log(p)
21 | }
22 | }).then(() => {
23 | resolve(outputPath)
24 | })
25 | } catch (error) {
26 | reject(error);
27 | }
28 | })
29 | }
--------------------------------------------------------------------------------
/src/background/ffmpeg/video/cutVideo.ts:
--------------------------------------------------------------------------------
1 |
2 | import ffmpeg, { fileTmpDir } from '../ffmpeg';
3 | import { noop } from 'lodash';
4 | const md5 = require('md5-node');
5 |
6 | export default function (options = {}, event, {
7 | start = noop,
8 | end = noop,
9 | progress = noop,
10 | error = noop
11 | }) {
12 | const { url, start: startTime, end: endTime, ext = 'mp4' } = options;
13 | console.log(startTime, endTime)
14 | const fileName = md5(new Date().getTime() + url);
15 | const filePath = `${fileTmpDir}/${fileName}.${ext}`;
16 | ffmpeg(url).output(filePath)
17 | .setStartTime(startTime)
18 | .setDuration(endTime - startTime)
19 | .on('start', (commandLine) => {
20 | console.log('cutVideo========', commandLine)
21 | start({
22 | filePath: filePath
23 | })
24 | })
25 | .on('end', () => {
26 | end({
27 | filePath: filePath
28 | }); // 异步发送
29 | }).on('progress', (p) => {
30 | progress({
31 | filePath: filePath,
32 | percent: p.percent
33 | }); // 异步发送
34 | }).on('error', (err, stdout, stderr) => {
35 | error({
36 | err,
37 | filePath: filePath
38 | }); // 异步发送
39 | })
40 | .run();
41 | }
42 |
--------------------------------------------------------------------------------
/src/background/ffmpeg/video/getScreenshots.ts:
--------------------------------------------------------------------------------
1 |
2 | import ffmpeg, { fileDataDir } from '../ffmpeg';
3 | const md5 = require('md5-node');
4 | const path = require('path')
5 | export default function (videoUrl: string) {
6 | let key = md5(videoUrl);
7 | return new Promise((resolve, reject) => {
8 | ffmpeg(videoUrl).screenshots({
9 | timestamps: ['50%'],
10 | filename: key + '-%w.png',
11 | folder: fileDataDir,
12 | size: '320x240',
13 | }).on('end', function () {
14 | let pic = path.resolve(fileDataDir, key + "-320.png")
15 | // let status = "start";
16 | // let status_msg = "等待开始";
17 | resolve(pic)
18 | }).on('error', (error: any) => {
19 | reject(new Error('获取视频封面失败:' + error));
20 | })
21 | });
22 | }
--------------------------------------------------------------------------------
/src/background/ffmpeg/video/getVideoInfo.ts:
--------------------------------------------------------------------------------
1 |
2 | import ffmpeg from '../ffmpeg';
3 | import formatBytes from '../utils/formatBytes';
4 | import formatSeconds from '../utils/formatSeconds';
5 | const date = require("silly-datetime");
6 | const md5 = require('md5-node');
7 |
8 | export default function (videoUrl:string) {
9 | let result = { status: false, msg: '获取失败', data: {} };
10 | let da = {};
11 | return new Promise((resolve, reject) => {
12 | ffmpeg(videoUrl).ffprobe((err, data) => {
13 | if (err) {
14 | console.log(err)
15 | reject(new Error(err));
16 | } else {
17 | const { streams, format } = data;
18 | const { width, height } = streams[0];
19 | let { filename, format_long_name, duration, size } = format;
20 | let key = md5(filename);
21 | filename = key;
22 | duration = formatSeconds(duration);
23 | size = formatBytes(size);
24 | let adddate = date.format(new Date(), 'YYYY-MM-DD HH:mm:ss');
25 | da = { width, height, filename, format_long_name, duration, size, date: adddate, url: videoUrl };
26 | result = { status: true, msg: '处理成功', data: da };
27 | }
28 | resolve(result)
29 | });
30 | })
31 | }
--------------------------------------------------------------------------------
/src/background/ffmpeg/video/saveVideo.ts:
--------------------------------------------------------------------------------
1 | import ffmpeg, {
2 | fileDataDir,
3 | } from '../ffmpeg';
4 |
5 | import { noop } from 'lodash'
6 | const md5 = require('md5-node');
7 | export default function (arg: any, {
8 | start = noop,
9 | end = noop,
10 | progress = noop,
11 | error = noop
12 | }) {
13 | let { videotype, videosize, url, id } = arg;
14 | const fileName = md5(url);
15 | const ext = (videotype || 'mp4');
16 | const size = (videosize.replace("|", "x") || '320x240');
17 | const file = `${fileDataDir}/${fileName}.${ext}`;
18 | ffmpeg(url).output(file).audioCodec('libmp3lame').size(size)
19 | .videoCodec('libx264')
20 | .on('start', (commandLine) => {
21 | console.log('saveVideo=====: ', commandLine)
22 | start({
23 | filePath: file
24 | })
25 | }).on('end', () => {
26 | end({
27 | filePath: file
28 | }); // 异步发送
29 | }).on('progress', (p) => {
30 | progress({
31 | filePath: file,
32 | percent: p.percent
33 | }); // 异步发送
34 | }).on('error', (err, stdout, stderr) => {
35 | error({
36 | err,
37 | filePath: file
38 | }); // 异步发送
39 | })
40 | .run();
41 | }
--------------------------------------------------------------------------------
/src/background/index.ts:
--------------------------------------------------------------------------------
1 | import './clearwater';
2 | import './edit-video/index';
3 | import './audio/index'
4 | import './terminal/index'
--------------------------------------------------------------------------------
/src/background/terminal/index.ts:
--------------------------------------------------------------------------------
1 | import { ipcMain } from 'electron';
2 | import { wait } from '../utils';
3 | const exec = require('child-process-promise').exec;
4 | const ffmpegInstaller = require('@ffmpeg-installer/ffmpeg');
5 | const command = (cmd: string) => {
6 | return new Promise((resolve, reject) => {
7 | exec(cmd)
8 | .then(function (result: any) {
9 | resolve(result)
10 | })
11 | .catch(function (err: any) {
12 | console.error('ERROR: ', err);
13 | reject(err);
14 | });
15 | })
16 | }
17 | ipcMain.on('terminal-cmd', (event: any, arg: string) => {
18 | let cmd = (arg || '').replace('ffmpeg', ffmpegInstaller.path);
19 | Promise.race([wait(300000), command(cmd)]).then((message: any) => {
20 | message.cmd = cmd;
21 | event.sender.send('terminal-success', message);
22 | }).catch((message: any) => {
23 | message.cmd = cmd;
24 | event.sender.send('terminal-error', message);
25 | })
26 | })
27 |
--------------------------------------------------------------------------------
/src/background/utils.ts:
--------------------------------------------------------------------------------
1 | export const wait = async (time: number) => {
2 | return await new Promise((resolve) => {
3 | setTimeout(() => {
4 | resolve(true)
5 | }, time);
6 | })
7 | }
--------------------------------------------------------------------------------
/src/components/Permission/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Back Home
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/components/Result/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
{{title}}
15 |
{{subtitle}}
16 |
17 |
18 |
19 |
20 |
57 |
--------------------------------------------------------------------------------
/src/components/Result/svg/error.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/src/components/Result/svg/info.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/src/components/Result/svg/noFound.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/src/components/Result/svg/serverError.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/src/components/Result/svg/success.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/src/components/Result/svg/unauthorized.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/src/components/Result/svg/warning.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/src/config/clearwater.ts:
--------------------------------------------------------------------------------
1 | const statusText = {
2 | ing: "转换中",
3 | start: "未开始",
4 | success: "已完成",
5 | error: "转换失败",
6 | };
7 | const progressStatus = {
8 | ing: "exception",
9 | start: "exception",
10 | success: "success",
11 | error: "warning",
12 | };
13 | const vSizes = {
14 | '320|240': '320x240',
15 | '640|400': '640x400',
16 | '720|480': '720x480',
17 | '720|576': '720x576',
18 | '1280|720': '1280x720',
19 | '1920|1080': '1920x1080',
20 | }
21 |
22 | export {
23 | statusText,
24 | progressStatus,
25 | vSizes
26 | }
--------------------------------------------------------------------------------
/src/config/languageList.ts:
--------------------------------------------------------------------------------
1 | const languageList = [
2 | {
3 | name: '中英文混合',
4 | value: 'CHN_ENG'
5 | },
6 | {
7 | name: '英文',
8 | value: 'ENG'
9 | },
10 | {
11 | name: '葡萄牙语',
12 | value: 'POR'
13 | },
14 | {
15 | name: '法语',
16 | value: 'FRE'
17 | },
18 | {
19 | name: '德语',
20 | value: 'GER'
21 | },
22 | {
23 | name: '意大利语',
24 | value: 'ITA'
25 | },
26 | {
27 | name: '日语',
28 | value: '日语'
29 | },
30 | {
31 | name: '韩语',
32 | value: 'KOR'
33 | },
34 | {
35 | name: '俄语',
36 | value: 'RUS'
37 | }
38 | ];
39 |
40 | export default languageList;
--------------------------------------------------------------------------------
/src/config/settings.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * 站点配置
3 | * @author LinQian
4 | */
5 | export interface SettingsType {
6 | /**
7 | * 站点名称
8 | */
9 | siteTitle: string;
10 |
11 | /**
12 | * 顶部菜单开启
13 | */
14 | topNavEnable: boolean;
15 |
16 | /**
17 | * 头部固定开启
18 | */
19 | headFixed: boolean;
20 |
21 | /**
22 | * 站点本地存储Token 的 Key值
23 | */
24 | siteTokenKey: string;
25 |
26 | /**
27 | * Ajax请求头发送Token 的 Key值
28 | */
29 | ajaxHeadersTokenKey: string;
30 |
31 | /**
32 | * Ajax返回值不参加统一验证的api地址
33 | */
34 | ajaxResponseNoVerifyUrl: string[];
35 |
36 | /**
37 | * iconfont.cn 项目在线生成的 js 地址
38 | */
39 | iconfontUrl: string[];
40 | }
41 |
42 | const settings: SettingsType = {
43 | siteTitle: 'ADMIN-ELEMENT-VUE',
44 | topNavEnable: true,
45 | headFixed: true,
46 | siteTokenKey: 'token',
47 | ajaxHeadersTokenKey: 'authorization',
48 | ajaxResponseNoVerifyUrl: [
49 | '/user/login', // 用户登录
50 | '/user/info', // 获取用户信息
51 | ],
52 | iconfontUrl: [],
53 | };
54 |
55 | export default settings;
56 |
--------------------------------------------------------------------------------
/src/config/videoConfig.ts:
--------------------------------------------------------------------------------
1 |
2 | const BaiDu = [
3 | {
4 | name: '情感男声',
5 | value: 3
6 | },
7 | {
8 | name: '情感女声',
9 | value: 4
10 | },
11 | {
12 | name: '标准女声',
13 | value: 0
14 | },
15 | {
16 | name: '标准男声',
17 | value: 1
18 | },
19 |
20 | ];
21 |
22 |
23 | const TengXun = [
24 | {
25 | name: '智侠(男声)',
26 | value: 1000
27 | },
28 | {
29 | name: '智瑜(女声)',
30 | value: 1001
31 | },
32 | {
33 | name: '智聆(女声)',
34 | value: 1002
35 | },
36 | {
37 | name: '智美(客服女声)',
38 | value: 1003
39 | },
40 | {
41 | name: 'WeJack(英文男声)',
42 | value: 1050
43 | },
44 | {
45 | name: 'WeRose(英文女声)',
46 | value: 1051
47 | },
48 | {
49 | name: '云小宁',
50 | value: 1
51 | },
52 | {
53 | name: '云小晚',
54 | value: 2
55 | },
56 | {
57 | name: '云小叶',
58 | value: 4
59 | },
60 | {
61 | name: '云小欣',
62 | value: 5
63 | },
64 | {
65 | name: '云小龙',
66 | value: 6
67 | },
68 | {
69 | name: '云小曼',
70 | value: 7
71 | },
72 | ];
73 | const XunFei = [
74 | {
75 | name: '讯飞小燕',
76 | value: 'xiaoyan'
77 | },
78 | {
79 | name: '讯飞许久',
80 | value: 'aisjiuxu'
81 | },
82 | {
83 | name: '讯飞小萍',
84 | value: 'aisxping'
85 | },
86 | {
87 | name: '讯飞小婧',
88 | value: 'aisjinger'
89 | },
90 | {
91 | name: '讯飞许小宝',
92 | value: 'aisbabyxu'
93 | }
94 | ]
95 |
96 |
97 | interface VoiceConfig {
98 | [k: string]: any
99 | }
100 | const voiceConfig :VoiceConfig = {
101 | voice: [
102 | {
103 | name: '腾讯语音',
104 | value: 'tengxun',
105 | },
106 | {
107 | name: '讯飞语音',
108 | value: 'xunfei'
109 | },
110 | {
111 | name: '百度语音',
112 | value: 'baidu',
113 | },
114 | ],
115 | volume: {
116 | baidu: {
117 | min: 1,
118 | max: 15,
119 | default: 5
120 | },
121 | tengxun: {
122 | min: 0,
123 | max: 10,
124 | default: 2
125 | },
126 | xunfei: {
127 | min: 1,
128 | max: 100,
129 | default: 50
130 | }
131 | },
132 | speed: {
133 | baidu: {
134 | min: 0,
135 | max: 9,
136 | default: 5
137 | },
138 | tengxun: {
139 | min: -2,
140 | max: 2,
141 | default: 0
142 | },
143 | xunfei: {
144 | min: 1,
145 | max: 100,
146 | default: 50
147 | }
148 | },
149 | pitch: {
150 | baidu: {
151 | min: 0,
152 | max: 9,
153 | default: 5
154 | },
155 | tengxun: {
156 | min: -2,
157 | max: 2,
158 | default: 0
159 | },
160 | xunfei: {
161 | min: 1,
162 | max: 100,
163 | default: 50
164 | }
165 | },
166 | per: {
167 | baidu: 3,
168 | tengxun: 1000,
169 | xunfei: 'xiaoyan'
170 | },
171 | baidu: BaiDu,
172 | tengxun: TengXun,
173 | xunfei: XunFei
174 | };
175 |
176 | export {
177 | BaiDu,
178 | TengXun,
179 | XunFei,
180 | voiceConfig
181 | }
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import { createApp } from 'vue';
2 |
3 | // 样式初始化
4 | import 'normalize.css';
5 |
6 | // 全局样式
7 | import '@/assets/css/global.scss';
8 |
9 | // 引入 ElementUI
10 | import ElementPlus from 'element-plus';
11 |
12 |
13 | import App from '@/App.vue';
14 | import router from '@/router';
15 | import store from '@/store';
16 | import packageJSon from '../package.json';
17 | localStorage.version = packageJSon.version;
18 | const app = createApp(App)
19 | app.use(store);
20 | app.use(router)
21 | app.use(ElementPlus, { size: 'small' });
22 | app.mount('#app');
--------------------------------------------------------------------------------
/src/router/index.ts:
--------------------------------------------------------------------------------
1 |
2 | import { createRouter, createWebHashHistory } from 'vue-router';
3 | const routes: Array = [
4 | {
5 | title: '首页',
6 | path: '/',
7 | redirect: '/home',
8 | component: () => import('@/views/index.vue'),
9 | children: [
10 | {
11 | title: '首页',
12 | path: '/home',
13 | component: () => import('@/views/home/index.vue')
14 | },
15 | {
16 | title: '去水印',
17 | path: '/clearwater',
18 | component: () => import('@/views/clearwater/index.vue')
19 | },
20 | {
21 | title: '语音合成',
22 | path: '/audio',
23 | component: () => import('@/views/audio/index.vue')
24 | },
25 | {
26 | title: '权重查询',
27 | path: '/weight',
28 | component: () => import('@/views/weight/index.vue')
29 | },
30 | {
31 | title: '编辑视频',
32 | path: '/discern',
33 | component: () => import('@/views/discern/index.vue')
34 | },
35 | {
36 | title: '登录',
37 | path: '/login',
38 | component: () => import('@/views/login/index.vue'),
39 | },
40 | {
41 | title: '终端',
42 | path: '/terminal',
43 | component: () => import('@/views/terminal/index.vue')
44 | },
45 | ]
46 | },
47 |
48 | {
49 | title: '404',
50 | path: '/:pathMatch(.*)*',
51 | component: () => import('@/views/404/index.vue'),
52 | }
53 | ]
54 |
55 | const router = createRouter({
56 | scrollBehavior(/* to, from, savedPosition */) {
57 | return { top: 0 }
58 | },
59 | history: createWebHashHistory(process.env.BASE_URL),
60 | routes: routes as any,
61 | });
62 |
63 | export default router;
64 |
--------------------------------------------------------------------------------
/src/services/audio.ts:
--------------------------------------------------------------------------------
1 | import request from './request';
2 |
3 | export async function getAudio(data: any): Promise {
4 | return request({
5 | url: '/audio/create',
6 | method: 'post',
7 | data: data
8 | });
9 | }
10 |
--------------------------------------------------------------------------------
/src/services/clearwater.ts:
--------------------------------------------------------------------------------
1 | import request from './request';
2 |
3 | export async function dspUrl(url: string): Promise {
4 | return request({
5 | url: '/dspUrl',
6 | method: 'post',
7 | data: {
8 | url
9 | }
10 | });
11 | }
12 |
--------------------------------------------------------------------------------
/src/services/discern.ts:
--------------------------------------------------------------------------------
1 | import request from './request';
2 |
3 | export async function discern(data: any): Promise {
4 | return request({
5 | url: '/image/discern',
6 | method: 'post',
7 | data: data
8 | });
9 | }
10 |
--------------------------------------------------------------------------------
/src/services/indexData.ts:
--------------------------------------------------------------------------------
1 | import request from './request';
2 |
3 | export async function getIndexData(): Promise {
4 | return request({
5 | url: '/indexData',
6 | method: 'get'
7 | });
8 | }
9 |
--------------------------------------------------------------------------------
/src/services/request.ts:
--------------------------------------------------------------------------------
1 |
2 | import axios, { AxiosPromise, AxiosRequestConfig, AxiosResponse } from 'axios';
3 | import { ElNotification } from 'element-plus';
4 | import router from '@/router';
5 | import settings from '@/config/settings';
6 | import { getToken, setToken } from '@/utils/localToken';
7 | export interface ResponseData {
8 | code?: number;
9 | data?: any;
10 | msg?: string;
11 | token?: string;
12 | }
13 |
14 | export const baseURL = process.env.VUE_APP_APIHOST;
15 |
16 | const customCodeMessage: { [key: number]: string } = {
17 | 10002: '当前用户登入信息已失效,请重新登入再操作', // 未登陆
18 | };
19 |
20 | const serverCodeMessage: { [key: number]: string } = {
21 | 200: '服务器成功返回请求的数据',
22 | 400: 'Bad Request',
23 | 401: 'Unauthorized',
24 | 403: 'Forbidden',
25 | 404: 'Not Found',
26 | 500: '服务器发生错误,请检查服务器(Internal Server Error)',
27 | 502: '网关错误(Bad Gateway)',
28 | 503: '服务不可用,服务器暂时过载或维护(Service Unavailable)',
29 | 504: '网关超时(Gateway Timeout)',
30 | };
31 |
32 | /**
33 | * 异常处理程序
34 | */
35 | const errorHandler = (error: any) => {
36 | const { response, message } = error;
37 | if (message === 'CustomError') {
38 | // 自定义错误
39 | const { config, data } = response;
40 | const { url, baseURL } = config;
41 | const { code, msg } = data;
42 | const reqUrl = url.split("?")[0].replace(baseURL, '');
43 | const noVerifyBool = settings.ajaxResponseNoVerifyUrl.includes(reqUrl);
44 | if (!noVerifyBool) {
45 | ElNotification({
46 | type: 'error',
47 | title: `提示`,
48 | message: customCodeMessage[code] || msg || 'Error',
49 | });
50 |
51 | if (code === 10002) {
52 | router.replace('/user/login');
53 | }
54 | }
55 | } else if (message === 'CancelToken') {
56 | // 取消请求 Token
57 | // eslint-disable-next-line no-console
58 | console.log(message);
59 | } else if (response && response.status) {
60 | const errorText = serverCodeMessage[response.status] || response.statusText;
61 | const { status, request, data = {}, config } = response;
62 | if (status != 403) {
63 | ElNotification({
64 | type: 'error',
65 | title: `请求错误 ${status}: ${errorText}`,
66 | message: data.message || data.msg || errorText,
67 | });
68 | }
69 | if (status == 403 && config.url == '/login') {
70 | ElNotification({
71 | type: 'error',
72 | title: `登录失败 ${status}`,
73 | message: data.msg || errorText,
74 | duration: 3000
75 | });
76 | }
77 | } else if (!response) {
78 | ElNotification({
79 | type: 'error',
80 | title: '网络异常',
81 | message: '您的网络发生异常,无法连接服务器',
82 | });
83 | }
84 |
85 | return Promise.reject(error);
86 | }
87 |
88 | /**
89 | * 配置request请求时的默认参数
90 | */
91 |
92 | const request = axios.create({
93 | baseURL, // url = api url + request url
94 | // withCredentials: true, // 当跨域请求时发送cookie
95 | timeout: 40000 // 请求超时时间,5000(单位毫秒) / 0 不做限制
96 | });
97 |
98 | // 全局设置 - post请求头
99 | request.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8';
100 |
101 | /**
102 | * 请求前
103 | * 请求拦截器
104 | */
105 | request.interceptors.request.use(
106 | async (config: AxiosRequestConfig & { cType?: boolean }) => {
107 |
108 | // 如果设置了cType 说明是自定义 添加 Content-Type类型 为自定义post 做铺垫
109 | // if (config['cType']) {
110 | // config.headers['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8';
111 | // }
112 |
113 | // 自定义添加token header
114 | const token = await getToken();
115 | if (!token && config.url != '/login' && config.url != '/indexData') {
116 | ElNotification({
117 | type: 'warning',
118 | title: '提示',
119 | message: '请先登录',
120 | duration: 1000,
121 | onClose() {
122 | router.push('/login');
123 | }
124 | })
125 | return config
126 | }
127 |
128 | config.headers[settings.ajaxHeadersTokenKey] = 'Bearer ' + await getToken();
129 |
130 | return config;
131 | },
132 | /* error=> {} */ // 已在 export default catch
133 | );
134 |
135 | /**
136 | * 请求后
137 | * 响应拦截器
138 | */
139 | request.interceptors.response.use(
140 | async (response: AxiosResponse) => {
141 |
142 | const res: ResponseData = response.data;
143 | const { code, token } = res;
144 |
145 | // 重置刷新token
146 | if (token) {
147 | await setToken(token);
148 | }
149 |
150 | return response;
151 | },
152 | /* error => {} */ // 已在 export default catch
153 | );
154 |
155 | /**
156 | * ajax 导出
157 | *
158 | * Method: get
159 | * Request Headers
160 | * 无 - Content-Type
161 | * Query String Parameters
162 | * name: name
163 | * age: age
164 | *
165 | * Method: post
166 | * Request Headers
167 | * Content-Type:application/json;charset=UTF-8
168 | * Request Payload
169 | * { name: name, age: age }
170 | * Custom config parameters
171 | * { cType: true } Mandatory Settings Content-Type:application/json;charset=UTF-8
172 | * ......
173 | */
174 | export default function (config: AxiosRequestConfig): AxiosPromise {
175 | return request(config).then((response: AxiosResponse) => response.data).catch(error => errorHandler(error));
176 | }
177 |
--------------------------------------------------------------------------------
/src/services/user.ts:
--------------------------------------------------------------------------------
1 | import request from './request';
2 |
3 | export async function login(code: string, password?: string): Promise {
4 | return request({
5 | url: '/login',
6 | method: 'post',
7 | data: {
8 | code,
9 | password
10 | }
11 | });
12 | }
13 |
14 | export async function queryMessage(): Promise {
15 | return request({
16 | url: '/user/message'
17 | });
18 | }
--------------------------------------------------------------------------------
/src/services/weight.ts:
--------------------------------------------------------------------------------
1 | import request from './request';
2 |
3 | export async function getWeight(data: any): Promise {
4 | return request({
5 | url: '/getWeight',
6 | method: 'post',
7 | data: data
8 | });
9 | }
10 |
--------------------------------------------------------------------------------
/src/shims-vue.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.vue' {
2 | import type { DefineComponent } from 'vue'
3 | const component: DefineComponent<{}, {}, any>
4 | export default component
5 | }
6 |
7 | declare module '@ckeditor/ckeditor5-vue';
8 | declare module '@ckeditor/ckeditor5-build-decoupled-document';
9 | declare module "vue-terminal" {
10 | import VueTerminal from 'vue-terminal';
11 | export default VueTerminal;
12 | };
13 | declare module '*.json' {
14 | const value: any;
15 | export default value;
16 | }
--------------------------------------------------------------------------------
/src/store/global.ts:
--------------------------------------------------------------------------------
1 |
2 | import { Mutation/* , Action */ } from 'vuex';
3 |
4 |
5 |
6 | export default {};
7 |
--------------------------------------------------------------------------------
/src/store/index.ts:
--------------------------------------------------------------------------------
1 | import { createStore } from 'vuex'
2 | import createPersistedState from "vuex-persistedstate";
3 | import userModel from './user';
4 | const store = createStore({
5 | modules: {
6 | user: userModel
7 | },
8 | plugins: [createPersistedState()]
9 | })
10 |
11 | export default store;
--------------------------------------------------------------------------------
/src/store/user.ts:
--------------------------------------------------------------------------------
1 | import { Mutation, Action } from 'vuex';
2 | import { login, queryMessage } from "@/services/user";
3 | import { removeToken } from "@/utils/localToken";
4 | import { stat } from 'fs';
5 |
6 | export interface UserState {
7 | code: string | number;
8 | id: string;
9 | token: string;
10 | name?: string;
11 | status?: string
12 | }
13 |
14 |
15 | export interface ModuleType {
16 | namespaced?: boolean,
17 | name: string,
18 | state: UserState;
19 | mutations: {
20 | saveUser: Mutation;
21 | };
22 | actions: {
23 | login: Action;
24 | logout: Action;
25 | };
26 | }
27 |
28 | const initState: UserState = {
29 | code: 0,
30 | name: '',
31 | id: '',
32 | token: '',
33 | }
34 |
35 | const StoreModel: ModuleType = {
36 | namespaced: true,
37 | name: 'user',
38 | state: {
39 | ...initState
40 | },
41 | mutations: {
42 | saveUser(state, { code, name, id, token }) {
43 | state.code = code || initState.code;
44 | state.token = token || initState.token;
45 | state.id = id || initState.id;
46 | state.name = name || initState.name;
47 |
48 | }
49 | },
50 | actions: {
51 | async login({ commit, state }, playload) {
52 | try {
53 | const { code, password } = playload;
54 | const response = await login(code, password);
55 | commit('saveUser', {
56 | code: response.code,
57 | id: response.id,
58 | token: response.token,
59 | status: response.status
60 | });
61 | return true;
62 | } catch (error) {
63 | return false;
64 | }
65 | },
66 | async logout({ commit }) {
67 | try {
68 | await removeToken();
69 | commit('saveUser', { ...initState });
70 | return true;
71 | } catch (error) {
72 | return false;
73 | }
74 | }
75 | }
76 | }
77 |
78 |
79 |
80 | export default StoreModel;
81 |
--------------------------------------------------------------------------------
/src/utils/localToken.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * 自定义 token 操作
3 | * @author LinQian
4 | */
5 | import localforage from 'localforage';
6 | import settings from '@/config/settings';
7 |
8 | /**
9 | * 获取本地Token
10 | */
11 | export const getToken = async (): Promise => {
12 | return await localforage.getItem(settings.siteTokenKey);
13 | };
14 |
15 | /**
16 | * 设置存储本地Token
17 | */
18 | export const setToken = async (token: string): Promise => {
19 | try {
20 | await localforage.setItem(settings.siteTokenKey, token);
21 | return true;
22 | } catch (error) {
23 | return false;
24 | }
25 | };
26 |
27 | /**
28 | * 移除本地Token
29 | */
30 | export const removeToken = async (): Promise => {
31 | try {
32 | await localforage.removeItem(settings.siteTokenKey);
33 | return true;
34 | } catch (error) {
35 | return false;
36 | }
37 | };
--------------------------------------------------------------------------------
/src/utils/validate.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * 判断是否是外链
3 | * @param {string} path
4 | * @returns {Boolean}
5 | * @author LinQian
6 | */
7 | export const isExternal = (path: string): boolean => {
8 | return /^(https?:|mailto:|tel:)/.test(path);
9 | };
10 |
--------------------------------------------------------------------------------
/src/views/404/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Back Home
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/views/audio/config.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 语音引擎设置
7 |
8 |
9 | {{ item.name }}
15 |
16 |
17 |
18 |
24 | {{ item.name }}
25 | 推荐
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | 音量大小:{{ options.vol }}
34 |
40 |
41 |
42 | 音调高低:{{ options.pit }}
43 |
49 |
50 |
51 | 语速快慢:{{ options.spd }}
52 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
97 |
--------------------------------------------------------------------------------
/src/views/audio/history.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
16 |
17 |
18 |
19 |
20 |
25 |
26 |
27 |
28 |
29 | 删除
34 | 打开目录
40 |
41 |
42 |
43 |
52 |
53 |
54 | 历史记录
55 |
56 |
57 |
58 |
128 |
--------------------------------------------------------------------------------
/src/views/audio/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
16 |
17 |
18 |
19 |
20 | 快 速 合 成
28 |
29 |
30 |
31 |
32 |
120 |
--------------------------------------------------------------------------------
/src/views/audio/play.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
合成成功,点击播放试听或者下载保存为本地文件
4 |
7 |
8 |
9 | 文件名称:
10 |
11 | 点击下载文件
14 |
15 |
16 |
17 |
55 |
--------------------------------------------------------------------------------
/src/views/audio/textarea.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
12 |
13 |
25 |
--------------------------------------------------------------------------------
/src/views/clearwater/config.ts:
--------------------------------------------------------------------------------
1 | const statusText = {
2 | ing: "转换中",
3 | start: "未开始",
4 | success: "已完成",
5 | error: "转换失败",
6 | };
7 | const progressStatus = {
8 | ing: "exception",
9 | start: "exception",
10 | success: "success",
11 | error: "warning",
12 | };
13 | const vSizes = {
14 | '320|240': '320x240',
15 | '640|400': '640x400',
16 | '720|480': '720x480',
17 | '720|576': '720x576',
18 | '1280|720': '1280x720',
19 | '1920|1080': '1920x1080',
20 | }
21 |
22 | export {
23 | statusText,
24 | progressStatus,
25 | vSizes
26 | }
--------------------------------------------------------------------------------
/src/views/clearwater/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | 获取视频信息
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
34 |
35 |
36 |
37 | {{ videoInfo.filename || "---" }}
38 |
39 |
40 |
41 | {{ videoInfo.duration || "---" }}
42 |
43 |
44 |
45 | {{ videoInfo.width || "---" }}*{{
47 | videoInfo.height || "---"
48 | }}
50 |
51 |
52 |
53 |
54 |
55 | {{ videoInfo.format_long_name || "---" }}
56 |
57 |
58 |
59 | {{ videoInfo.size || "---" }}
60 |
61 |
62 |
63 |
64 | {{ videoInfo.url || "---" }}
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 | {{ dspSuccessUrl }}
80 |
81 |
导出数据设置
82 |
83 |
84 |
85 | 视频类型:
86 | MP4
87 | AVI
88 | RMVB
89 |
90 |
91 | 视频尺寸:
92 |
93 | 原始大小
94 | {{
95 | videoInfo.width ? `:${videoInfo.width}x${videoInfo.height}` : ""
96 | }}
97 |
98 | {{ item }}
105 |
106 |
114 |
115 |
116 |
历史数据
117 |
118 |
119 |
120 |
236 |
--------------------------------------------------------------------------------
/src/views/clearwater/table.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
14 |
15 |
16 |
21 |
22 |
28 |
29 |
30 |
31 | {{
32 | (scope.row.convertWidth || scope.row.width) +
33 | "*" +
34 | (scope.row.convertHeight || scope.row.height)
35 | }}
36 |
37 |
38 |
39 |
40 |
46 |
47 |
48 |
49 |
50 | {{ statusText[scope.row.status] || "未知状态" }}
51 |
57 | [查看]
58 |
59 |
60 | {{ scope.row.msg || scope.row.errorMsg || "未知错误" }}
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 | {{
69 | scope.row.originUrl || "---"
70 | }}
71 |
72 |
73 |
74 |
75 | {{ scope.row.updateDate || scope.row.date || "未知状态" }}
76 |
77 |
78 |
79 |
80 |
81 |
88 | 删除
89 |
90 | 打开/编辑
98 |
99 | 下载/导出
107 |
108 |
109 |
110 |
111 |
112 |
121 |
122 |
123 |
--------------------------------------------------------------------------------
/src/views/clearwater/utils.ts:
--------------------------------------------------------------------------------
1 | import { ipcRenderer } from 'electron';
2 | const getVideoInfo = (url: string, originUrl: string, title: string): any => {
3 | ipcRenderer.send("get-video-info", { url, originUrl ,title});
4 | return new Promise((resolve, reject) => {
5 | try {
6 | ipcRenderer.on("reply-video-info", (event, arg) => {
7 | if (arg) {
8 | resolve(arg);
9 | } else {
10 | resolve(null);
11 | }
12 | });
13 | } catch (error) {
14 | console.log(error)
15 | reject(error);
16 | }
17 | })
18 | }
19 |
20 | const saveVideo = (url: string | undefined, videoType: string | undefined, videoSize: string | undefined, id: string): void => {
21 | ipcRenderer.send("save-video", {
22 | videotype: videoType,
23 | videosize: videoSize,
24 | id,
25 | url
26 | });
27 | }
28 | export {
29 | getVideoInfo,
30 | saveVideo
31 | }
--------------------------------------------------------------------------------
/src/views/discern/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
选择识别语言:
5 |
6 |
7 |
8 | {{ item.name }}
15 |
16 |
17 |
18 |
19 |
25 |
26 |
图片文件:
27 |
28 |
39 |
40 |
41 | 将文件拖到此处,或
42 | 点击上传
43 |
44 |
45 | 只能上传图片格式的文件,如jpg,png等等
46 |
47 |
48 |
![]()
54 |
55 |
56 |
57 |
识别结果:
58 |
59 |
{{ resultText }}
60 |
61 |
62 |
63 |
64 | 复制内容
65 | 清空内容重新上传
68 |
69 |
70 | 开 始 识 别
78 |
79 |
80 |
81 |
161 |
--------------------------------------------------------------------------------
/src/views/edit-video/config.vue:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lqomg/zimeiti/3c6d3c188d61ea1696855883eaf71311ab0a8e9a/src/views/edit-video/config.vue
--------------------------------------------------------------------------------
/src/views/edit-video/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/views/edit-video/textarea.vue:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lqomg/zimeiti/3c6d3c188d61ea1696855883eaf71311ab0a8e9a/src/views/edit-video/textarea.vue
--------------------------------------------------------------------------------
/src/views/home/PageLoading/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/views/home/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
19 |
20 |
{{ item.name }}
21 |
22 |
23 | {{ item.info }}
24 |
25 | 点击进入
26 |
27 |
28 |
29 |
30 |
35 | 开 发 计 划
39 |
40 | -
45 |
50 | {{ item.content }}
51 |
52 |
53 |
54 |
55 |
56 |
57 |
160 |
210 |
--------------------------------------------------------------------------------
/src/views/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
40 |
41 |
--------------------------------------------------------------------------------
/src/views/login/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 微信扫描下方二维码或搜索自媒体自助,进入小程序我的管理,即可免费获取code。默认密码1234
9 |
10 |
14 |
15 |
16 |
用 户 登 录
17 |
18 |
19 |
24 |
27 |
28 |
29 |
30 |
36 |
39 |
40 |
41 |
42 |
48 | 提交
49 |
50 |
51 |
52 | 注册
53 |
54 |
55 |
56 |
57 |
64 |
65 |
66 |
67 |
68 |
167 |
--------------------------------------------------------------------------------
/src/views/login/qrcode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lqomg/zimeiti/3c6d3c188d61ea1696855883eaf71311ab0a8e9a/src/views/login/qrcode.png
--------------------------------------------------------------------------------
/src/views/terminal/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 | Ffmpeg
11 | 是领先的多媒体框架,能够解码、编码、转码、混合、解密、流媒体、过滤和播放人类和机器创造的几乎所有东西。
12 | 本软件已经自动集成安装,你可以免去安装流程直接使用,交流学习群:576301295。
13 | 参照使用教程
14 | 《FFmpeg 视频处理入门教程》,你可以完成对视频进行基本的转码、裁剪、添加水印、字幕、提取视/音频等等功能!
17 |
18 |
19 |
20 |
输入完成之后点击回车[Enter]执行命令
21 |
22 |
27 |
33 |
34 |
42 | 终端命令:{{ cmdResult }}
43 | {{ msgNotice }}
44 |
45 |
46 |
47 |
101 |
--------------------------------------------------------------------------------
/src/views/weight/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
数据仅供参考,目前仅支持头条号权重查询
5 |
6 |
27 |
28 |
{{ updateTime }}
29 |
34 |
35 | {{ item }}
36 |
37 |
38 |
39 |
43 |
44 |
45 |
46 |
47 | [关键词]
48 | {{ item[0] }}
49 |
50 |
51 | [移动指数]
52 | {{ item[1] }}
53 |
54 |
55 | [预估流量]
56 | {{ item[3] }}
57 |
58 |
59 |
65 |
66 |
67 | [链接地址] {{ item[4] }}
68 |
69 |
70 |
71 |
72 | 没有查询相关数据,可能暂未被收录。
73 |
74 |
75 |
76 |
77 |
133 |
140 |
290 |
291 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "esnext",
4 | "module": "esnext",
5 | "strict": true,
6 | "jsx": "preserve",
7 | "importHelpers": true,
8 | "moduleResolution": "node",
9 | "experimentalDecorators": true,
10 | "skipLibCheck": true,
11 | "esModuleInterop": true,
12 | "allowSyntheticDefaultImports": true,
13 | "sourceMap": true,
14 | "baseUrl": ".",
15 | "types": [
16 | "webpack-env",
17 | "jest"
18 | ],
19 | "paths": {
20 | "@/*": [
21 | "src/*"
22 | ]
23 | },
24 | "lib": [
25 | "esnext",
26 | "dom",
27 | "dom.iterable",
28 | "scripthost"
29 | ]
30 | },
31 | "include": [
32 | "src/**/*.ts",
33 | "src/**/*.tsx",
34 | "src/**/*.vue",
35 | ],
36 | "exclude": [
37 | "node_modules"
38 | ]
39 | }
40 |
--------------------------------------------------------------------------------
/vue.config.js:
--------------------------------------------------------------------------------
1 | const { NODE_ENV, VUE_APP_PORT } = process.env;
2 | module.exports = {
3 | publicPath: '/',
4 | outputDir: 'dist',
5 | productionSourceMap: false,
6 | devServer: {
7 | host: '0.0.0.0',
8 | port: VUE_APP_PORT || 8000,
9 | // 配置反向代理
10 | /*
11 | proxy: {
12 | '/api': {
13 | target: '',
14 | ws: true,
15 | changeOrigin: true
16 | },
17 | '/foo': {
18 | target: ''
19 | }
20 | }
21 | */
22 |
23 | },
24 | // 修改webpack的配置
25 | configureWebpack: {
26 | devtool: 'source-map',
27 | // 不需要打包的插件
28 | externals: {
29 | // 'vue': 'Vue'
30 | }
31 | },
32 | chainWebpack(config) {
33 | // config.module
34 | // .rule('svgo')
35 | // .test(/.svg$/)
36 | // .include.add(/iconsvg/)
37 | // .end()
38 | // .use('svgo-loader')
39 | // .loader('svgo-loader')
40 | // .options({
41 | // });
42 |
43 | },
44 | pluginOptions: {
45 | electronBuilder: {
46 | externals: ['fluent-ffmpeg', 'ffmpeg-concat'],
47 | chainWebpackRendererProcess: config => {
48 | config.plugin('define').tap(args => {
49 | args[0]['process.env.FLUENTFFMPEG_COV'] = false;
50 | return args;
51 | });
52 | },
53 | nodeIntegration: true,
54 | customFileProtocol: './',
55 | // electron 构建配置
56 | builderOptions: {
57 | productName: '自媒体自助', // 项目名,也是生成的安装文件名
58 | appId: 'zimeitizizhu.com', // 包名
59 | copyright: 'Copyright © 2021-present LinQian', // 版权
60 | nsis: {
61 | perMachine:false,
62 | oneClick: false, // 是否一键安装
63 | allowElevation: true, // 允许请求提升。 如果为false,则用户必须使用提升的权限重新启动安装程序。
64 | allowToChangeInstallationDirectory: true, // 允许修改安装目录
65 | installerIcon: './build/icons/icon.ico', // 安装图标
66 | uninstallerIcon: './build/icons/icon.ico', // 卸载图标
67 | installerHeaderIcon: './build/icons/icon.ico', // 安装时头部图标
68 | createDesktopShortcut: true, // 创建桌面图标
69 | createStartMenuShortcut: true, // 创建开始菜单图标
70 | shortcutName: '自媒体自助' // 图标名称
71 | },
72 | publish: [
73 | {
74 | "provider": "generic",
75 | "url": "https://zimeitizizhu.com/public/app/"
76 | }
77 | ],
78 | dmg: { // macOSdmg
79 | contents: [
80 | {
81 | "x": 410,
82 | "y": 150,
83 | "type": "link",
84 | "path": "/Applications"
85 | },
86 | {
87 | "x": 130,
88 | "y": 150,
89 | "type": "file"
90 | }
91 | ]
92 | },
93 | mac: { // mac
94 | icon: "./build/icons/icon.icns",
95 | artifactName: '${productName}-v${version}-mac.${ext}'
96 | },
97 | win: { // win 相关配置
98 | artifactName: 'zimeitizizhu-v${version}-setup.${ext}',
99 | target: [
100 | {
101 | target: "nsis", // 利用nsis制作安装程序
102 | arch: [ // 这个意思是打出来32 bit + 64 bit的包,但是要注意:这样打包出来的安装包体积比较大,所以建议直接打32的安装包。
103 | "x64",
104 | "ia32"
105 | ]
106 | }
107 | ]
108 | },
109 | linux: {
110 | icon: "./build/icons",
111 | artifactName: '${productName}-v${version}-linux.${ext}'
112 | }
113 | }
114 | }
115 | }
116 | }
--------------------------------------------------------------------------------