Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7074f698e5 | ||
|
|
b254bb6dba | ||
|
|
1caca0ea9a | ||
|
|
12ec142a77 |
3
.github/workflows/build.yml
vendored
3
.github/workflows/build.yml
vendored
@@ -68,6 +68,8 @@ jobs:
|
|||||||
$source = Join-Path 'ffmpeg' '${{ matrix.ffmpeg_inner_path }}'
|
$source = Join-Path 'ffmpeg' '${{ matrix.ffmpeg_inner_path }}'
|
||||||
$destination = Join-Path 'resources' '${{ matrix.ffmpeg_output }}'
|
$destination = Join-Path 'resources' '${{ matrix.ffmpeg_output }}'
|
||||||
Copy-Item -Path $source -Destination $destination -Force
|
Copy-Item -Path $source -Destination $destination -Force
|
||||||
|
Remove-Item ffmpeg.zip -Force
|
||||||
|
Remove-Item ffmpeg -Recurse -Force
|
||||||
|
|
||||||
- name: Download ffmpeg binary (macOS)
|
- name: Download ffmpeg binary (macOS)
|
||||||
if: matrix.platform == 'macos'
|
if: matrix.platform == 'macos'
|
||||||
@@ -112,6 +114,7 @@ jobs:
|
|||||||
tar -xf ffmpeg.tar.xz -C ffmpeg
|
tar -xf ffmpeg.tar.xz -C ffmpeg
|
||||||
cp "ffmpeg/${{ matrix.ffmpeg_inner_path }}" "resources/${{ matrix.ffmpeg_output }}"
|
cp "ffmpeg/${{ matrix.ffmpeg_inner_path }}" "resources/${{ matrix.ffmpeg_output }}"
|
||||||
chmod +x "resources/${{ matrix.ffmpeg_output }}"
|
chmod +x "resources/${{ matrix.ffmpeg_output }}"
|
||||||
|
rm -rf ffmpeg.tar.xz ffmpeg
|
||||||
|
|
||||||
- name: Download yt-dlp binary
|
- name: Download yt-dlp binary
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|||||||
6
.github/workflows/release.yml
vendored
6
.github/workflows/release.yml
vendored
@@ -42,3 +42,9 @@ jobs:
|
|||||||
dist/*.blockmap
|
dist/*.blockmap
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }}
|
||||||
|
|
||||||
|
- name: Notify Cloudflare Pages
|
||||||
|
env:
|
||||||
|
CLOUDFLARE_WEBHOOK_URL: ${{ secrets.CLOUDFLARE_WEBHOOK_URL }}
|
||||||
|
run: |
|
||||||
|
curl -X POST "$CLOUDFLARE_WEBHOOK_URL"
|
||||||
|
|||||||
@@ -8,11 +8,16 @@ protocols:
|
|||||||
- vidbee
|
- vidbee
|
||||||
files:
|
files:
|
||||||
- '!**/.vscode/*'
|
- '!**/.vscode/*'
|
||||||
|
- '!**/.context/**'
|
||||||
|
- '!**/.github/**'
|
||||||
- '!src/*'
|
- '!src/*'
|
||||||
- '!electron.vite.config.{js,ts,mjs,cjs}'
|
- '!electron.vite.config.{js,ts,mjs,cjs}'
|
||||||
- '!{.eslintcache,eslint.config.mjs,dev-app-update.yml,CHANGELOG.md}'
|
- '!{.eslintcache,eslint.config.mjs,dev-app-update.yml,CHANGELOG.md}'
|
||||||
- '!{.env,.env.*,.npmrc,pnpm-lock.yaml}'
|
- '!{.env,.env.*,.npmrc,pnpm-lock.yaml}'
|
||||||
- '!{tsconfig.json,tsconfig.node.json,tsconfig.web.json}'
|
- '!{tsconfig.json,tsconfig.node.json,tsconfig.web.json}'
|
||||||
|
- '!extension/**'
|
||||||
|
- '!monkey/**'
|
||||||
|
- '!screenshots/**'
|
||||||
asarUnpack:
|
asarUnpack:
|
||||||
- resources/**
|
- resources/**
|
||||||
win:
|
win:
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "vidbee",
|
"name": "vidbee",
|
||||||
"version": "1.1.6",
|
"version": "1.1.7",
|
||||||
"description": "A modern Electron application for downloading videos and audios",
|
"description": "A modern Electron application for downloading videos and audios",
|
||||||
"main": "./out/main/index.js",
|
"main": "./out/main/index.js",
|
||||||
"author": "VidBee",
|
"author": "VidBee",
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
const fs = require('node:fs')
|
const fs = require('node:fs')
|
||||||
const path = require('node:path')
|
const path = require('node:path')
|
||||||
const os = require('node:os')
|
const os = require('node:os')
|
||||||
const { execSync } = require('node:child_process')
|
const { execSync, spawnSync } = require('node:child_process')
|
||||||
const https = require('node:https')
|
const https = require('node:https')
|
||||||
const http = require('node:http')
|
const http = require('node:http')
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ const PLATFORM_CONFIG = {
|
|||||||
extract: 'unzip',
|
extract: 'unzip',
|
||||||
release: {
|
release: {
|
||||||
repos: ['yt-dlp/FFmpeg-Builds', 'BtbN/FFmpeg-Builds'],
|
repos: ['yt-dlp/FFmpeg-Builds', 'BtbN/FFmpeg-Builds'],
|
||||||
assetPattern: /win64.*gpl.*\.zip$/i,
|
assetPattern: /ffmpeg-master-latest-win64-gpl\.zip$/i,
|
||||||
binaryName: 'ffmpeg.exe'
|
binaryName: 'ffmpeg.exe'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -79,7 +79,7 @@ const PLATFORM_CONFIG = {
|
|||||||
extract: 'tar',
|
extract: 'tar',
|
||||||
release: {
|
release: {
|
||||||
repos: ['yt-dlp/FFmpeg-Builds', 'BtbN/FFmpeg-Builds'],
|
repos: ['yt-dlp/FFmpeg-Builds', 'BtbN/FFmpeg-Builds'],
|
||||||
assetPattern: /linux64.*gpl.*\.tar\.xz$/i,
|
assetPattern: /ffmpeg-master-latest-linux64-gpl\.tar\.xz$/i,
|
||||||
binaryName: 'ffmpeg'
|
binaryName: 'ffmpeg'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -125,24 +125,43 @@ function downloadFile(url, dest) {
|
|||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const protocol = url.startsWith('https') ? https : http
|
const protocol = url.startsWith('https') ? https : http
|
||||||
const file = fs.createWriteStream(dest)
|
const file = fs.createWriteStream(dest)
|
||||||
|
let downloadedBytes = 0
|
||||||
|
|
||||||
const request = protocol.get(url, { headers: getDownloadHeaders(url) }, (response) => {
|
const request = protocol.get(url, { headers: getDownloadHeaders(url) }, (response) => {
|
||||||
if (response.statusCode === 302 || response.statusCode === 301) {
|
if (response.statusCode === 302 || response.statusCode === 301) {
|
||||||
// Handle redirect
|
// Handle redirect
|
||||||
file.close()
|
file.close()
|
||||||
safeUnlink(dest)
|
safeUnlink(dest)
|
||||||
return downloadFile(response.headers.location, dest).then(resolve).catch(reject)
|
const redirectUrl = response.headers.location
|
||||||
|
if (!redirectUrl) {
|
||||||
|
return reject(new Error(`Redirect without location for ${url}`))
|
||||||
|
}
|
||||||
|
log(`Redirected to ${redirectUrl}`, 'info')
|
||||||
|
return downloadFile(redirectUrl, dest).then(resolve).catch(reject)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const contentLength = response.headers['content-length']
|
||||||
if (response.statusCode !== 200) {
|
if (response.statusCode !== 200) {
|
||||||
file.close()
|
file.close()
|
||||||
safeUnlink(dest)
|
safeUnlink(dest)
|
||||||
return reject(new Error(`Failed to download: ${response.statusCode}`))
|
return reject(
|
||||||
|
new Error(
|
||||||
|
`Failed to download ${url}: ${response.statusCode} (length: ${contentLength || 'unknown'})`
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
response.on('data', (chunk) => {
|
||||||
|
downloadedBytes += chunk.length
|
||||||
|
})
|
||||||
|
|
||||||
response.pipe(file)
|
response.pipe(file)
|
||||||
file.on('finish', () => {
|
file.on('finish', () => {
|
||||||
file.close()
|
file.close()
|
||||||
|
log(
|
||||||
|
`Downloaded ${formatBytes(downloadedBytes)} from ${url}`,
|
||||||
|
downloadedBytes ? 'success' : 'warn'
|
||||||
|
)
|
||||||
resolve()
|
resolve()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -154,6 +173,7 @@ function downloadFile(url, dest) {
|
|||||||
request.on('error', (err) => {
|
request.on('error', (err) => {
|
||||||
file.close()
|
file.close()
|
||||||
safeUnlink(dest)
|
safeUnlink(dest)
|
||||||
|
log(`Download error for ${url}: ${err.message}`, 'error')
|
||||||
reject(err)
|
reject(err)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -163,6 +183,7 @@ async function downloadFileWithRetry(url, dest, retries = 3, delayMs = 2000) {
|
|||||||
let lastError
|
let lastError
|
||||||
for (let attempt = 1; attempt <= retries; attempt += 1) {
|
for (let attempt = 1; attempt <= retries; attempt += 1) {
|
||||||
try {
|
try {
|
||||||
|
log(`Downloading ${url} (attempt ${attempt}/${retries})...`, 'download')
|
||||||
await downloadFile(url, dest)
|
await downloadFile(url, dest)
|
||||||
return
|
return
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -170,7 +191,7 @@ async function downloadFileWithRetry(url, dest, retries = 3, delayMs = 2000) {
|
|||||||
safeUnlink(dest)
|
safeUnlink(dest)
|
||||||
if (attempt < retries) {
|
if (attempt < retries) {
|
||||||
const backoff = delayMs * attempt
|
const backoff = delayMs * attempt
|
||||||
log(`Download failed (attempt ${attempt}/${retries}): ${error.message}`, 'warn')
|
log(`Download failed for ${url} (attempt ${attempt}/${retries}): ${error.message}`, 'warn')
|
||||||
await new Promise((resolve) => setTimeout(resolve, backoff))
|
await new Promise((resolve) => setTimeout(resolve, backoff))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -298,6 +319,37 @@ function fileExists(filePath) {
|
|||||||
return fs.existsSync(filePath)
|
return fs.existsSync(filePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatBytes(bytes) {
|
||||||
|
if (!bytes || bytes <= 0) {
|
||||||
|
return 'unknown size'
|
||||||
|
}
|
||||||
|
if (bytes >= 1024 * 1024) {
|
||||||
|
return `${Math.round(bytes / (1024 * 1024))} MB`
|
||||||
|
}
|
||||||
|
return `${Math.round(bytes / 1024)} KB`
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkBinary(filePath, args, label) {
|
||||||
|
const result = spawnSync(filePath, args, {
|
||||||
|
encoding: 'utf8',
|
||||||
|
timeout: 8000,
|
||||||
|
windowsHide: true
|
||||||
|
})
|
||||||
|
|
||||||
|
if (result.error) {
|
||||||
|
return { ok: false, message: result.error.message }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.status !== 0) {
|
||||||
|
const output = `${result.stdout || ''}\n${result.stderr || ''}`.trim()
|
||||||
|
return { ok: false, message: output || `exit code ${result.status}` }
|
||||||
|
}
|
||||||
|
|
||||||
|
const output = `${result.stdout || ''}\n${result.stderr || ''}`.trim()
|
||||||
|
const firstLine = output.split(/\r?\n/).find((line) => line.trim())
|
||||||
|
return { ok: true, message: firstLine ? firstLine.trim() : `${label} version check ok` }
|
||||||
|
}
|
||||||
|
|
||||||
function getDenoAssetName(platform, arch) {
|
function getDenoAssetName(platform, arch) {
|
||||||
if (platform === 'win32') {
|
if (platform === 'win32') {
|
||||||
if (arch === 'arm64') {
|
if (arch === 'arm64') {
|
||||||
@@ -330,6 +382,10 @@ async function downloadYtDlp(config) {
|
|||||||
const outputPath = path.join(RESOURCES_DIR, output)
|
const outputPath = path.join(RESOURCES_DIR, output)
|
||||||
|
|
||||||
if (fileExists(outputPath)) {
|
if (fileExists(outputPath)) {
|
||||||
|
const validation = checkBinary(outputPath, ['--version'], 'yt-dlp')
|
||||||
|
if (!validation.ok) {
|
||||||
|
log(`Existing ${output} failed version check: ${validation.message}`, 'warn')
|
||||||
|
}
|
||||||
log(`${output} already exists, skipping download`, 'info')
|
log(`${output} already exists, skipping download`, 'info')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -342,6 +398,11 @@ async function downloadYtDlp(config) {
|
|||||||
await downloadFileWithRetry(url, tempPath)
|
await downloadFileWithRetry(url, tempPath)
|
||||||
fs.renameSync(tempPath, outputPath)
|
fs.renameSync(tempPath, outputPath)
|
||||||
setExecutable(outputPath)
|
setExecutable(outputPath)
|
||||||
|
const validation = checkBinary(outputPath, ['--version'], 'yt-dlp')
|
||||||
|
if (!validation.ok) {
|
||||||
|
safeUnlink(outputPath)
|
||||||
|
throw new Error(`Downloaded ${output} failed version check: ${validation.message}`)
|
||||||
|
}
|
||||||
log(`Downloaded ${output} successfully`, 'success')
|
log(`Downloaded ${output} successfully`, 'success')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (fs.existsSync(tempPath)) {
|
if (fs.existsSync(tempPath)) {
|
||||||
@@ -356,6 +417,10 @@ async function downloadFfmpegWindows(config) {
|
|||||||
const outputPath = path.join(RESOURCES_DIR, output)
|
const outputPath = path.join(RESOURCES_DIR, output)
|
||||||
|
|
||||||
if (fileExists(outputPath)) {
|
if (fileExists(outputPath)) {
|
||||||
|
const validation = checkBinary(outputPath, ['-version'], 'ffmpeg')
|
||||||
|
if (!validation.ok) {
|
||||||
|
log(`Existing ${output} failed version check: ${validation.message}`, 'warn')
|
||||||
|
}
|
||||||
log(`${output} already exists, skipping download`, 'info')
|
log(`${output} already exists, skipping download`, 'info')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -392,6 +457,11 @@ async function downloadFfmpegWindows(config) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fs.copyFileSync(sourcePath, outputPath)
|
fs.copyFileSync(sourcePath, outputPath)
|
||||||
|
const validation = checkBinary(outputPath, ['-version'], 'ffmpeg')
|
||||||
|
if (!validation.ok) {
|
||||||
|
safeUnlink(outputPath)
|
||||||
|
throw new Error(`Downloaded ${output} failed version check: ${validation.message}`)
|
||||||
|
}
|
||||||
log(`Downloaded ${output} successfully`, 'success')
|
log(`Downloaded ${output} successfully`, 'success')
|
||||||
|
|
||||||
// Cleanup
|
// Cleanup
|
||||||
@@ -416,6 +486,10 @@ async function downloadFfmpegMac(config) {
|
|||||||
const outputPath = path.join(RESOURCES_DIR, output)
|
const outputPath = path.join(RESOURCES_DIR, output)
|
||||||
|
|
||||||
if (fileExists(outputPath)) {
|
if (fileExists(outputPath)) {
|
||||||
|
const validation = checkBinary(outputPath, ['-version'], 'ffmpeg')
|
||||||
|
if (!validation.ok) {
|
||||||
|
log(`Existing ${output} failed version check: ${validation.message}`, 'warn')
|
||||||
|
}
|
||||||
log(`${output} already exists, skipping download`, 'info')
|
log(`${output} already exists, skipping download`, 'info')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -448,6 +522,11 @@ async function downloadFfmpegMac(config) {
|
|||||||
|
|
||||||
fs.copyFileSync(sourcePath, outputPath)
|
fs.copyFileSync(sourcePath, outputPath)
|
||||||
setExecutable(outputPath)
|
setExecutable(outputPath)
|
||||||
|
const validation = checkBinary(outputPath, ['-version'], 'ffmpeg')
|
||||||
|
if (!validation.ok) {
|
||||||
|
safeUnlink(outputPath)
|
||||||
|
throw new Error(`Downloaded ${output} failed version check: ${validation.message}`)
|
||||||
|
}
|
||||||
log(`Downloaded ${output} successfully`, 'success')
|
log(`Downloaded ${output} successfully`, 'success')
|
||||||
|
|
||||||
// Cleanup
|
// Cleanup
|
||||||
@@ -465,6 +544,10 @@ async function downloadFfmpegLinux(config) {
|
|||||||
const outputPath = path.join(RESOURCES_DIR, output)
|
const outputPath = path.join(RESOURCES_DIR, output)
|
||||||
|
|
||||||
if (fileExists(outputPath)) {
|
if (fileExists(outputPath)) {
|
||||||
|
const validation = checkBinary(outputPath, ['-version'], 'ffmpeg')
|
||||||
|
if (!validation.ok) {
|
||||||
|
log(`Existing ${output} failed version check: ${validation.message}`, 'warn')
|
||||||
|
}
|
||||||
log(`${output} already exists, skipping download`, 'info')
|
log(`${output} already exists, skipping download`, 'info')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -502,6 +585,11 @@ async function downloadFfmpegLinux(config) {
|
|||||||
|
|
||||||
fs.copyFileSync(sourcePath, outputPath)
|
fs.copyFileSync(sourcePath, outputPath)
|
||||||
setExecutable(outputPath)
|
setExecutable(outputPath)
|
||||||
|
const validation = checkBinary(outputPath, ['-version'], 'ffmpeg')
|
||||||
|
if (!validation.ok) {
|
||||||
|
safeUnlink(outputPath)
|
||||||
|
throw new Error(`Downloaded ${output} failed version check: ${validation.message}`)
|
||||||
|
}
|
||||||
log(`Downloaded ${output} successfully`, 'success')
|
log(`Downloaded ${output} successfully`, 'success')
|
||||||
|
|
||||||
// Cleanup
|
// Cleanup
|
||||||
@@ -528,6 +616,10 @@ async function downloadDenoRuntime() {
|
|||||||
const outputPath = path.join(RESOURCES_DIR, outputName)
|
const outputPath = path.join(RESOURCES_DIR, outputName)
|
||||||
|
|
||||||
if (fileExists(outputPath)) {
|
if (fileExists(outputPath)) {
|
||||||
|
const validation = checkBinary(outputPath, ['--version'], 'deno')
|
||||||
|
if (!validation.ok) {
|
||||||
|
log(`Existing ${outputName} failed version check: ${validation.message}`, 'warn')
|
||||||
|
}
|
||||||
log(`${outputName} already exists, skipping download`, 'info')
|
log(`${outputName} already exists, skipping download`, 'info')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -549,6 +641,11 @@ async function downloadDenoRuntime() {
|
|||||||
|
|
||||||
fs.copyFileSync(sourcePath, outputPath)
|
fs.copyFileSync(sourcePath, outputPath)
|
||||||
setExecutable(outputPath)
|
setExecutable(outputPath)
|
||||||
|
const validation = checkBinary(outputPath, ['--version'], 'deno')
|
||||||
|
if (!validation.ok) {
|
||||||
|
safeUnlink(outputPath)
|
||||||
|
throw new Error(`Downloaded ${outputName} failed version check: ${validation.message}`)
|
||||||
|
}
|
||||||
log(`Downloaded ${outputName} successfully`, 'success')
|
log(`Downloaded ${outputName} successfully`, 'success')
|
||||||
|
|
||||||
fs.unlinkSync(tempZip)
|
fs.unlinkSync(tempZip)
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ export const buildDownloadArgs = (
|
|||||||
settings: AppSettings,
|
settings: AppSettings,
|
||||||
jsRuntimeArgs: string[] = []
|
jsRuntimeArgs: string[] = []
|
||||||
): string[] => {
|
): string[] => {
|
||||||
const args: string[] = ['--no-playlist', '--embed-chapters', '--no-mtime']
|
const args: string[] = ['--no-playlist', '--no-mtime']
|
||||||
|
|
||||||
// Add encoding support for proper handling of non-ASCII characters
|
// Add encoding support for proper handling of non-ASCII characters
|
||||||
args.push('--encoding', 'utf-8')
|
args.push('--encoding', 'utf-8')
|
||||||
@@ -95,11 +95,26 @@ export const buildDownloadArgs = (
|
|||||||
args.push('--download-sections', `*${start}-${end || ''}`)
|
args.push('--download-sections', `*${start}-${end || ''}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const embedSubs = settings.embedSubs
|
||||||
|
const embedMetadata = settings.embedMetadata
|
||||||
|
const embedChapters = settings.embedChapters
|
||||||
|
|
||||||
// Subtitles
|
// Subtitles
|
||||||
if (options.downloadSubs) {
|
if (options.downloadSubs || embedSubs) {
|
||||||
args.push('--write-subs', '--sub-langs', 'all')
|
args.push('--sub-langs', 'all')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options.downloadSubs) {
|
||||||
|
args.push('--write-subs')
|
||||||
|
}
|
||||||
|
|
||||||
|
args.push(embedSubs ? '--embed-subs' : '--no-embed-subs')
|
||||||
|
if (process.platform !== 'darwin') {
|
||||||
|
args.push(settings.embedThumbnail ? '--embed-thumbnail' : '--no-embed-thumbnail')
|
||||||
|
}
|
||||||
|
args.push(embedMetadata ? '--embed-metadata' : '--no-embed-metadata')
|
||||||
|
args.push(embedChapters ? '--embed-chapters' : '--no-embed-chapters')
|
||||||
|
|
||||||
// Output path with proper encoding handling
|
// Output path with proper encoding handling
|
||||||
const baseDownloadPath = options.customDownloadPath?.trim() || downloadPath
|
const baseDownloadPath = options.customDownloadPath?.trim() || downloadPath
|
||||||
const filenameTemplate = sanitizeFilenameTemplate(
|
const filenameTemplate = sanitizeFilenameTemplate(
|
||||||
|
|||||||
@@ -89,6 +89,7 @@
|
|||||||
"currentLocation": "Current download location - ",
|
"currentLocation": "Current download location - ",
|
||||||
"downloadLocation": "Download location",
|
"downloadLocation": "Download location",
|
||||||
"downloadSubs": "Download subtitles if available",
|
"downloadSubs": "Download subtitles if available",
|
||||||
|
"downloadSubsHint": "Save subtitles as separate files when available",
|
||||||
"end": "End",
|
"end": "End",
|
||||||
"endHint": "If kept empty, it will be downloaded to the end",
|
"endHint": "If kept empty, it will be downloaded to the end",
|
||||||
"endPlaceholder": "10:00",
|
"endPlaceholder": "10:00",
|
||||||
@@ -420,6 +421,14 @@
|
|||||||
"launchAtLoginUnsupported": "Auto launch is only available on macOS and Windows.",
|
"launchAtLoginUnsupported": "Auto launch is only available on macOS and Windows.",
|
||||||
"enableAnalytics": "Help improve VidBee",
|
"enableAnalytics": "Help improve VidBee",
|
||||||
"enableAnalyticsDescription": "Share anonymous usage data to help us understand how the app is used and prioritize improvements.",
|
"enableAnalyticsDescription": "Share anonymous usage data to help us understand how the app is used and prioritize improvements.",
|
||||||
|
"embedChapters": "Embed chapters",
|
||||||
|
"embedChaptersDescription": "Add chapter markers to the file when available",
|
||||||
|
"embedMetadata": "Embed metadata",
|
||||||
|
"embedMetadataDescription": "Write title, artist, and other metadata when available",
|
||||||
|
"embedSubs": "Embed subtitles",
|
||||||
|
"embedSubsDescription": "Embed subtitles into the video file (mp4, webm, mkv)",
|
||||||
|
"embedThumbnail": "Embed thumbnail",
|
||||||
|
"embedThumbnailDescription": "Add the thumbnail as cover art",
|
||||||
"maxConcurrentDownloads": "Maximum number of active downloads",
|
"maxConcurrentDownloads": "Maximum number of active downloads",
|
||||||
"maxConcurrentDownloadsDescription": "Maximum number of simultaneous downloads",
|
"maxConcurrentDownloadsDescription": "Maximum number of simultaneous downloads",
|
||||||
"none": "None",
|
"none": "None",
|
||||||
|
|||||||
@@ -519,6 +519,94 @@ export function Settings() {
|
|||||||
</Item>
|
</Item>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Item variant="muted">
|
||||||
|
<ItemContent>
|
||||||
|
<ItemTitle>{t('settings.embedSubs')}</ItemTitle>
|
||||||
|
<ItemDescription>{t('settings.embedSubsDescription')}</ItemDescription>
|
||||||
|
</ItemContent>
|
||||||
|
<ItemActions>
|
||||||
|
<Switch
|
||||||
|
checked={settings.embedSubs ?? false}
|
||||||
|
onCheckedChange={(value) => {
|
||||||
|
try {
|
||||||
|
handleSettingChange('embedSubs', value)
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('[Settings] Error toggling embedSubs:', error)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</ItemActions>
|
||||||
|
</Item>
|
||||||
|
|
||||||
|
<ItemSeparator />
|
||||||
|
|
||||||
|
{platform !== 'darwin' && (
|
||||||
|
<>
|
||||||
|
<Item variant="muted">
|
||||||
|
<ItemContent>
|
||||||
|
<ItemTitle>{t('settings.embedThumbnail')}</ItemTitle>
|
||||||
|
<ItemDescription>{t('settings.embedThumbnailDescription')}</ItemDescription>
|
||||||
|
</ItemContent>
|
||||||
|
<ItemActions>
|
||||||
|
<Switch
|
||||||
|
checked={settings.embedThumbnail ?? false}
|
||||||
|
onCheckedChange={(value) => {
|
||||||
|
try {
|
||||||
|
handleSettingChange('embedThumbnail', value)
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('[Settings] Error toggling embedThumbnail:', error)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</ItemActions>
|
||||||
|
</Item>
|
||||||
|
|
||||||
|
<ItemSeparator />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Item variant="muted">
|
||||||
|
<ItemContent>
|
||||||
|
<ItemTitle>{t('settings.embedMetadata')}</ItemTitle>
|
||||||
|
<ItemDescription>{t('settings.embedMetadataDescription')}</ItemDescription>
|
||||||
|
</ItemContent>
|
||||||
|
<ItemActions>
|
||||||
|
<Switch
|
||||||
|
checked={settings.embedMetadata ?? false}
|
||||||
|
onCheckedChange={(value) => {
|
||||||
|
try {
|
||||||
|
handleSettingChange('embedMetadata', value)
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('[Settings] Error toggling embedMetadata:', error)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</ItemActions>
|
||||||
|
</Item>
|
||||||
|
|
||||||
|
<ItemSeparator />
|
||||||
|
|
||||||
|
<Item variant="muted">
|
||||||
|
<ItemContent>
|
||||||
|
<ItemTitle>{t('settings.embedChapters')}</ItemTitle>
|
||||||
|
<ItemDescription>{t('settings.embedChaptersDescription')}</ItemDescription>
|
||||||
|
</ItemContent>
|
||||||
|
<ItemActions>
|
||||||
|
<Switch
|
||||||
|
checked={settings.embedChapters ?? true}
|
||||||
|
onCheckedChange={(value) => {
|
||||||
|
try {
|
||||||
|
handleSettingChange('embedChapters', value)
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('[Settings] Error toggling embedChapters:', error)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</ItemActions>
|
||||||
|
</Item>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Item variant="muted">
|
<Item variant="muted">
|
||||||
<ItemContent>
|
<ItemContent>
|
||||||
|
|||||||
@@ -271,6 +271,10 @@ export interface AppSettings {
|
|||||||
autoUpdate: boolean
|
autoUpdate: boolean
|
||||||
subscriptionOnlyLatestDefault: boolean
|
subscriptionOnlyLatestDefault: boolean
|
||||||
enableAnalytics: boolean
|
enableAnalytics: boolean
|
||||||
|
embedSubs: boolean
|
||||||
|
embedThumbnail: boolean
|
||||||
|
embedMetadata: boolean
|
||||||
|
embedChapters: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DEFAULT_SUBSCRIPTION_FILENAME_TEMPLATE = '%(uploader)s/%(title)s.%(ext)s'
|
export const DEFAULT_SUBSCRIPTION_FILENAME_TEMPLATE = '%(uploader)s/%(title)s.%(ext)s'
|
||||||
@@ -294,5 +298,9 @@ export const defaultSettings: AppSettings = {
|
|||||||
launchAtLogin: false,
|
launchAtLogin: false,
|
||||||
autoUpdate: true,
|
autoUpdate: true,
|
||||||
subscriptionOnlyLatestDefault: true,
|
subscriptionOnlyLatestDefault: true,
|
||||||
enableAnalytics: true
|
enableAnalytics: true,
|
||||||
|
embedSubs: true,
|
||||||
|
embedThumbnail: true,
|
||||||
|
embedMetadata: true,
|
||||||
|
embedChapters: true
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user