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
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ jobs:
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
- name: Release notes composer
run: python3 tool/compose_release_notes_test.py
- uses: subosito/flutter-action@v2
with:
channel: stable
Expand Down
13 changes: 13 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1027,8 +1027,21 @@ jobs:
V: ${{ needs.version.outputs.name }}
BUILD: ${{ needs.version.outputs.build }}
NOTES: ${{ inputs.notes }}
SHA: ${{ needs.version.outputs.sha }}
TAG: ${{ needs.version.outputs.tag }}
run: |
set -euo pipefail
# Hand-written notes and screenshots, when this version has them.
status=0
python3 tool/compose_release_notes.py --version "${V}" --sha "${SHA}" \
--repo "${GITHUB_REPOSITORY}" --tag "${TAG}" --assets rel \
--out RELEASE_NOTES.md || status=$?
if [ "${status}" -eq 0 ]; then
echo "── notes ──"; cat RELEASE_NOTES.md
exit 0
elif [ "${status}" -ne 3 ]; then
exit "${status}"
fi
{
if [ -n "${NOTES}" ]; then
printf '%s\n\n' "${NOTES}"
Expand Down
143 changes: 143 additions & 0 deletions docs/releases/v3.2.1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
<p align="center">
<b>Sozo v3.2.1 is a patch release that fixes what you reported after v3.2.0.</b><br/>
Videos no longer load forever, dropped connections recover, and more downloads work.<br/>
It also adds a corner player on desktop, Vietnamese and Hindi, and sorting in My List.
</p>

<p align="center">
<a href="#-download"><img src="https://img.shields.io/badge/⬇_Download_v3.2.1-E8E8E8?style=for-the-badge&labelColor=111111" alt="Download" height="36"/></a>
<a href="https://telegram.me/sozoapp"><img src="https://img.shields.io/badge/Telegram-Sozo_channel-2CA5E0?style=for-the-badge&logo=telegram&logoColor=white&labelColor=111111" alt="Telegram" height="36"/></a>
<a href="https://discord.gg/n22URhYvMR"><img src="https://img.shields.io/badge/Discord-Join_the_server-5865F2?style=for-the-badge&logo=discord&logoColor=white&labelColor=111111" alt="Discord" height="36"/></a>
<a href="https://github.com/professorDeveloper/sozo"><img src="https://img.shields.io/github/stars/professorDeveloper/sozo?style=for-the-badge&logo=github&label=Star&color=FFB27A&labelColor=111111" alt="Star Sozo" height="36"/></a>
</p>

---

## ▶️ Playback that recovers

| | |
|---|---|
| ⏱️ **No more endless "Video loading"** | A server now has about 25 seconds to start (about 35 for streams found inside a web page, about 90 for torrents). After that, the player moves to the next server. On Android, if the main player hands a stream to the system player, that player gets its own time. If the servers it tries all fail, it tells you. |
| 🔌 **Dropped connections** | After "Connection reset by peer", the player reconnects and carries on from the same position. A file cut short by the network now reconnects too, instead of stopping silently. You see an error only if that fails. |
| 🪜 **More servers get tried** | Many sources played the first server and then showed an error, with the others untried. They now try other servers, and your remembered quality is used. |
| 🎯 **1080p means 1080p** | On Android's system player, a quality you pick stays locked instead of starting at the lowest one. Auto now shows plain "Auto" instead of the size it started at. |
| 🎞️ **Video the player can't decode** | On Android, Sozo now switches to the system player when its main player can't open a video's codec. A single bad audio or video packet no longer stops playback with an error. |
| 🌑 **Black picture** | On Android, a video no longer stays black until your first seek. If the main player can't show a picture, Sozo switches to the system player. |
| 🖼️ **Seek previews** | Frames should now arrive on slow hosts, a frame still loading when you lift your finger should be kept for the next drag, and a dead host should no longer leave a skeleton forever. The Android changes have not been tested on a device yet. The card now reads `00:49 / 02:07`. |

<p align="center"><img src="../images/v3.2.1/server-switch.png" alt="The player showing the Switching server notice" width="70%"/></p>

---

## 🖥️ Corner player on desktop

<p align="center"><img src="../images/v3.2.1/desktop-corner-player.png" alt="The corner player floating over a Sozo page on desktop" width="85%"/></p>

On Windows, macOS and Linux, press **I** or the mini player button to dock the video in a corner of the app. It keeps playing while you move around Home, Search, detail pages and settings. The video does not reload.

- **Move and resize it.** Drag it and it snaps to the nearest corner. Drag an inner edge to resize it. Sozo remembers the corner and the width.
- **Hover controls.** Close, back to the full player, previous and next episode, play and pause, 10-second steps when there is room, and a timeline.
- **The keyboard stays with your page.** Click the corner player to give it the keyboard. Esc or a click elsewhere gives it back without closing the video.
- **Above other apps.** Press **Shift+I** or long-press the button for a small always-on-top window. If you switch to another app while a video is playing, Sozo now shrinks to this window by itself. On macOS it also appears on every Space and beside full-screen apps.

---

## ⬇️ Downloads

> **If a download failed with "The source sent a web page instead of the video", delete it and download it again on v3.2.1.** A failed download keeps its old address, so retrying it may not help.

- **Fewer false "web page" errors.** Some hosts label real video as a web page. Sozo now checks the first bytes of the file, and refuses only real web pages.
- **Downloads ask for a file the way the player does.** Some hosts refused downloads of an episode that played fine.
- **One bad mirror no longer fails the episode.** It fails only if every mirror serves a page. Otherwise, the bad ones are left out of the choices.
- **Android: Save to Downloads now writes the video of an HLS download.** It used to save only a small playlist file. The parts are now joined into one `.ts` or `.mp4` file. Downloads with a separate audio track or SAMPLE-AES encryption are refused with a reason.

---

## ✨ Added

<p align="center"><img src="../images/v3.2.1/languages.png" alt="The language picker with Vietnamese and Hindi" width="45%"/> </p>

- 🌍 **Vietnamese and Hindi**, so Sozo now has 16 languages. Subtitles in these languages are found by their labels and offered as translation targets. Both translations are machine-written and no native speaker has read them yet, so corrections are welcome.
- 📋 **Sort My List** by recently added, oldest added or title. Sozo remembers your choice on the device.
- 🔀 **Auto in "Try another source".** Sozo goes through the matches it trusts and opens the first one that can play the title.
- 📺 **One season at a time.** Sources that list every season together, such as CloudStream plugins, now get season chips, and episodes show as `S2 E1`. Seasons no longer sort into each other.
- 🎬 **Relations for films and series.** A title that isn't anime now shows the rest of its film series and TMDB's recommendations. AniList results appear only when the title really matches.
- 🔎 **Pinch zoom in the continuous manga reader.** Before, only the paged mode could zoom.
- 🚫 **Spliced-in ads.** A source can now mark ad segments in a stream, and the player leaves them out.
- 💬 **Burned-in subtitles.** A source can now say which language is already in the picture, and auto-translate won't draw a second line over it.
- 🍎 **iPhone player.** Clear glass under the controls and the system spinner when a video stalls. On all phones, locking or unlocking gives a firm tap and a double-tap seek gives a tick.

<p align="center"><img src="../images/v3.2.1/my-list.png" alt="My List with the sort menu open" width="45%"/></p>

<details>
<summary><b>🔧 Other fixes</b></summary>

- **Windows:** turning on Anime4K no longer gives a black screen with a playback error.
- **Arabic:** tapping a tab in the bottom bar opened the mirror-image tab. It now opens the one you tapped.
- **Sources with no video for a title.** When a source moved a title to its own app or has no player, Sozo says so in your language. After that first try, Play is replaced by a way to find the title on another source. If a series was opened as a film, Sozo opens its episode list when the source gave one.
- **Fairer "Down" badges.** A source that runs on your phone no longer shows as Down only because our server timed out or couldn't reach its site. Long-press its row to check from your device. An empty catalogue now counts as a problem.
- **"Hide down sources"** now shows the number it really hides, and the quick-switch sheet uses the same health rules as the Sources screen.
- **Source check:** the message and the Hide button had swapped text in every language.
- **CloudStream:** search now works for plugins that only have paged search, such as MovieBox.
- **Streams stored inside image files** now play through the built-in proxy, and the proxy answers range requests the way players expect.
- **Subtitles:** you now get a message when a translation stops part way.
- **Subtitles:** a track in your subtitle language now starts ahead of the source's default, and one-tap translate uses a human subtitle in that language when there is one.
- **Long Vietnamese titles** keep their tone marks, and Hindi gets the same extra line height.
- **My List:** a catalogue title shows its name, not `cat:anilist`.
- **Support:** a follow-up on a ticket that had diagnostics now sends a newer report.
- **Corner player:** signing out or switching profile closes it first, so your last history save lands in the right account.

</details>

---

## ⬇ Download

<!-- downloads -->

<details>
<summary>🍎 macOS says the app is "damaged" or won't open</summary>

The DMG isn't signed with an Apple certificate. Run this once in Terminal:

```
xattr -dr com.apple.quarantine /Applications/Sozo.app
```

Or in Applications, right-click Sozo.app → **Open** → **Open**.
</details>

---

## 💬 Join the community

<p align="center">
<a href="https://telegram.me/sozoapp"><img src="https://img.shields.io/badge/Telegram_channel-@sozoapp-2CA5E0?style=for-the-badge&logo=telegram&logoColor=white" alt="Telegram channel" height="34"/></a>
<a href="https://discord.gg/n22URhYvMR"><img src="https://img.shields.io/badge/Discord-Talk_features_&_bugs-5865F2?style=for-the-badge&logo=discord&logoColor=white" alt="Discord" height="34"/></a>
<a href="https://sozo.azamov.me"><img src="https://img.shields.io/badge/Website-sozo.azamov.me-111111?style=for-the-badge&logo=googlechrome&logoColor=white" alt="Website" height="34"/></a>
<a href="https://t.me/ackles"><img src="https://img.shields.io/badge/Developer-@ackles-2CA5E0?style=for-the-badge&logo=telegram&logoColor=white" alt="Developer on Telegram" height="34"/></a>
<a href="https://www.buymeacoffee.com/chihaku"><img src="https://img.shields.io/badge/Buy_me_a_coffee-FFDD00?style=for-the-badge&logo=buymeacoffee&logoColor=black" alt="Buy me a coffee" height="34"/></a>
</p>

- 📣 **Telegram**: [@sozoapp](https://telegram.me/sozoapp) for new versions and announcements first.
- 🎧 **Discord**: [discord.gg/n22URhYvMR](https://discord.gg/n22URhYvMR) to suggest ideas and report bugs.
- 🛟 **In the app**: Profile → **Help & Support**. Many of these fixes started as tickets there.
- 🐛 **On GitHub**: [a source isn't working](https://github.com/professorDeveloper/sozo/issues/new?template=source_not_working.yml) · [bug report](https://github.com/professorDeveloper/sozo/issues/new?template=bug_report.yml) · [feature request](https://github.com/professorDeveloper/sozo/issues/new?template=feature_request.yml)
- ⭐ **If Sozo is useful to you, [star the repo](https://github.com/professorDeveloper/sozo)**.

<details>
<summary>🇺🇿 <b>O'zbekcha qisqacha</b></summary>

**Sozo v3.2.1: v3.2.0 dan keyin bildirilgan muammolar tuzatildi.**

- ▶️ **Pleer.** Endi "Video loading" cheksiz turmaydi: server taxminan 25 soniyada ochilmasa (torrent uchun taxminan 90 soniya), pleer keyingi serverga o'tadi. Aloqa uzilsa, video o'sha joyidan davom etadi.
- ⬇️ **Yuklab olish.** Noto'g'ri "web page" xatolari tuzatildi. **Avval xato bergan yuklamani o'chirib, v3.2.1 da qaytadan yuklab oling.** Android'da *Save to Downloads* endi HLS yuklamaning o'zini video fayl qilib saqlaydi.
- 🖥️ **Desktop.** Yangi burchak pleeri: **I** tugmasini bosing, video ilova burchagida davom etadi, siz esa boshqa sahifalarga o'tasiz. **Shift+I** boshqa ilovalar ustida turadigan kichik oynani ochadi.
- 🌍 **Vyetnam va hind tillari** qo'shildi, jami 16 ta til. Bu ikki tarjima mashinada qilingan.
- ✨ **Yangi.** My List'ni saralash, *Try another source* ichida **Auto**, CloudStream seriallari uchun fasllar bo'yicha ko'rinish, film va seriallar uchun Relations, manga reader'ning uzluksiz rejimida pinch zoom.
- 🔧 **Tuzatildi.** Windows'da Anime4K qora ekrani, arab tilida pastki panel, "Down" belgilari va boshqalar.

Yangiliklar: [@sozoapp](https://telegram.me/sozoapp) · Fikr va xatolar: [Discord](https://discord.gg/n22URhYvMR)
</details>

<sub>Full list of changes: <a href="https://github.com/professorDeveloper/sozo/compare/v3.2.0...v3.2.1">v3.2.0...v3.2.1</a></sub>
124 changes: 124 additions & 0 deletions tool/compose_release_notes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
#!/usr/bin/env python3
"""Builds a release body from docs/releases/v<version>.md.

Images may be written relative to the notes file. They become links to the
exact commit being released, so they work in a draft before anything reaches
the default branch. An image that is not in the repository is removed and
reported, so a release never shows a broken picture.

<!-- downloads --> becomes a table of the files actually attached.

Exit codes: 0 written, 3 no notes file for this version (the workflow then falls
back to its generated notes).
"""
import argparse
import posixpath
import re
import sys
from pathlib import Path

# Longest suffix first: "tv-arm64-v8a.apk" also ends in "arm64-v8a.apk".
PLATFORMS = (
("tv-arm64-v8a.apk", "📺", "Android TV"),
("tv-armeabi-v7a.apk", "📺", "Android TV (older 32-bit devices)"),
("arm64-v8a.apk", "🟢", "Android (most phones)"),
("armeabi-v7a.apk", "🟡", "Android (older 32-bit phones)"),
("universal.apk", "🔵", "Android (if you're not sure; larger, works everywhere)"),
("x86_64.apk", "⚪", "Android (x86_64 and emulators)"),
("Windows-Setup.exe", "🪟", "Windows"),
("Windows-portable.zip", "🪟", "Windows (no install)"),
("macOS.dmg", "🍎", "macOS 12+"),
("Linux-x64.tar.gz", "🐧", "Linux x64"),
("iOS.ipa", "📱", "iOS 15+ (install with AltStore, SideStore or Sideloadly)"),
)

IMAGE_BLOCK = re.compile(r'<p[^>]*>\s*(<img\b[^>]*>)\s*</p>\n?', re.I)
HTML_IMG = re.compile(r'<img\b[^>]*?\bsrc="([^"]+)"[^>]*>', re.I)
MD_IMG = re.compile(r'!\[[^\]]*\]\(([^)\s]+)\)')


def is_relative(src):
return not re.match(r'^(?:[a-z][a-z0-9+.-]*:|//|#|/)', src, re.I)


def rewrite_images(text, notes_path, repo_root, repo, sha, warn):
base = f"https://raw.githubusercontent.com/{repo}/{sha}/"
notes_dir = notes_path.parent.relative_to(repo_root).as_posix()

def target(src):
path = posixpath.normpath(posixpath.join(notes_dir, src))
if path.startswith("../") or not (repo_root / path).is_file():
warn(f"image not found, removed: {src}")
return None
return base + path

def block(match):
src = HTML_IMG.search(match.group(1))
if src and is_relative(src.group(1)) and target(src.group(1)) is None:
return ""
return match.group(0)

def html(match):
src = match.group(1)
if not is_relative(src):
return match.group(0)
url = target(src)
return match.group(0).replace(src, url, 1) if url else ""

def markdown(match):
src = match.group(1)
if not is_relative(src):
return match.group(0)
url = target(src)
return match.group(0).replace(src, url, 1) if url else ""

text = IMAGE_BLOCK.sub(block, text)
# A missing image in a block was already removed and warned about once.
return MD_IMG.sub(markdown, HTML_IMG.sub(html, text))


def downloads_table(assets, repo, tag, version):
names = sorted(p.name for p in assets.iterdir()) if assets.is_dir() else []
rows = []
for suffix, icon, label in PLATFORMS:
name = f"Sozo-v{version}-{suffix}"
if name in names:
url = f"https://github.com/{repo}/releases/download/{tag}/{name}"
rows.append(f"| {icon} {label} | [`{name}`]({url}) |")
if not rows:
return "The files are attached to this release."
lines = ["| Platform | File |", "|---|---|", *rows]
sums = f"Sozo-v{version}-SHA256SUMS.txt"
if sums in names:
url = f"https://github.com/{repo}/releases/download/{tag}/{sums}"
lines += ["", f"🔐 To check a download, compare it with [`{sums}`]({url})."]
return "\n".join(lines)


def compose(version, sha, repo, tag, assets, repo_root, warn):
notes = repo_root / "docs" / "releases" / f"v{version}.md"
if not notes.is_file():
return None
text = notes.read_text(encoding="utf-8")
text = rewrite_images(text, notes, repo_root, repo, sha, warn)
return text.replace("<!-- downloads -->", downloads_table(assets, repo, tag, version))


def main():
parser = argparse.ArgumentParser()
for name in ("version", "sha", "repo", "tag", "assets", "out"):
parser.add_argument(f"--{name}", required=True)
parser.add_argument("--root", default=".")
args = parser.parse_args()
warn = lambda message: print(f"::warning::{message}")
body = compose(args.version, args.sha, args.repo, args.tag, Path(args.assets),
Path(args.root).resolve(), warn)
if body is None:
print(f"No docs/releases/v{args.version}.md")
return 3
Path(args.out).write_text(body, encoding="utf-8")
return 0


if __name__ == "__main__":
sys.exit(main())
Loading
Loading