Compare commits

..

5 Commits

Author SHA1 Message Date
Nexmoe
b229225b6e chore: release v1.1.2 2025-12-26 13:04:52 +08:00
Nexmoe
5497ed6245 chore(templates): simplify bug report (#57) 2025-12-26 13:04:30 +08:00
Nexmoe
155bf652f2 fix(ci): harden download steps (#56) 2025-12-26 13:02:21 +08:00
Nexmoe
86bd77d995 fix(download): improve format fallbacks (#55) 2025-12-26 13:02:07 +08:00
Nexmoe
b38b356c22 feat(issue): add issue templates (#54) 2025-12-26 12:53:51 +08:00
8 changed files with 150 additions and 51 deletions

37
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View 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

View 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

View File

@@ -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

View File

@@ -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"
} }
} }

View File

@@ -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",

View File

@@ -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
} }

View File

@@ -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">

View File

@@ -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)
} }