├── .gitignore
├── LICENSE
├── README.md
├── editor
├── .env.dist
├── .gitignore
├── README.md
├── babel.config.js
├── package-lock.json
├── package.json
├── public
│ ├── .htaccess
│ ├── css
│ │ ├── app.css
│ │ └── fonts
│ │ │ ├── Ballet-Regular.woff
│ │ │ ├── Ballet-Regular.woff2
│ │ │ ├── GrandHotel-Regular.woff
│ │ │ ├── GrandHotel-Regular.woff2
│ │ │ ├── Hanalei-Regular.woff
│ │ │ ├── Hanalei-Regular.woff2
│ │ │ ├── Monoton-Regular.woff
│ │ │ ├── Monoton-Regular.woff2
│ │ │ ├── Montserrat-Thin.woff
│ │ │ ├── Montserrat-Thin.woff2
│ │ │ ├── OpenSans.woff
│ │ │ ├── OpenSans.woff2
│ │ │ ├── ReggaeOne-Regular.woff
│ │ │ ├── ReggaeOne-Regular.woff2
│ │ │ └── stylesheet.css
│ ├── favicon.ico
│ ├── img
│ │ ├── close.svg
│ │ ├── effect.svg
│ │ ├── filter.svg
│ │ ├── logo.svg
│ │ ├── select_arrow.svg
│ │ └── transition.svg
│ ├── index.html
│ └── js
│ │ ├── fabric.min.js
│ │ ├── jquery-3.5.1.min.js
│ │ ├── recorder.js
│ │ ├── subtitles.js
│ │ └── volume-meter.js
├── src
│ ├── App.vue
│ ├── assets
│ │ ├── img
│ │ │ ├── arrow_right_icon.svg
│ │ │ ├── camera_icon.svg
│ │ │ ├── export_icon.svg
│ │ │ ├── fullscreen_icon.svg
│ │ │ ├── header_user_img_1.jpg
│ │ │ ├── header_user_img_2.jpg
│ │ │ ├── header_user_img_3.jpg
│ │ │ ├── instagram
│ │ │ │ ├── 1.jpg
│ │ │ │ ├── 2.jpg
│ │ │ │ ├── 3.jpg
│ │ │ │ └── pieroborgo.jpg
│ │ │ ├── instagram_bottom.png
│ │ │ ├── instagram_top.png
│ │ │ ├── logo.svg
│ │ │ ├── main_video_img.jpg
│ │ │ ├── pencil.svg
│ │ │ ├── play_icon.svg
│ │ │ ├── plus_icon.svg
│ │ │ ├── preview_mode_icon.svg
│ │ │ ├── redo_icon.svg
│ │ │ ├── right_step_item.jpg
│ │ │ ├── search_icon.svg
│ │ │ ├── select_arrow.svg
│ │ │ ├── shape_element_1.svg
│ │ │ ├── shape_element_2.svg
│ │ │ ├── shape_element_3.svg
│ │ │ ├── texture_element_1.jpg
│ │ │ ├── texture_element_2.jpg
│ │ │ ├── track_time_lines.svg
│ │ │ ├── undo_icon.svg
│ │ │ ├── upload_icon.svg
│ │ │ ├── video_chat_img_1.jpg
│ │ │ ├── video_chat_img_2.jpg
│ │ │ ├── video_chat_img_3.jpg
│ │ │ ├── video_chat_img_4.jpg
│ │ │ ├── video_play.svg
│ │ │ ├── video_time_back.svg
│ │ │ ├── video_time_forward.svg
│ │ │ ├── volume_icon.svg
│ │ │ ├── your_logos_1.svg
│ │ │ ├── your_logos_2.svg
│ │ │ ├── your_logos_3.svg
│ │ │ ├── your_videos_1.jpg
│ │ │ └── your_videos_2.jpg
│ │ └── logo.png
│ ├── components
│ │ ├── AudioPlayer.vue
│ │ ├── TimelineHorizontal.vue
│ │ ├── base
│ │ │ ├── AudioControls.vue
│ │ │ ├── AudioFile.vue
│ │ │ ├── Bbutton.vue
│ │ │ ├── Burger.vue
│ │ │ ├── Ccontrol.vue
│ │ │ ├── ColorCirclesPicker.vue
│ │ │ ├── DMobToggle.vue
│ │ │ ├── ExampleTimeline.vue
│ │ │ ├── GrayLabel.vue
│ │ │ ├── Headline.vue
│ │ │ ├── IconBtn.vue
│ │ │ ├── Logo.vue
│ │ │ ├── LogoFile.vue
│ │ │ ├── Pbutton.vue
│ │ │ ├── Preloader.vue
│ │ │ ├── SelectField.vue
│ │ │ ├── Step.vue
│ │ │ ├── StepRight.vue
│ │ │ ├── SubtitleItem.vue
│ │ │ ├── Tcontrol.vue
│ │ │ ├── TextElement.vue
│ │ │ ├── TextareaField.vue
│ │ │ ├── TextureElement.vue
│ │ │ ├── Trackrows.vue
│ │ │ ├── Tracktime.vue
│ │ │ ├── VideoEmbed.vue
│ │ │ ├── VideoFile.vue
│ │ │ ├── VolumeControl.vue
│ │ │ ├── index.js
│ │ │ └── svg.js
│ │ ├── layouts
│ │ │ ├── AudioControls.vue
│ │ │ ├── Controlbar.vue
│ │ │ ├── Header.vue
│ │ │ ├── Leftbars.vue
│ │ │ ├── Rightbars.vue
│ │ │ ├── Timeline.vue
│ │ │ ├── Workspace.vue
│ │ │ ├── Wrapper.vue
│ │ │ └── index.js
│ │ └── modals
│ │ │ ├── AudioSetting.vue
│ │ │ ├── DragModal.vue
│ │ │ ├── ExportModal.vue
│ │ │ ├── ObjectPopup.vue
│ │ │ ├── index.js
│ │ │ └── subtitles
│ │ │ └── EditSubtitle.vue
│ ├── config
│ │ └── index.js
│ ├── libs
│ │ ├── editor
│ │ │ ├── base.js
│ │ │ └── index.js
│ │ ├── history.js
│ │ ├── keyboard.js
│ │ ├── recorder.js
│ │ ├── utils.js
│ │ └── volumeMeter.js
│ ├── main.js
│ ├── plugins
│ │ ├── vue-drag-resize
│ │ │ ├── .babelrc
│ │ │ ├── .npmignore
│ │ │ ├── LICENSE
│ │ │ ├── README.md
│ │ │ ├── config
│ │ │ │ ├── dev.conf.js
│ │ │ │ ├── index.js
│ │ │ │ ├── prod.conf.js
│ │ │ │ └── test.conf.js
│ │ │ ├── package-lock.json
│ │ │ ├── package.json
│ │ │ ├── postcss.config.js
│ │ │ ├── src
│ │ │ │ ├── components
│ │ │ │ │ ├── vue-drag-resize.css
│ │ │ │ │ ├── vue-drag-resize.html
│ │ │ │ │ ├── vue-drag-resize.js
│ │ │ │ │ └── vue-drag-resize.vue
│ │ │ │ ├── demo
│ │ │ │ │ ├── app.js
│ │ │ │ │ ├── app.vue
│ │ │ │ │ ├── components
│ │ │ │ │ │ └── toolbar
│ │ │ │ │ │ │ ├── toolbar.css
│ │ │ │ │ │ │ ├── toolbar.html
│ │ │ │ │ │ │ ├── toolbar.js
│ │ │ │ │ │ │ └── toolbar.vue
│ │ │ │ │ ├── icons
│ │ │ │ │ │ ├── index.js
│ │ │ │ │ │ ├── lock.js
│ │ │ │ │ │ ├── toBottom.js
│ │ │ │ │ │ └── toTop.js
│ │ │ │ │ └── store
│ │ │ │ │ │ ├── index.js
│ │ │ │ │ │ └── modules
│ │ │ │ │ │ └── rect
│ │ │ │ │ │ ├── actions.js
│ │ │ │ │ │ ├── getters.js
│ │ │ │ │ │ ├── index.js
│ │ │ │ │ │ ├── mutation-types.js
│ │ │ │ │ │ ├── mutations.js
│ │ │ │ │ │ └── state.js
│ │ │ │ └── index.js
│ │ │ └── webpack.config.js
│ │ └── vue-range-component
│ │ │ ├── LICENSE
│ │ │ ├── README.md
│ │ │ └── package.json
│ ├── providers
│ │ ├── api
│ │ │ └── index.js
│ │ ├── helpers
│ │ │ └── index.js
│ │ └── models
│ │ │ ├── Edit.js
│ │ │ ├── Timeline.js
│ │ │ └── index.js
│ ├── router
│ │ └── index.js
│ ├── store
│ │ └── index.js
│ └── views
│ │ └── Home.vue
└── vue.config.js
├── screenshot.png
└── server
├── .env.dist
├── .gitignore
├── app.js
├── config.js
├── package-lock.json
├── package.json
├── public
├── fonts
│ ├── Ballet.ttf
│ ├── Grand-Hotel.ttf
│ ├── Hanalei.ttf
│ ├── Monoton.ttf
│ ├── Montserrat.ttf
│ ├── OpenSans-Regular.ttf
│ └── Reggae-One.ttf
└── storage
│ └── .gitignore
├── routes
├── api.js
└── hook.js
└── src
├── io
├── index.js
└── subscribeStorage.js
└── services
├── files
└── index.js
└── shotstack
├── index.js
└── utils.js
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 | .pnpm-debug.log*
9 |
10 | # Diagnostic reports (https://nodejs.org/api/report.html)
11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
12 |
13 | # Runtime data
14 | pids
15 | *.pid
16 | *.seed
17 | *.pid.lock
18 |
19 | # Directory for instrumented libs generated by jscoverage/JSCover
20 | lib-cov
21 |
22 | # Coverage directory used by tools like istanbul
23 | coverage
24 | *.lcov
25 |
26 | # nyc test coverage
27 | .nyc_output
28 |
29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
30 | .grunt
31 |
32 | # Bower dependency directory (https://bower.io/)
33 | bower_components
34 |
35 | # node-waf configuration
36 | .lock-wscript
37 |
38 | # Compiled binary addons (https://nodejs.org/api/addons.html)
39 | build/Release
40 |
41 | # Dependency directories
42 | node_modules/
43 | jspm_packages/
44 |
45 | # Snowpack dependency directory (https://snowpack.dev/)
46 | web_modules/
47 |
48 | # TypeScript cache
49 | *.tsbuildinfo
50 |
51 | # Optional npm cache directory
52 | .npm
53 |
54 | # Optional eslint cache
55 | .eslintcache
56 |
57 | # Microbundle cache
58 | .rpt2_cache/
59 | .rts2_cache_cjs/
60 | .rts2_cache_es/
61 | .rts2_cache_umd/
62 |
63 | # Optional REPL history
64 | .node_repl_history
65 |
66 | # Output of 'npm pack'
67 | *.tgz
68 |
69 | # Yarn Integrity file
70 | .yarn-integrity
71 |
72 | # dotenv environment variables file
73 | .env
74 | .env.test
75 | .env.production
76 |
77 | # parcel-bundler cache (https://parceljs.org/)
78 | .cache
79 | .parcel-cache
80 |
81 | # Next.js build output
82 | .next
83 | out
84 |
85 | # Nuxt.js build / generate output
86 | .nuxt
87 | dist
88 |
89 | # Gatsby files
90 | .cache/
91 | # Comment in the public line in if your project uses Gatsby and not Next.js
92 | # https://nextjs.org/blog/next-9-1#public-directory-support
93 | # public
94 |
95 | # vuepress build output
96 | .vuepress/dist
97 |
98 | # Serverless directories
99 | .serverless/
100 |
101 | # FuseBox cache
102 | .fusebox/
103 |
104 | # DynamoDB Local files
105 | .dynamodb/
106 |
107 | # TernJS port file
108 | .tern-port
109 |
110 | # Stores VSCode versions used for testing VSCode extensions
111 | .vscode-test
112 |
113 | # yarn v2
114 | .yarn/cache
115 | .yarn/unplugged
116 | .yarn/build-state.yml
117 | .yarn/install-state.gz
118 | .pnp.*
119 |
120 | # Others
121 | .DS_Store
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Studio Pink
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Pink Editor
2 |
3 | The Pink Editor has been built on top of [Shotstack](https://shotstack.io).
4 |
5 | 
6 |
7 | ## Description
8 |
9 | The Pink Editor is a Vue.js video editing application that provides the following capabilities:
10 |
11 | - Timeline editing
12 | - Drag and Drop interface for titles
13 | - Custom fonts and styling of titles
14 | - Manuall add subtitles or import SRT file
15 | - Adding intros and outros to video
16 | - Add audio, soundtracks
17 | - Record and add audio to video via microphone
18 | - Trimming and merging of videos
19 | - Export as MP4, GIF or MP3
20 | - Export aspect ratios
21 | - Export in SD, HD (720p) and full HD (1080p)
22 | - Edit videos in different aspect ratio's (16:9, Instagram Story, TikTok, Square)
23 | - Add transitions between clips
24 | - Add effects and filters to titles and clips
25 |
26 | ## Summary
27 |
28 | The application consists out of two components. The front-end editor and the backend server.
29 |
30 | ### Editor
31 |
32 | The editor component provides the front-end interface to edit videos. The frontend communicates
33 | with the server using `socket.io`.
34 |
35 | ### Server
36 |
37 | The server component provides the backend functionality to store media assets, subtitles and send
38 | completed edits to Shot
39 |
40 | ## Requirements
41 |
42 | `Node.js v12.13.0+`
43 |
44 | ## Installation
45 |
46 | Install Node dependencies.
47 |
48 | ```bash
49 | npm --prefix ./editor install
50 | npm --prefix ./server install
51 | ```
52 |
53 | ## Configuration
54 |
55 | The below instructions will allow you to set up the editor locally. This will allow you to use the
56 | front-end interface and create video edits. To render your videos the application needs to be set
57 | up where the media assets and font files are publicly accessible - for Shotstack to download the
58 | assets for rendering.
59 |
60 | ### Editor
61 |
62 | The editor requires you to set environment variables for `VUE_APP_SOCKET_API` of the `socket.io` connection
63 | and the `VUE_APP_API` for the API endpoints exposed on the server. The `BASE_URL` is used for routing.
64 |
65 | Run the following commands to create a `.env` file in your editor directory:
66 |
67 | ```bash
68 | cp ./editor/.env.dist ./editor/.env
69 | ```
70 |
71 | Edit the following in the new `./editor/.env` file as needed:
72 |
73 | ```bash
74 | VUE_APP_SOCKET_API=ws://localhost:3000
75 | VUE_APP_API=https://localhost:3000
76 | BASE_URL=https://localhost:3000
77 | ```
78 |
79 | ### Server
80 |
81 | The server requires you to set up environment variables for the `PORT` and `DOMAIN` for the server to run on.
82 |
83 | Run the following commands to create a `.env` file in your server directory:
84 |
85 | ```bash
86 | cp ./server/.env.dist ./server/.env
87 | ```
88 |
89 | Edit the following in the new `./server/.env` file as needed:
90 |
91 | ```bash
92 | PORT=3000
93 | DOMAIN=https://localhost:3000
94 | SHOTSTACK_API=https://api.shotstack.io/edit/stage
95 | SHOTSTACK_API_KEY=SHOTSTACK_API_KEY
96 | SSL_privkey=key.pem
97 | SSL_fullchain=cert.pem
98 | ```
99 |
100 | The `SHOTSTACK_API` is the base url for the [Shotstack video editing API](https://shotstack.io) that renders
101 | the videos. The URL can be either `https://api.shotstack.io/edit/stage` or `https://api.shotstack.io/edit/v1`.
102 |
103 | `SHOTSTACK_API_KEY` is the API key to connect to the Shotstack API. You can
104 | [register](https://dashboard.shotstack.io/register) on the Shotstack website for a free developer sandbox account. Make
105 | sure you use the correct API key for the stage or v1 `SHOTSTACK_API` url.
106 |
107 | To enable a secure connection between the Editor and the Server you need to reference the SSL keypair you want
108 | to use to encrypt the connection. For development purposes you can use a self-signed certificate using an utility
109 | such as [mkcert](https://github.com/FiloSottile/mkcert). For production you can use
110 | [Let's Encrypt](https://letsencrypt.org/).
111 |
112 | ## Run locally
113 |
114 | To run the editor locally run the following commands:
115 |
116 | ### Editor
117 |
118 | The following command builds the editor and watches for changes:
119 |
120 | ```bash
121 | cd editor
122 | npm run dev
123 | ```
124 |
125 | ### Server
126 |
127 | The following command starts the server and watches for changes:
128 |
129 | ```bash
130 | cd server
131 | npm run dev
132 | ```
133 |
134 | Once both services have started visit https://localhost:3000. If you used a different port or URL visit that instead.
135 |
136 | If you are running locally you may still receive a certificate error in the browser, because it is self signed. You can
137 | safely continue and ignore the warning.
--------------------------------------------------------------------------------
/editor/.env.dist:
--------------------------------------------------------------------------------
1 | VUE_APP_SOCKET_API=ws://localhost:3000
2 | VUE_APP_API=https://localhost:3000
3 | BASE_URL=https://localhost:3000
4 |
--------------------------------------------------------------------------------
/editor/.gitignore:
--------------------------------------------------------------------------------
1 | .env
--------------------------------------------------------------------------------
/editor/README.md:
--------------------------------------------------------------------------------
1 | # editor
2 |
3 | ## Project setup
4 | ```
5 | npm install
6 | ```
7 |
8 | ### Compiles and hot-reloads for development
9 | ```
10 | npm run serve
11 | ```
12 |
13 | ### Compiles and minifies for production
14 | ```
15 | npm run build
16 | ```
17 |
18 | ### Customize configuration
19 | See [Configuration Reference](https://cli.vuejs.org/config/).
20 |
--------------------------------------------------------------------------------
/editor/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/cli-plugin-babel/preset'
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/editor/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "editor",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "serve": "vue-cli-service serve",
7 | "build": "vue-cli-service build",
8 | "dev": "vue-cli-service build --watch --mode development"
9 | },
10 | "dependencies": {
11 | "axios": "^0.21.1",
12 | "config": "^3.3.6",
13 | "core-js": "^3.6.5",
14 | "lodash": "^4.17.21",
15 | "vue": "^2.6.11",
16 | "vue-color": "^2.8.1",
17 | "vue-drag-resize": "^1.4.2",
18 | "vue-dragscroll": "^2.1.3",
19 | "vue-js-modal": "^2.0.0-rc.6",
20 | "vue-loaders": "^4.1.4",
21 | "vue-range-component": "^1.0.3",
22 | "vue-range-slider": "^0.6.0",
23 | "vue-router": "^3.2.0",
24 | "vue-simple-context-menu": "^3.4.1",
25 | "vue-socket.io": "^3.0.10",
26 | "vue-sweetalert2": "^4.2.0",
27 | "vue-uuid": "^2.0.2",
28 | "vuex": "^3.4.0"
29 | },
30 | "devDependencies": {
31 | "@vue/cli-plugin-babel": "~4.5.0",
32 | "@vue/cli-plugin-router": "~4.5.0",
33 | "@vue/cli-plugin-vuex": "~4.5.0",
34 | "@vue/cli-service": "~4.5.0",
35 | "node-sass": "^4.12.0",
36 | "sass-loader": "^8.0.2",
37 | "vue-template-compiler": "^2.6.11"
38 | },
39 | "browserslist": [
40 | "> 1%",
41 | "last 2 versions",
42 | "not dead"
43 | ]
44 | }
45 |
--------------------------------------------------------------------------------
/editor/public/.htaccess:
--------------------------------------------------------------------------------
1 |
2 | RewriteEngine On
3 | RewriteBase /StudioPinkEditor/vue/editor/
4 | RewriteRule ^StudioPinkEditor/vue/editor/index\.html$ - [L]
5 | RewriteCond %{REQUEST_FILENAME} !-f
6 | RewriteCond %{REQUEST_FILENAME} !-d
7 | RewriteRule . /StudioPinkEditor/vue/editor/index.html [L]
8 |
--------------------------------------------------------------------------------
/editor/public/css/app.css:
--------------------------------------------------------------------------------
1 | .video_tracks_block {
2 | width: -moz-fit-content;
3 | }
4 |
5 | .btn_pink.disabled {
6 | pointer-events: none;
7 | background: rgb(224 73 173 / .3);
8 | color: #909090;
9 | }
10 |
11 | .btn_pink.disabled path {
12 | fill: #909090;
13 | }
14 |
15 | .bottom_section_top_controls {
16 | user-select: none;
17 | }
18 |
19 | .red_text {
20 | color: #f00 !important;
21 | }
22 |
23 | .modal_window {
24 | color: #9BAFB3;
25 | }
26 |
27 | .modal_window .content {
28 | width: 100%;
29 | height: 100%;
30 | }
31 |
32 | .modal_window .body {
33 | width: 100%;
34 | height: calc(100% - 35px - 10px);
35 | padding: 0 5px;
36 | overflow: auto;
37 | overflow-x: hidden;
38 | }
39 |
40 | .modal_window .header {
41 | width: 100%;
42 | display: flex;
43 | min-height: 35px;
44 | background: #021F23;
45 | margin-bottom: 5px;
46 | line-height: 1.16;
47 | }
48 |
49 | .modal_window .header .left_header {
50 | flex: 1;
51 | display: flex;
52 | justify-content: left;
53 | align-items: center;
54 | font-size: 26px;
55 | padding: 0 10px;
56 | cursor: move;
57 | user-select: none;
58 | }
59 |
60 | .modal_window .header button {
61 | width: 26px;
62 | padding: 0;
63 | background: none;
64 | border: none;
65 | margin: 8px 0;
66 | position: relative;
67 | }
68 |
69 | .modal_window .header button svg {
70 | width: 15px;
71 | height: 15px;
72 | position: absolute;
73 | left: 0;
74 | top: 0;
75 | }
76 |
77 | .modal_window .header button svg path {
78 | fill: #bdbdbd;
79 | }
80 |
81 | .transparent_bg > .vm--overlay {
82 | background: rgba(0, 0, 0, 0);
83 | }
84 |
85 | .video_tracks_block_wrpr {
86 | scrollbar-color: #6882864d #001B1F;
87 | scrollbar-width: 10px;
88 | }
89 |
90 | .video_tracks_block_wrpr::-webkit-scrollbar {
91 | height: 10px;
92 | }
93 |
94 | .app_content .center_section {
95 | height: calc(100vh - 326px);
96 | }
97 |
98 | .subtitle_item p {
99 | font-weight: 600;
100 | font-size: 9px;
101 | line-height: 10px;
102 | color: rgb(0 27 31 / 78%);
103 | }
104 |
105 | .editor_content_box.screen_mode_desktop {
106 | max-width: 100%;
107 | height: 100%;
108 | }
109 |
110 | .editor_content_box.screen_mode_mobile {
111 | height: 100%;
112 | }
113 |
114 | .undo_redo_btns a.disabled svg path {
115 | stroke: #1a3b3f;
116 | }
117 |
118 | .undo_redo_btns {
119 | user-select: none;
120 | }
121 |
122 | .volume_range_wrpr {
123 | min-width: 100% !important;
124 | }
125 |
126 | .volume_range {
127 | height: 12px;
128 | background: transparent;
129 | }
130 |
131 | .campaign_name input {
132 | font-size: 16px;
133 | line-height: 22px;
134 | color: #9BAFB3;
135 | margin-left: 20px;
136 | font-weight: 600;
137 | background: transparent;
138 | border: none;
139 | }
140 |
141 | .campaign_name img {
142 | margin-left: 10px;
143 | width:16px;
144 | height: 16px;
145 | cursor: pointer;
146 | }
--------------------------------------------------------------------------------
/editor/public/css/fonts/Ballet-Regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/studiopink/pink-editor/56aab3e077ae95a269449405595f0ba75b1d283d/editor/public/css/fonts/Ballet-Regular.woff
--------------------------------------------------------------------------------
/editor/public/css/fonts/Ballet-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/studiopink/pink-editor/56aab3e077ae95a269449405595f0ba75b1d283d/editor/public/css/fonts/Ballet-Regular.woff2
--------------------------------------------------------------------------------
/editor/public/css/fonts/GrandHotel-Regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/studiopink/pink-editor/56aab3e077ae95a269449405595f0ba75b1d283d/editor/public/css/fonts/GrandHotel-Regular.woff
--------------------------------------------------------------------------------
/editor/public/css/fonts/GrandHotel-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/studiopink/pink-editor/56aab3e077ae95a269449405595f0ba75b1d283d/editor/public/css/fonts/GrandHotel-Regular.woff2
--------------------------------------------------------------------------------
/editor/public/css/fonts/Hanalei-Regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/studiopink/pink-editor/56aab3e077ae95a269449405595f0ba75b1d283d/editor/public/css/fonts/Hanalei-Regular.woff
--------------------------------------------------------------------------------
/editor/public/css/fonts/Hanalei-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/studiopink/pink-editor/56aab3e077ae95a269449405595f0ba75b1d283d/editor/public/css/fonts/Hanalei-Regular.woff2
--------------------------------------------------------------------------------
/editor/public/css/fonts/Monoton-Regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/studiopink/pink-editor/56aab3e077ae95a269449405595f0ba75b1d283d/editor/public/css/fonts/Monoton-Regular.woff
--------------------------------------------------------------------------------
/editor/public/css/fonts/Monoton-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/studiopink/pink-editor/56aab3e077ae95a269449405595f0ba75b1d283d/editor/public/css/fonts/Monoton-Regular.woff2
--------------------------------------------------------------------------------
/editor/public/css/fonts/Montserrat-Thin.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/studiopink/pink-editor/56aab3e077ae95a269449405595f0ba75b1d283d/editor/public/css/fonts/Montserrat-Thin.woff
--------------------------------------------------------------------------------
/editor/public/css/fonts/Montserrat-Thin.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/studiopink/pink-editor/56aab3e077ae95a269449405595f0ba75b1d283d/editor/public/css/fonts/Montserrat-Thin.woff2
--------------------------------------------------------------------------------
/editor/public/css/fonts/OpenSans.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/studiopink/pink-editor/56aab3e077ae95a269449405595f0ba75b1d283d/editor/public/css/fonts/OpenSans.woff
--------------------------------------------------------------------------------
/editor/public/css/fonts/OpenSans.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/studiopink/pink-editor/56aab3e077ae95a269449405595f0ba75b1d283d/editor/public/css/fonts/OpenSans.woff2
--------------------------------------------------------------------------------
/editor/public/css/fonts/ReggaeOne-Regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/studiopink/pink-editor/56aab3e077ae95a269449405595f0ba75b1d283d/editor/public/css/fonts/ReggaeOne-Regular.woff
--------------------------------------------------------------------------------
/editor/public/css/fonts/ReggaeOne-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/studiopink/pink-editor/56aab3e077ae95a269449405595f0ba75b1d283d/editor/public/css/fonts/ReggaeOne-Regular.woff2
--------------------------------------------------------------------------------
/editor/public/css/fonts/stylesheet.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'Ballet';
3 | src: url('Ballet-Regular.woff2') format('woff2'),
4 | url('Ballet-Regular.woff') format('woff');
5 | font-weight: normal;
6 | font-style: normal;
7 | font-display: swap;
8 | }
9 |
10 | @font-face {
11 | font-family: 'Grand Hotel';
12 | src: url('GrandHotel-Regular.woff2') format('woff2'),
13 | url('GrandHotel-Regular.woff') format('woff');
14 | font-weight: normal;
15 | font-style: normal;
16 | font-display: swap;
17 | }
18 |
19 | @font-face {
20 | font-family: 'Monoton';
21 | src: url('Monoton-Regular.woff2') format('woff2'),
22 | url('Monoton-Regular.woff') format('woff');
23 | font-weight: normal;
24 | font-style: normal;
25 | font-display: swap;
26 | }
27 |
28 | @font-face {
29 | font-family: 'Hanalei';
30 | src: url('Hanalei-Regular.woff2') format('woff2'),
31 | url('Hanalei-Regular.woff') format('woff');
32 | font-weight: normal;
33 | font-style: normal;
34 | font-display: swap;
35 | }
36 |
37 | @font-face {
38 | font-family: 'Montserrat';
39 | src: url('Montserrat-Thin.woff2') format('woff2'),
40 | url('Montserrat-Thin.woff') format('woff');
41 | font-weight: 100;
42 | font-style: normal;
43 | font-display: swap;
44 | }
45 |
46 | @font-face {
47 | font-family: 'Open Sans';
48 | src: url('OpenSans.woff2') format('woff2'),
49 | url('OpenSans.woff') format('woff');
50 | font-weight: normal;
51 | font-style: normal;
52 | font-display: swap;
53 | }
54 |
55 | @font-face {
56 | font-family: 'Reggae One';
57 | src: url('ReggaeOne-Regular.woff2') format('woff2'),
58 | url('ReggaeOne-Regular.woff') format('woff');
59 | font-weight: normal;
60 | font-style: normal;
61 | font-display: swap;
62 | }
63 |
64 |
--------------------------------------------------------------------------------
/editor/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/studiopink/pink-editor/56aab3e077ae95a269449405595f0ba75b1d283d/editor/public/favicon.ico
--------------------------------------------------------------------------------
/editor/public/img/close.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/editor/public/img/effect.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/editor/public/img/filter.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/editor/public/img/select_arrow.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/editor/public/img/transition.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
66 |
--------------------------------------------------------------------------------
/editor/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Studio Pink.io |
10 | <%= htmlWebpackPlugin.options.title %>
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/editor/public/js/subtitles.js:
--------------------------------------------------------------------------------
1 | var parser = (function() {
2 | var pItems = {};
3 |
4 | /**
5 | * Converts SubRip subtitles into array of objects
6 | * [{
7 | * id: `Number of subtitle`
8 | * startTime: `Start time of subtitle`
9 | * endTime: `End time of subtitle
10 | * text: `Text of subtitle`
11 | * }]
12 | *
13 | * @param {String} data SubRip suntitles string
14 | * @param {Boolean} ms Optional: use milliseconds for startTime and endTime
15 | * @return {Array}
16 | */
17 | pItems.fromSrt = function(data, ms) {
18 | var useMs = ms ? true : false;
19 |
20 | data = data.replace(/\r/g, '');
21 | var regex = /(\d+)\n(\d{2}:\d{2}:\d{2},\d{3}) --> (\d{2}:\d{2}:\d{2},\d{3})/g;
22 | data = data.split(regex);
23 | data.shift();
24 |
25 | var items = [];
26 | for (var i = 0; i < data.length; i += 4) {
27 | items.push({
28 | id: data[i].trim(),
29 | startTime: useMs ? timeMs(data[i + 1].trim()) : data[i + 1].trim(),
30 | endTime: useMs ? timeMs(data[i + 2].trim()) : data[i + 2].trim(),
31 | text: data[i + 3].trim()
32 | });
33 | }
34 |
35 | return items;
36 | };
37 |
38 | /**
39 | * Converts Array of objects created by this module to SubRip subtitles
40 | * @param {Array} data
41 | * @return {String} SubRip subtitles string
42 | */
43 | pItems.toSrt = function(data) {
44 | if (!data instanceof Array) return '';
45 | var res = '';
46 |
47 | for (var i = 0; i < data.length; i++) {
48 | var s = data[i];
49 |
50 | if (!isNaN(s.startTime) && !isNaN(s.endTime)) {
51 | s.startTime = msTime(parseInt(s.startTime, 10));
52 | s.endTime = msTime(parseInt(s.endTime, 10));
53 | }
54 |
55 | res += s.id + '\r\n';
56 | res += s.startTime + ' --> ' + s.endTime + '\r\n';
57 | res += s.text.replace('\n', '\r\n') + '\r\n\r\n';
58 | }
59 |
60 | return res;
61 | };
62 |
63 | var timeMs = function(val) {
64 | var regex = /(\d+):(\d{2}):(\d{2}),(\d{3})/;
65 | var parts = regex.exec(val);
66 |
67 | if (parts === null) {
68 | return 0;
69 | }
70 |
71 | for (var i = 1; i < 5; i++) {
72 | parts[i] = parseInt(parts[i], 10);
73 | if (isNaN(parts[i])) parts[i] = 0;
74 | }
75 |
76 | // hours + minutes + seconds + ms
77 | return parts[1] * 3600000 + parts[2] * 60000 + parts[3] * 1000 + parts[4];
78 | };
79 |
80 | var msTime = function(val) {
81 | var measures = [ 3600000, 60000, 1000 ];
82 | var time = [];
83 |
84 | for (var i in measures) {
85 | var res = (val / measures[i] >> 0).toString();
86 |
87 | if (res.length < 2) res = '0' + res;
88 | val %= measures[i];
89 | time.push(res);
90 | }
91 |
92 | var ms = val.toString();
93 | if (ms.length < 3) {
94 | for (i = 0; i <= 3 - ms.length; i++) ms = '0' + ms;
95 | }
96 |
97 | return time.join(':') + ',' + ms;
98 | };
99 |
100 | return pItems;
101 | })();
102 |
103 | // ignore exports for browser
104 | if (typeof exports === 'object') {
105 | module.exports = parser;
106 | }
107 |
--------------------------------------------------------------------------------
/editor/public/js/volume-meter.js:
--------------------------------------------------------------------------------
1 | function createAudioMeter(audioContext, volumeCallback = () => {}) {
2 | var processor = audioContext.createScriptProcessor(512);
3 | processor.onaudioprocess = function (event) {
4 | var buf = event.inputBuffer.getChannelData(0);
5 | var bufLength = buf.length;
6 | var sum = 0;
7 | var x;
8 |
9 | for (var i = 0; i < bufLength; i++) {
10 | x = buf[i];
11 | if (Math.abs(x) >= this.clipLevel) {
12 | this.clipping = true;
13 | this.lastClip = window.performance.now();
14 | }
15 | sum += x * x;
16 | }
17 |
18 | var rms = Math.sqrt(sum / bufLength);
19 | this.volume = Math.max(rms, this.volume * this.averaging);
20 |
21 | volumeCallback({ currentTime: this.context.currentTime, volume: this.volume });
22 | };
23 |
24 | processor.clipping = false;
25 | processor.lastClip = 0;
26 | processor.volume = 0;
27 | processor.clipLevel = 0.98;
28 | processor.averaging = 0.95;
29 | processor.clipLag = 750;
30 |
31 | processor.connect(audioContext.destination);
32 |
33 | processor.checkClipping = function () {
34 | if (!this.clipping)
35 | return false;
36 | if ((this.lastClip + this.clipLag) < window.performance.now())
37 | this.clipping = false;
38 | return this.clipping;
39 | };
40 |
41 | processor.shutdown = function () {
42 | this.disconnect();
43 | this.onaudioprocess = null;
44 | };
45 |
46 | return processor;
47 | }
--------------------------------------------------------------------------------
/editor/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
56 |
57 |
--------------------------------------------------------------------------------
/editor/src/assets/img/arrow_right_icon.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/editor/src/assets/img/camera_icon.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/editor/src/assets/img/export_icon.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/editor/src/assets/img/fullscreen_icon.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/editor/src/assets/img/header_user_img_1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/studiopink/pink-editor/56aab3e077ae95a269449405595f0ba75b1d283d/editor/src/assets/img/header_user_img_1.jpg
--------------------------------------------------------------------------------
/editor/src/assets/img/header_user_img_2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/studiopink/pink-editor/56aab3e077ae95a269449405595f0ba75b1d283d/editor/src/assets/img/header_user_img_2.jpg
--------------------------------------------------------------------------------
/editor/src/assets/img/header_user_img_3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/studiopink/pink-editor/56aab3e077ae95a269449405595f0ba75b1d283d/editor/src/assets/img/header_user_img_3.jpg
--------------------------------------------------------------------------------
/editor/src/assets/img/instagram/1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/studiopink/pink-editor/56aab3e077ae95a269449405595f0ba75b1d283d/editor/src/assets/img/instagram/1.jpg
--------------------------------------------------------------------------------
/editor/src/assets/img/instagram/2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/studiopink/pink-editor/56aab3e077ae95a269449405595f0ba75b1d283d/editor/src/assets/img/instagram/2.jpg
--------------------------------------------------------------------------------
/editor/src/assets/img/instagram/3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/studiopink/pink-editor/56aab3e077ae95a269449405595f0ba75b1d283d/editor/src/assets/img/instagram/3.jpg
--------------------------------------------------------------------------------
/editor/src/assets/img/instagram/pieroborgo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/studiopink/pink-editor/56aab3e077ae95a269449405595f0ba75b1d283d/editor/src/assets/img/instagram/pieroborgo.jpg
--------------------------------------------------------------------------------
/editor/src/assets/img/instagram_bottom.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/studiopink/pink-editor/56aab3e077ae95a269449405595f0ba75b1d283d/editor/src/assets/img/instagram_bottom.png
--------------------------------------------------------------------------------
/editor/src/assets/img/instagram_top.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/studiopink/pink-editor/56aab3e077ae95a269449405595f0ba75b1d283d/editor/src/assets/img/instagram_top.png
--------------------------------------------------------------------------------
/editor/src/assets/img/main_video_img.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/studiopink/pink-editor/56aab3e077ae95a269449405595f0ba75b1d283d/editor/src/assets/img/main_video_img.jpg
--------------------------------------------------------------------------------
/editor/src/assets/img/pencil.svg:
--------------------------------------------------------------------------------
1 |
2 |
48 |
--------------------------------------------------------------------------------
/editor/src/assets/img/play_icon.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/editor/src/assets/img/plus_icon.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/editor/src/assets/img/preview_mode_icon.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/editor/src/assets/img/redo_icon.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/editor/src/assets/img/right_step_item.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/studiopink/pink-editor/56aab3e077ae95a269449405595f0ba75b1d283d/editor/src/assets/img/right_step_item.jpg
--------------------------------------------------------------------------------
/editor/src/assets/img/search_icon.svg:
--------------------------------------------------------------------------------
1 |
12 |
--------------------------------------------------------------------------------
/editor/src/assets/img/select_arrow.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/editor/src/assets/img/shape_element_1.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/editor/src/assets/img/shape_element_2.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/editor/src/assets/img/shape_element_3.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/editor/src/assets/img/texture_element_1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/studiopink/pink-editor/56aab3e077ae95a269449405595f0ba75b1d283d/editor/src/assets/img/texture_element_1.jpg
--------------------------------------------------------------------------------
/editor/src/assets/img/texture_element_2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/studiopink/pink-editor/56aab3e077ae95a269449405595f0ba75b1d283d/editor/src/assets/img/texture_element_2.jpg
--------------------------------------------------------------------------------
/editor/src/assets/img/track_time_lines.svg:
--------------------------------------------------------------------------------
1 |
12 |
--------------------------------------------------------------------------------
/editor/src/assets/img/undo_icon.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/editor/src/assets/img/video_chat_img_1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/studiopink/pink-editor/56aab3e077ae95a269449405595f0ba75b1d283d/editor/src/assets/img/video_chat_img_1.jpg
--------------------------------------------------------------------------------
/editor/src/assets/img/video_chat_img_2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/studiopink/pink-editor/56aab3e077ae95a269449405595f0ba75b1d283d/editor/src/assets/img/video_chat_img_2.jpg
--------------------------------------------------------------------------------
/editor/src/assets/img/video_chat_img_3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/studiopink/pink-editor/56aab3e077ae95a269449405595f0ba75b1d283d/editor/src/assets/img/video_chat_img_3.jpg
--------------------------------------------------------------------------------
/editor/src/assets/img/video_chat_img_4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/studiopink/pink-editor/56aab3e077ae95a269449405595f0ba75b1d283d/editor/src/assets/img/video_chat_img_4.jpg
--------------------------------------------------------------------------------
/editor/src/assets/img/video_play.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/editor/src/assets/img/video_time_back.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/editor/src/assets/img/video_time_forward.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/editor/src/assets/img/volume_icon.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/editor/src/assets/img/your_logos_1.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/editor/src/assets/img/your_logos_3.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/editor/src/assets/img/your_videos_1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/studiopink/pink-editor/56aab3e077ae95a269449405595f0ba75b1d283d/editor/src/assets/img/your_videos_1.jpg
--------------------------------------------------------------------------------
/editor/src/assets/img/your_videos_2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/studiopink/pink-editor/56aab3e077ae95a269449405595f0ba75b1d283d/editor/src/assets/img/your_videos_2.jpg
--------------------------------------------------------------------------------
/editor/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/studiopink/pink-editor/56aab3e077ae95a269449405595f0ba75b1d283d/editor/src/assets/logo.png
--------------------------------------------------------------------------------
/editor/src/components/TimelineHorizontal.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
138 |
139 |
--------------------------------------------------------------------------------
/editor/src/components/base/AudioControls.vue:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/studiopink/pink-editor/56aab3e077ae95a269449405595f0ba75b1d283d/editor/src/components/base/AudioControls.vue
--------------------------------------------------------------------------------
/editor/src/components/base/AudioFile.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
24 |
25 |
26 |
30 |
31 |
{{ title }}
32 | {{ author }} [{{ getCurrentTime() }}/{{ parseDuration() }}]
35 |
36 |
37 |
38 |
107 |
108 |
129 |
--------------------------------------------------------------------------------
/editor/src/components/base/Bbutton.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
13 |
14 |
15 |
29 |
30 |
76 |
77 |
--------------------------------------------------------------------------------
/editor/src/components/base/Burger.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/editor/src/components/base/Ccontrol.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 | {{ getFormatName(format) }}
10 |
11 |
12 |
17 |
18 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/editor/src/components/base/ColorCirclesPicker.vue:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
54 |
55 |
--------------------------------------------------------------------------------
/editor/src/components/base/DMobToggle.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | Desktop
4 | Mobile
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/editor/src/components/base/GrayLabel.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
--------------------------------------------------------------------------------
/editor/src/components/base/Headline.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/editor/src/components/base/IconBtn.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
9 |
10 |
24 |
25 |
--------------------------------------------------------------------------------
/editor/src/components/base/Logo.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
14 |
--------------------------------------------------------------------------------
/editor/src/components/base/LogoFile.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
![]()
4 |
5 |
6 |
7 |
19 |
20 |
--------------------------------------------------------------------------------
/editor/src/components/base/Pbutton.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/editor/src/components/base/Preloader.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
19 |
20 |
39 |
--------------------------------------------------------------------------------
/editor/src/components/base/SelectField.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
22 |
23 |
--------------------------------------------------------------------------------
/editor/src/components/base/Step.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/editor/src/components/base/StepRight.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{title}}
4 |
5 |
![]()
6 |
7 |
8 |
9 |
10 |
16 |
17 |
--------------------------------------------------------------------------------
/editor/src/components/base/SubtitleItem.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{mainText}}
4 | {{subText}}
5 |
6 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/editor/src/components/base/Tcontrol.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/editor/src/components/base/TextElement.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ textObject.text }}
4 |
5 |
6 |
--------------------------------------------------------------------------------
/editor/src/components/base/TextareaField.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
15 |
16 |
--------------------------------------------------------------------------------
/editor/src/components/base/TextureElement.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
![]()
4 |
5 |
6 |
--------------------------------------------------------------------------------
/editor/src/components/base/Tracktime.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{stamp}}
5 |
6 |
7 |
--------------------------------------------------------------------------------
/editor/src/components/base/VideoFile.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
![]()
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/editor/src/components/base/VolumeControl.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
11 |
12 |
13 |
16 |
17 |
19 |
32 |
33 |
35 |
36 |
37 |
38 |
39 |
121 |
122 |
140 |
--------------------------------------------------------------------------------
/editor/src/components/base/index.js:
--------------------------------------------------------------------------------
1 | import Burger from "./Burger"
2 | import Logo from "./Logo"
3 | import Headline from "./Headline"
4 | import Tcontrol from "./Tcontrol"
5 | import Pbutton from "./Pbutton"
6 | import Ccontrol from "./Ccontrol"
7 | import VolumeControl from "./VolumeControl"
8 | import Tracktime from "./Tracktime"
9 | import Trackrows from "./Trackrows"
10 | import Step from "./Step"
11 | import Bbutton from "./Bbutton"
12 | import LogoFile from "./LogoFile"
13 | import AudioFile from "./AudioFile"
14 | import VideoFile from "./VideoFile"
15 | import GrayLabel from "./GrayLabel"
16 | import StepRight from "./StepRight"
17 | import DMobToggle from "./DMobToggle"
18 | import IconBtn from "./IconBtn"
19 | import SubtitleItem from "./SubtitleItem"
20 | import SelectField from "./SelectField"
21 | import TextareaField from "./TextareaField"
22 | import TextElement from "./TextElement"
23 | import TextureElement from "./TextureElement"
24 | import ColorCirclesPicker from "./ColorCirclesPicker"
25 | import VideoEmbed from "./VideoEmbed"
26 | import Preloader from "./Preloader"
27 |
28 | export default {
29 | Burger,
30 | Logo,
31 | Headline,
32 | Tcontrol,
33 | Pbutton,
34 | Ccontrol,
35 | VolumeControl,
36 | Tracktime,
37 | Trackrows,
38 | Step,
39 | Bbutton,
40 | LogoFile,
41 | AudioFile,
42 | VideoFile,
43 | GrayLabel,
44 | StepRight,
45 | DMobToggle,
46 | IconBtn,
47 | SubtitleItem,
48 | SelectField,
49 | TextareaField,
50 | TextElement,
51 | TextureElement,
52 | ColorCirclesPicker,
53 | VideoEmbed,
54 | Preloader
55 | }
--------------------------------------------------------------------------------
/editor/src/components/layouts/AudioControls.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/editor/src/components/layouts/Controlbar.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/editor/src/components/layouts/Header.vue:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/editor/src/components/layouts/Leftbars.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
9 |
10 |
13 |
14 |
17 |
18 |
19 |
20 |
21 |
22 |
25 |
26 |
29 |
30 |
33 |
34 |
38 |
39 |
42 |
43 |
44 |
45 |
57 |
58 |
--------------------------------------------------------------------------------
/editor/src/components/layouts/Rightbars.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/editor/src/components/layouts/Timeline.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
28 |
29 |
--------------------------------------------------------------------------------
/editor/src/components/layouts/Workspace.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/editor/src/components/layouts/Wrapper.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/editor/src/components/layouts/index.js:
--------------------------------------------------------------------------------
1 | import Header from "./Header"
2 |
3 | import Wrapper from "./Wrapper"
4 | import Leftbars from "./Leftbars"
5 | import Rightbars from "./Rightbars"
6 | import Workspace from "./Workspace"
7 |
8 | import AudioControls from "./AudioControls"
9 |
10 | import Controlbar from "./Controlbar"
11 | import Timeline from "./Timeline"
12 |
13 | export default {
14 | Header,
15 | Wrapper,
16 | Leftbars,
17 | Rightbars,
18 | Workspace,
19 | AudioControls,
20 | Controlbar,
21 | Timeline,
22 | }
--------------------------------------------------------------------------------
/editor/src/components/modals/AudioSetting.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
9 |
10 |
11 |
12 |
16 |
17 |
21 |
22 |
23 |
24 |
25 |
Volume
26 |
{{ parseInt((audio.volume || 1) * 100) }}
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
147 |
148 |
--------------------------------------------------------------------------------
/editor/src/components/modals/DragModal.vue:
--------------------------------------------------------------------------------
1 |
2 |
16 |
27 |
28 |
29 |
30 |
92 |
93 |
--------------------------------------------------------------------------------
/editor/src/components/modals/index.js:
--------------------------------------------------------------------------------
1 | import ExportModal from './ExportModal';
2 | import DragModal from './DragModal';
3 | import EditSubtitle from './subtitles/EditSubtitle';
4 | import AudioSetting from './AudioSetting';
5 |
6 | export default {
7 | ExportModal, DragModal, EditSubtitle, AudioSetting
8 | }
--------------------------------------------------------------------------------
/editor/src/config/index.js:
--------------------------------------------------------------------------------
1 | export default {
2 | API_URL: process.env.VUE_APP_API
3 | }
--------------------------------------------------------------------------------
/editor/src/libs/keyboard.js:
--------------------------------------------------------------------------------
1 | import store from '../store';
2 | import editor from './editor';
3 | import history from './history';
4 |
5 | export default new class {
6 | constructor() {
7 | this.selectedContextObjectType = null;
8 | this.selectedVideo = null;
9 | }
10 |
11 | // WATCHS
12 | watchSelectedContextObjectType(data) {
13 | this.selectedContextObjectType = data;
14 | }
15 |
16 | watchSelectedVideo(active) {
17 | this.selectedVideo = active;
18 | }
19 |
20 | //
21 |
22 | bindKeyUp(e) {
23 | store.state.keys = store.state.keys.filter(key => key != e.code);
24 | }
25 |
26 | bindKeyDown(e) {
27 | //if(!store.state.keys.find(key => key == e.code)) {
28 | store.state.keys.push(e.code);
29 |
30 | switch(e.code) {
31 | case 'Delete': {
32 | let isLock = false;
33 | if(editor && editor.canvas && editor.canvas.getActiveObject()) {
34 | editor.removeActiveObjects();
35 | isLock = true;
36 | } else if(this.selectedVideo && confirm('Delete selected video?')) {
37 | isLock = true;
38 | store.dispatch('deleteVideoById', this.selectedVideo);
39 | } else if(store.state.editor.audio && store.state.editor.audio._id) {
40 | store.state.videoPlayer.timelines = store.state.videoPlayer.timelines
41 | .filter(tl => tl._id != store.state.editor.audio._id);
42 | store.state.editor.audio = null;
43 | history.add({ type: 'videoPlayer' });
44 | } else if(store.state.editor.subtitles && store.state.editor.subtitles._id) {
45 | store.state.videoPlayer.timelines = store.state.videoPlayer.timelines
46 | .filter(tl => tl._id != store.state.editor.subtitles._id);
47 | store.state.editor.subtitles = null;
48 | history.add({ type: 'videoPlayer' });
49 | }
50 |
51 | if(isLock) {
52 | e.preventDefault();
53 | e.stopPropagation();
54 | }
55 |
56 | break;
57 | }
58 |
59 | case 'Escape': {
60 | const timelines = store.state.videoPlayer.timelines;
61 | if(timelines && timelines.length) {
62 | this.discardActiveTimelinesObjects();
63 | editor.discardActiveObject();
64 |
65 | store.state.editor.audio = null;
66 |
67 | e.preventDefault();
68 | e.stopPropagation();
69 | }
70 |
71 | break;
72 | }
73 | }
74 | //}
75 |
76 | //console.log(JSON.parse(JSON.stringify(store.state.keys)));
77 | }
78 |
79 | discardActiveTimelinesObjects() {
80 | const timelines = store.state.videoPlayer.timelines;
81 |
82 | if(timelines.find(tl => tl.active)) {
83 | store.state.videoPlayer.timelines = store.state.videoPlayer.timelines.map(tl => {
84 | tl.active = false;
85 |
86 | return tl;
87 | });
88 | }
89 | }
90 |
91 | mouseDown(ev) {
92 | if(window.innerHeight - ev.pageY < 10) return;
93 | if(ev.button > 0) return;
94 |
95 | if(this.selectedContextObjectType && this.selectedContextObjectType == 'logo'
96 | && !ev.target.classList.contains('upper-canvas') && !$(ev.target).closest('.logo_properties_sidebar')[0]) {
97 | store.state.selectedContextObject = false;
98 | this.discardActiveTimelinesObjects();
99 | editor.discardActiveObject();
100 |
101 | console.log(1);
102 | }
103 |
104 | if(this.selectedContextObjectType && this.selectedContextObjectType == 'text'
105 | && !ev.target.classList.contains('upper-canvas') && !$(ev.target).closest('.text_properties_sidebar')[0]) {
106 | store.state.selectedContextObject = false;
107 | this.discardActiveTimelinesObjects();
108 | editor.discardActiveObject();
109 | console.log(2);
110 | }
111 |
112 | if(store.state.editor.audio && store.state.editor.audio._id && !$(ev.target).closest('.audio_properties_menu')[0]) {
113 | store.state.editor.audio = false;
114 | this.discardActiveTimelinesObjects();
115 | console.log(3);
116 | }
117 |
118 | if(store.state.selectedContextObject
119 | && store.state.selectedContextObject.typeItem == 'video' && !$(ev.target).closest('.video_properties_sidebar')[0]) {
120 | store.state.selectedContextObject = false;
121 | this.discardActiveTimelinesObjects();
122 | console.log(4);
123 | }
124 |
125 | if(store.state.editor.subtitles && store.state.editor.subtitles._id && store.state.editor.subtitles.data
126 | && !$(ev.target).closest('.subtitles_properties_sidebar')[0]) {
127 | store.state.editor.subtitles = {};
128 | this.discardActiveTimelinesObjects();
129 |
130 | console.log(5);
131 | }
132 |
133 | store.state.events.clickElement = ev.target;
134 | }
135 | }
--------------------------------------------------------------------------------
/editor/src/libs/recorder.js:
--------------------------------------------------------------------------------
1 | import API from '../providers/api';
2 |
3 | export default class CustomRecorder {
4 | constructor() {
5 | this.URL = window.URL || window.webkitURL;
6 | this.AudioContext = window.AudioContext || window.webkitAudioContext;
7 | }
8 |
9 | startRecording(recordingVolumecallback = () => {}) {
10 | return new Promise((resolve, reject) => {
11 | const constraints = { audio: true, video: false };
12 | navigator.mediaDevices.getUserMedia(constraints).then(stream => {
13 | this.audioContext = new AudioContext();
14 | this.mediaStreamSource = this.audioContext.createMediaStreamSource(stream);
15 |
16 | this.meter = createAudioMeter(this.audioContext, recordingVolumecallback);
17 | this.mediaStreamSource.connect(this.meter);
18 |
19 | this.gumStream = stream;
20 | this.input = this.audioContext.createMediaStreamSource(stream);
21 | this.rec = new Recorder(this.input, { numChannels: 1 });
22 | this.rec.record();
23 |
24 | resolve(this);
25 | }).catch(reject);
26 | });
27 | }
28 |
29 | pauseRecording() {
30 | if (this.rec.recording) {
31 | this.rec.stop();
32 | } else {
33 | this.rec.record();
34 | }
35 | }
36 |
37 | stopRecording() {
38 | return new Promise(resolve => {
39 | this.rec.stop();
40 | this.gumStream.getAudioTracks()[0].stop();
41 | this.meter.shutdown();
42 | const time = this.rec.context.currentTime;
43 | this.rec.exportWAV(async blob => {
44 | blob.name = `Record_${new Date().toISOString()}.wav`;
45 | // const data = await API.uploadFile(blob);
46 |
47 | resolve({ file: blob, duration: time });
48 | });
49 | });
50 | }
51 |
52 | createDownloadLink(blob) {
53 | console.log(blob);
54 | return;
55 | const url = this.URL.createObjectURL(blob);
56 |
57 | const a = document.createElement('a');
58 | a.href = url;
59 | a.download = new Date().toISOString() + '.wav';
60 | a.click();
61 | }
62 | }
--------------------------------------------------------------------------------
/editor/src/libs/volumeMeter.js:
--------------------------------------------------------------------------------
1 | export default {
2 | createAudioMeter(audioContext,clipLevel,averaging,clipLag) {
3 | var processor = audioContext.createScriptProcessor(512);
4 | processor.onaudioprocess = this.volumeAudioProcess;
5 | processor.clipping = false;
6 | processor.lastClip = 0;
7 | processor.volume = 0;
8 | processor.clipLevel = clipLevel || 0.98;
9 | processor.averaging = averaging || 0.95;
10 | processor.clipLag = clipLag || 750;
11 |
12 | // this will have no effect, since we don't copy the input to the output,
13 | // but works around a current Chrome bug.
14 | processor.connect(audioContext.destination);
15 |
16 | processor.checkClipping =
17 | function(){
18 | if (!this.clipping)
19 | return false;
20 | if ((this.lastClip + this.clipLag) < window.performance.now())
21 | this.clipping = false;
22 | return this.clipping;
23 | };
24 |
25 | processor.shutdown =
26 | function(){
27 | this.disconnect();
28 | this.onaudioprocess = null;
29 | };
30 |
31 | return processor;
32 | },
33 |
34 | volumeAudioProcess(event) {
35 | var buf = event.inputBuffer.getChannelData(0);
36 | var bufLength = buf.length;
37 | var sum = 0;
38 | var x;
39 |
40 | // Do a root-mean-square on the samples: sum up the squares...
41 | for (var i=0; i=this.clipLevel) {
44 | this.clipping = true;
45 | this.lastClip = window.performance.now();
46 | }
47 | sum += x * x;
48 | }
49 |
50 | // ... then take the square root of the sum.
51 | var rms = Math.sqrt(sum / bufLength);
52 |
53 | // Now smooth this out with the averaging factor applied
54 | // to the previous sample - take the max here because we
55 | // want "fast attack, slow release."
56 | this.volume = Math.max(rms, this.volume*this.averaging);
57 | }
58 | }
--------------------------------------------------------------------------------
/editor/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import App from './App.vue';
3 | import router from './router';
4 | import store from './store';
5 | import api from './providers/api/';
6 | import VueDragResize from 'vue-drag-resize';
7 | import VueSocketIO from 'vue-socket.io';
8 | import vmodal from 'vue-js-modal';
9 | import 'vue-loaders/dist/vue-loaders.css';
10 | import VueLoadersBallBeat from 'vue-loaders/dist/loaders/ball-beat';
11 | import VueLoadersLineScale from 'vue-loaders/dist/loaders/line-scale';
12 | import 'vue-simple-context-menu/dist/vue-simple-context-menu.css'
13 | import VueSimpleContextMenu from 'vue-simple-context-menu';
14 | import VueSweetalert2 from 'vue-sweetalert2';
15 | import 'sweetalert2/dist/sweetalert2.min.css';
16 | import VueDragscroll from 'vue-dragscroll';
17 | import 'vue-range-component/dist/vue-range-slider.css';
18 | import VueRangeSlider from 'vue-range-component';
19 |
20 | Vue.component('vue-simple-context-menu', VueSimpleContextMenu);
21 | Vue.component('vue-drag-resize', VueDragResize);
22 | Vue.component('vue-range-slider', VueRangeSlider);
23 | Vue.config.productionTip = false;
24 |
25 | Vue.directive('click-outside', {
26 | bind: function (el, binding, vnode) {
27 | el.eventOnClick = function (event) {
28 | if (!(el == event.target || el.contains(event.target))) {
29 | vnode.context[binding.expression](event);
30 | }
31 | };
32 | document.addEventListener('click', el.eventOnClick);
33 | },
34 | unbind: function (el) {
35 | document.removeEventListener('click', el.eventOnClick);
36 | },
37 | });
38 |
39 | Vue.use(VueSweetalert2);
40 | Vue.use(VueDragscroll);
41 | Vue.use(new VueSocketIO({ debug: false, connection: process.env.VUE_APP_SOCKET_API }));
42 | Vue.component('vue-loaders-ball-beat', VueLoadersBallBeat.component);
43 | Vue.component('vue-loaders-line-scale-party', VueLoadersLineScale.component);
44 | Vue.use(vmodal);
45 |
46 | Vue.prototype.$api = api;
47 | window.store = store;
48 |
49 | window.app = new Vue({ router, store, render: h => h(App) }).$mount('#app');
--------------------------------------------------------------------------------
/editor/src/plugins/vue-drag-resize/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "env"
5 | ]
6 | ]
7 | }
--------------------------------------------------------------------------------
/editor/src/plugins/vue-drag-resize/.npmignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules/
3 | dist/demo.js
4 | static/
5 | npm-debug.log
--------------------------------------------------------------------------------
/editor/src/plugins/vue-drag-resize/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Maurizio Bonani
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/editor/src/plugins/vue-drag-resize/config/dev.conf.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | entry: {
3 | "app": './src/demo/app.js'
4 | },
5 | build: {
6 | env: '"development"',
7 | sourceMap: true,
8 | uglify: false,
9 | cssSourceMap: false,
10 | gzip: false
11 | }
12 | };
--------------------------------------------------------------------------------
/editor/src/plugins/vue-drag-resize/config/index.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | var envConfig = process.env.NODE_ENV === 'develop' ?
3 | require('./dev.conf') : process.env.NODE_ENV === 'testing' ?
4 | require('./test.conf') : require('./prod.conf');
5 |
6 | var deepExtend = require('deep-extend');
7 |
8 | module.exports = deepExtend({
9 | build: {
10 | distPath: path.resolve(__dirname, '../dist'),
11 | bundleAnalyzerReport: false
12 | },
13 | server: {
14 | assetsPath: path.resolve(__dirname, '../static'),
15 | port: 8081,
16 | host: '0.0.0.0',
17 | autoOpenBrowser: true,
18 | }
19 | }, envConfig);
--------------------------------------------------------------------------------
/editor/src/plugins/vue-drag-resize/config/prod.conf.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | entry: {
3 | "index": './src',
4 | "demo": './src/demo/app'
5 | },
6 | build: {
7 | env: '"production"',
8 | sourceMap: false,
9 | uglify: true,
10 | cssSourceMap: false,
11 | gzip: false
12 | }
13 | };
--------------------------------------------------------------------------------
/editor/src/plugins/vue-drag-resize/config/test.conf.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | entry: {
3 | "app": './src/app.js'
4 | },
5 | build: {
6 | env: '"testing"',
7 | sourceMap: true,
8 | uglify: true,
9 | cssSourceMap: false,
10 | gzip: false
11 | }
12 | };
--------------------------------------------------------------------------------
/editor/src/plugins/vue-drag-resize/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "_args": [
3 | [
4 | "vue-drag-resize@1.4.2",
5 | "/home/koctrx/GIT/Work/shotstack/editor"
6 | ]
7 | ],
8 | "_from": "vue-drag-resize@1.4.2",
9 | "_id": "vue-drag-resize@1.4.2",
10 | "_inBundle": false,
11 | "_integrity": "sha512-rPd8JkkueBegCX8niJg3aWwQBfys9upaxLDyH6+1Gkz6pUzldhVqpF0hragppo68q6NWqE9fbvypWVI43AFyqw==",
12 | "_location": "/vue-drag-resize",
13 | "_phantomChildren": {},
14 | "_requested": {
15 | "type": "version",
16 | "registry": true,
17 | "raw": "vue-drag-resize@1.4.2",
18 | "name": "vue-drag-resize",
19 | "escapedName": "vue-drag-resize",
20 | "rawSpec": "1.4.2",
21 | "saveSpec": null,
22 | "fetchSpec": "1.4.2"
23 | },
24 | "_requiredBy": [
25 | "/"
26 | ],
27 | "_resolved": "https://registry.npmjs.org/vue-drag-resize/-/vue-drag-resize-1.4.2.tgz",
28 | "_spec": "1.4.2",
29 | "_where": "/home/koctrx/GIT/Work/shotstack/editor",
30 | "author": {
31 | "name": "Kirill Murashov",
32 | "email": "me@kirillmurashov.com"
33 | },
34 | "browserslist": [
35 | "> 1%",
36 | "last 2 versions",
37 | "not ie <= 9"
38 | ],
39 | "bugs": {
40 | "url": "https://github.com/kirillmurashov/vue-drag-resize/issues"
41 | },
42 | "description": "Vue Component for resize and drag elements",
43 | "devDependencies": {
44 | "acorn-dynamic-import": "^3.0.0",
45 | "autoprefixer": "^6.7.2",
46 | "babel": "^6.23.0",
47 | "babel-cli": "^6.26.0",
48 | "babel-eslint": "^7.2.3",
49 | "babel-loader": "^7.0.0",
50 | "babel-plugin-syntax-async-functions": "^6.13.0",
51 | "babel-plugin-syntax-dynamic-import": "^6.18.0",
52 | "babel-plugin-transform-es2015-for-of": "^6.23.0",
53 | "babel-plugin-transform-es2015-function-name": "^6.24.1",
54 | "babel-plugin-transform-object-assign": "^6.22.0",
55 | "babel-plugin-transform-object-rest-spread": "^6.26.0",
56 | "babel-plugin-transform-regenerator": "^6.26.0",
57 | "babel-plugin-transform-runtime": "^6.22.0",
58 | "babel-polyfill": "^6.23.0",
59 | "babel-preset-env": "^1.6.1",
60 | "babel-preset-es2015": "^6.24.1",
61 | "babel-preset-stage-2": "^6.24.1",
62 | "babel-register": "^6.24.1",
63 | "browserify": "^14.4.0",
64 | "chai": "^3.5.0",
65 | "chalk": "^1.1.3",
66 | "compression-webpack-plugin": "^1.0.0",
67 | "connect-history-api-fallback": "^1.3.0",
68 | "copy-webpack-plugin": "^4.0.1",
69 | "cross-env": "^5.0.5",
70 | "css-color-function": "^1.3.0",
71 | "css-loader": "^0.28.7",
72 | "cssnano": "^3.10.0",
73 | "deep-extend": "^0.5.0",
74 | "es6-promise": "^4.1.1",
75 | "eslint": "^4.18.2",
76 | "eslint-config-airbnb-base": "^11.1.3",
77 | "eslint-friendly-formatter": "^2.0.7",
78 | "eslint-import-resolver-webpack": "^0.8.1",
79 | "eslint-loader": "^1.7.1",
80 | "eslint-plugin-html": "^2.0.0",
81 | "eslint-plugin-import": "^2.2.0",
82 | "eventsource-polyfill": "^0.9.6",
83 | "express": "^4.14.1",
84 | "extract-text-webpack-plugin": "^3.0.2",
85 | "file-loader": "^1.1.11",
86 | "file-type": "^6.1.0",
87 | "friendly-errors-webpack-plugin": "^1.1.3",
88 | "function-bind": "^1.1.0",
89 | "gulp": "^3.9.1",
90 | "gulp-browserify": "^0.5.1",
91 | "gulp-bump": "^2.4.0",
92 | "gulp-clean": "^0.3.2",
93 | "gulp-concat": "^2.6.1",
94 | "gulp-concat-util": "^0.5.5",
95 | "gulp-copy": "0.0.2",
96 | "gulp-delete-file": "^1.0.2",
97 | "gulp-dotify": "^0.1.2",
98 | "gulp-file": "^0.3.0",
99 | "gulp-header": "^1.8.9",
100 | "gulp-htmlmin": "^3.0.0",
101 | "gulp-iconfont": "^9.0.1",
102 | "gulp-iconfont-css": "^2.1.0",
103 | "gulp-if": "^2.0.1",
104 | "gulp-json-css": "^0.2.3",
105 | "gulp-json-editor": "^2.2.1",
106 | "gulp-jst2": "^0.1.4",
107 | "gulp-load-plugins": "^1.3.0",
108 | "gulp-logwarn": "0.0.9",
109 | "gulp-match": "^1.0.3",
110 | "gulp-merge": "^0.1.1",
111 | "gulp-mocha": "^3.0.1",
112 | "gulp-multi-dest": "0.0.4",
113 | "gulp-postcss": "^7.0.0",
114 | "gulp-rename": "^1.2.2",
115 | "gulp-replace": "^0.5.4",
116 | "gulp-rtlcss": "^1.0.0",
117 | "gulp-run-sequence": "^0.3.2",
118 | "gulp-sourcemaps": "^2.6.1",
119 | "gulp-uglify": "^2.0.0",
120 | "html-webpack-plugin": "^2.28.0",
121 | "http-proxy-middleware": "^0.17.3",
122 | "jade": "^1.11.0",
123 | "json-loader": "^0.5.4",
124 | "karma": "^1.4.1",
125 | "karma-chai": "^0.1.0",
126 | "karma-chai-dom": "^1.1.0",
127 | "karma-chrome-launcher": "^2.2.0",
128 | "karma-coverage": "^1.1.1",
129 | "karma-firefox-launcher": "^1.1.0",
130 | "karma-mocha": "^1.3.0",
131 | "karma-phantomjs-launcher": "^1.0.2",
132 | "karma-phantomjs-shim": "^1.4.0",
133 | "karma-safari-launcher": "^1.0.0",
134 | "karma-sinon-chai": "^1.3.1",
135 | "karma-sourcemap-loader": "^0.3.7",
136 | "karma-spec-reporter": "0.0.31",
137 | "karma-webpack": "^2.0.2",
138 | "less": "^2.7.2",
139 | "less-loader": "^4.0.3",
140 | "mini-css-extract-plugin": "^0.4.0",
141 | "minimist": "^1.2.0",
142 | "mocha": "^3.0.2",
143 | "node-gyp": "3.4.0",
144 | "opn": "^4.0.2",
145 | "optimize-css-assets-webpack-plugin": "^1.3.1",
146 | "ora": "^1.2.0",
147 | "path": "^0.12.7",
148 | "phantomjs-prebuilt": "^2.1.14",
149 | "postcss-color-function": "^4.0.0",
150 | "postcss-cssnext": "^3.0.2",
151 | "postcss-import": "^10.0.0",
152 | "postcss-loader": "^2.0.6",
153 | "require-dir": "^0.3.0",
154 | "rimraf": "^2.6.0",
155 | "run-sequence": "^2.1.0",
156 | "script-loader": "^0.7.0",
157 | "semver": "^5.3.0",
158 | "sinon": "^2.1.0",
159 | "sinon-chai": "^2.8.0",
160 | "streamqueue": "^1.1.1",
161 | "style-loader": "^0.18.2",
162 | "stylus": "^0.54.5",
163 | "stylus-loader": "^3.0.1",
164 | "sugarss": "^1.0.0",
165 | "uglifyjs-webpack-plugin": "^0.4.6",
166 | "underscore": "^1.8.3",
167 | "url": "^0.11.0",
168 | "url-loader": "^0.5.9",
169 | "vue": "^2.5.16",
170 | "vue-loader": "^15.0.3",
171 | "vue-style-loader": "^3.0.0",
172 | "vue-svgicon": "^2.1.3",
173 | "vue-template-compiler": "^2.5.16",
174 | "vuex": "^3.0.1",
175 | "watchjs": "0.0.0",
176 | "watchpack": "^1.5.0",
177 | "webpack": "^4.6.0",
178 | "webpack-bundle-analyzer": "^3.3.2",
179 | "webpack-cli": "^2.0.14",
180 | "webpack-dev-middleware": "^3.1.2",
181 | "webpack-dev-server": "^3.1.3",
182 | "webpack-hot-middleware": "^2.18.0",
183 | "webpack-merge": "^4.1.0",
184 | "yargs": "^5.0.0"
185 | },
186 | "engines": {
187 | "node": ">= 4.0.0",
188 | "npm": ">= 3.0.0"
189 | },
190 | "homepage": "https://github.com/kirillmurashov/vue-drag-resize.git",
191 | "license": "MIT",
192 | "main": "dist/index.js",
193 | "name": "vue-drag-resize",
194 | "repository": {
195 | "type": "git",
196 | "url": "git+https://github.com/kirillmurashov/vue-drag-resize.git"
197 | },
198 | "scripts": {
199 | "build": "webpack",
200 | "start": "NODE_ENV=develop webpack-dev-server -d",
201 | "svg": "vsvg -s ./src/demo/svg -t ./src/demo/icons"
202 | },
203 | "version": "1.4.2"
204 | }
205 |
--------------------------------------------------------------------------------
/editor/src/plugins/vue-drag-resize/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | 'postcss-import': {},
4 | 'postcss-cssnext': {},
5 | 'cssnano': {autoprefixer: false}
6 | }
7 | };
--------------------------------------------------------------------------------
/editor/src/plugins/vue-drag-resize/src/components/vue-drag-resize.css:
--------------------------------------------------------------------------------
1 | .vdr {
2 | position: absolute;
3 | box-sizing: border-box;
4 | }
5 | .vdr.active:before{
6 | content: '';
7 | width: 100%;
8 | height: 100%;
9 | position: absolute;
10 | top: 0;
11 | left: 0;
12 | box-sizing: border-box;
13 | outline: 1px dashed #d6d6d6;
14 | }
15 | .vdr-stick {
16 | box-sizing: border-box;
17 | position: absolute;
18 | font-size: 1px;
19 | background: #ffffff;
20 | border: 1px solid #6c6c6c;
21 | box-shadow: 0 0 2px #bbb;
22 | }
23 | .inactive .vdr-stick {
24 | display: none;
25 | }
26 | .vdr-stick-tl, .vdr-stick-br {
27 | cursor: nwse-resize;
28 | }
29 | .vdr-stick-tm, .vdr-stick-bm {
30 | left: 50%;
31 | cursor: ns-resize;
32 | }
33 | .vdr-stick-tr, .vdr-stick-bl {
34 | cursor: nesw-resize;
35 | }
36 | .vdr-stick-ml, .vdr-stick-mr {
37 | top: 50%;
38 | cursor: ew-resize;
39 | }
40 | .vdr-stick.not-resizable{
41 | display: none;
42 | }
--------------------------------------------------------------------------------
/editor/src/plugins/vue-drag-resize/src/components/vue-drag-resize.html:
--------------------------------------------------------------------------------
1 |
17 |
--------------------------------------------------------------------------------
/editor/src/plugins/vue-drag-resize/src/components/vue-drag-resize.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/editor/src/plugins/vue-drag-resize/src/demo/app.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import app from './app.vue';
3 | import store from './store';
4 | import * as svgicon from 'vue-svgicon';
5 |
6 | Vue.use(svgicon, {
7 | tagName: 'svgicon'
8 | });
9 |
10 | new Vue({
11 | el: '#app',
12 | store,
13 | template: '',
14 | components: { app }
15 | });
--------------------------------------------------------------------------------
/editor/src/plugins/vue-drag-resize/src/demo/app.vue:
--------------------------------------------------------------------------------
1 |
2 |
34 |
35 |
36 |
73 |
74 |
--------------------------------------------------------------------------------
/editor/src/plugins/vue-drag-resize/src/demo/components/toolbar/toolbar.css:
--------------------------------------------------------------------------------
1 | :root{
2 | --toolbar-width: 220px;
3 | }
4 | .toolbar{
5 | position: absolute;
6 | right: 0;
7 | top: 0;
8 | width: var(--toolbar-width);
9 | padding: 15px 15px 0 15px;
10 | box-shadow: 0 0 2px #AAA;
11 | box-sizing: border-box;
12 | background-color: white;
13 | margin: 30px 30px 30px 0;
14 | }
15 | .toolbar-wh-row{
16 |
17 | margin-bottom: 20px;
18 | }
19 | .toolbar-row-title{
20 | width: var(--toolbar-width);
21 | font-size: 14px;
22 | font-family: 'Lato', sans-serif;
23 | font-weight: 500;
24 | margin: 0 0 3px 0;
25 | color: #1A173B;
26 | }
27 | .toolbar-position-inp, .toolbar-size-inp{
28 | width: 90px;
29 | font-size: 11px;
30 | color: #BBB;
31 | font-weight: 300;
32 | display: inline-block;
33 | position: relative;
34 | }
35 |
36 | .toolbar-size-inp input,.toolbar-position-inp input{
37 | width: 70px;
38 | display: inline-block;
39 | border: 1px solid #bfbfca;
40 | margin-top: 2px;
41 | height: 16px;
42 | }
43 |
44 | .toolbar-size-inp input[disabled],.toolbar-position-inp input[disabled]{
45 | border: 1px solid #dcdce7;
46 | color: #AAAAAA;
47 | }
48 |
49 | .position-lock-icon, .size-lock-icon{
50 | position: absolute;
51 | bottom: 3px;
52 | right: 17px;
53 | cursor: pointer;
54 | }
55 | .size-lock-icon{
56 | bottom: 2px;
57 | right: -3px;
58 | }
59 |
60 | .toolbar-check-inp{
61 | color: #445477;
62 | font-size: 13px;
63 | width: 180px;
64 | display: inline-block;
65 | margin: 2px 0;
66 | }
67 | .toolbar-row-title+label{
68 | margin-top: 5px;
69 | }
70 | .toolbar-check-inp input{
71 | border: 1px solid #bfbfca;
72 | }
73 | .to-top-icon, .to-bottom-icon{
74 | margin: 10px 30px;
75 | cursor: pointer;
76 | }
77 |
--------------------------------------------------------------------------------
/editor/src/plugins/vue-drag-resize/src/demo/components/toolbar/toolbar.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/editor/src/plugins/vue-drag-resize/src/demo/components/toolbar/toolbar.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/editor/src/plugins/vue-drag-resize/src/demo/icons/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | require('./lock')
3 | require('./toBottom')
4 | require('./toTop')
5 |
--------------------------------------------------------------------------------
/editor/src/plugins/vue-drag-resize/src/demo/icons/lock.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | var icon = require('vue-svgicon')
3 | icon.register({
4 | 'lock': {
5 | width: 100,
6 | height: 100,
7 | viewBox: '0 0 100 100',
8 | data: ''
9 | }
10 | })
11 |
--------------------------------------------------------------------------------
/editor/src/plugins/vue-drag-resize/src/demo/icons/toBottom.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | var icon = require('vue-svgicon')
3 | icon.register({
4 | 'toBottom': {
5 | width: 100,
6 | height: 100,
7 | viewBox: '0 0 100 100',
8 | data: ''
9 | }
10 | })
11 |
--------------------------------------------------------------------------------
/editor/src/plugins/vue-drag-resize/src/demo/icons/toTop.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | var icon = require('vue-svgicon')
3 | icon.register({
4 | 'toTop': {
5 | width: 100,
6 | height: 100,
7 | viewBox: '0 0 100 100',
8 | data: ''
9 | }
10 | })
11 |
--------------------------------------------------------------------------------
/editor/src/plugins/vue-drag-resize/src/demo/store/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import Vuex from 'vuex';
3 | import rect from './modules/rect'
4 |
5 | Vue.use(Vuex);
6 |
7 | const debug = process.env.NODE_ENV !== 'production';
8 |
9 | export default new Vuex.Store({
10 | /**
11 | * Assign the modules to the store
12 | */
13 | modules: {'rect': rect },
14 |
15 | /**
16 | * If strict mode should be enabled
17 | */
18 | strict: debug
19 | });
20 |
--------------------------------------------------------------------------------
/editor/src/plugins/vue-drag-resize/src/demo/store/modules/rect/actions.js:
--------------------------------------------------------------------------------
1 | import types, {CHANGE_ZINDEX} from './mutation-types';
2 |
3 | export default {
4 | setActive({commit, state}, {id}) {
5 | for (let i = 0, l = state.rects.length; i < l; i++) {
6 | if (i === id) {
7 | commit(types.ENABLE_ACTIVE, i);
8 | continue;
9 | }
10 |
11 | commit(types.DISABLE_ACTIVE, i);
12 | }
13 | },
14 | unsetActive({commit}, {id}) {
15 | commit(types.DISABLE_ACTIVE, id);
16 | },
17 |
18 | toggleDraggable({commit, state}, {id}) {
19 | if (!state.rects[id].draggable) {
20 | commit(types.ENABLE_DRAGGABLE, id);
21 | } else {
22 | commit(types.DISABLE_DRAGGABLE, id);
23 | }
24 | },
25 |
26 | toggleResizable({commit, state}, {id}) {
27 | if (!state.rects[id].resizable) {
28 | commit(types.ENABLE_RESIZABLE, id);
29 | } else {
30 | commit(types.DISABLE_RESIZABLE, id);
31 | }
32 | },
33 |
34 | toggleParentLimitation({commit, state}, {id}) {
35 | if (!state.rects[id].parentLim) {
36 | commit(types.ENABLE_PARENT_LIMITATION, id);
37 | } else {
38 | commit(types.DISABLE_PARENT_LIMITATION, id);
39 | }
40 | },
41 |
42 | toggleSnapToGrid({commit, state}, {id}) {
43 | if (!state.rects[id].snapToGrid) {
44 | commit(types.ENABLE_SNAP_TO_GRID, id);
45 | } else {
46 | commit(types.DISABLE_SNAP_TO_GRID, id);
47 | }
48 | },
49 |
50 | setAspect({commit}, {id}) {
51 | commit(types.ENABLE_ASPECT, id);
52 | },
53 | unsetAspect({commit}, {id}) {
54 | commit(types.DISABLE_ASPECT, id);
55 | },
56 |
57 | setWidth({commit}, {id, width}) {
58 | commit(types.CHANGE_WIDTH, {id, width});
59 | },
60 |
61 | setHeight({commit}, {id, height}) {
62 | commit(types.CHANGE_HEIGHT, {id, height});
63 | },
64 |
65 | setTop({commit}, {id, top}) {
66 | commit(types.CHANGE_TOP, {id, top});
67 | },
68 |
69 | setLeft({commit}, {id, left}) {
70 | commit(types.CHANGE_LEFT, {id, left});
71 | },
72 |
73 | changeXLock({commit, state}, {id}) {
74 | switch (state.rects[id].axis) {
75 | case 'both':
76 | commit(types.ENABLE_Y_AXIS, id);
77 | break;
78 | case 'x':
79 | commit(types.ENABLE_NONE_AXIS, id);
80 | break;
81 | case 'y':
82 | commit(types.ENABLE_BOTH_AXIS, id);
83 | break;
84 | case 'none':
85 | commit(types.ENABLE_X_AXIS, id);
86 | break;
87 | }
88 | },
89 |
90 | changeYLock({commit, state}, {id}) {
91 | switch (state.rects[id].axis) {
92 | case 'both':
93 | commit(types.ENABLE_X_AXIS, id);
94 | break;
95 | case 'x':
96 | commit(types.ENABLE_BOTH_AXIS, id);
97 | break;
98 | case 'y':
99 | commit(types.ENABLE_NONE_AXIS, id);
100 | break;
101 | case 'none':
102 | commit(types.ENABLE_Y_AXIS, id);
103 | break;
104 | }
105 | },
106 |
107 | changeZToBottom({commit, state}, {id}) {
108 | if (state.rects[id].zIndex === 1) {
109 | return
110 | }
111 |
112 | commit(types.CHANGE_ZINDEX, {id, zIndex: 1});
113 |
114 | for (let i = 0, l = state.rects.length; i < l; i++) {
115 | if (i !== id) {
116 | if(state.rects[i].zIndex === state.rects.length){
117 | continue
118 | }
119 | commit(types.CHANGE_ZINDEX, {id: i, zIndex: state.rects[i].zIndex + 1});
120 | }
121 | }
122 | },
123 |
124 | changeZToTop({commit, state}, {id}) {
125 | if (state.rects[id].zIndex === state.rects.length) {
126 | return
127 | }
128 |
129 | commit(types.CHANGE_ZINDEX, {id, zIndex: state.rects.length});
130 |
131 | for (let i = 0, l = state.rects.length; i < l; i++) {
132 | if (i !== id) {
133 | if(state.rects[i].zIndex === 1){
134 | continue
135 | }
136 | commit(types.CHANGE_ZINDEX, {id: i, zIndex: state.rects[i].zIndex - 1});
137 | }
138 | }
139 | },
140 |
141 | setMinWidth({commit}, {id, width}) {
142 | commit(types.CHANGE_MINW, {id, minw:width});
143 | },
144 |
145 | setMinHeight({commit}, {id, height}) {
146 | commit(types.CHANGE_MINH, {id, minh:height});
147 | }
148 | };
149 |
--------------------------------------------------------------------------------
/editor/src/plugins/vue-drag-resize/src/demo/store/modules/rect/getters.js:
--------------------------------------------------------------------------------
1 | export default {
2 | getActive: state => {
3 | for (let i = 0, l = state.rects.length; i < l; i++) {
4 | let rect = state.rects[i];
5 |
6 | if (rect.active) {
7 | return i;
8 | }
9 | }
10 | return null;
11 | }
12 | }
--------------------------------------------------------------------------------
/editor/src/plugins/vue-drag-resize/src/demo/store/modules/rect/index.js:
--------------------------------------------------------------------------------
1 | import actions from './actions';
2 | import getters from './getters';
3 | import mutations from './mutations';
4 | import state from './state';
5 |
6 | export default {
7 | namespaced: true,
8 | actions,
9 | getters,
10 | mutations,
11 | state
12 | };
13 |
--------------------------------------------------------------------------------
/editor/src/plugins/vue-drag-resize/src/demo/store/modules/rect/mutation-types.js:
--------------------------------------------------------------------------------
1 | export const ENABLE_ACTIVE = 'ENABLE_ACTIVE';
2 | export const DISABLE_ACTIVE = 'DISABLE_ACTIVE';
3 |
4 | export const ENABLE_DRAGGABLE = 'ENABLE_DRAGGABLE';
5 | export const DISABLE_DRAGGABLE = 'DISABLE_DRAGGABLE';
6 |
7 | export const ENABLE_RESIZABLE = 'ENABLE_RESIZABLE';
8 | export const DISABLE_RESIZABLE = 'DISABLE_RESIZABLE';
9 |
10 | export const ENABLE_PARENT_LIMITATION = 'ENABLE_PARENT_LIMITATION';
11 | export const DISABLE_PARENT_LIMITATION = 'DISABLE_PARENT_LIMITATION';
12 |
13 | export const ENABLE_SNAP_TO_GRID = 'ENABLE_SNAP_TO_GRID';
14 | export const DISABLE_SNAP_TO_GRID = 'DISABLE_SNAP_TO_GRID';
15 |
16 | export const ENABLE_ASPECT = 'ENABLE_ASPECT';
17 | export const DISABLE_ASPECT = 'DISABLE_ASPECT';
18 |
19 | export const ENABLE_X_AXIS = 'ENABLE_X_AXIS';
20 | export const ENABLE_Y_AXIS = 'ENABLE_Y_AXIS';
21 | export const ENABLE_BOTH_AXIS = 'ENABLE_BOTH_AXIS';
22 | export const ENABLE_NONE_AXIS = 'ENABLE_NONE_AXIS';
23 |
24 | export const CHANGE_ZINDEX = 'CHANGE_ZINDEX';
25 |
26 | export const CHANGE_MINW = 'CHANGE_MINW';
27 | export const CHANGE_MINH = 'CHANGE_MINH';
28 |
29 | export const CHANGE_WIDTH = 'CHANGE_WIDTH';
30 | export const CHANGE_HEIGHT = 'CHANGE_HEIGHT';
31 | export const CHANGE_TOP = 'CHANGE_TOP';
32 | export const CHANGE_LEFT = 'CHANGE_LEFT';
33 |
34 | export default {
35 | ENABLE_ACTIVE,
36 | DISABLE_ACTIVE,
37 | ENABLE_DRAGGABLE,
38 | DISABLE_DRAGGABLE,
39 | ENABLE_RESIZABLE,
40 | DISABLE_RESIZABLE,
41 | ENABLE_PARENT_LIMITATION,
42 | DISABLE_PARENT_LIMITATION,
43 | ENABLE_SNAP_TO_GRID,
44 | DISABLE_SNAP_TO_GRID,
45 | ENABLE_ASPECT,
46 | DISABLE_ASPECT,
47 | ENABLE_X_AXIS,
48 | ENABLE_Y_AXIS,
49 | ENABLE_NONE_AXIS,
50 | ENABLE_BOTH_AXIS,
51 | CHANGE_ZINDEX,
52 | CHANGE_MINW,
53 | CHANGE_MINH,
54 | CHANGE_WIDTH,
55 | CHANGE_HEIGHT,
56 | CHANGE_TOP,
57 | CHANGE_LEFT
58 | }
--------------------------------------------------------------------------------
/editor/src/plugins/vue-drag-resize/src/demo/store/modules/rect/mutations.js:
--------------------------------------------------------------------------------
1 | import {
2 | ENABLE_ACTIVE,
3 | DISABLE_ACTIVE,
4 | ENABLE_ASPECT,
5 | DISABLE_ASPECT,
6 | ENABLE_DRAGGABLE,
7 | DISABLE_DRAGGABLE,
8 | ENABLE_RESIZABLE,
9 | DISABLE_RESIZABLE,
10 | ENABLE_PARENT_LIMITATION,
11 | DISABLE_PARENT_LIMITATION,
12 | ENABLE_SNAP_TO_GRID,
13 | DISABLE_SNAP_TO_GRID,
14 | CHANGE_ZINDEX,
15 | ENABLE_BOTH_AXIS,
16 | ENABLE_X_AXIS,
17 | ENABLE_Y_AXIS,
18 | ENABLE_NONE_AXIS,
19 | CHANGE_HEIGHT,
20 | CHANGE_LEFT,
21 | CHANGE_MINH,
22 | CHANGE_MINW,
23 | CHANGE_TOP,
24 | CHANGE_WIDTH
25 | } from './mutation-types';
26 |
27 | export default {
28 | [ENABLE_ACTIVE](state, id) {
29 | state.rects[id].active = true;
30 | },
31 | [DISABLE_ACTIVE](state, id) {
32 | state.rects[id].active = false;
33 | },
34 |
35 | [ENABLE_ASPECT](state, id) {
36 | state.rects[id].aspectRatio = true;
37 | },
38 | [DISABLE_ASPECT](state, id) {
39 | state.rects[id].aspectRatio = false;
40 | },
41 |
42 | [ENABLE_DRAGGABLE](state, id) {
43 | state.rects[id].draggable = true;
44 | },
45 | [DISABLE_DRAGGABLE](state, id) {
46 | state.rects[id].draggable = false;
47 | },
48 |
49 | [ENABLE_RESIZABLE](state, id) {
50 | state.rects[id].resizable = true;
51 | },
52 | [DISABLE_RESIZABLE](state, id) {
53 | state.rects[id].resizable = false;
54 | },
55 |
56 | [ENABLE_SNAP_TO_GRID](state, id) {
57 | state.rects[id].snapToGrid = true;
58 | },
59 | [DISABLE_SNAP_TO_GRID](state, id) {
60 | state.rects[id].snapToGrid = false;
61 | },
62 |
63 | [ENABLE_BOTH_AXIS](state, id) {
64 | state.rects[id].axis = 'both';
65 | },
66 | [ENABLE_NONE_AXIS](state, id) {
67 | state.rects[id].axis = 'none';
68 | },
69 | [ENABLE_X_AXIS](state, id) {
70 | state.rects[id].axis = 'x';
71 | },
72 | [ENABLE_Y_AXIS](state, id) {
73 | state.rects[id].axis = 'y';
74 | },
75 |
76 | [ENABLE_PARENT_LIMITATION](state, id) {
77 | state.rects[id].parentLim = true;
78 | },
79 | [DISABLE_PARENT_LIMITATION](state, id) {
80 | state.rects[id].parentLim = false;
81 | },
82 |
83 | [CHANGE_ZINDEX](state, payload) {
84 | state.rects[payload.id].zIndex = payload.zIndex;
85 | },
86 |
87 | [CHANGE_HEIGHT](state, payload) {
88 | state.rects[payload.id].height = payload.height;
89 | },
90 |
91 | [CHANGE_WIDTH](state, payload) {
92 | state.rects[payload.id].width = payload.width;
93 | },
94 |
95 | [CHANGE_TOP](state, payload) {
96 | state.rects[payload.id].top = payload.top;
97 | },
98 |
99 | [CHANGE_LEFT](state, payload) {
100 | state.rects[payload.id].left = payload.left;
101 | },
102 |
103 | [CHANGE_MINH](state, payload) {
104 |
105 | state.rects[payload.id].minh = payload.minh;
106 | },
107 |
108 | [CHANGE_MINW](state, payload) {
109 | state.rects[payload.id].minw = payload.minw;
110 | }
111 | };
--------------------------------------------------------------------------------
/editor/src/plugins/vue-drag-resize/src/demo/store/modules/rect/state.js:
--------------------------------------------------------------------------------
1 | export default {
2 | 'rects': [
3 | {
4 | 'width': 200,
5 | 'height': 150,
6 | 'top': 10,
7 | 'left': 10,
8 | 'draggable': true,
9 | 'resizable': true,
10 | 'minw': 10,
11 | 'minh': 10,
12 | 'axis': 'both',
13 | 'parentLim': true,
14 | 'snapToGrid': false,
15 | 'aspectRatio': false,
16 | 'zIndex': 1,
17 | 'color': '#EF9A9A',
18 | 'active': false
19 | },
20 | {
21 | 'width': 200,
22 | 'height': 150,
23 | 'top': 170,
24 | 'left': 220,
25 | 'draggable': true,
26 | 'resizable': true,
27 | 'minw': 10,
28 | 'minh': 10,
29 | 'axis': 'both',
30 | 'parentLim': true,
31 | 'snapToGrid': false,
32 | 'aspectRatio': false,
33 | 'zIndex': 1,
34 | 'color': '#E6C27A',
35 | 'active': false,
36 | 'class': 'box-shaddow'
37 | },
38 | {
39 | 'width': 200,
40 | 'height': 150,
41 | 'top': 10,
42 | 'left': 220,
43 | 'draggable': true,
44 | 'resizable': true,
45 | 'minw': 10,
46 | 'minh': 10,
47 | 'axis': 'both',
48 | 'parentLim': true,
49 | 'snapToGrid': false,
50 | 'aspectRatio': false,
51 | 'zIndex': 2,
52 | 'color': '#AED581',
53 | 'active': false
54 | },
55 | {
56 | 'width': 200,
57 | 'height': 150,
58 | 'top': 170,
59 | 'left': 10,
60 | 'draggable': true,
61 | 'resizable': true,
62 | 'minw': 10,
63 | 'minh': 10,
64 | 'axis': 'both',
65 | 'parentLim': true,
66 | 'snapToGrid': false,
67 | 'aspectRatio': false,
68 | 'zIndex': 3,
69 | 'color': '#81D4FA',
70 | 'active': false
71 | }
72 | ]
73 | };
74 |
--------------------------------------------------------------------------------
/editor/src/plugins/vue-drag-resize/src/index.js:
--------------------------------------------------------------------------------
1 | export { default } from './components/vue-drag-resize.vue'
--------------------------------------------------------------------------------
/editor/src/plugins/vue-drag-resize/webpack.config.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack');
2 | const config = require('./config');
3 | const CompressionPlugin = require("compression-webpack-plugin");
4 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
5 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
6 | const { VueLoaderPlugin } = require('vue-loader');
7 |
8 | let plugins = [];
9 |
10 | plugins.push(new VueLoaderPlugin());
11 |
12 | if(config.build.sourceMap){
13 | plugins.push(new webpack.SourceMapDevToolPlugin({
14 | filename: '[name].map'
15 | }))
16 | }
17 |
18 | plugins.push(new webpack.DefinePlugin({
19 | 'process.env': {
20 | NODE_ENV: config.build.env
21 | }
22 | }));
23 |
24 |
25 | if(config.build.gzip){
26 | plugins.push(new CompressionPlugin({
27 | asset: "[path].gz[query]",
28 | algorithm: "gzip",
29 | test: /\.(js)$/,
30 | threshold: 10240,
31 | minRatio: 0.8
32 | }));
33 | }
34 |
35 | if(config.build.bundleAnalyzerReport){
36 | plugins.push(new BundleAnalyzerPlugin({
37 | analyzerMode: 'server',
38 | analyzerHost: '127.0.0.1',
39 | analyzerPort: 8888,
40 | reportFilename: 'report.html',
41 | defaultSizes: 'parsed',
42 | openAnalyzer: true,
43 | generateStatsFile: false,
44 | statsFilename: 'stats.json',
45 | statsOptions: null,
46 | logLevel: 'info'
47 | }))
48 | }
49 |
50 | module.exports = {
51 | resolve: {
52 | alias: {
53 | vue: 'vue/dist/vue.common.js'
54 | }
55 | },
56 | module: {
57 | rules: [
58 |
59 | {
60 | test: /\.vue$/,
61 | loader: 'vue-loader'
62 | },
63 | {
64 | test: /\.js$/,
65 | exclude: /(node_modules|bower_components)/,
66 | use: {
67 | loader: 'babel-loader'
68 | }
69 | },
70 | {
71 | test: /\.css$/,
72 | use: [ 'style-loader', 'css-loader', 'postcss-loader' ]
73 | }
74 | ]
75 | },
76 |
77 | devServer: {
78 | contentBase: [config.build.distPath, config.server.assetsPath],
79 | historyApiFallback: true,
80 | noInfo: true,
81 | open: config.server.autoOpenBrowser,
82 | port: config.server.port
83 | },
84 |
85 | entry: config.entry,
86 |
87 | output: {
88 | path: config.build.distPath,
89 | library: 'VueDragResize',
90 | filename: '[name].js',
91 | libraryTarget: 'umd'
92 | },
93 |
94 | optimization: {
95 | minimizer: [
96 | new UglifyJsPlugin({
97 | sourceMap: false
98 | })
99 | ]
100 | },
101 |
102 | plugins: plugins
103 |
104 | };
--------------------------------------------------------------------------------
/editor/src/plugins/vue-range-component/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Lemon
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/editor/src/plugins/vue-range-component/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "_from": "vue-range-component",
3 | "_id": "vue-range-component@1.0.3",
4 | "_inBundle": false,
5 | "_integrity": "sha512-J/rE7KJa61XsLXREvSw0Ia+dSTaLoP5Ou7NfOy5pm/1FMgYn7E14iQj7baA8H4ZxoIuKuCfrhjWglPHtWUPGmg==",
6 | "_location": "/vue-range-component",
7 | "_phantomChildren": {},
8 | "_requested": {
9 | "type": "tag",
10 | "registry": true,
11 | "raw": "vue-range-component",
12 | "name": "vue-range-component",
13 | "escapedName": "vue-range-component",
14 | "rawSpec": "",
15 | "saveSpec": null,
16 | "fetchSpec": "latest"
17 | },
18 | "_requiredBy": [
19 | "#USER",
20 | "/"
21 | ],
22 | "_resolved": "https://registry.npmjs.org/vue-range-component/-/vue-range-component-1.0.3.tgz",
23 | "_shasum": "17a1faa0cf3ccb67bdcc767564fd9b9b1b71c237",
24 | "_spec": "vue-range-component",
25 | "_where": "/home/koctrx/GIT/Work/shotstack/editor",
26 | "author": {
27 | "name": "xwpongithub"
28 | },
29 | "bugs": {
30 | "url": "https://github.com/xwpongithub/vue-range-slider/issues"
31 | },
32 | "bundleDependencies": false,
33 | "deprecated": false,
34 | "description": "A range slider component based on vue (Vue滑块组件).",
35 | "devDependencies": {
36 | "@babel/core": "^7.1.2",
37 | "@babel/plugin-transform-runtime": "^7.1.0",
38 | "@babel/preset-env": "^7.1.0",
39 | "autoprefixer": "^9.1.5",
40 | "babel-loader": "^8.0.4",
41 | "cssnano": "^4.1.4",
42 | "gulp": "^3.9.1",
43 | "gulp-postcss": "^8.0.0",
44 | "gulp-rename": "^1.4.0",
45 | "gulp-sourcemaps": "^2.6.4",
46 | "gulp-stylus": "^2.7.0",
47 | "postcss": "^7.0.5",
48 | "postcss-pxtorem": "^4.0.1",
49 | "rollup": "^0.66.6",
50 | "rollup-plugin-babel": "^4.0.3",
51 | "rollup-plugin-json": "^3.1.0",
52 | "stylus": "^0.54.5",
53 | "uglify-js": "^3.4.9",
54 | "zlib": "^1.0.5"
55 | },
56 | "homepage": "https://github.com/xwpongithub/vue-range-slider",
57 | "keywords": [
58 | "javascript",
59 | "rollup",
60 | "vuejs",
61 | "stylus"
62 | ],
63 | "license": "MIT",
64 | "main": "dist/vue-range-slider.js",
65 | "module": "dist/vue-range-slider.esm.js",
66 | "name": "vue-range-component",
67 | "repository": {
68 | "type": "git",
69 | "url": "git+ssh://git@github.com/xwpongithub/vue-range-slider.git"
70 | },
71 | "scripts": {
72 | "build": "gulp"
73 | },
74 | "version": "1.0.3"
75 | }
76 |
--------------------------------------------------------------------------------
/editor/src/providers/api/index.js:
--------------------------------------------------------------------------------
1 | import config from '@/config/';
2 | import axios from 'axios';
3 |
4 | const API_URL = config.API_URL;
5 |
6 | export default {
7 |
8 | async uploadFileSubtitles(file, processCallback = () => { }) {
9 | const b64 = await new Promise(resolve => {
10 | var reader = new FileReader();
11 | reader.readAsDataURL(file);
12 | reader.onloadend = function() {
13 | resolve(reader.result);
14 | }
15 | });
16 |
17 | return await $.ajax({
18 | url: `${API_URL}/api/upload_subtitles`,
19 | method: 'post',
20 | data: {
21 | b64: b64.split(';base64,')[1],
22 | filename: file.name
23 | },
24 |
25 | dataType: 'json',
26 | //contentType: false,
27 | cache: false,
28 | // processData: false,
29 | xhr: function () {
30 | var myXhr = $.ajaxSettings.xhr();
31 | if (myXhr.upload) {
32 | myXhr.upload.addEventListener('progress', e => processCallback(e), false);
33 | }
34 |
35 | return myXhr;
36 | }
37 | });
38 | },
39 |
40 | async downloadVideo(videoURL = '', processCallback = () => { }) {
41 | return await $.ajax({
42 | url: videoURL,
43 | contentType: false,
44 | cache: false,
45 | processData: false,
46 | xhrFields:{ responseType: 'blob' },
47 | xhr: function () {
48 | var myXhr = $.ajaxSettings.xhr();
49 | myXhr.addEventListener('progress', e => processCallback(e), false);
50 |
51 | return myXhr;
52 | }
53 | })
54 | },
55 |
56 | async renderVideo(data) {
57 | return await $.ajax({
58 | method: 'post',
59 | data: JSON.stringify(data), url: `${API_URL}/api/render`,
60 | contentType: 'application/json',
61 | });
62 | },
63 |
64 | async uploadFile(file, processCallback = () => { }) {
65 | return await $.ajax({
66 | url: `${API_URL}/api/upload?filename=${file.name}`,
67 | method: 'post',
68 | data: file,
69 | contentType: false,
70 | cache: false,
71 | processData: false,
72 | headers: { 'content-type': file.type },
73 | xhr: function () {
74 | var myXhr = $.ajaxSettings.xhr();
75 | if (myXhr.upload) {
76 | myXhr.upload.addEventListener('progress', e => processCallback(e), false);
77 | }
78 | return myXhr;
79 | }
80 | });
81 | }
82 | }
--------------------------------------------------------------------------------
/editor/src/providers/helpers/index.js:
--------------------------------------------------------------------------------
1 | export const debounce = function(func, wait, immediate) {
2 | let timeout;
3 |
4 | return function executedFunction() {
5 | const context = this;
6 | const args = arguments;
7 |
8 | const later = function() {
9 | timeout = null;
10 | if (!immediate) func.apply(context, args);
11 | };
12 |
13 | const callNow = immediate && !timeout;
14 |
15 | clearTimeout(timeout);
16 |
17 | timeout = setTimeout(later, wait);
18 |
19 | if (callNow) func.apply(context, args);
20 | };
21 | };
22 |
23 | export const trottle = function(func, time, immediate) {
24 | let lastCall;
25 | let previousCall;
26 | return function executedFunction() {
27 | const context = this;
28 | const args = arguments;
29 |
30 | previousCall = lastCall;
31 | lastCall = Date.now();
32 |
33 | if (previousCall === undefined ||
34 | (lastCall - previousCall) > time) {
35 | func.apply(context, args);
36 | //console.log('trottle');
37 | }
38 | }
39 | }
--------------------------------------------------------------------------------
/editor/src/providers/models/Edit.js:
--------------------------------------------------------------------------------
1 | export default class Edit {
2 | constructor(timeline, output, callback) {
3 | this.timeline = timeline;
4 | this.output = output;
5 | this.callback = callback;
6 | }
7 | }
--------------------------------------------------------------------------------
/editor/src/providers/models/Timeline.js:
--------------------------------------------------------------------------------
1 | export default class Timeline {
2 | constructor(soundtrack, background, fonts, tracks) {
3 | this.soundtrack = {...soundtrack };
4 | this.background = background; //string
5 | this.fonts = fonts; //array
6 | this.tracks = tracks;
7 | }
8 |
9 | // get soundtrack() {
10 | // return {...this.soundtrack }
11 | // }
12 |
13 | // set soundtrack(val) {
14 | // this.soundtrack = {...val }
15 | // }
16 |
17 | get bg() {
18 | return this.background
19 | }
20 |
21 | set bg(val) {
22 | this.background = val
23 | }
24 | }
--------------------------------------------------------------------------------
/editor/src/providers/models/index.js:
--------------------------------------------------------------------------------
1 | import Timeline from './Timeline'
2 | import Edit from './Edit'
3 |
4 | export default {
5 | Timeline,
6 | Edit
7 | }
--------------------------------------------------------------------------------
/editor/src/router/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import VueRouter from 'vue-router'
3 | import Home from '../views/Home.vue'
4 |
5 | Vue.use(VueRouter)
6 |
7 | const routes = [{
8 | path: '/',
9 | name: 'Home',
10 | component: Home
11 | }, ]
12 |
13 | const router = new VueRouter({
14 | mode: 'history',
15 | base: process.env.BASE_URL,
16 | routes
17 | })
18 |
19 | export default router
--------------------------------------------------------------------------------
/editor/vue.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | publicPath: '/',
3 | outputDir: '../server/views'
4 | }
--------------------------------------------------------------------------------
/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/studiopink/pink-editor/56aab3e077ae95a269449405595f0ba75b1d283d/screenshot.png
--------------------------------------------------------------------------------
/server/.env.dist:
--------------------------------------------------------------------------------
1 | PORT=3000
2 | DOMAIN=https://localhost:3000
3 | SHOTSTACK_API=https://api.shotstack.io/edit/stage
4 | SHOTSTACK_API_KEY=SHOTSTACK_API_KEY
5 | SSL_privkey=key.pem
6 | SSL_fullchain=cert.pem
7 |
--------------------------------------------------------------------------------
/server/.gitignore:
--------------------------------------------------------------------------------
1 | .env
2 | .env.example
3 | .env.local
4 | .env.staging
5 | /views/
6 | /localStorage/*
7 | *.pem
--------------------------------------------------------------------------------
/server/app.js:
--------------------------------------------------------------------------------
1 | require('dotenv').config({ path: '.env' });
2 | const config = require('./config');
3 |
4 | const express = require('express');
5 | const app = express();
6 | const fs = require('fs');
7 | const mongoose = require('mongoose');
8 | const cors = require('cors');
9 |
10 | app.use(express.json());
11 | app.use(express.static('public'));
12 | app.use(express.static('views'));
13 | app.use(express.json({ limit: '500mb' }));
14 | app.use(express.urlencoded({ limit: '500mb', extended: true }));
15 | app.use(express.raw({ type: '*/*', limit: '900mb' }));
16 | app.use(cors());
17 |
18 | app.get('/', (req, res, next) => {
19 | res.sendFile(__dirname + '/views/index.html');
20 | });
21 |
22 | app.use('/api', require('./routes/api'));
23 |
24 | app.use('**', (req, res) => {
25 | // res.sendFile(__dirname + '/view/index.html');
26 | res.sendStatus(404);
27 | });
28 |
29 | app.use((err, req, res, next) => {
30 | if (!config.production) {
31 | console.error(err);
32 | return res.status(500).send(err.stack);
33 | }
34 |
35 | res.status(500).send('Server error');
36 | });
37 |
38 | try {
39 | if (!process.env.SSL_privkey || !process.env.SSL_fullchain) throw Error('NOT FOUND KEYS\n');
40 |
41 | const options = {
42 | key: fs.readFileSync(process.env.SSL_privkey),
43 | cert: fs.readFileSync(process.env.SSL_fullchain)
44 | };
45 |
46 | const httpsServer = require('https').createServer(options, app);
47 | createServer(httpsServer);
48 |
49 | const httpServer = express();
50 | httpServer.get('*', function (req, res) {
51 | res.redirect('https://' + req.headers.host + req.url);
52 | })
53 |
54 | httpServer.listen(8080);
55 | } catch (err) {
56 | const httpServer = require('http').createServer(app);
57 | createServer(httpServer);
58 | }
59 |
60 | async function createServer(server) {
61 | try {
62 | await mongoose.connect(process.env.MONGOOSE_URL, {
63 | useNewUrlParser: true,
64 | useUnifiedTopology: true,
65 | useFindAndModify: false,
66 | useCreateIndex: true
67 | });
68 |
69 | console.log('Database connected.');
70 | } catch(err) {
71 | console.log(err)
72 | }
73 |
74 | require('./src/io')(server);
75 | server.listen(config.port, () => console.log(config.domain));
76 | }
77 |
--------------------------------------------------------------------------------
/server/config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | port: process.env.PORT,
3 | domain: process.env.DOMAIN,
4 | production: process.env.NODE_ENV == 'production'
5 | };
--------------------------------------------------------------------------------
/server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "server",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "dependencies": {
7 | "aws-sdk": "^2.840.0",
8 | "axios": "^0.21.1",
9 | "bson-objectid": "^1.3.1",
10 | "cors": "^2.8.5",
11 | "dotenv": "^8.2.0",
12 | "exiftool": "0.0.3",
13 | "express": "^4.17.1",
14 | "ffmetadata": "^1.6.0",
15 | "fluent-ffmpeg": "^2.1.2",
16 | "get-video-duration": "^3.0.2",
17 | "image-thumbnail": "^1.0.13",
18 | "mime": "^2.5.0",
19 | "mongoose": "^6.2.3",
20 | "socket.io": "^3.1.1"
21 | },
22 | "devDependencies": {
23 | "nodemon": "^2.0.7"
24 | },
25 | "scripts": {
26 | "start": "NODE_ENV=production node app.js",
27 | "dev": "nodemon app.js",
28 | "test": "echo \"Error: no test specified\" && exit 1"
29 | },
30 | "author": "",
31 | "license": "ISC"
32 | }
33 |
--------------------------------------------------------------------------------
/server/public/fonts/Ballet.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/studiopink/pink-editor/56aab3e077ae95a269449405595f0ba75b1d283d/server/public/fonts/Ballet.ttf
--------------------------------------------------------------------------------
/server/public/fonts/Grand-Hotel.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/studiopink/pink-editor/56aab3e077ae95a269449405595f0ba75b1d283d/server/public/fonts/Grand-Hotel.ttf
--------------------------------------------------------------------------------
/server/public/fonts/Hanalei.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/studiopink/pink-editor/56aab3e077ae95a269449405595f0ba75b1d283d/server/public/fonts/Hanalei.ttf
--------------------------------------------------------------------------------
/server/public/fonts/Monoton.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/studiopink/pink-editor/56aab3e077ae95a269449405595f0ba75b1d283d/server/public/fonts/Monoton.ttf
--------------------------------------------------------------------------------
/server/public/fonts/Montserrat.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/studiopink/pink-editor/56aab3e077ae95a269449405595f0ba75b1d283d/server/public/fonts/Montserrat.ttf
--------------------------------------------------------------------------------
/server/public/fonts/OpenSans-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/studiopink/pink-editor/56aab3e077ae95a269449405595f0ba75b1d283d/server/public/fonts/OpenSans-Regular.ttf
--------------------------------------------------------------------------------
/server/public/fonts/Reggae-One.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/studiopink/pink-editor/56aab3e077ae95a269449405595f0ba75b1d283d/server/public/fonts/Reggae-One.ttf
--------------------------------------------------------------------------------
/server/public/storage/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | */
3 | !.gitignore
--------------------------------------------------------------------------------
/server/routes/api.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const router = express.Router();
3 |
4 | const config = require('../config');
5 | const shotstackService = require('../src/services/shotstack');
6 | const fileService = require('../src/services/files');
7 |
8 | router.use('/hook', require('./hook'));
9 |
10 | router.post('/render', async (req, res, next) => {
11 | try {
12 | res.json(await shotstackService.render(req.body));
13 | } catch { next(err); }
14 | });
15 |
16 | router.post('/upload', async (req, res, next) => {
17 | try {
18 | res.json(await fileService.uploadFile(req));
19 | } catch (err) { next(err); }
20 | });
21 |
22 | router.post('/upload_subtitles', async (req, res, next) => {
23 | try {
24 | res.json(await fileService.uploadSubtitles(req));
25 | } catch (err) { next(err); }
26 | });
27 |
28 | module.exports = router;
--------------------------------------------------------------------------------
/server/routes/hook.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const router = express.Router();
3 | const shotstackService = require('../src/services/shotstack');
4 |
5 | router.post('/render', (req, res) => {
6 | shotstackService.emitRender(req.body);
7 |
8 | res.json({ success: true });
9 | });
10 |
11 | module.exports = router;
--------------------------------------------------------------------------------
/server/src/io/index.js:
--------------------------------------------------------------------------------
1 | const socketIO = require('socket.io');
2 | const subStorage = require('./subscribeStorage');
3 | const shotstackSErvice = require('../services/shotstack');
4 |
5 | module.exports = server => {
6 | const io = global.io = socketIO(server, {
7 | allowEIO3: true, cors: { origin: '*', credentials: true }
8 | });
9 |
10 | io.on('connection', socket => {
11 | socket.on('render_subscribe', async id => {
12 | const sub = subStorage.find(id);
13 | if(sub) {
14 | subStorage.replaceSubByRenderId(id, { ...sub, socketId: socket.id });
15 | const result = await shotstackSErvice.getStatusBuild(id);
16 | if(result) {
17 | shotstackSErvice.emitRender(result);
18 | }
19 | } else {
20 | subStorage.add(socket.id, id);
21 | }
22 | });
23 | });
24 | };
--------------------------------------------------------------------------------
/server/src/io/subscribeStorage.js:
--------------------------------------------------------------------------------
1 | module.exports = new class {
2 | constructor() {
3 | this.store = [];
4 | }
5 |
6 | find(id) {
7 | return this.store.find(data => data.renderId == id);
8 | }
9 |
10 | replaceSubByRenderId(renderId, data) {
11 | this.store = this.store.map(item => {
12 | if(item.renderId == renderId) {
13 | return data;
14 | }
15 |
16 | return item;
17 | });
18 | }
19 |
20 | add(socketId, renderId) {
21 | this.removeSubscribe(renderId);
22 | this.store.push({ socketId, renderId });
23 | }
24 |
25 | removeSubscribe(renderId) {
26 | this.store = this.store.filter(data => data.renderId != renderId);
27 | }
28 | };
--------------------------------------------------------------------------------
/server/src/services/shotstack/index.js:
--------------------------------------------------------------------------------
1 | const axios = require('axios');
2 | const config = require('../../../config');
3 | const utils = require('./utils');
4 |
5 | const subStorage = require('../../io/subscribeStorage');
6 |
7 | const URL = process.env.SHOTSTACK_API;
8 | const API_KEY = process.env.SHOTSTACK_API_KEY;
9 |
10 | module.exports = new class {
11 | async getStatusBuild(renderId) {
12 | const result = await this.send('/render/' + renderId, {}, 'GET');
13 | if(result.success && result.response && result.response.status == 'done') {
14 | return result.response;
15 | }
16 |
17 | return null;
18 | }
19 |
20 | emitRender(data) {
21 | const subscribe = subStorage.find(data.id);
22 | if(subscribe && global.io.engine.clients[subscribe.socketId]) {
23 | global.io.to(subscribe.socketId).emit('render_done', {
24 | id: data.id,
25 | url: data.url,
26 | poster: data.poster,
27 | thumbnail: data.thumbnail
28 | });
29 |
30 | subStorage.removeSubscribe(data.id);
31 | }
32 | }
33 |
34 | async render(data = {}) {
35 | try {
36 | data.canvas.objects = data.canvas.objects.reverse();
37 |
38 | const callback = `${config.domain}/api/hook/render`;
39 | const videosClips = utils.parseVideosToClips(data.videos);
40 |
41 | let subtitles = utils.parseSubtitles(data.timelines, {
42 | canvas: data.canvas,
43 | resolution: data.videoResolution,
44 | aspectRatio: data.output.aspectRatio
45 | });
46 |
47 | subtitles = subtitles && subtitles.length ? [{ clips: subtitles }] : [];
48 | const timelineClips = utils.parseTimeLinesToClips(data.timelines, data.canvas.objects, {
49 | canvas: data.canvas,
50 | resolution: data.videoResolution,
51 | aspectRatio: data.output.aspectRatio
52 | });
53 |
54 | const timelines = (timelineClips.length ? timelineClips.reverse().map(timeLine => ({ clips: [timeLine] })) : []);
55 | const videoDuration = utils.getVideoStartTimeByVideoIndex(data.videos, data.videos.length);
56 |
57 | const build = {
58 | timeline: {
59 | fonts: utils.loadFonts(data.canvas.objects),
60 | background: data.output.backgroundColor || "#000000",
61 | tracks: [
62 | ...subtitles,
63 | ...timelines,
64 | { clips: [ ...videosClips ] }
65 | ]
66 | },
67 | output: {
68 | format: data.output.type,
69 | resolution: data.output.resolution,
70 | aspectRatio: data.output.aspectRatio,
71 | fps: data.output.fps,
72 | range: {
73 | start: 0,
74 | length: videoDuration
75 | },
76 | poster: {
77 | capture: videoDuration * .2
78 | },
79 | thumbnail: {
80 | capture: videoDuration / 2,
81 | scale: 0.3
82 | }
83 | }, callback
84 | };
85 |
86 | try {
87 | const result = await this.send('/render', build, 'POST');
88 |
89 | return { success: result.success, id: result.response.id };
90 | } catch(err) {
91 | if(err.response && err.response.data && err.response.data.response) {
92 | return {
93 | success: false,
94 | error: 'Bad request',
95 | data: err.response.data.response
96 | };
97 | }
98 |
99 | console.log(err);
100 |
101 | return { success: false, error: 'Bad request' };
102 | }
103 | } catch (err) {
104 | if(err.message != 'debug') {
105 | console.error(err);
106 | }
107 |
108 | return { success: false, error: 'Some error' };
109 | }
110 | }
111 |
112 | async send(endpoint = '', data = {}, method = 'POST', payloadHeaders = {}) {
113 | const result = await axios({
114 | method, url: URL + endpoint, headers: {
115 | 'x-api-key': API_KEY,
116 | ...payloadHeaders
117 | }, data
118 | });
119 |
120 | return result.data
121 | }
122 | };
--------------------------------------------------------------------------------