Files
Barış Keser db6dcf7225 v1.3.0: crossfade + boslukssuz (gapless) calma ve Discord Rich Presence
- Motor: siradaki parcayi onden coz, sinirda es-guclu crossfade (0-12 sn) ya da
  boslukssuz terfi; kilit-guvenli callback, elle geciste onyukleme iptal
- Bridge: setCrossfade/setGapless/setDiscordRpc/setDiscordClientId slotlari;
  sirali calmada otomatik onyukleme; motorun kendiliginden ilerlemesinde arayuzu
  yeniden yukleme yapmadan (boslukssuz) guncelle
- richpresence.py: opsiyonel pypresence sarmalayici (Discord acik + Client ID ister),
  hataya dayanikli + throttle'li; calan parca/oynat-duraklat Discord'a yansir
- Web: Efektler'de 'Gecis & Discord' karti (crossfade slider, gapless toggle,
  Discord toggle + Client ID alani); TR/EN i18n
- requirements/build: pypresence eklendi, --collect-all pypresence
2026-09-20 17:01:18 +03:00

170 lines
5.4 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Discord Rich Presence — çalan parçayı Discord profilinde gösterir (opsiyonel).
`pypresence` gerektirir (opsiyonel bağımlılık) ve bir Discord **Application Client
ID** ister (https://discord.com/developers/applications → yeni uygulama → ID kopyala).
Discord masaüstü uygulaması açık değilse ya da Client ID verilmemişse sessizce devre
dışı kalır — çağıran taraf her zaman güvenle çağırabilir.
Görsellerin (logo/play/pause) görünmesi için aynı Discord uygulamasının
"Rich Presence → Art Assets" bölümüne bu anahtarlarla resim yüklenmelidir; anahtar
yoksa yalnız metin görünür (hata olmaz).
"""
from __future__ import annotations
import threading
import time
def pypresence_available() -> bool:
try:
import pypresence # noqa: F401
return True
except Exception:
return False
class DiscordPresence:
"""İş parçacığına duyarlı, hataya dayanıklı Discord RPC sarmalayıcısı."""
def __init__(self, client_id: str = "") -> None:
self.client_id = (client_id or "").strip()
self._rpc = None
self._connected = False
self._enabled = False
self._lock = threading.Lock()
self._last_push = 0.0
self._last_key = None
# ------------------------------------------------------------------ ayar
def set_client_id(self, cid: str) -> None:
cid = (cid or "").strip()
if cid != self.client_id:
self.client_id = cid
self._disconnect()
if self._enabled:
self._ensure()
def enable(self) -> None:
self._enabled = True
self._ensure()
def disable(self) -> None:
self._enabled = False
with self._lock:
rpc = self._rpc
if rpc is not None:
try:
rpc.clear()
except Exception:
pass
self._disconnect()
def close(self) -> None:
self.disable()
# -------------------------------------------------------------- bağlantı
def _disconnect(self) -> None:
with self._lock:
rpc = self._rpc
self._rpc = None
self._connected = False
self._last_key = None
if rpc is not None:
try:
rpc.close()
except Exception:
pass
def _ensure(self) -> bool:
if self._connected and self._rpc is not None:
return True
if not self._enabled or not self.client_id:
return False
# Discord IPC handshake bloklayabilir -> arka planda bağlan
threading.Thread(target=self._connect_worker,
args=(self.client_id,), daemon=True).start()
return False
def _connect_worker(self, cid: str) -> None:
try:
from pypresence import Presence
except Exception:
return
rpc = None
try:
rpc = Presence(cid)
rpc.connect()
except Exception:
# Discord açık değil / geçersiz ID -> yarım kalan bağlantıyı kapat
if rpc is not None:
try:
rpc.close()
except Exception:
pass
return
with self._lock:
if cid != self.client_id or not self._enabled:
try:
rpc.close()
except Exception:
pass
return
self._rpc = rpc
self._connected = True
self._last_key = None
# ------------------------------------------------------------ güncelleme
def update(self, title: str, artist: str, playing: bool,
position: float = 0.0, duration: float = 0.0,
large_text: str = "Just Music Premium") -> None:
"""Çalan parçayı yayınlar. Devre dışı/bağlanmamışsa sessizce döner."""
if not self._enabled or not self.client_id:
return
if not self._connected:
self._ensure()
return
# aşırı istek engeli (Discord ~5/20s sınırlı); parça değişince hemen güncelle
key = (title, artist, bool(playing), int(max(0.0, position) // 5))
now = time.time()
if key == self._last_key and (now - self._last_push) < 15:
return
self._last_key = key
self._last_push = now
kwargs = {
"details": (title or "Bilinmeyen parça")[:128],
"large_image": "logo",
"large_text": large_text[:128],
}
if artist:
kwargs["state"] = artist[:128]
if playing and duration and duration > 0:
start = int(now - max(0.0, position))
kwargs["start"] = start
kwargs["end"] = int(start + duration)
kwargs["small_image"] = "play"
kwargs["small_text"] = "Çalıyor"
else:
kwargs["small_image"] = "pause"
kwargs["small_text"] = "Duraklatıldı"
with self._lock:
rpc = self._rpc
if rpc is None:
return
try:
rpc.update(**kwargs)
except Exception:
self._disconnect()
def clear(self) -> None:
with self._lock:
rpc = self._rpc
if rpc is not None:
try:
rpc.clear()
except Exception:
pass
self._last_key = None