mirror of
https://github.com/bitinflow/rerun-encoder.git
synced 2026-03-13 13:46:00 +00:00
first commit
This commit is contained in:
261
src/App.vue
Normal file
261
src/App.vue
Normal file
@@ -0,0 +1,261 @@
|
||||
<script setup lang="ts">
|
||||
import {computed, onMounted, ref} from "vue";
|
||||
import ProgressBar from "./components/ProgressBar.vue";
|
||||
import AppTitleBar from "./components/AppTitleBar.vue";
|
||||
import VButton from "./components/VButton.vue";
|
||||
import {Settings, Video} from "../shared/schema";
|
||||
|
||||
console.log("[App.vue]", `Hello world from Electron ${process.versions.electron}!`)
|
||||
|
||||
const isEncoding = ref(false)
|
||||
const isCompleted = ref(false)
|
||||
const encodingProgress = ref(0)
|
||||
const encodingProgressStage = ref<string>('unknown')
|
||||
const encodingError = ref<string | null>(null)
|
||||
const version = ref<string>('unknown')
|
||||
|
||||
const reset = () => {
|
||||
encodingProgress.value = 0
|
||||
encodingProgressStage.value = 'unknown'
|
||||
encodingError.value = null
|
||||
isEncoding.value = false
|
||||
isCompleted.value = false
|
||||
}
|
||||
|
||||
const requestEncode = (event: any) => {
|
||||
// get file from @change event
|
||||
const file = event.target.files[0] as File
|
||||
|
||||
// @ts-ignore
|
||||
window.api.on('encode-start', (event: any, id: string) => {
|
||||
encodingProgressStage.value = 'starting'
|
||||
encodingProgress.value = 0
|
||||
console.log('encode-start', id)
|
||||
})
|
||||
|
||||
// @ts-ignore
|
||||
window.api.on('encode-progress', (event: any, id: string, progress: number) => {
|
||||
encodingProgress.value = progress
|
||||
encodingProgressStage.value = 'transcoding'
|
||||
console.log('encode-progress', id, progress)
|
||||
})
|
||||
|
||||
// @ts-ignore
|
||||
window.api.on('encode-upload-progress', (event: any, id: string, progress: number) => {
|
||||
encodingProgress.value = progress
|
||||
encodingProgressStage.value = 'uploading'
|
||||
console.log('encode-upload-progress', id, progress)
|
||||
})
|
||||
|
||||
// @ts-ignore
|
||||
window.api.on('encode-upload-complete', (event: any, id: string, video: Video) => {
|
||||
encodingProgressStage.value = 'complete'
|
||||
encodingProgress.value = 100
|
||||
isEncoding.value = false
|
||||
isCompleted.value = true
|
||||
console.log('encode-upload-complete', id, video)
|
||||
})
|
||||
|
||||
// @ts-ignore
|
||||
window.api.on('encode-error', (event: any, id: string, error: any) => {
|
||||
encodingProgressStage.value = 'error'
|
||||
encodingProgress.value = 0
|
||||
isEncoding.value = false
|
||||
isCompleted.value = false
|
||||
encodingError.value = error
|
||||
console.log('encode-error', id, error)
|
||||
})
|
||||
|
||||
try {
|
||||
isEncoding.value = true
|
||||
encodingError.value = null
|
||||
isCompleted.value = false
|
||||
// @ts-ignore
|
||||
window.api.encode('file-upload-123', file.path, {
|
||||
title: file.name,
|
||||
publishOnComplete: true,
|
||||
})
|
||||
} catch (err) {
|
||||
isEncoding.value = false
|
||||
isCompleted.value = false
|
||||
encodingError.value = 'Failed to encode video'
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
|
||||
const settings = ref<Settings | null>(null);
|
||||
const fileInput = ref<HTMLInputElement | null>(null)
|
||||
|
||||
// Authorization Code Grant with PKCE
|
||||
const oauthHref = computed(() => {
|
||||
const params = new URLSearchParams({
|
||||
client_id: '97be2e0d-fc64-4a0c-af7b-959f754f516e',
|
||||
redirect_uri: 'http://localhost:8361/oauth',
|
||||
response_type: 'token',
|
||||
scope: 'user:read video:read video:manage',
|
||||
})
|
||||
|
||||
return `https://rerunmanager.com/oauth/authorize?${params.toString()}`
|
||||
})
|
||||
|
||||
const settingsChanged = (settings: any) => {
|
||||
console.log('settingsChanged', settings)
|
||||
}
|
||||
|
||||
const logout = () => {
|
||||
// @ts-ignore
|
||||
window.api.logout()
|
||||
}
|
||||
|
||||
const storageUpgradeRequired = computed(() => {
|
||||
if (!settings.value) return false
|
||||
// @ts-ignore
|
||||
return !settings.value.credentials.user.config.premium
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
// @ts-ignore
|
||||
window.api.onSettingsChanged((x: any) => settings.value = x)
|
||||
// @ts-ignore
|
||||
window.api.getSettings()
|
||||
.then((x: any) => settings.value = x)
|
||||
.catch((err: any) => console.error(err))
|
||||
|
||||
// @ts-ignore
|
||||
window.api.getVersion().then((x: any) => version.value = `v${x}`)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="app h-screen w-screen bg-base-800 text-white flex flex-col justify-between">
|
||||
<div>
|
||||
<app-title-bar/>
|
||||
|
||||
<div class="px-6 flex">
|
||||
<div>
|
||||
<img src="./assets/ffmpeg.svg" class="h-16" alt="ffmpeg">
|
||||
</div>
|
||||
<div>
|
||||
<div class="font-bold text-xl mt-2">Rerun Encoder</div>
|
||||
<div class="text-sm text-zinc-400">Powered by FFmpeg</div>
|
||||
|
||||
<div class="flex gap-4 mt-4">
|
||||
<a
|
||||
class="text-rose-400"
|
||||
href="https://www.bitinflow.com/legal/privacy/"
|
||||
target="_blank"
|
||||
>
|
||||
<i class="far fa-arrow-up-right-from-square"></i>
|
||||
Privacy & terms
|
||||
</a>
|
||||
<a
|
||||
class="text-rose-400"
|
||||
href="https://github.com/bitinflow/rerun-encoder#readme"
|
||||
target="_blank"
|
||||
>
|
||||
<i class="far fa-arrow-up-right-from-square"></i>
|
||||
More details
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div>
|
||||
<div v-if="settings && settings.credentials" class="p-4 flex justify-between bg-base-900">
|
||||
<div class="test flex gap-2">
|
||||
<div>
|
||||
<img
|
||||
class="h-16 rounded-full"
|
||||
:src="settings.credentials.user.avatar_url"
|
||||
alt="channel avatar"
|
||||
>
|
||||
</div>
|
||||
<div class="self-center">
|
||||
<div class="text-lg">{{ settings.credentials.user.name }}</div>
|
||||
<div class="text-sm text-zinc-400">
|
||||
{{ settings.credentials.user.config.storage_used }}
|
||||
of {{ settings.credentials.user.config.storage_limit }}
|
||||
GB used
|
||||
</div>
|
||||
<div class="flex text-sm text-zinc-600 gap-1">
|
||||
<div>
|
||||
{{ version }}
|
||||
</div>
|
||||
<div class="text-zinc-700">|</div>
|
||||
<a href="#" @click.prevent="logout">Logout</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="self-center">
|
||||
<div v-if="encodingError" class="text-right">
|
||||
<div class="text-rose-400 text-sm w-64">
|
||||
{{ encodingError }}
|
||||
</div>
|
||||
<a href="#"
|
||||
class="text-zinc-400 text-sm"
|
||||
@click="reset">
|
||||
Retry
|
||||
</a>
|
||||
</div>
|
||||
<div v-else-if="isCompleted" class="text-right">
|
||||
<div class="text-emerald-400 w-64">
|
||||
<i class="fa-solid fa-check"></i>
|
||||
Video uploaded successfully!
|
||||
</div>
|
||||
<a href="#"
|
||||
class="text-zinc-400 text-sm"
|
||||
@click="reset">
|
||||
Upload another
|
||||
</a>
|
||||
</div>
|
||||
<template v-else-if="isEncoding">
|
||||
<progress-bar :progress="encodingProgress" :stage="encodingProgressStage"/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<input
|
||||
ref="fileInput"
|
||||
class="hidden"
|
||||
type="file"
|
||||
accept="video/mp4"
|
||||
@change="requestEncode"
|
||||
>
|
||||
<v-button
|
||||
@click="fileInput && fileInput.click()"
|
||||
:disabled="isEncoding"
|
||||
>
|
||||
<i class="fal fa-upload"></i>
|
||||
Transcode & Upload
|
||||
</v-button>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="p-4 flex justify-between bg-base-900">
|
||||
<div class="self-center opacity-70">
|
||||
Login required
|
||||
</div>
|
||||
<div class="py-5">
|
||||
<a :href="oauthHref" class="bg-primary-500 py-1.5 px-4 rounded text-white" target="_blank">
|
||||
<i class="fal fa-lock"></i>
|
||||
Login with Rerun Manager
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="storageUpgradeRequired"
|
||||
class="bg-primary-500 text-white px-4 py-1 text-sm flex justify-between">
|
||||
<div>
|
||||
<i class="fa-solid fa-stars"></i>
|
||||
Need more storage? Get Rerun Manager Premium!
|
||||
</div>
|
||||
<div class="text-sm">
|
||||
<a href="https://www.rerunmanager.com/plans" target="_blank" class="text-white">
|
||||
<i class="far fa-arrow-up-right-from-square"></i>
|
||||
Learn more
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
1
src/assets/electron.svg
Normal file
1
src/assets/electron.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="256"><g fill="none" fill-rule="evenodd"><circle fill="#2B2E3A" cx="128" cy="128" r="128"/><g fill="#9FEAF9" fill-rule="nonzero"><path d="M100.502 71.69c-26.005-4.736-46.567.221-54.762 14.415-6.115 10.592-4.367 24.635 4.24 39.646a2.667 2.667 0 1 0 4.626-2.653c-7.752-13.522-9.261-25.641-4.247-34.326 6.808-11.791 25.148-16.213 49.187-11.835a2.667 2.667 0 0 0 .956-5.247zm-36.999 72.307c10.515 11.555 24.176 22.394 39.756 31.388 37.723 21.78 77.883 27.601 97.675 14.106a2.667 2.667 0 1 0-3.005-4.406c-17.714 12.078-55.862 6.548-92.003-14.318-15.114-8.726-28.343-19.222-38.478-30.36a2.667 2.667 0 1 0-3.945 3.59z"/><path d="M194.62 140.753c17.028-20.116 22.973-40.348 14.795-54.512-6.017-10.423-18.738-15.926-35.645-16.146a2.667 2.667 0 0 0-.069 5.333c15.205.198 26.165 4.939 31.096 13.48 6.792 11.765 1.49 29.807-14.248 48.399a2.667 2.667 0 1 0 4.071 3.446zm-43.761-68.175c-15.396 3.299-31.784 9.749-47.522 18.835-38.942 22.483-64.345 55.636-60.817 79.675a2.667 2.667 0 1 0 5.277-.775c-3.133-21.344 20.947-52.769 58.207-74.281 15.267-8.815 31.135-15.06 45.972-18.239a2.667 2.667 0 1 0-1.117-5.215z"/><path d="M87.77 187.753c8.904 24.86 23.469 40.167 39.847 40.167 11.945 0 22.996-8.143 31.614-22.478a2.667 2.667 0 1 0-4.571-2.748c-7.745 12.883-17.258 19.892-27.043 19.892-13.605 0-26.596-13.652-34.825-36.63a2.667 2.667 0 1 0-5.021 1.797zm81.322-4.863c4.61-14.728 7.085-31.718 7.085-49.423 0-44.179-15.463-82.263-37.487-92.042a2.667 2.667 0 0 0-2.164 4.874c19.643 8.723 34.317 44.866 34.317 87.168 0 17.177-2.397 33.63-6.84 47.83a2.667 2.667 0 1 0 5.09 1.593zm50.224-2.612c0-7.049-5.714-12.763-12.763-12.763-7.049 0-12.763 5.714-12.763 12.763 0 7.049 5.714 12.763 12.763 12.763 7.049 0 12.763-5.714 12.763-12.763zm-5.333 0a7.43 7.43 0 1 1-14.86 0 7.43 7.43 0 0 1 14.86 0zM48.497 193.041c7.05 0 12.764-5.714 12.764-12.763 0-7.049-5.715-12.763-12.764-12.763-7.048 0-12.763 5.714-12.763 12.763 0 7.049 5.715 12.763 12.763 12.763zm0-5.333a7.43 7.43 0 1 1 0-14.86 7.43 7.43 0 0 1 0 14.86z"/><path d="M127.617 54.444c7.049 0 12.763-5.714 12.763-12.763 0-7.049-5.714-12.763-12.763-12.763-7.049 0-12.763 5.714-12.763 12.763 0 7.049 5.714 12.763 12.763 12.763zm0-5.333a7.43 7.43 0 1 1 0-14.86 7.43 7.43 0 0 1 0 14.86zm1.949 93.382c-4.985 1.077-9.896-2.091-10.975-7.076a9.236 9.236 0 0 1 7.076-10.976c4.985-1.077 9.896 2.091 10.976 7.076 1.077 4.985-2.091 9.897-7.077 10.976z"/></g></g></svg>
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
4
src/assets/ffmpeg.svg
Normal file
4
src/assets/ffmpeg.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px">
|
||||
<path fill="#ffffff"
|
||||
d="M39.5,42h-9c-1,0-1.9-0.6-2.3-1.5c-0.4-0.9-0.2-2,0.5-2.7l8.3-8.3v-3.9L21.3,41.3 c-0.5,0.5-1.1,0.7-1.8,0.7h-11c-1,0-1.9-0.6-2.3-1.5c-0.4-0.9-0.2-2,0.5-2.7L33.5,11h-3.9L10.3,30.3c-0.7,0.7-1.8,0.9-2.7,0.5 C6.6,30.4,6,29.5,6,28.5v-11c0-0.7,0.3-1.3,0.7-1.8l4.7-4.7h-3C7.1,11,6,9.9,6,8.5S7.1,6,8.5,6h9c1,0,1.9,0.6,2.3,1.5 c0.4,0.9,0.2,2-0.5,2.7L11,18.5v3.9L26.7,6.7C27.2,6.3,27.8,6,28.5,6h11c1,0,1.9,0.6,2.3,1.5c0.4,0.9,0.2,2-0.5,2.7L14.5,37h3.9 l19.3-19.3c0.7-0.7,1.8-0.9,2.7-0.5c0.9,0.4,1.5,1.3,1.5,2.3v11c0,0.7-0.3,1.3-0.7,1.8L36.5,37h3c1.4,0,2.5,1.1,2.5,2.5 S40.9,42,39.5,42z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 708 B |
1
src/assets/vue.svg
Normal file
1
src/assets/vue.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 496 B |
34
src/components/AppTitleBar.vue
Normal file
34
src/components/AppTitleBar.vue
Normal file
@@ -0,0 +1,34 @@
|
||||
<template>
|
||||
<div class="flex justify-between p-4 text-sm title-bar">
|
||||
<div class="flex gap-2">
|
||||
<i class="fas fa-solid fa-arrow-rotate-right self-center"></i>
|
||||
<div>
|
||||
Rerun Manager - Encoder
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-3">
|
||||
<a href="#" @click="minimizeCompanion">
|
||||
<i class="fal fa-dash"></i>
|
||||
</a>
|
||||
<a href="#" @click="closeCompanion">
|
||||
<i class="fal fa-xmark"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: "AppTitleBar",
|
||||
methods: {
|
||||
minimizeCompanion() {
|
||||
// @ts-ignore
|
||||
window.api.minimize()
|
||||
},
|
||||
closeCompanion() {
|
||||
// @ts-ignore
|
||||
window.api.quit();
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
37
src/components/ProgressBar.vue
Normal file
37
src/components/ProgressBar.vue
Normal file
@@ -0,0 +1,37 @@
|
||||
<template>
|
||||
<div class="flex flex-col items-center justify-center gap-1.5 w-64">
|
||||
<div class="text-xs text-zinc-400 flex justify-between w-full">
|
||||
<div class="font-bold capitalize">{{ stage }}</div>
|
||||
<div>{{ format(progress) }}%</div>
|
||||
</div>
|
||||
<div class="w-full h-2 bg-base-500 rounded-full">
|
||||
<div
|
||||
class="h-2 bg-primary-500 rounded-full"
|
||||
:style="{ width: `${progress}%` }"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: "ProgressBar",
|
||||
|
||||
props: {
|
||||
progress: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
stage: {
|
||||
type: String,
|
||||
default: ""
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
format(progress: number) {
|
||||
return Math.round(progress)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
16
src/components/VButton.vue
Normal file
16
src/components/VButton.vue
Normal file
@@ -0,0 +1,16 @@
|
||||
<template>
|
||||
<button
|
||||
type="button"
|
||||
@click="$emit('click')"
|
||||
class="bg-primary-500 py-1.5 px-4 rounded text-white"
|
||||
>
|
||||
<slot/>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: "VButton",
|
||||
emits: ['click']
|
||||
}
|
||||
</script>
|
||||
10
src/main.ts
Normal file
10
src/main.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { createApp } from 'vue'
|
||||
import './style.css'
|
||||
import App from './App.vue'
|
||||
import './samples/node-api'
|
||||
|
||||
createApp(App)
|
||||
.mount('#app')
|
||||
.$nextTick(() => {
|
||||
postMessage({ payload: 'removeLoading' }, '*')
|
||||
})
|
||||
13
src/samples/node-api.ts
Normal file
13
src/samples/node-api.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { lstat } from 'node:fs/promises'
|
||||
import { cwd } from 'node:process'
|
||||
import { ipcRenderer } from 'electron'
|
||||
|
||||
ipcRenderer.on('main-process-message', (_event, ...args) => {
|
||||
console.log('[Receive Main-process message]:', ...args)
|
||||
})
|
||||
|
||||
lstat(cwd()).then(stats => {
|
||||
console.log('[fs.lstat]', stats)
|
||||
}).catch(err => {
|
||||
console.error(err)
|
||||
})
|
||||
12
src/style.css
Normal file
12
src/style.css
Normal file
@@ -0,0 +1,12 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
body {
|
||||
-webkit-user-select: none;
|
||||
-webkit-app-region: drag;
|
||||
}
|
||||
|
||||
button, a, input, label {
|
||||
-webkit-app-region: no-drag;
|
||||
}
|
||||
7
src/vite-env.d.ts
vendored
Normal file
7
src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
declare module '*.vue' {
|
||||
import type { DefineComponent } from 'vue'
|
||||
const component: DefineComponent<{}, {}, any>
|
||||
export default component
|
||||
}
|
||||
Reference in New Issue
Block a user