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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Changelog

## Unreleased
- **Sharper App Icon**: The icon shipped at a single small size, so Windows stretched it for shortcuts, folder views and notifications — it looked fuzzy nearly everywhere. It now includes every size Windows asks for, up to 256px

## v1.6.0
- **Progress Bar No Longer Sticks at 50%**: Downloads that come as a single file — an mp3, or a video served directly by the developer — used to leave the bar stuck at half forever, even though the download had finished. Fixed
- **Accurate Progress Bar**: The bar now tracks how many bytes are actually left, instead of giving the video and the audio half each. A 1080p download's audio is a small part of the total, so the bar no longer crawls and then jumps
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG_ro.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Istoric versiuni

## Unreleased
- **Pictogramă mai clară**: Pictograma era livrată într-o singură dimensiune mică, așa că Windows o întindea pentru scurtături, foldere și notificări — arăta neclară aproape peste tot. Acum include toate dimensiunile cerute de Windows, până la 256px

## v1.6.0
- **Bara nu mai rămâne blocată la 50%**: Descărcările care vin ca un singur fișier — un mp3 sau un video servit direct de dezvoltator — lăsau bara blocată la jumătate pentru totdeauna, chiar dacă descărcarea se terminase. Rezolvat
- **Bară de progres corectă**: Bara urmărește acum câți octeți au rămas de fapt, în loc să dea câte o jumătate imaginii și sunetului. La o descărcare 1080p sunetul este o parte mică din total, așa că bara nu mai avansează greu și apoi sare brusc
Expand Down
Binary file added app/frontend/assets/icon4.icns
Binary file not shown.
Binary file modified app/frontend/assets/icon4.ico
Binary file not shown.
64 changes: 64 additions & 0 deletions scripts/build_icon.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#!/usr/bin/env python3
"""Regenerate app/frontend/assets/icon4.ico from the .icns master.

The shipped .ico used to contain **a single 32x32 image**, so Windows scaled it
up everywhere it wanted something bigger - shortcuts and toast notifications ask
for 256, Explorer for 48 - and the icon looked soft in every one of those
places. The .icns beside it holds real renders up to 1024, so the fix is simply
to emit the sizes Windows actually asks for.

Native .icns entries are preferred at each size; only 16, 24 and 48 have no
native entry and are resampled from the 1024 bitmap.

venv/bin/python scripts/build_icon.py
"""

import os
import sys

from PIL import Image, IcnsImagePlugin

ASSETS = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
"app", "frontend", "assets")
ICNS = os.path.join(ASSETS, "icon4.icns")
ICO = os.path.join(ASSETS, "icon4.ico")

# 16 tray/menus, 24+32 taskbar and title bar, 48 Explorer, 64/128 large icons,
# 256 shortcuts and notification toasts.
SIZES = [16, 24, 32, 48, 64, 128, 256]


def main():
if not os.path.isfile(ICNS):
sys.exit(f"missing master icon: {ICNS}")

with open(ICNS, "rb") as f:
icns = IcnsImagePlugin.IcnsFile(f)
native = {}
for key in icns.itersizes():
img = icns.getimage(key).convert("RGBA")
native.setdefault(img.size[0], img)
if not native:
sys.exit("no images found in the .icns")

biggest = native[max(native)]
frames, notes = [], []
for size in SIZES:
if size in native:
frames.append(native[size])
notes.append(f"{size}=native")
else:
frames.append(biggest.resize((size, size), Image.LANCZOS))
notes.append(f"{size}=scaled")

frames[-1].save(ICO, format="ICO", sizes=[(s, s) for s in SIZES])
print(" ".join(notes))

written = sorted(Image.open(ICO).ico.sizes())
print(f"wrote {ICO}: {written}")
assert written == sorted((s, s) for s in SIZES), written
return 0


if __name__ == "__main__":
sys.exit(main())
35 changes: 35 additions & 0 deletions tests/test_smoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,38 @@ def test_diaspora_exclusion():
}
url, _ = find_video_url("https://youtube.com/c/Test", "10.05.2025")
assert url == "https://www.youtube.com/watch?v=vid2"


# ---------------------------------------------------------------------------
# The app icon
#
# The shipped .ico once held a single 32x32 image, so Windows upscaled it for
# shortcuts (256), Explorer (48) and toast notifications (256) - it looked soft
# everywhere. Regenerate with scripts/build_icon.py.
# ---------------------------------------------------------------------------

def test_app_icon_has_the_sizes_windows_asks_for():
import os
from PIL import Image

root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
ico = os.path.join(root, "app", "frontend", "assets", "icon4.ico")
sizes = {w for w, h in Image.open(ico).ico.sizes()}

for needed in (16, 32, 48, 256):
assert needed in sizes, (
f"icon4.ico has no {needed}px image (has {sorted(sizes)}); "
"run scripts/build_icon.py")


def test_app_icon_master_is_kept_alongside():
"""Without the .icns there is nothing to regenerate the .ico from, which is
how it ended up 32px-only in the first place."""
import os
from PIL import Image

root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
icns = os.path.join(root, "app", "frontend", "assets", "icon4.icns")
assert os.path.isfile(icns), "the high-resolution master must stay in the repo"
im = Image.open(icns)
assert max(w for w, h, s in im.info["sizes"]) >= 256
9 changes: 8 additions & 1 deletion youtubeweekly.spec
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ PyInstaller spec file for YoutubeWeekly.
Build with: pyinstaller youtubeweekly.spec
"""
import os
import sys

# The .ico carries every size Windows asks for (16-256) and the .icns is the
# high-resolution master both are generated from - see scripts/build_icon.py.
# macOS wants the .icns natively rather than a converted .ico.
APP_ICON = os.path.join("app", "frontend", "assets",
"icon4.icns" if sys.platform == "darwin" else "icon4.ico")

block_cipher = None

Expand Down Expand Up @@ -58,7 +65,7 @@ exe = EXE(
strip=False,
upx=True,
console=False,
icon="app/frontend/assets/icon4.ico",
icon=APP_ICON,
)

coll = COLLECT(
Expand Down