commit 4ab5e76d85d93bf2bd84564058c17eb7fc3daa31 Author: RenΓ© Preuß Date: Wed Mar 1 19:24:20 2023 +0100 first commit diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..2bc0b86 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,11 @@ +--- + +name: 🐞 Bug report +about: Create a report to help us improve +title: "[Bug] the title of bug report" +labels: bug +assignees: '' + +--- + +#### Describe the bug diff --git a/.github/ISSUE_TEMPLATE/help_wanted.md b/.github/ISSUE_TEMPLATE/help_wanted.md new file mode 100644 index 0000000..6fba797 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/help_wanted.md @@ -0,0 +1,10 @@ +--- +name: πŸ₯Ί Help wanted +about: Confuse about the use of electron-vue-vite +title: "[Help] the title of help wanted report" +labels: help wanted +assignees: '' + +--- + +#### Describe the problem you confuse diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..c533dfb --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,12 @@ + + +### Description + + + +### What is the purpose of this pull request? + +- [ ] Bug fix +- [ ] New Feature +- [ ] Documentation update +- [ ] Other diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..fe250eb --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "npm" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "monthly" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..29a1e2d --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,47 @@ +name: Build + +on: + push: + branches: [main] + paths-ignore: + - "**.md" + - "**.spec.js" + - ".idea" + - ".vscode" + - ".dockerignore" + - "Dockerfile" + - ".gitignore" + - ".github/**" + - "!.github/workflows/build.yml" + +jobs: + build: + runs-on: ${{ matrix.os }} + + strategy: + matrix: + os: [macos-latest, ubuntu-latest, windows-latest] + + steps: + - name: Checkout Code + uses: actions/checkout@v3 + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: 18 + + - name: Install Dependencies + run: npm install + + - name: Build Release Files + run: npm run build + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Upload Artifact + uses: actions/upload-artifact@v3 + with: + name: release_on_${{ matrix. os }} + path: release/ + retention-days: 5 \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..3d3b53e --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,81 @@ +name: CI + +on: + pull_request_target: + branches: + - main + +permissions: + pull-requests: write + +jobs: + job1: + name: Check Not Allowed File Changes + runs-on: ubuntu-latest + outputs: + markdown_change: ${{ steps.filter_markdown.outputs.change }} + markdown_files: ${{ steps.filter_markdown.outputs.change_files }} + steps: + + - name: Check Not Allowed File Changes + uses: dorny/paths-filter@v2 + id: filter_not_allowed + with: + list-files: json + filters: | + change: + - 'package-lock.json' + - 'yarn.lock' + - 'pnpm-lock.yaml' + + # ref: https://github.com/github/docs/blob/main/.github/workflows/triage-unallowed-contributions.yml + - name: Comment About Changes We Can't Accept + if: ${{ steps.filter_not_allowed.outputs.change == 'true' }} + uses: actions/github-script@v6 + with: + script: | + let workflowFailMessage = "It looks like you've modified some files that we can't accept as contributions." + try { + const badFilesArr = [ + 'package-lock.json', + 'yarn.lock', + 'pnpm-lock.yaml', + ] + const badFiles = badFilesArr.join('\n- ') + const reviewMessage = `πŸ‘‹ Hey there spelunker. It looks like you've modified some files that we can't accept as contributions. The complete list of files we can't accept are:\n- ${badFiles}\n\nYou'll need to revert all of the files you changed in that list using [GitHub Desktop](https://docs.github.com/en/free-pro-team@latest/desktop/contributing-and-collaborating-using-github-desktop/managing-commits/reverting-a-commit) or \`git checkout origin/main \`. Once you get those files reverted, we can continue with the review process. :octocat:\n\nMore discussion:\n- https://github.com/electron-vite/electron-vite-vue/issues/192` + createdComment = await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.number, + body: reviewMessage, + }) + workflowFailMessage = `${workflowFailMessage} Please see ${createdComment.data.html_url} for details.` + } catch(err) { + console.log("Error creating comment.", err) + } + core.setFailed(workflowFailMessage) + + - name: Check Not Linted Markdown + if: ${{ always() }} + uses: dorny/paths-filter@v2 + id: filter_markdown + with: + list-files: shell + filters: | + change: + - added|modified: '*.md' + + + job2: + name: Lint Markdown + runs-on: ubuntu-latest + needs: job1 + if: ${{ always() && needs.job1.outputs.markdown_change == 'true' }} + steps: + - name: Checkout Code + uses: actions/checkout@v3 + with: + ref: ${{ github.event.pull_request.head.sha }} + + - name: Lint markdown + run: npx markdownlint-cli ${{ needs.job1.outputs.markdown_files }} --ignore node_modules \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f2a9d20 --- /dev/null +++ b/.gitignore @@ -0,0 +1,31 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +dist-electron +release +out +*.local + +# Editor directories and files +.vscode/.debug.env +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +# lockfile +package-lock.json +pnpm-lock.yaml +yarn.lock \ No newline at end of file diff --git a/.vscode/.debug.script.mjs b/.vscode/.debug.script.mjs new file mode 100644 index 0000000..e3674cb --- /dev/null +++ b/.vscode/.debug.script.mjs @@ -0,0 +1,23 @@ +import fs from 'node:fs' +import path from 'node:path' +import { fileURLToPath } from 'node:url' +import { createRequire } from 'node:module' +import { spawn } from 'node:child_process' + +const pkg = createRequire(import.meta.url)('../package.json') +const __dirname = path.dirname(fileURLToPath(import.meta.url)) + +// write .debug.env +const envContent = Object.entries(pkg.debug.env).map(([key, val]) => `${key}=${val}`) +fs.writeFileSync(path.join(__dirname, '.debug.env'), envContent.join('\n')) + +// bootstrap +spawn( + // TODO: terminate `npm run dev` when Debug exits. + process.platform === 'win32' ? 'npm.cmd' : 'npm', + ['run', 'dev'], + { + stdio: 'inherit', + env: Object.assign(process.env, { VSCODE_DEBUG: 'true' }), + }, +) diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..232ead7 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,6 @@ +{ + "recommendations": [ + "Vue.volar", + "Vue.vscode-typescript-vue-plugin" + ] +} diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..3d01d65 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,53 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "compounds": [ + { + "name": "Debug App", + "preLaunchTask": "Before Debug", + "configurations": [ + "Debug Main Process", + "Debug Renderer Process" + ], + "presentation": { + "hidden": false, + "group": "", + "order": 1 + }, + "stopAll": true + } + ], + "configurations": [ + { + "name": "Debug Main Process", + "type": "node", + "request": "launch", + "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron", + "windows": { + "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron.cmd" + }, + "runtimeArgs": [ + "--remote-debugging-port=9229", + "." + ], + "envFile": "${workspaceFolder}/.vscode/.debug.env", + "console": "integratedTerminal" + }, + { + "name": "Debug Renderer Process", + "port": 9229, + "request": "attach", + "type": "chrome", + "timeout": 60000, + "skipFiles": [ + "/**", + "${workspaceRoot}/node_modules/**", + "${workspaceRoot}/dist-electron/**", + // Skip files in host(VITE_DEV_SERVER_URL) + "http://127.0.0.1:3344/**" + ] + }, + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..1e3e2cd --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,13 @@ +{ + "typescript.tsdk": "node_modules/typescript/lib", + "typescript.tsc.autoDetect": "off", + "json.schemas": [ + { + "fileMatch": [ + "/*electron-builder.json5", + "/*electron-builder.json" + ], + "url": "https://json.schemastore.org/electron-builder" + } + ] +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..adb5c58 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,31 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + "label": "Before Debug", + "type": "shell", + "command": "node .vscode/.debug.script.mjs", + "isBackground": true, + "problemMatcher": { + "owner": "typescript", + "fileLocation": "relative", + "pattern": { + // TODO: correct "regexp" + "regexp": "^([a-zA-Z]\\:\/?([\\w\\-]\/?)+\\.\\w+):(\\d+):(\\d+): (ERROR|WARNING)\\: (.*)$", + "file": 1, + "line": 3, + "column": 4, + "code": 5, + "message": 6 + }, + "background": { + "activeOnStart": true, + "beginsPattern": "^.*VITE v.* ready in \\d* ms.*$", + "endsPattern": "^.*\\[startup\\] Electron App.*$" + } + } + } + ] +} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..db6dbaa --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,33 @@ +## 2022-10-03 + +[v2.1.0](https://github.com/electron-vite/electron-vite-vue/pull/267) + +- `vite-electron-plugin` is Fast, and WYSIWYG. 🌱 +- last-commit: db2e830 v2.1.0: use `vite-electron-plugin` instead `vite-plugin-electron` + +## 2022-06-04 + +[v2.0.0](https://github.com/electron-vite/electron-vite-vue/pull/156) + +- πŸ–– Based on the `vue-ts` template created by `npm create vite`, integrate `vite-plugin-electron` +- ⚑️ More simplify, is in line with Vite project structure +- last-commit: a15028a (HEAD -> main) feat: hoist `process.env` + +## 2022-01-30 + +[v1.0.0](https://github.com/electron-vite/electron-vite-vue/releases/tag/v1.0.0) + +- ⚑️ Main、Renderer、preload, all built with vite + +## 2022-01-27 +- Refactor the scripts part. +- Remove `configs` directory. + +## 2021-11-11 +- Refactor the project. Use vite.config.ts build `Main-process`, `Preload-script` and `Renderer-process` alternative rollup. +- Scenic `Vue>=3.2.13`, `@vue/compiler-sfc` is no longer necessary. +- If you prefer Rollup, Use rollup branch. + +```bash +Error: @vitejs/plugin-vue requires vue (>=3.2.13) or @vue/compiler-sfc to be present in the dependency tree. +``` diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..22edc0e --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 θ‰ιž‹ζ²‘ε· + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..0672633 --- /dev/null +++ b/README.md @@ -0,0 +1,46 @@ +# Rerun Encoder for Rerun Manager + +![](https://cdn.discordapp.com/attachments/1041779193979092992/1080483735264301156/NVIDIA_Share_tHZrdoEVPu.gif) + +## Introduction + +The Rerun Encoder is a tool that allows streamers to transcode videos on their own PCs, eliminating the need to process +videos on the rerunmanager.com servers. This is extremely useful for streamers who have a lot of videos to process, or +streamers who want to process videos as soon as they are done streaming. + +## Features + +- Transcode videos on your own PC +- Upload videos directly to rerunmanager.com +- Videos up to 12 hours long + +## Installation + +### Windows + +> If Windows Defender SmartScreen blocks the installation, just click "Run anyway". This is because of a missing code +> signing certificate, which we still need to obtain. + +1. Download the latest release from the [releases page](https://github.com/bitinflow/rerun-encoder/releases/latest). +2. Once downloaded, execute the installer and follow the instructions. +3. Once the installation is complete, you need to log in with your rerunmanager.com account. You can do this by + clicking the "Login with Rerun Manager" button in the bottom right corner of the application. + +## Frequently Asked Questions + +### Can I upload more than one video? + +No. Right now, the Rerun Encoder only supports uploading one video at a time. + +### How many videos can I upload? + +Up to 100 videos, currently limited to 10 GB in total, and 12 hours per video (public beta). + +### My upload failed, what now? + +Uploads may fail if they are not uploaded within two hours. Also, uploads are rejected if they are either too large or +if too many videos have already been uploaded. If you encounter this issue, please contact us on Discord. + +### How do I uninstall Rerun Encoder? + +Select Start > Settings > Apps > Apps & features. Find the app you want to remove, select More > Uninstall. \ No newline at end of file diff --git a/electron/electron-env.d.ts b/electron/electron-env.d.ts new file mode 100644 index 0000000..d02e86c --- /dev/null +++ b/electron/electron-env.d.ts @@ -0,0 +1,11 @@ +/// + +declare namespace NodeJS { + interface ProcessEnv { + VSCODE_DEBUG?: 'true' + DIST_ELECTRON: string + DIST: string + /** /dist/ or /public/ */ + PUBLIC: string + } +} diff --git a/electron/main/helpers.ts b/electron/main/helpers.ts new file mode 100644 index 0000000..6642894 --- /dev/null +++ b/electron/main/helpers.ts @@ -0,0 +1,8 @@ +import {BrowserWindow} from "electron"; + +export function emit(event: any, ...args: any) { + // Send a message to all windows + BrowserWindow.getAllWindows().forEach((win) => { + win.webContents.send(event, ...args) + }); +} \ No newline at end of file diff --git a/electron/main/index.ts b/electron/main/index.ts new file mode 100644 index 0000000..0e0b9f4 --- /dev/null +++ b/electron/main/index.ts @@ -0,0 +1,171 @@ +import {app, BrowserWindow, ipcMain, shell} from 'electron' +import {release} from 'node:os' +import {join} from 'node:path' +import {Settings} from "../../shared/schema"; +import {SettingsRepository} from "../rerun-manager/settings-repository"; +import {Encoder, EncoderOptions} from "../rerun-manager/encoder"; +import IpcMainInvokeEvent = Electron.IpcMainInvokeEvent; +import {InternalServer} from "../rerun-manager/internal-server"; +import {emit} from "./helpers"; +import {platform} from "node:process"; + +// The built directory structure +// +// β”œβ”€β”¬ dist-electron +// β”‚ β”œβ”€β”¬ main +// β”‚ β”‚ └── index.js > Electron-Main +// β”‚ └─┬ preload +// β”‚ └── index.js > Preload-Scripts +// β”œβ”€β”¬ dist +// β”‚ └── index.html > Electron-Renderer +// +process.env.DIST_ELECTRON = join(__dirname, '..') +process.env.DIST = join(process.env.DIST_ELECTRON, '../dist') +process.env.PUBLIC = process.env.VITE_DEV_SERVER_URL + ? join(process.env.DIST_ELECTRON, '../public') + : process.env.DIST + +// Disable GPU Acceleration for Windows 7 +if (release().startsWith('6.1')) app.disableHardwareAcceleration() + +// Set application name for Windows 10+ notifications +if (process.platform === 'win32') app.setAppUserModelId(app.getName()) + +if (!app.requestSingleInstanceLock()) { + app.quit() + process.exit(0) +} + +// Handle creating/removing shortcuts on Windows when installing/uninstalling. +if (require('electron-squirrel-startup')) { + app.quit() +} + +require('update-electron-app')() + +// Remove electron security warnings +// This warning only shows in development mode +// Read more on https://www.electronjs.org/docs/latest/tutorial/security +// process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = 'true' + +let win: BrowserWindow | null = null +// Here, you can also use other preload +const preload = join(__dirname, '../preload/index.js') +const url = process.env.VITE_DEV_SERVER_URL +const indexHtml = join(process.env.DIST, 'index.html') + +async function createWindow(settings: Settings) { + win = new BrowserWindow({ + title: 'Rerun Manager - Encoder', + icon: join(process.env.PUBLIC, 'logo.ico'), + frame: false, + maximizable: false, + resizable: false, + width: 560, + height: 300, + backgroundColor: '#0c0c0e', + webPreferences: { + preload, + // Warning: Enable nodeIntegration and disable contextIsolation is not secure in production + // Consider using contextBridge.exposeInMainWorld + // Read more on https://www.electronjs.org/docs/latest/tutorial/context-isolation + nodeIntegration: true, + contextIsolation: false, + }, + }) + + if (process.env.VITE_DEV_SERVER_URL) { // electron-vite-vue#298 + win.loadURL(url) + // Open devTool if the app is not packaged + // win.webContents.openDevTools() + } else { + win.loadFile(indexHtml) + } + + // Test actively push message to the Electron-Renderer + win.webContents.on('did-finish-load', () => { + win?.webContents.send('main-process-message', new Date().toLocaleString()) + }) + + // Make all links open with the browser, not with the application + win.webContents.setWindowOpenHandler(({url}) => { + if (url.startsWith('https:')) shell.openExternal(url) + return {action: 'deny'} + }) + // win.webContents.on('will-navigate', (event, url) => { }) #344 +} + +const settingsRepository = new SettingsRepository(); + +// This method will be called when Electron has finished +// initialization and is ready to create browser windows. +// Some APIs can only be used after this event occurs. +app.on('ready', () => { + settingsRepository.restore().then((settings: Settings) => { + createWindow(settings) + }) +}) + +// Quit when all windows are closed, except on macOS. There, it's common +// for applications and their menu bar to stay active until the user quits +// explicitly with Cmd + Q. +app.on('window-all-closed', () => { + if (platform !== 'darwin') { + app.quit() + } +}) + +app.on('second-instance', () => { + if (win) { + // Focus on the main window if the user tried to open another + if (win.isMinimized()) win.restore() + win.focus() + } +}) + +app.on('activate', () => { + // On OS X it's common to re-create a window in the app when the + // dock icon is clicked and there are no other windows open. + if (BrowserWindow.getAllWindows().length === 0) { + createWindow(settingsRepository.getSettings()) + } +}) + +// New window example arg: new windows url +ipcMain.handle('open-win', (_, arg) => { + const childWindow = new BrowserWindow({ + webPreferences: { + preload, + nodeIntegration: true, + contextIsolation: false, + }, + }) + + if (process.env.VITE_DEV_SERVER_URL) { + childWindow.loadURL(`${url}#${arg}`) + } else { + childWindow.loadFile(indexHtml, {hash: arg}) + } +}) + +ipcMain.handle('version', async () => app.getVersion()) +ipcMain.handle('settings', async () => settingsRepository.getSettings()) +ipcMain.handle('logout', async () => settingsRepository.logout()) +ipcMain.handle('quit', async () => app.quit()) +ipcMain.handle('minimize', async () => win.minimize()) +ipcMain.handle('encode', async (event: IpcMainInvokeEvent, ...args: any[]) => { + const arg = args[0] as { id: string, input: string, options: EncoderOptions }; + const encoder = new Encoder(arg.id, arg.input, arg.options, { + onStart: (id) => event.sender.send('encode-start', id), + onProgress: (id, progress) => event.sender.send('encode-progress', id, progress), + onUploadProgress: (id, progress) => event.sender.send('encode-upload-progress', id, progress), + onUploadComplete: (id, video) => event.sender.send('encode-upload-complete', id, video), + onError: (id, error) => event.sender.send('encode-error', id, error), + }, settingsRepository.getSettings()); + return await encoder.encode() +}) +ipcMain.handle('commitSettings', async (event: IpcMainInvokeEvent, ...args: any[]) => settingsRepository.commitSettings(args[0])) +settingsRepository.watch((settings: Settings) => emit('settings', settings)); + +const internalServer = new InternalServer(); +internalServer.listen(settingsRepository); \ No newline at end of file diff --git a/electron/preload/index.ts b/electron/preload/index.ts new file mode 100644 index 0000000..51dcf8a --- /dev/null +++ b/electron/preload/index.ts @@ -0,0 +1,176 @@ +import {EncoderOptions} from "../rerun-manager/encoder"; + +function domReady(condition: DocumentReadyState[] = ['complete', 'interactive']) { + return new Promise((resolve) => { + if (condition.includes(document.readyState)) { + resolve(true) + } else { + document.addEventListener('readystatechange', () => { + if (condition.includes(document.readyState)) { + resolve(true) + } + }) + } + }) +} + +const safeDOM = { + append(parent: HTMLElement, child: HTMLElement) { + if (!Array.from(parent.children).find(e => e === child)) { + return parent.appendChild(child) + } + }, + remove(parent: HTMLElement, child: HTMLElement) { + if (Array.from(parent.children).find(e => e === child)) { + return parent.removeChild(child) + } + }, +} + +/** + * https://tobiasahlin.com/spinkit + * https://connoratherton.com/loaders + * https://projects.lukehaas.me/css-loaders + * https://matejkustec.github.io/SpinThatShit + */ +function useLoading() { + const className = `loaders-css__square-spin` + const styleContent = ` +@keyframes square-spin { + 25% { transform: perspective(100px) rotateX(180deg) rotateY(0); } + 50% { transform: perspective(100px) rotateX(180deg) rotateY(180deg); } + 75% { transform: perspective(100px) rotateX(0) rotateY(180deg); } + 100% { transform: perspective(100px) rotateX(0) rotateY(0); } +} +.${className} > div { + animation-fill-mode: both; + width: 50px; + height: 50px; + background: #fff; + animation: square-spin 3s 0s cubic-bezier(0.09, 0.57, 0.49, 0.9) infinite; +} +.app-loading-wrap { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + display: flex; + align-items: center; + justify-content: center; + background: #0c0c0e; + z-index: 9; +} + ` + const oStyle = document.createElement('style') + const oDiv = document.createElement('div') + + oStyle.id = 'app-loading-style' + oStyle.innerHTML = styleContent + oDiv.className = 'app-loading-wrap' + oDiv.innerHTML = `
` + + return { + appendLoading() { + safeDOM.append(document.head, oStyle) + safeDOM.append(document.body, oDiv) + }, + removeLoading() { + safeDOM.remove(document.head, oStyle) + safeDOM.remove(document.body, oDiv) + }, + } +} + +// ---------------------------------------------------------------------- + +const {appendLoading, removeLoading} = useLoading() +domReady().then(appendLoading) + +window.onmessage = (ev) => { + ev.data.payload === 'removeLoading' && removeLoading() +} + +setTimeout(removeLoading, 4999) + + +// ---------------------------------------------------------------------- + +// See the Electron documentation for details on how to use preload scripts: +// https://www.electronjs.org/docs/latest/tutorial/process-model#preload-scripts +// boilerplate code for electron... +import {Settings} from "../../shared/schema"; + +const { + contextBridge, + ipcRenderer +} = require('electron') + +// All the Node.js APIs are available in the preload process. +// It has the same sandbox as a Chrome extension. +window.addEventListener('DOMContentLoaded', () => { + const replaceText = (selector: string, text: string) => { + const element = document.getElementById(selector) + if (element) element.innerText = text + } + + for (const type of ['chrome', 'node', 'electron']) { + replaceText(`${type}-version`, process.versions[type]) + } +}) + +// end boilerplate code, on to your stuff.. +window.api = { + invoke: (channel: string, data: any) => { + let validChannels = [ + 'encode-start', + 'encode-progress', + 'encode-upload-progress', + 'encode-upload-complete', + 'encode-error', + ] // list of ipcMain.handle channels you want access in frontend to + if (validChannels.includes(channel)) { + // ipcRenderer.invoke accesses ipcMain.handle channels like 'myfunc' + // make sure to include this return statement or you won't get your Promise back + return ipcRenderer.invoke(channel, data) + } + }, + on(channel: string, func: any) { + let validChannels = [ + 'encode-start', + 'encode-progress', + 'encode-upload-progress', + 'encode-upload-complete', + 'encode-error', + ] // list of ipcMain.on channels you want access in frontend to + if (validChannels.includes(channel)) { + // ipcRenderer.on accesses ipcMain.on channels like 'myevent' + // make sure to include this return statement or you won't get your Promise back + return ipcRenderer.on(channel, func) + } + }, + minimize() { + return ipcRenderer.invoke('minimize') + }, + quit() { + return ipcRenderer.invoke('quit') + }, + getVersion() { + return ipcRenderer.invoke('version') + }, + logout: () => { + return ipcRenderer.invoke('logout') + }, + encode: (id: string, input: string, options: EncoderOptions) => { + return ipcRenderer.invoke('encode', {id, input, options}) + }, + commitSettings: (settings: Settings) => { + return ipcRenderer.invoke('commitSettings', settings) + }, + getSettings: () => { + return ipcRenderer.invoke('settings') + }, + onSettingsChanged: (callback: (settings: Settings) => void) => { + return ipcRenderer.on('settings', (event: any, ...args: any) => callback(args[0])) + }, +} \ No newline at end of file diff --git a/electron/rerun-manager/encoder.ts b/electron/rerun-manager/encoder.ts new file mode 100644 index 0000000..0a46045 --- /dev/null +++ b/electron/rerun-manager/encoder.ts @@ -0,0 +1,147 @@ + +import {EncoderListeners, EncoderOptions, Settings, User, Video} from "../../shared/schema"; +import * as fs from "fs"; +import axios, {AxiosInstance} from "axios"; + +const ffmpeg = require('fluent-ffmpeg') + +export class Encoder { + private readonly id: string; + private readonly input: string; + private readonly output: string; + private readonly options: EncoderOptions; + private readonly listeners: EncoderListeners; + private readonly settings: Settings; + private api: AxiosInstance; + private s3: AxiosInstance; + + constructor( + id: string, + input: string, + options: EncoderOptions, + listeners: EncoderListeners, + settings: Settings + ) { + this.id = id; + this.input = input; + this.output = this.input.replace(/\.mp4$/, '.flv') + this.options = options; + this.listeners = listeners; + this.settings = settings; + + this.api = axios.create({ + baseURL: settings.endpoint, + headers: { + Authorization: `${settings.credentials.token_type} ${settings.credentials.access_token}` + } + }) + + this.s3 = axios.create({}); + } + + async encode(): Promise { + this.listeners.onStart(this.id) + + ffmpeg(this.input) + .outputOptions(this.getOutputOptions()) + .output(this.output) + .on('start', () => { + console.log('start') + }) + .on('progress', (progress) => { + console.log('progress', progress) + this.listeners.onProgress(this.id, progress.percent) + }) + .on('end', async () => { + console.log('end') + // @ts-ignore + try { + const video = await this.requestUploadUrl(this.settings.credentials.user) + await this.upload(video) + } catch (error) { + console.log('error', error) + this.listeners.onError(this.id, error.message) + } + }) + .on('error', (error) => { + console.log('error', error) + this.listeners.onError(this.id, error.message) + }) + .run() + } + + private getOutputOptions() { + return [ + '-c:v libx264', + '-preset veryfast', + '-crf 23', + '-maxrate 6000k', + '-bufsize 6000k', + '-c:a aac', + '-b:a 128k', + '-ac 2', + '-f flv', + '-movflags +faststart', + '-y' + ] + } + + private async requestUploadUrl(user: User): Promise