├── .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 | ![Editor](./screenshot.png) 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 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /editor/public/img/transition.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 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 | 6 | 7 | 56 | 57 | -------------------------------------------------------------------------------- /editor/src/assets/img/arrow_right_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /editor/src/assets/img/camera_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /editor/src/assets/img/export_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /editor/src/assets/img/fullscreen_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 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 | 4 | 5 | 6 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /editor/src/assets/img/play_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /editor/src/assets/img/plus_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /editor/src/assets/img/preview_mode_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /editor/src/assets/img/redo_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 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 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /editor/src/assets/img/select_arrow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /editor/src/assets/img/shape_element_1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /editor/src/assets/img/shape_element_2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /editor/src/assets/img/shape_element_3.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 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 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /editor/src/assets/img/undo_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 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 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /editor/src/assets/img/video_time_back.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /editor/src/assets/img/video_time_forward.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /editor/src/assets/img/volume_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /editor/src/assets/img/your_logos_1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /editor/src/assets/img/your_logos_3.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 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 | 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 | 38 | 107 | 108 | 129 | -------------------------------------------------------------------------------- /editor/src/components/base/Bbutton.vue: -------------------------------------------------------------------------------- 1 | 30 | 76 | 77 | -------------------------------------------------------------------------------- /editor/src/components/base/Burger.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | -------------------------------------------------------------------------------- /editor/src/components/base/Ccontrol.vue: -------------------------------------------------------------------------------- 1 | 29 | -------------------------------------------------------------------------------- /editor/src/components/base/ColorCirclesPicker.vue: -------------------------------------------------------------------------------- 1 | 11 | 54 | 55 | -------------------------------------------------------------------------------- /editor/src/components/base/DMobToggle.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | -------------------------------------------------------------------------------- /editor/src/components/base/GrayLabel.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /editor/src/components/base/Headline.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | -------------------------------------------------------------------------------- /editor/src/components/base/IconBtn.vue: -------------------------------------------------------------------------------- 1 | 10 | 24 | 25 | -------------------------------------------------------------------------------- /editor/src/components/base/Logo.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 14 | -------------------------------------------------------------------------------- /editor/src/components/base/LogoFile.vue: -------------------------------------------------------------------------------- 1 | 7 | 19 | 20 | -------------------------------------------------------------------------------- /editor/src/components/base/Pbutton.vue: -------------------------------------------------------------------------------- 1 | 19 | -------------------------------------------------------------------------------- /editor/src/components/base/Preloader.vue: -------------------------------------------------------------------------------- 1 | 8 | 19 | 20 | 39 | -------------------------------------------------------------------------------- /editor/src/components/base/SelectField.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 22 | 23 | -------------------------------------------------------------------------------- /editor/src/components/base/Step.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | -------------------------------------------------------------------------------- /editor/src/components/base/StepRight.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 16 | 17 | -------------------------------------------------------------------------------- /editor/src/components/base/SubtitleItem.vue: -------------------------------------------------------------------------------- 1 | 13 | -------------------------------------------------------------------------------- /editor/src/components/base/Tcontrol.vue: -------------------------------------------------------------------------------- 1 | 10 | -------------------------------------------------------------------------------- /editor/src/components/base/TextElement.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /editor/src/components/base/TextareaField.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 15 | 16 | -------------------------------------------------------------------------------- /editor/src/components/base/TextureElement.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /editor/src/components/base/Tracktime.vue: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /editor/src/components/base/VideoFile.vue: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /editor/src/components/base/VolumeControl.vue: -------------------------------------------------------------------------------- 1 | 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 | 6 | -------------------------------------------------------------------------------- /editor/src/components/layouts/Controlbar.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | -------------------------------------------------------------------------------- /editor/src/components/layouts/Header.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | -------------------------------------------------------------------------------- /editor/src/components/layouts/Leftbars.vue: -------------------------------------------------------------------------------- 1 | 44 | 45 | 57 | 58 | -------------------------------------------------------------------------------- /editor/src/components/layouts/Rightbars.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | -------------------------------------------------------------------------------- /editor/src/components/layouts/Timeline.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 28 | 29 | -------------------------------------------------------------------------------- /editor/src/components/layouts/Workspace.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | -------------------------------------------------------------------------------- /editor/src/components/layouts/Wrapper.vue: -------------------------------------------------------------------------------- 1 | 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 | 42 | 43 | 147 | 148 | -------------------------------------------------------------------------------- /editor/src/components/modals/DragModal.vue: -------------------------------------------------------------------------------- 1 | 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 |
6 | 7 |
14 |
15 | 16 |
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 | 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 |
2 |
3 |

Position

4 | top 5 | 6 | 7 | 14 | 15 | left 16 | 17 | 24 | 25 |
26 |
27 |

Size

28 | width 29 | 30 | 37 | 38 | height 39 | 40 | 41 |
42 | 43 |
44 |

Minimal size

45 | width 46 | 47 | 48 | height 49 | 50 | 51 |
52 | 53 |
54 |

Restrictions

55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 |
63 |
-------------------------------------------------------------------------------- /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 | }; --------------------------------------------------------------------------------