Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
776e31c
feat(labs): base release channel on Plezy 2.9.1
github-actions[bot] Jul 16, 2026
10f9e90
chore(labs): register enabled feature overlays
github-actions[bot] Jul 16, 2026
3ef51a9
feat(player): add local video clip export for desktop
RyanTheTechMan Jul 16, 2026
7a222ff
feat(player): add clip preview audio and screenshots
RyanTheTechMan Jul 16, 2026
00ba63a
fix(player): keep clip preview aligned with sheet
RyanTheTechMan Jul 16, 2026
4aca6fb
feat(settings): add configurable clip & screenshot capture locations
RyanTheTechMan Jul 16, 2026
98343e8
fix(player): harden clip export and localize UI
RyanTheTechMan Jul 16, 2026
357bf2a
refactor(player): match clip format selector to settings
RyanTheTechMan Jul 16, 2026
1356d2f
feat(labs): reuse localized clip translations
RyanTheTechMan Jul 16, 2026
8b0a051
fix(macos) resolve volume control issues
RyanTheTechMan Jul 10, 2026
dab58e3
fix(macos): prevent stale volume restore from resuming playback
RyanTheTechMan Jul 10, 2026
635c2a9
feat(libraries): show item count in browse toolbar
RyanTheTechMan Jul 10, 2026
5891d11
feat(player): show original bitrate in quality controls
RyanTheTechMan Jul 11, 2026
3ec991e
fix(player): avoid multiple original bitrates shown
RyanTheTechMan Jul 11, 2026
1beb298
backport(player): make desktop chapter selector interactive on 2.9.1
github-actions[bot] Jul 14, 2026
40d2b6f
feat: add pull-to-refresh to Discover and Library Browse
RyanTheTechMan Jul 10, 2026
821034b
test(discover): scope pull-to-refresh scroll view finder
RyanTheTechMan Jul 11, 2026
892161d
feat(player): add frame-by-frame navigation
RyanTheTechMan Jul 16, 2026
c959aa3
chore(labs): regenerate translations
github-actions[bot] Jul 16, 2026
bdb0cdb
feat(settings): add 2048MB buffer option
davyalan1 Jul 16, 2026
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
1,018 changes: 1,018 additions & 0 deletions .github/workflows/labs-build.yml

Large diffs are not rendered by default.

63 changes: 63 additions & 0 deletions .github/workflows/labs-watch.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
name: Watch Official Plezy Releases

on:
schedule:
- cron: '0 8 * * *'
timezone: America/New_York
workflow_dispatch:

permissions:
actions: write
contents: read

jobs:
watch:
runs-on: ubuntu-latest
steps:
- name: Prune older watcher runs
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh api --paginate "repos/${{ github.repository }}/actions/workflows/labs-watch.yml/runs?per_page=100" \
--jq ".workflow_runs[] | select(.id != ${{ github.run_id }} and .status == \"completed\") | .id" \
| while read -r run_id; do
gh api --method DELETE "repos/${{ github.repository }}/actions/runs/$run_id"
done

- name: Detect a new published official release
id: release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
OFFICIAL_TAG=$(gh api repos/edde746/plezy/releases/latest --jq 'select(.draft == false and .prerelease == false) | .tag_name')
OFFICIAL_VERSION=${OFFICIAL_TAG#v}
LABS_TAG=$(gh api "repos/${{ github.repository }}/releases?per_page=100" \
--jq '[.[] | select(.draft == false and .prerelease == false and (.tag_name | startswith("labs-v")))] | first | .tag_name // ""')
LABS_VERSION=$(sed -n 's/^labs-v\([0-9][0-9.]*\)-r[0-9][0-9]*$/\1/p' <<<"$LABS_TAG")

if [ "$OFFICIAL_VERSION" = "$LABS_VERSION" ]; then
echo "Plezy Labs already tracks official Plezy $OFFICIAL_VERSION."
echo "sync=false" >> "$GITHUB_OUTPUT"
exit 0
fi

NEWEST=$(printf '%s\n%s\n' "$LABS_VERSION" "$OFFICIAL_VERSION" | sed '/^$/d' | sort -V | tail -1)
if [ "$NEWEST" != "$OFFICIAL_VERSION" ]; then
echo "Labs base $LABS_VERSION is newer than official $OFFICIAL_VERSION; refusing to sync." >&2
exit 1
fi

echo "sync=true" >> "$GITHUB_OUTPUT"
echo "tag=$OFFICIAL_TAG" >> "$GITHUB_OUTPUT"

- name: Dispatch official tag sync
if: steps.release.outputs.sync == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh workflow run labs-build.yml \
--repo "${{ github.repository }}" \
--ref labs \
-f "official_tag=${{ steps.release.outputs.tag }}" \
-f publish=true
51 changes: 51 additions & 0 deletions docs/plezy-labs-releases.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Plezy Labs releases

Plezy Labs releases are built only from the `labs` branch. Every candidate is
reconstructed from an exact published Plezy tag, followed by the Labs core and
the enabled feature overlays in `tool/labs_features.json`. Unreleased upstream
history is never merged into Labs.

## Feature overlays and backports

Normal features record immutable native commit SHAs. An ahead-of-release
feature may also record compatibility commits and a `use_native_after` upstream
commit. Reconstruction uses the compatibility backport until the official tag
contains that prerequisite, then switches to the native commits automatically.
If either form is already official, no overlay is applied. Partial adoption,
missing commits, or conflicts stop the release before publishing.

Keep every referenced feature and backport source branch available on the fork.
Branch names are labels; the immutable SHAs in the manifest select the code.
When a feature has an upstream Plezy pull request, record its URL as
`upstream_pr`; generated Labs release notes link the applied feature to that PR.

## One-time repository setup

1. Push `labs` and make it the fork's default branch so the scheduled watcher
runs from the Labs workflow definitions.
2. Run `scripts/generate-labs-updater-key.sh`, back up the private key, and set
the printed `LABS_SPARKLE_PRIVATE_KEY` secret and
`LABS_UPDATE_PUBLIC_KEY` repository variable.
3. Configure the same macOS Developer ID and notarization secrets used by the
inherited desktop build: `MACOS_CERTIFICATE_BASE64`,
`MACOS_CERTIFICATE_PASSWORD`, `KEYCHAIN_PASSWORD`, `APPLE_ID`,
`APPLE_APP_SPECIFIC_PASSWORD`, and `APPLE_TEAM_ID`.
4. In GitHub notification settings, select failed-workflow-only notifications.

The build deliberately fails instead of publishing unsigned macOS or unsigned
Sparkle/WinSparkle update artifacts when any required value is missing.

## Publishing

- Run **Plezy Labs** for a normal Labs change. It computes the next revision
and updater build, then creates a draft GitHub release.
- The generated description lists the applied Labs feature overlays first and
embeds the official Plezy release notes below them. Edit it if desired
before publishing. The final GitHub Release description is the only
release-notes source used by the app and native updater.
- The daily watcher runs at 8:00 AM in `America/New_York`. It can also be run
manually. A clean new official release sync automatically publishes revision
1; conflicts create an issue and publish nothing.

Official-to-Labs and Labs-to-official transitions are replacement installs.
Only Labs-to-Labs updates use the native updater.
93 changes: 86 additions & 7 deletions lib/i18n/bg.i18n.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"app": {
"title": "Plezy"
"title": "Plezy Labs"
},
"auth": {
"signInWithPlex": "Вход с Plex",
Expand Down Expand Up @@ -79,7 +79,15 @@
"skipVersion": "Пропусни тази версия",
"viewRelease": "Виж версията",
"latestVersion": "Използвате най-новата версия",
"checkFailed": "Неуспешна проверка за актуализации"
"checkFailed": "Неуспешна проверка за актуализации",
"chooseChannelTitle": "Choose your update channel",
"chooseChannelDescription": "Plezy Labs adds experimental features on top of published Plezy releases. You can use Labs updates or return to official Plezy.",
"useLabs": "Use Plezy Labs",
"returnToOfficial": "Return to Official Plezy",
"returnToOfficialTitle": "Leave Plezy Labs?",
"returnToOfficialWarning": "Labs-only features and preferences may no longer be available. Official Plezy cannot update back to Plezy Labs; reinstall Labs manually if you want to return.",
"openOfficialRelease": "Open Official Release",
"releaseNotes": "Release notes"
},
"settings": {
"title": "Настройки",
Expand Down Expand Up @@ -207,6 +215,12 @@
"updates": "Актуализации",
"updateAvailable": "Налична е актуализация",
"checkForUpdates": "Провери за актуализации",
"officialPlezy": "Official Plezy",
"plezyLabs": "Plezy Labs",
"labsNotAvailable": "Plezy Labs for Plezy ${version} is not available yet",
"latestLabsRelease": "Latest Labs release: ${version}",
"latestOfficialRelease": "Latest official release: ${version}",
"releaseStatusUnavailable": "Release status unavailable",
"autoCheckUpdatesOnStartup": "Автоматично проверявай за актуализации при стартиране",
"autoCheckUpdatesOnStartupDescription": "Уведомявай, когато има актуализация при стартиране",
"validationErrorEnterNumber": "Моля, въведете валидно число",
Expand Down Expand Up @@ -238,6 +252,16 @@
"downloadLocationReset": "Местоположението за изтегляния е върнато по подразбиране",
"downloadLocationInvalid": "Избраната папка не е записваема",
"downloadLocationSelectError": "Неуспешен избор на папка",
"mediaCapture": "Media Capture",
"clips": "клипове",
"screenshots": "Екранни снимки",
"captureLocationTitle": "${title} Местоположение",
"clipLocationDescription": "Изберете къде да се записват клиповете.",
"screenshotLocationDescription": "Изберете къде да се запазват екранните снимки.",
"clipLocationChanged": "Местоположението на клипа е променено",
"screenshotLocationChanged": "Местоположението на екранната снимка е променено",
"clipLocationReset": "Местоположението на клипа се нулира на работния плот",
"screenshotLocationReset": "Местоположението на екранната снимка се нулира на работния плот",
"downloadOnWifiOnly": "Изтегляне само през WiFi",
"downloadOnWifiOnlyDescription": "Предотвратявай изтегляния през мобилни данни",
"autoRemoveWatchedDownloads": "Автоматично премахвай изгледаните изтегляния",
Expand Down Expand Up @@ -336,6 +360,8 @@
"clearShortcut": "Изчисти клавишната комбинация",
"noShortcutSet": "Няма зададена клавишна комбинация",
"currentShortcut": "Текуща комбинация:",
"pressToRecord": "Select to record a shortcut",
"recordingShortcut": "Press the shortcut now",
"actions": {
"playPause": "Пускане/пауза",
"volumeUp": "Увеличи звука",
Expand All @@ -361,7 +387,9 @@
"subSeekPrev": "Отиди до предишен субтитър",
"shaderToggle": "Превключи шейдъри",
"skipMarker": "Прескочи интро/финални надписи",
"screenshot": "Направи екранна снимка"
"screenshot": "Направи екранна снимка",
"framePrevious": "Previous Frame",
"frameNext": "Next Frame"
}
},
"fileInfo": {
Expand Down Expand Up @@ -434,7 +462,9 @@
"hue": "Нюанс",
"saturation": "Наситеност",
"brightness": "Яркост",
"hexColor": "Шестнадесетичен цвят"
"hexColor": "Шестнадесетичен цвят",
"expandText": "Expand text",
"collapseText": "Collapse text"
},
"tooltips": {
"shufflePlay": "Разбъркано възпроизвеждане",
Expand Down Expand Up @@ -502,6 +532,10 @@
"pipActive": "Възпроизвеждане в режим картина в картината",
"pipFailed": "Режимът картина в картината не успя да стартира",
"screenshotSaved": "Екранната снимка е запазена",
"frameCount": {
"one": "${n} frame",
"other": "${n} frames"
},
"zoomPercent": "Мащаб ${percent}%",
"pipErrors": {
"androidVersion": "Изисква Android 8.0 или по-нова версия",
Expand All @@ -525,7 +559,44 @@
"noTracksAvailable": "Няма налични писти",
"subtitleDownloaded": "Субтитърът е изтеглен",
"subtitleDownloadFailed": "Неуспешно изтегляне на субтитър",
"searchLanguages": "Търсене на езици..."
"searchLanguages": "Търсене на езици...",
"clip": {
"title": "Клип",
"vodOnly": "Налични са клипове за възпроизвеждане на видео при поискване.",
"sourceUnavailable": "Източникът на клип не е наличен за тази сесия на възпроизвеждане.",
"playAtLeastOneSecond": "Пуснете поне 1 секунда преди изрязване.",
"startBeforeBeginning": "Началото на клипа не може да бъде преди началото на видеоклипа.",
"endAfterStart": "Краят на клипа трябва да е след началото.",
"minimumDuration": "Клиповете трябва да са с дължина поне 1 секунда.",
"endPastVideo": "Краят на клипа е след края на видеоклипа.",
"exportCanceled": "Експортирането на клип е анулирано.",
"cacheUnavailable": "Избраният диапазон не може да бъде напълно кеширан за първоначално експортиране. Опитайте с по-кратък клип или пуснете визуализацията веднъж, преди да запазите.",
"sourceCopyNoEncoder": "Експортирането на изходно копие не използва енкодер.",
"encodingDesktopOnly": "H.264 и HEVC кодирането на клипове в момента е налично в macOS и Windows.",
"hdrRequiresSource": "HDR експортирането изисква HDR10 или HLG-съвместим източник за директно възпроизвеждане.",
"transcodeStartUnavailable": "Този клип започва преди активния транскодиран поток. Търсете по-рано и отворете отново изрезката или превключете към оригинално качество.",
"previewRequired": "Визуализацията на клип трябва да завърши зареждането, преди да може да бъде запазена.",
"h264Failed": "Този източник не може да бъде кодиран като H.264 SDR MP4.",
"hevcSdrFailed": "Този източник не може да бъде кодиран като HEVC SDR MP4.",
"hevcHdrFailed": "Този източник не можа да бъде кодиран като HEVC HDR MP4.",
"originalFailed": "Този източник не можа да бъде копиран от mpv кеша.",
"previewUnavailable": "Възпроизвеждането на предварителен преглед на клип не е налично в тази компилация.",
"previewFailed": "Неуспешно възпроизвеждане на визуализация на клип.",
"previewLoadingScreenshot": "Визуализацията на клипа трябва да завърши зареждането, преди да направите екранна снимка.",
"screenshotInProgress": "Вече се запазва екранна снимка.",
"saveAsDialog": "Запазване на клипа като",
"savedTo": "Запазено в ${fileName}",
"openFolder": "Отворете папката",
"saveAs": "Запиши като",
"cancelExport": "Отказ от експортиране",
"saving": "Запазва се...",
"savingProgress": "Спестяване на ${percent}%",
"mutePreview": "Визуализация без звук",
"unmutePreview": "Включване на звука предварителен преглед",
"formatHevcSdr": "HEVC SDR",
"formatH264Sdr": "H.264 SDR",
"formatHevcHdr": "HEVC HDR"
}
},
"userStatus": {},
"messages": {
Expand Down Expand Up @@ -814,8 +885,10 @@
"title": "Относно",
"openSourceLicenses": "Лицензи с отворен код",
"versionLabel": "Версия ${version}",
"appDescription": "Красив Plex и Jellyfin клиент за Flutter",
"viewLicensesDescription": "Виж лицензите на библиотеки на трети страни"
"viewLicensesDescription": "Виж лицензите на библиотеки на трети страни",
"labsDescription": "An experimental Plezy edition built only on published official releases",
"labsModifiedNotice": "Plezy Labs is a modified GPL-3.0 build maintained by RyanTheTechMan, not an official Plezy release.",
"labsSource": "Plezy Labs source code"
},
"serverSelection": {
"noServersFoundForAccount": "Не са намерени сървъри за ${username} (${email})",
Expand Down Expand Up @@ -1048,6 +1121,8 @@
"hostingSession": "Сесия с домакин",
"inSession": "В сесия",
"sessionCode": "Код на сесията",
"openSessionControls": "Open Watch Together session controls",
"copySessionCode": "Copy session code",
"hostControlsPlayback": "Домакинът управлява възпроизвеждането",
"anyoneCanControl": "Всеки може да управлява възпроизвеждането",
"hostControls": "Контроли на домакина",
Expand Down Expand Up @@ -1358,6 +1433,8 @@
"artworkUpdated": "Обложката е обновена",
"artworkUpdateFailed": "Неуспешно обновяване на обложката",
"noArtworkAvailable": "Няма налична обложка",
"artworkOption": "Artwork option ${index}",
"selectedArtworkOption": "Artwork option ${index}, selected",
"notSet": "Не е зададено",
"libraryDefault": "По подразбиране за библиотеката",
"accountDefault": "По подразбиране за акаунта",
Expand Down Expand Up @@ -1485,13 +1562,15 @@
"title": "Активиране на Plezy в ${service}",
"body": "Посетете ${url} и въведете този код:",
"openToActivate": "Отворете ${service}, за да активирате",
"copyCode": "Copy activation code",
"waitingForAuthorization": "Изчакване на оторизация…",
"codeCopied": "Кодът е копиран"
},
"oauthProxy": {
"title": "Вход в ${service}",
"body": "Сканирайте този QR код или отворете URL-а на което и да е устройство.",
"openToSignIn": "Отворете ${service}, за да влезете",
"copyUrl": "Copy sign-in URL",
"urlCopied": "URL адресът е копиран"
},
"libraryFilter": {
Expand Down
Loading