Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ jobs:
libwebkit2gtk-4.1-dev \
librsvg2-dev \
patchelf \
squashfs-tools \
libssl-dev \
libgtk-3-dev \
libayatana-appindicator3-dev
Expand Down Expand Up @@ -129,6 +130,19 @@ jobs:
bash scripts/fix-deb-deep-link-desktop-entry.sh \
"src-tauri/target/${{ matrix.target }}/release/bundle/deb"

- name: Repair AppImage desktop runtime compatibility
if: matrix.os_name == 'linux'
run: |
APPIMAGE_DIR="src-tauri/target/${{ matrix.target }}/release/bundle/appimage"
APPIMAGE=$(find "$APPIMAGE_DIR" -maxdepth 1 -type f -name "*.AppImage" | head -n 1)
test -n "$APPIMAGE"
bash scripts/repair-appimage-runtime.sh "$APPIMAGE" "youwee"

# The AppImage payload changed after Tauri produced updater artifacts.
# Regenerate its signature so latest.json always matches the release file.
rm -f "$APPIMAGE.sig"
bun run tauri signer sign "$APPIMAGE"
Comment on lines +141 to +144

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Pass the signing key to the re-signing step

The signing credentials are scoped to the preceding tauri-action step, so this subsequent shell step does not receive TAURI_SIGNING_PRIVATE_KEY or its password. On every Linux release, after the AppImage is rebuilt and its original .sig is deleted, tauri signer sign will fail for lack of a private key and the release job cannot publish artifacts. Add the same signing env variables to this step (or promote them to job-level env).

Useful? React with 👍 / 👎.


- name: Rename artifacts with friendly names (macOS)
if: matrix.os_name == 'macos'
run: |
Expand Down
33 changes: 31 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ jobs:
libwebkit2gtk-4.1-dev \
librsvg2-dev \
patchelf \
squashfs-tools \
libssl-dev \
libgtk-3-dev \
libayatana-appindicator3-dev
Expand Down Expand Up @@ -138,14 +139,14 @@ jobs:
tauriScript: bun run tauri
args: --target ${{ matrix.target }} --no-bundle

- name: Build Linux deb bundle (unsigned)
- name: Build Linux deb and AppImage bundles (unsigned)
if: matrix.platform == 'ubuntu-22.04'
uses: tauri-apps/tauri-action@v0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tauriScript: bun run tauri
args: --target ${{ matrix.target }} --bundles deb --no-sign --ci
args: --target ${{ matrix.target }} --bundles deb,appimage --no-sign --ci

- name: Validate Linux deep-link desktop entry
if: matrix.platform == 'ubuntu-22.04'
Expand All @@ -164,5 +165,33 @@ jobs:
grep -Fx 'Exec=youwee %U' "$DESKTOP_FILE"
grep -Fqx 'MimeType=x-scheme-handler/youwee;' "$DESKTOP_FILE"

- name: Validate repaired Linux AppImage runtime
if: matrix.platform == 'ubuntu-22.04'
run: |
set -eu
APPIMAGE_DIR="src-tauri/target/${{ matrix.target }}/release/bundle/appimage"
APPIMAGE="$(find "$APPIMAGE_DIR" -maxdepth 1 -type f -name '*.AppImage' | sort | head -n 1)"
test -n "$APPIMAGE"
APPIMAGE="$(cd "$(dirname "$APPIMAGE")" && pwd)/$(basename "$APPIMAGE")"
bash scripts/repair-appimage-runtime.sh "$APPIMAGE" "youwee"

INSPECT_DIR="$(mktemp -d)"
trap 'rm -rf "$INSPECT_DIR"' EXIT
(
cd "$INSPECT_DIR"
APPIMAGE_EXTRACT_AND_RUN=1 "$APPIMAGE" --appimage-extract >/dev/null
)

test -x "$INSPECT_DIR/squashfs-root/AppRun"
grep -Fqx 'unset GST_PLUGIN_SYSTEM_PATH GST_PLUGIN_SYSTEM_PATH_1_0 GST_PLUGIN_PATH_1_0' \
"$INSPECT_DIR/squashfs-root/AppRun"
! find "$INSPECT_DIR/squashfs-root/usr/lib" -maxdepth 1 \( -type f -o -type l \) \( \
-name 'libwayland-*.so*' -o \
-name 'libglib-2.0.so*' -o \
-name 'libgio-2.0.so*' -o \
-name 'libgobject-2.0.so*' -o \
-name 'libgmodule-2.0.so*' \
\) -print -quit | grep -q .

- name: Skip artifact upload
run: echo "build-test validates build artifacts but does not upload them."
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Fixed
- **Browser extension download settings** - Preserve global Youwee download settings such as container, output folder, playlists, and subtitles when a browser extension starts a download
- **AppImage runtime compatibility** - Rebuild Linux AppImages without bundled Wayland, GLib, and GStreamer runtime libraries that can conflict with newer Mesa and WebKitGTK, preventing blank startup windows on modern Linux distributions
- **Auto subtitle language selection** - Respect selected languages for automatic subtitles instead of requesting every available or translated subtitle language

### Changed
- **AppImage release validation** - Validate the rebuilt AppImage runtime layout in Linux CI and re-sign the finalized artifact for updater integrity

## [0.20.3] - 2026-08-23

Expand Down
5 changes: 5 additions & 0 deletions docs/CHANGELOG.vi.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ và dự án tuân theo [Semantic Versioning](https://semver.org/spec/v2.0.0.htm

### Sửa lỗi
- **Cài đặt tải từ browser extension** - Giữ lại các cài đặt Youwee global như container, thư mục đầu ra, playlist và subtitle khi browser extension bắt đầu tải xuống
- **Tương thích runtime AppImage** - Đóng gói lại AppImage Linux không kèm các thư viện runtime Wayland, GLib và GStreamer có thể xung đột với Mesa và WebKitGTK mới hơn, tránh cửa sổ trắng khi khởi động trên các bản Linux hiện đại
- **Chọn ngôn ngữ subtitle tự động** - Tôn trọng các ngôn ngữ đã chọn cho subtitle tự động thay vì yêu cầu mọi ngôn ngữ subtitle có sẵn hoặc được dịch

### Thay đổi
- **Kiểm tra release AppImage** - Kiểm tra layout runtime AppImage sau khi đóng gói lại trong CI Linux và ký lại artifact hoàn chỉnh để đảm bảo tính toàn vẹn khi cập nhật

## [0.20.3] - 2026-08-23

Expand Down
5 changes: 5 additions & 0 deletions docs/CHANGELOG.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ Youwee 的所有重要更改都将记录在此文件中。

### 修复
- **浏览器扩展下载设置** - 浏览器扩展启动下载时保留 Youwee 的全局设置,例如容器、输出目录、播放列表和字幕
- **AppImage 运行时兼容性** - 重新打包 Linux AppImage,不再捆绑可能与新版 Mesa 和 WebKitGTK 冲突的 Wayland、GLib 与 GStreamer 运行时库,避免现代 Linux 发行版启动时出现白色窗口
- **自动字幕语言选择** - 自动字幕现在会遵循已选择的语言,而非请求所有可用或翻译后的字幕语言

### 变更
- **AppImage 发布验证** - 在 Linux CI 中验证重新打包后的 AppImage 运行时布局,并重新签名最终产物以确保更新完整性

## [0.20.3] - 2026-08-23

Expand Down
105 changes: 105 additions & 0 deletions scripts/repair-appimage-runtime.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
#!/usr/bin/env bash
# Make Tauri's default AppImage safe on hosts newer than the build image.
#
# linuxdeploy copies core Wayland/GLib/GStreamer libraries into the AppImage.
# Those libraries must stay in sync with the host Mesa and desktop stack; mixing
# the Ubuntu 22.04 copies with a newer host causes WebKitGTK to abort while
# creating its EGL display. This script keeps application libraries (including
# WebKitGTK) bundled, but defers the desktop-stack libraries to the host.
set -euo pipefail

if [ "$#" -ne 2 ]; then
echo "Usage: $0 <AppImage> <main-binary-name>" >&2
exit 64
fi

appimage=$1
main_binary=$2

if [ ! -f "$appimage" ]; then
echo "AppImage does not exist: $appimage" >&2
exit 1
fi

for required_command in dd mksquashfs mktemp; do
command -v "$required_command" >/dev/null || {
echo "Missing required command: $required_command" >&2
exit 1
}
done

workspace=$(mktemp -d)
trap 'rm -rf "$workspace"' EXIT

appimage_abs=$(cd "$(dirname "$appimage")" && pwd)/$(basename "$appimage")
cd "$workspace"

# --appimage-extract works without FUSE. APPIMAGE_EXTRACT_AND_RUN also makes
# extraction work on GitHub runners where FUSE is unavailable.
APPIMAGE_EXTRACT_AND_RUN=1 "$appimage_abs" --appimage-extract >/dev/null
appdir="$workspace/squashfs-root"

if [ ! -x "$appdir/usr/bin/$main_binary" ]; then
echo "Expected executable is missing from AppImage: usr/bin/$main_binary" >&2
exit 1
fi

# Keep these ABI-coupled libraries on the host. Do not broaden this list to
# application dependencies: the goal is to preserve AppImage portability while
# avoiding the Mesa/Wayland and GLib ABI mismatch that causes EGL_BAD_PARAMETER.
for pattern in \
'libwayland-*.so*' \
'libxkbcommon*.so*' \
'libglib-2.0.so*' \
'libgio-2.0.so*' \
'libgobject-2.0.so*' \
'libgmodule-2.0.so*' \
'libgst*.so*' \
'libmount.so*' \
'libblkid.so*' \
'libselinux.so*' \
'libpcre2-8.so*' \
'libzstd.so*' \
'libelf.so*' \
'libffi.so*'; do
find "$appdir/usr/lib" -maxdepth 1 \( -type f -o -type l \) -name "$pattern" -delete
done

# Bypass linuxdeploy's AppRun.wrapped, which exports paths for bundled
# GStreamer/Python/Qt components that Youwee does not ship. In particular, an
# empty GST_PLUGIN_SYSTEM_PATH prevents WebKitGTK from finding host plugins.
cat > "$appdir/AppRun" <<APP_RUN
#!/usr/bin/env bash
set -euo pipefail
APPDIR="\$(cd "\$(dirname "\${BASH_SOURCE[0]}")" && pwd)"
export APPDIR

if [ -f "\$APPDIR/apprun-hooks/linuxdeploy-plugin-gtk.sh" ]; then
# shellcheck disable=SC1091
source "\$APPDIR/apprun-hooks/linuxdeploy-plugin-gtk.sh"
fi

unset PYTHONHOME PYTHONPATH PERLLIB QT_PLUGIN_PATH
unset GST_PLUGIN_SYSTEM_PATH GST_PLUGIN_SYSTEM_PATH_1_0 GST_PLUGIN_PATH_1_0
unset GST_PLUGIN_SCANNER_1_0 GST_PTP_HELPER_1_0
export LD_LIBRARY_PATH="\$APPDIR/usr/lib\${LD_LIBRARY_PATH:+:\$LD_LIBRARY_PATH}"
export PATH="\$APPDIR/usr/bin:\$PATH"

# WebKitGTK helper processes resolve bundled libraries relative to usr.
cd "\$APPDIR/usr"
exec "\$APPDIR/usr/bin/$main_binary" "\$@"
APP_RUN
chmod +x "$appdir/AppRun"

offset=$(APPIMAGE_EXTRACT_AND_RUN=1 "$appimage_abs" --appimage-offset)
runtime="$workspace/runtime"
payload="$workspace/payload.squashfs"
rebuilt="$workspace/$(basename "$appimage")"

dd if="$appimage_abs" of="$runtime" bs=1 count="$offset" status=none
mksquashfs "$appdir" "$payload" -noappend -all-root -comp zstd -no-progress >/dev/null
cat "$runtime" "$payload" > "$rebuilt"
chmod +x "$rebuilt"
mv "$rebuilt" "$appimage_abs"

echo "Rebuilt AppImage with host Wayland/GLib/GStreamer runtime libraries: $appimage_abs"
106 changes: 90 additions & 16 deletions src-tauri/src/commands/download.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,89 @@ fn extract_time_range(download_sections: &Option<String>) -> Option<String> {
})
}

fn append_subtitle_args(
args: &mut Vec<String>,
subtitle_mode: &str,
subtitle_langs: &str,
subtitle_embed: bool,
subtitle_format: &str,
) {
if subtitle_mode == "off" {
return;
}

args.push("--write-subs".to_string());
if subtitle_mode == "auto" {
args.push("--write-auto-subs".to_string());
}

args.push("--sub-langs".to_string());
if subtitle_mode == "auto" && subtitle_langs.trim().is_empty() {
// An empty Auto selection deliberately means every available language.
args.push("all".to_string());
} else {
args.push(subtitle_langs.to_string());
}

args.push("--sub-format".to_string());
args.push(subtitle_format.to_string());
if subtitle_embed {
args.push("--embed-subs".to_string());
}
}

#[cfg(test)]
mod subtitle_args_tests {
use super::*;

#[test]
fn auto_subtitles_respect_selected_languages() {
let mut args = Vec::new();

append_subtitle_args(&mut args, "auto", "en,ru", false, "srt");

assert_eq!(
args,
vec![
"--write-subs",
"--write-auto-subs",
"--sub-langs",
"en,ru",
"--sub-format",
"srt",
]
);
}

#[test]
fn auto_subtitles_use_all_only_without_a_language_selection() {
let mut args = Vec::new();

append_subtitle_args(&mut args, "auto", " ", false, "vtt");

assert_eq!(args[3], "all");
}

#[test]
fn manual_subtitles_do_not_request_auto_generated_captions() {
let mut args = Vec::new();

append_subtitle_args(&mut args, "manual", "vi", true, "ass");

assert_eq!(
args,
vec![
"--write-subs",
"--sub-langs",
"vi",
"--sub-format",
"ass",
"--embed-subs",
]
);
}
}

fn number_width(total: Option<u32>) -> usize {
total
.filter(|value| *value >= 100)
Expand Down Expand Up @@ -1329,22 +1412,13 @@ pub async fn download_video(
}

// Subtitle settings
if subtitle_mode != "off" {
args.push("--write-subs".to_string());
if subtitle_mode == "auto" {
args.push("--write-auto-subs".to_string());
args.push("--sub-langs".to_string());
args.push("all".to_string());
} else {
args.push("--sub-langs".to_string());
args.push(subtitle_langs.clone());
}
args.push("--sub-format".to_string());
args.push(subtitle_format.clone());
if subtitle_embed {
args.push("--embed-subs".to_string());
}
}
append_subtitle_args(
&mut args,
&subtitle_mode,
&subtitle_langs,
subtitle_embed,
&subtitle_format,
);

args.extend(build_site_header_args(&url));

Expand Down