Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b229225b6e | ||
|
|
5497ed6245 | ||
|
|
155bf652f2 | ||
|
|
86bd77d995 | ||
|
|
b38b356c22 |
37
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
37
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
name: Bug Report
|
||||||
|
description: Report a problem or regression
|
||||||
|
title: "[Bug]: "
|
||||||
|
labels:
|
||||||
|
- bug
|
||||||
|
body:
|
||||||
|
- type: input
|
||||||
|
id: app_version
|
||||||
|
attributes:
|
||||||
|
label: App version
|
||||||
|
description: The VidBee version (e.g., 1.2.3)
|
||||||
|
placeholder: 1.2.3
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: input
|
||||||
|
id: os_version
|
||||||
|
attributes:
|
||||||
|
label: OS version
|
||||||
|
description: Your operating system and version (e.g., macOS 14.2, Windows 11 23H2)
|
||||||
|
placeholder: macOS 14.2
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: actual
|
||||||
|
attributes:
|
||||||
|
label: Observed behavior
|
||||||
|
placeholder: What actually happened
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: logs
|
||||||
|
attributes:
|
||||||
|
label: Logs or screenshots
|
||||||
|
description: Paste relevant logs or add screenshots
|
||||||
|
placeholder: Attach files or paste logs here
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
38
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
38
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
name: Feature Request
|
||||||
|
description: Suggest an idea or improvement
|
||||||
|
title: "[Feature]: "
|
||||||
|
labels:
|
||||||
|
- enhancement
|
||||||
|
body:
|
||||||
|
- type: textarea
|
||||||
|
id: problem
|
||||||
|
attributes:
|
||||||
|
label: Problem to solve
|
||||||
|
description: What problem are you trying to solve?
|
||||||
|
placeholder: I want to...
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: proposal
|
||||||
|
attributes:
|
||||||
|
label: Proposed solution
|
||||||
|
description: Describe the feature or change you want
|
||||||
|
placeholder: It would be great if...
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: alternatives
|
||||||
|
attributes:
|
||||||
|
label: Alternatives considered
|
||||||
|
description: Other solutions or workarounds you considered
|
||||||
|
placeholder: I tried...
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
- type: textarea
|
||||||
|
id: extra
|
||||||
|
attributes:
|
||||||
|
label: Additional context
|
||||||
|
description: Add any other context or screenshots
|
||||||
|
placeholder: Links, screenshots, or related issues
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
13
.github/workflows/build.yml
vendored
13
.github/workflows/build.yml
vendored
@@ -76,10 +76,10 @@ jobs:
|
|||||||
FFMPEG_OUTPUT: ${{ matrix.ffmpeg_output }}
|
FFMPEG_OUTPUT: ${{ matrix.ffmpeg_output }}
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
curl -L "${{ matrix.ffmpeg_arm_url }}" -o ffmpeg-arm.zip
|
curl -fL --retry 3 --retry-delay 2 --retry-connrefused "${{ matrix.ffmpeg_arm_url }}" -o ffmpeg-arm.zip
|
||||||
unzip -q ffmpeg-arm.zip -d ffmpeg-arm
|
unzip -q ffmpeg-arm.zip -d ffmpeg-arm
|
||||||
|
|
||||||
curl -L "${{ matrix.ffmpeg_x86_url }}" -o ffmpeg-x86.zip
|
curl -fL --retry 3 --retry-delay 2 --retry-connrefused "${{ matrix.ffmpeg_x86_url }}" -o ffmpeg-x86.zip
|
||||||
unzip -q ffmpeg-x86.zip -d ffmpeg-x86
|
unzip -q ffmpeg-x86.zip -d ffmpeg-x86
|
||||||
|
|
||||||
arm_bin="ffmpeg-arm/${{ matrix.ffmpeg_inner_path }}"
|
arm_bin="ffmpeg-arm/${{ matrix.ffmpeg_inner_path }}"
|
||||||
@@ -103,7 +103,11 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
curl -L "${{ matrix.ffmpeg_url }}" -o ffmpeg.tar.xz
|
curl -fL --retry 3 --retry-delay 2 --retry-connrefused "${{ matrix.ffmpeg_url }}" -o ffmpeg.tar.xz
|
||||||
|
if ! tar -tf ffmpeg.tar.xz >/dev/null 2>&1; then
|
||||||
|
echo "::error::Downloaded ffmpeg archive is not a valid tar.xz"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
mkdir ffmpeg
|
mkdir ffmpeg
|
||||||
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 }}"
|
||||||
@@ -112,7 +116,7 @@ jobs:
|
|||||||
- name: Download yt-dlp binary
|
- name: Download yt-dlp binary
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
curl -L "https://github.com/yt-dlp/yt-dlp/releases/latest/download/${{ matrix.ytdlp_asset }}" -o "resources/${{ matrix.ytdlp_output }}"
|
curl -fL --retry 3 --retry-delay 2 --retry-connrefused "https://github.com/yt-dlp/yt-dlp/releases/latest/download/${{ matrix.ytdlp_asset }}" -o "resources/${{ matrix.ytdlp_output }}"
|
||||||
if [[ "${{ matrix.platform }}" == "linux" ]] || [[ "${{ matrix.platform }}" == "macos" ]]; then
|
if [[ "${{ matrix.platform }}" == "linux" ]] || [[ "${{ matrix.platform }}" == "macos" ]]; then
|
||||||
chmod +x "resources/${{ matrix.ytdlp_output }}"
|
chmod +x "resources/${{ matrix.ytdlp_output }}"
|
||||||
fi
|
fi
|
||||||
@@ -130,4 +134,3 @@ jobs:
|
|||||||
name: dist-${{ matrix.os }}
|
name: dist-${{ matrix.os }}
|
||||||
path: dist/
|
path: dist/
|
||||||
retention-days: 1
|
retention-days: 1
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"setup": "cp $CONDUCTOR_ROOT_PATH/resources/ resources/ && pnpm install",
|
"setup": "cp -r $CONDUCTOR_ROOT_PATH/resources resources/ && pnpm install",
|
||||||
"run": "pnpm run dev"
|
"run": "pnpm run dev"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "vidbee",
|
"name": "vidbee",
|
||||||
"version": "1.1.1",
|
"version": "1.1.2",
|
||||||
"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",
|
||||||
|
|||||||
@@ -21,6 +21,10 @@ export const resolveVideoFormatSelector = (options: DownloadOptions): string =>
|
|||||||
const format = options.format
|
const format = options.format
|
||||||
const audioFormat = options.audioFormat
|
const audioFormat = options.audioFormat
|
||||||
|
|
||||||
|
if (format && audioFormat === '') {
|
||||||
|
return format
|
||||||
|
}
|
||||||
|
|
||||||
if (format && (format.includes('/') || (audioFormat === undefined && format.includes('+')))) {
|
if (format && (format.includes('/') || (audioFormat === undefined && format.includes('+')))) {
|
||||||
return format
|
return format
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -114,10 +114,12 @@ const buildVideoFormatPreference = (settings: AppSettings): string => {
|
|||||||
combinations.push(video)
|
combinations.push(video)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Use bestvideo+bestaudio as fallback instead of 'best' to ensure merging
|
// Prefer merged formats, then allow 'best' as a compatibility fallback.
|
||||||
combinations.push('bestvideo+bestaudio')
|
combinations.push('bestvideo+bestaudio')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
combinations.push('best')
|
||||||
|
|
||||||
return dedupe(combinations).join('/')
|
return dedupe(combinations).join('/')
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -935,29 +937,33 @@ export function DownloadDialog({ onOpenSupportedSites, onOpenSettings }: Downloa
|
|||||||
{t('sites.viewAll')}
|
{t('sites.viewAll')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{settings.oneClickDownload && (
|
|
||||||
<div className="flex items-center gap-2 text-xs text-primary">
|
|
||||||
<div className="w-1.5 h-1.5 rounded-full bg-primary" />
|
|
||||||
<span>{t('download.oneClickDownloadEnabled')}</span>
|
|
||||||
{onOpenSettings && (
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
variant="ghost"
|
|
||||||
size="sm"
|
|
||||||
className="text-xs h-auto"
|
|
||||||
onClick={() => {
|
|
||||||
setOpen(false)
|
|
||||||
onOpenSettings()
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t('download.goToSettings')}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* One-click download indicator */}
|
||||||
|
{settings.oneClickDownload && (
|
||||||
|
<div className="w-full">
|
||||||
|
<div className="rounded-lg border bg-muted/30 p-2.5">
|
||||||
|
<div className="flex items-center gap-2 text-xs text-muted-foreground">
|
||||||
|
<span>{t('download.oneClickDownloadEnabled')}</span>
|
||||||
|
{onOpenSettings && (
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
className="text-xs h-auto"
|
||||||
|
onClick={() => {
|
||||||
|
setOpen(false)
|
||||||
|
onOpenSettings()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('download.goToSettings')}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Error Display */}
|
{/* Error Display */}
|
||||||
{error && (
|
{error && (
|
||||||
<div className="rounded-lg border border-destructive/20 bg-destructive/5 p-3">
|
<div className="rounded-lg border border-destructive/20 bg-destructive/5 p-3">
|
||||||
|
|||||||
@@ -73,23 +73,31 @@ export function FormatSelector({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Filter and sort formats
|
// Filter and sort formats
|
||||||
// Exclude m3u8/HLS formats as they are streaming formats not suitable for direct download
|
// Exclude m3u8/HLS formats as they are streaming formats not suitable for direct download
|
||||||
const videos = formats.filter(
|
const isVideoFormat = (format: VideoFormat) =>
|
||||||
(f) =>
|
format.video_ext !== 'none' && format.vcodec && format.vcodec !== 'none'
|
||||||
f.video_ext !== 'none' &&
|
const isAudioFormat = (format: VideoFormat) =>
|
||||||
f.vcodec &&
|
format.acodec &&
|
||||||
f.vcodec !== 'none' &&
|
format.acodec !== 'none' &&
|
||||||
f.protocol !== 'm3u8' &&
|
(format.video_ext === 'none' || !format.video_ext)
|
||||||
f.protocol !== 'm3u8_native'
|
const isHlsFormat = (format: VideoFormat) =>
|
||||||
|
format.protocol === 'm3u8' || format.protocol === 'm3u8_native'
|
||||||
|
|
||||||
|
const videoCandidates = formats.filter(
|
||||||
|
(format) => isVideoFormat(format) && !isHlsFormat(format)
|
||||||
)
|
)
|
||||||
const audios = formats.filter(
|
const audioCandidates = formats.filter(
|
||||||
(f) =>
|
(format) => isAudioFormat(format) && !isHlsFormat(format)
|
||||||
f.acodec &&
|
|
||||||
f.acodec !== 'none' &&
|
|
||||||
(f.video_ext === 'none' || !f.video_ext) &&
|
|
||||||
f.protocol !== 'm3u8' &&
|
|
||||||
f.protocol !== 'm3u8_native'
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const videos =
|
||||||
|
videoCandidates.length > 0
|
||||||
|
? videoCandidates
|
||||||
|
: formats.filter((format) => isVideoFormat(format))
|
||||||
|
const audios =
|
||||||
|
audioCandidates.length > 0
|
||||||
|
? audioCandidates
|
||||||
|
: formats.filter((format) => isAudioFormat(format))
|
||||||
|
|
||||||
// Apply showMoreFormats filter
|
// Apply showMoreFormats filter
|
||||||
const filteredVideos = settings.showMoreFormats
|
const filteredVideos = settings.showMoreFormats
|
||||||
? videos
|
? videos
|
||||||
@@ -99,6 +107,9 @@ export function FormatSelector({
|
|||||||
? audios
|
? audios
|
||||||
: audios.filter((f) => f.ext !== 'webm')
|
: audios.filter((f) => f.ext !== 'webm')
|
||||||
|
|
||||||
|
const finalVideos = filteredVideos.length > 0 ? filteredVideos : videos
|
||||||
|
const finalAudios = filteredAudios.length > 0 ? filteredAudios : audios
|
||||||
|
|
||||||
// Sort formats by quality (best first)
|
// Sort formats by quality (best first)
|
||||||
const sortVideoFormatsByQuality = (a: VideoFormat, b: VideoFormat) => {
|
const sortVideoFormatsByQuality = (a: VideoFormat, b: VideoFormat) => {
|
||||||
// Sort by height (higher is better)
|
// Sort by height (higher is better)
|
||||||
@@ -138,23 +149,23 @@ export function FormatSelector({
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
filteredVideos.sort(sortVideoFormatsByQuality)
|
finalVideos.sort(sortVideoFormatsByQuality)
|
||||||
filteredAudios.sort(sortAudioFormatsByQuality)
|
finalAudios.sort(sortAudioFormatsByQuality)
|
||||||
|
|
||||||
setVideoFormats(filteredVideos)
|
setVideoFormats(finalVideos)
|
||||||
setAudioFormats(filteredAudios)
|
setAudioFormats(finalAudios)
|
||||||
|
|
||||||
// Auto-select best format based on preferences
|
// Auto-select best format based on preferences
|
||||||
if (filteredVideos.length > 0 && !selectedVideo) {
|
if (finalVideos.length > 0 && !selectedVideo) {
|
||||||
const preferred = pickVideoFormatForPreset(filteredVideos, settings.oneClickQuality)
|
const preferred = pickVideoFormatForPreset(finalVideos, settings.oneClickQuality)
|
||||||
if (preferred) {
|
if (preferred) {
|
||||||
setSelectedVideo(preferred.format_id)
|
setSelectedVideo(preferred.format_id)
|
||||||
onVideoFormatChange?.(preferred.format_id)
|
onVideoFormatChange?.(preferred.format_id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filteredAudios.length > 0 && !selectedAudio) {
|
if (finalAudios.length > 0 && !selectedAudio) {
|
||||||
const best = filteredAudios[0]
|
const best = finalAudios[0]
|
||||||
setSelectedAudio(best.format_id)
|
setSelectedAudio(best.format_id)
|
||||||
onAudioFormatChange?.(best.format_id)
|
onAudioFormatChange?.(best.format_id)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user