mytunes-pro 2.0.8__py3-none-any.whl → 2.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mytunes/app.py +88 -36
- {mytunes_pro-2.0.8.dist-info → mytunes_pro-2.1.1.dist-info}/METADATA +15 -4
- mytunes_pro-2.1.1.dist-info/RECORD +8 -0
- mytunes_pro-2.0.8.dist-info/RECORD +0 -8
- {mytunes_pro-2.0.8.dist-info → mytunes_pro-2.1.1.dist-info}/WHEEL +0 -0
- {mytunes_pro-2.0.8.dist-info → mytunes_pro-2.1.1.dist-info}/entry_points.txt +0 -0
- {mytunes_pro-2.0.8.dist-info → mytunes_pro-2.1.1.dist-info}/licenses/LICENSE +0 -0
- {mytunes_pro-2.0.8.dist-info → mytunes_pro-2.1.1.dist-info}/top_level.txt +0 -0
mytunes/app.py
CHANGED
|
@@ -44,7 +44,21 @@ MPV_SOCKET = "/tmp/mpv_socket"
|
|
|
44
44
|
LOG_FILE = "/tmp/mytunes_mpv.log"
|
|
45
45
|
PID_FILE = "/tmp/mytunes_mpv.pid"
|
|
46
46
|
APP_NAME = "MyTunes Pro"
|
|
47
|
-
APP_VERSION = "2.
|
|
47
|
+
APP_VERSION = "2.1.1"
|
|
48
|
+
|
|
49
|
+
# Initial Locale Setup for WSL/Windows Multibyte/Emoji Harmony
|
|
50
|
+
try:
|
|
51
|
+
locale.setlocale(locale.LC_ALL, '')
|
|
52
|
+
except:
|
|
53
|
+
pass
|
|
54
|
+
|
|
55
|
+
# WSL Detection: EQ/IPC robustness logic
|
|
56
|
+
IS_WSL = False
|
|
57
|
+
if hasattr(os, 'uname'):
|
|
58
|
+
try:
|
|
59
|
+
IS_WSL = "microsoft" in os.uname().release.lower()
|
|
60
|
+
except: pass
|
|
61
|
+
|
|
48
62
|
|
|
49
63
|
# === [Strings & Localization] ===
|
|
50
64
|
STRINGS = {
|
|
@@ -305,7 +319,10 @@ class Player:
|
|
|
305
319
|
self.loading = False
|
|
306
320
|
self.loading_ts = 0
|
|
307
321
|
self.socket_fail_count = 0 # Track consecutive IPC failures
|
|
308
|
-
self.socket_ok = True
|
|
322
|
+
self.socket_ok = True # Socket health flag
|
|
323
|
+
self.last_socket_warn = 0 # Rate limit for socket error warnings
|
|
324
|
+
self.socket_retry_ts = 0 # Cool-down for reconnection attempts
|
|
325
|
+
|
|
309
326
|
|
|
310
327
|
# Cleanup pre-existing instance if any
|
|
311
328
|
# self.cleanup_orphaned_mpv() # Moved to play() per user request
|
|
@@ -360,10 +377,11 @@ class Player:
|
|
|
360
377
|
"--idle=yes"
|
|
361
378
|
]
|
|
362
379
|
|
|
363
|
-
# Inject Initial EQ (0ms Latency)
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
380
|
+
# Inject Initial EQ (0ms Latency) - Skip on WSL (causes freezing)
|
|
381
|
+
if not IS_WSL:
|
|
382
|
+
eq_af = self._get_eq_af_string(initial_eq_preset)
|
|
383
|
+
if eq_af:
|
|
384
|
+
cmd.append(f"--af={eq_af}")
|
|
367
385
|
|
|
368
386
|
cmd.append(url)
|
|
369
387
|
|
|
@@ -428,16 +446,29 @@ class Player:
|
|
|
428
446
|
self.send_cmd(["add", "volume", delta])
|
|
429
447
|
|
|
430
448
|
def send_cmd(self, command):
|
|
431
|
-
"""Send raw command list to MPV via JSON IPC."""
|
|
432
|
-
#
|
|
449
|
+
"""Send raw command list to MPV via JSON IPC with resilience."""
|
|
450
|
+
# 1. Fast-Detect Process Death
|
|
451
|
+
if self.current_proc and self.current_proc.poll() is not None:
|
|
452
|
+
self.socket_ok = False
|
|
453
|
+
self.current_proc = None
|
|
454
|
+
return None
|
|
455
|
+
|
|
456
|
+
# 2. Re-connection Cool-down (Throttle blocking connect() calls)
|
|
457
|
+
now = time.time()
|
|
458
|
+
if not self.socket_ok and now < self.socket_retry_ts:
|
|
459
|
+
return None
|
|
460
|
+
|
|
461
|
+
# 3. Pre-check: Skip if socket file doesn't exist (e.g. killed elsewhere)
|
|
433
462
|
if not os.path.exists(MPV_SOCKET):
|
|
434
463
|
self.socket_fail_count += 1
|
|
435
|
-
self.
|
|
464
|
+
if self.socket_fail_count >= 2:
|
|
465
|
+
self.socket_ok = False
|
|
466
|
+
self.socket_retry_ts = now + 1.5 # 1.5s cool-down
|
|
436
467
|
return None
|
|
437
468
|
|
|
438
469
|
try:
|
|
439
470
|
client = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
|
440
|
-
client.settimeout(0.5) # Fast timeout (Optimization for Sleep/Wake resilience)
|
|
471
|
+
client.settimeout(0.5) # Fast timeout (Optimization for Sleep/Wake/WSL resilience)
|
|
441
472
|
client.connect(MPV_SOCKET)
|
|
442
473
|
cmd_str = json.dumps({"command": command}) + "\n"
|
|
443
474
|
client.send(cmd_str.encode('utf-8'))
|
|
@@ -451,23 +482,29 @@ class Player:
|
|
|
451
482
|
if b"\n" in chunk: break
|
|
452
483
|
|
|
453
484
|
client.close()
|
|
454
|
-
# Success:
|
|
485
|
+
# Success: Fully Restore health
|
|
455
486
|
self.socket_fail_count = 0
|
|
456
487
|
self.socket_ok = True
|
|
457
488
|
return json.loads(response.decode('utf-8'))
|
|
458
489
|
except:
|
|
459
490
|
self.socket_fail_count += 1
|
|
460
|
-
if self.socket_fail_count >=
|
|
491
|
+
if self.socket_fail_count >= 2:
|
|
461
492
|
self.socket_ok = False
|
|
493
|
+
self.socket_retry_ts = now + 1.5 # 1.5s cool-down
|
|
462
494
|
return None
|
|
463
495
|
|
|
464
496
|
def get_property(self, prop):
|
|
497
|
+
"""Skip IPC if health is bad to prevent TUI freezing."""
|
|
498
|
+
if not self.socket_ok:
|
|
499
|
+
return None
|
|
465
500
|
res = self.send_cmd(["get_property", prop])
|
|
466
501
|
if res and "data" in res:
|
|
467
502
|
return res["data"]
|
|
468
503
|
return None
|
|
469
504
|
|
|
470
505
|
def set_property(self, prop, value):
|
|
506
|
+
if not self.socket_ok:
|
|
507
|
+
return
|
|
471
508
|
self.send_cmd(["set_property", prop, value])
|
|
472
509
|
|
|
473
510
|
def toggle_pause(self):
|
|
@@ -493,6 +530,8 @@ class Player:
|
|
|
493
530
|
|
|
494
531
|
def set_equalizer(self, preset_name):
|
|
495
532
|
"""Apply 10-band equalizer preset using lavfi."""
|
|
533
|
+
if IS_WSL:
|
|
534
|
+
return # Skip EQ on WSL (causes freezing)
|
|
496
535
|
af_str = self._get_eq_af_string(preset_name)
|
|
497
536
|
self.set_property("af", af_str)
|
|
498
537
|
|
|
@@ -639,24 +678,29 @@ class MyTunesApp:
|
|
|
639
678
|
def update_playback_state(self):
|
|
640
679
|
# Poll MPV for state with throttling to reduce CPU/IPC overhead
|
|
641
680
|
try:
|
|
681
|
+
# 0. Health Check: If socket is known-bad, exit immediately to keep TUI responsive
|
|
682
|
+
if not self.player.socket_ok:
|
|
683
|
+
return
|
|
684
|
+
|
|
642
685
|
# 1. Mandatory every loop: Current time (for progress bar)
|
|
643
686
|
t = self.player.get_property("time-pos")
|
|
644
|
-
if t is
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
self.player.loading = False
|
|
648
|
-
|
|
649
|
-
# Update Resume Data (Memory) - Throttle save logic
|
|
650
|
-
if self.current_track and self.playback_duration > 30:
|
|
651
|
-
if self.playback_time / self.playback_duration > 0.99:
|
|
652
|
-
self.dm.set_progress(self.current_track['url'], 0)
|
|
653
|
-
elif self.playback_time > 10:
|
|
654
|
-
self.dm.set_progress(self.current_track['url'], self.playback_time)
|
|
687
|
+
if t is None:
|
|
688
|
+
# If even time-pos fails, bail out immediately to prevent cascaded delay
|
|
689
|
+
return
|
|
655
690
|
|
|
691
|
+
self.playback_time = float(t)
|
|
692
|
+
if self.player.loading and self.playback_time >= 0:
|
|
693
|
+
self.player.loading = False
|
|
694
|
+
|
|
695
|
+
# Update Resume Data (Memory) - Throttle save logic
|
|
696
|
+
if self.current_track and self.playback_duration > 30:
|
|
697
|
+
if self.playback_time / self.playback_duration > 0.99:
|
|
698
|
+
self.dm.set_progress(self.current_track['url'], 0)
|
|
699
|
+
elif self.playback_time > 10:
|
|
700
|
+
self.dm.set_progress(self.current_track['url'], self.playback_time)
|
|
656
701
|
|
|
657
702
|
|
|
658
703
|
# Safety: If loading takes too long (> 8s), force reset to allow error handling/skip
|
|
659
|
-
# Consolidated redundancy checks into a single clean block
|
|
660
704
|
now = time.time()
|
|
661
705
|
if self.player.loading and (now - self.player.loading_ts > 8):
|
|
662
706
|
self.player.loading = False
|
|
@@ -686,6 +730,7 @@ class MyTunesApp:
|
|
|
686
730
|
if time.time() - getattr(self, 'last_save_time', 0) > 10:
|
|
687
731
|
self.dm.save_data()
|
|
688
732
|
self.last_save_time = time.time()
|
|
733
|
+
|
|
689
734
|
|
|
690
735
|
except: pass
|
|
691
736
|
|
|
@@ -866,7 +911,10 @@ class MyTunesApp:
|
|
|
866
911
|
self.stop_on_exit = False; self.running = False
|
|
867
912
|
|
|
868
913
|
elif cmd == "CYCLE_EQ":
|
|
869
|
-
|
|
914
|
+
if IS_WSL:
|
|
915
|
+
self.show_feedback("⚠️ EQ is disabled on WSL for stability" if self.lang=="en" else "⚠️ WSL 안정성을 위해 EQ 기능이 비활성화되었습니다")
|
|
916
|
+
else:
|
|
917
|
+
self.cycle_equalizer()
|
|
870
918
|
|
|
871
919
|
elif isinstance(cmd, tuple) and cmd[0] == "UNKNOWN":
|
|
872
920
|
key = cmd[1]
|
|
@@ -1620,6 +1668,10 @@ class MyTunesApp:
|
|
|
1620
1668
|
|
|
1621
1669
|
# Row 2: Actions
|
|
1622
1670
|
r2 = self.t("header_r2")
|
|
1671
|
+
if IS_WSL:
|
|
1672
|
+
# Remove "[E]이퀄라이저" or "[E]EQ"
|
|
1673
|
+
r2 = r2.replace("[E]이퀄라이저 ", "").replace("[E]이퀄라이저", "").replace("[E]EQ ", "").replace("[E]EQ", "")
|
|
1674
|
+
|
|
1623
1675
|
gap2 = w - 4 - self.get_display_width(r2)
|
|
1624
1676
|
if gap2 < 2: gap2 = 2
|
|
1625
1677
|
line2 = f"{' '*gap2}{r2}"
|
|
@@ -1646,17 +1698,15 @@ class MyTunesApp:
|
|
|
1646
1698
|
status_icon = "❚❚" if self.is_paused else "▶"
|
|
1647
1699
|
|
|
1648
1700
|
# Prepare EQ Info for Title Line
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
else:
|
|
1657
|
-
eq_display = f"🎚 EQ: {current_eq}"
|
|
1701
|
+
eq_display = ""
|
|
1702
|
+
if not IS_WSL:
|
|
1703
|
+
current_eq = EQUALIZER_KEYS[self.current_eq_index]
|
|
1704
|
+
if current_eq == "Auto":
|
|
1705
|
+
eq_display = f"🎚 Auto: {self.auto_preset_name}"
|
|
1706
|
+
else:
|
|
1707
|
+
eq_display = f"🎚 EQ: {current_eq}"
|
|
1658
1708
|
|
|
1659
|
-
eq_info = f" [{eq_display}]" # Right side content
|
|
1709
|
+
eq_info = f" [{eq_display}]" if eq_display else "" # Right side content
|
|
1660
1710
|
|
|
1661
1711
|
# Calculate space for Title
|
|
1662
1712
|
# Total Width - margins(2) - Icon(2) - Space(1) - EQ Info - Branding(Maybe separate line, but here we just need fit)
|
|
@@ -1821,8 +1871,10 @@ class MyTunesApp:
|
|
|
1821
1871
|
|
|
1822
1872
|
def check_autoplay(self):
|
|
1823
1873
|
# Auto-play next track from Global Queue
|
|
1824
|
-
|
|
1874
|
+
if not self.running: return
|
|
1825
1875
|
if self.player.loading: return
|
|
1876
|
+
# Logic check: Skip if playback just started or socket is dead to prevent feedback loops
|
|
1877
|
+
if not self.player.socket_ok: return
|
|
1826
1878
|
|
|
1827
1879
|
try:
|
|
1828
1880
|
is_idle = self.player.get_property("idle-active")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mytunes-pro
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.1.1
|
|
4
4
|
Summary: A lightweight, keyboard-centric terminal player for streaming YouTube music.
|
|
5
5
|
Author-email: loxo <loxo5432@gmail.com>
|
|
6
6
|
Project-URL: Homepage, https://github.com/postgresql-co-kr/mytunes
|
|
@@ -18,9 +18,9 @@ Requires-Dist: yt-dlp
|
|
|
18
18
|
Requires-Dist: pusher
|
|
19
19
|
Dynamic: license-file
|
|
20
20
|
|
|
21
|
-
# 🎵 MyTunes Pro - Professional TUI Edition v2.
|
|
21
|
+
# 🎵 MyTunes Pro - Professional TUI Edition v2.1.1
|
|
22
22
|
|
|
23
|
-
## 🚀 Terminal-based Media Workflow Experiment v2.
|
|
23
|
+
## 🚀 Terminal-based Media Workflow Experiment v2.1.1
|
|
24
24
|
|
|
25
25
|
> [!IMPORTANT]
|
|
26
26
|
> **Legal Disclaimer:** This project is a personal, non-commercial research experiment for developer education.
|
|
@@ -216,7 +216,7 @@ Executes immediately without worrying about input language status.
|
|
|
216
216
|
|
|
217
217
|
# 🎵 MyTunes Pro (Experimental Media Tool - KR)
|
|
218
218
|
|
|
219
|
-
## 🚀 터미널 기반 미디어 워크플로우 실험 v2.0
|
|
219
|
+
## 🚀 터미널 기반 미디어 워크플로우 실험 v2.1.0
|
|
220
220
|
|
|
221
221
|
> [!IMPORTANT]
|
|
222
222
|
> **법적 면책 고지:** 본 프로젝트는 개발자 교육 및 연구를 목적으로 하는 개인적, 비상업적 실험입니다.
|
|
@@ -390,6 +390,17 @@ Windows 환경에서 한글 검색이 안 되거나 설치가 어려운 분들
|
|
|
390
390
|
|
|
391
391
|
## 🔄 Changelog
|
|
392
392
|
|
|
393
|
+
### v2.1.1 (2026-02-02)
|
|
394
|
+
- **WSL UI Polish**: Hides Equalizer (EQ) labels and status in the TUI when running on WSL to avoid confusion, as the feature is disabled in that environment for stability.
|
|
395
|
+
- **Improved Feedback**: Provides a clear status message when the 'E' key is pressed on WSL.
|
|
396
|
+
|
|
397
|
+
### v2.1.0 (2026-02-02)
|
|
398
|
+
- **Zero-Freeze IPC Resilience**: Implemented a "Fast-Fail" mechanism that detects mpv process death within 0.1ms via `poll()`, preventing TUI freezes.
|
|
399
|
+
- **Fail-Early Polling**: Main loop now aborts all remaining IPC property checks immediately if any call fails, maintaining a smooth 5fps even on broken connections.
|
|
400
|
+
- **Connection Throttling**: Added a 1.5-second "cool-down" period for reconnection attempts to minimize blocking time on Windows/WSL environments.
|
|
401
|
+
- **Multibyte Harmony**: Explicitly configured `locale.setlocale` to ensure stable emoji and CJK character rendering across different terminal environments.
|
|
402
|
+
- **Improved Autoplay Stability**: Autoplay logic now skips status checks when the socket is unhealthy to prevent feedback loops.
|
|
403
|
+
|
|
393
404
|
### v2.0.8 (2026-02-02)
|
|
394
405
|
- **Windows/WSL Socket Recovery**: Fixed UI freezing when mpv socket disconnects during window switching.
|
|
395
406
|
- **IPC Resilience**: Added socket pre-check and failure counter to prevent blocking on broken connections.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
mytunes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
mytunes/app.py,sha256=ns6bWufnE3ElzBbLHtlfdjjqlKJq2B-VnaQiJLmLcUk,84259
|
|
3
|
+
mytunes_pro-2.1.1.dist-info/licenses/LICENSE,sha256=lOrP0EIjxcgJia__W3f3PVDZkRd2oRzFkyH2g3LRRCg,1063
|
|
4
|
+
mytunes_pro-2.1.1.dist-info/METADATA,sha256=K7UsewSpojOy421P374Hye4Bhe96hwDxKCxQwJs9VSk,30046
|
|
5
|
+
mytunes_pro-2.1.1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
6
|
+
mytunes_pro-2.1.1.dist-info/entry_points.txt,sha256=6-MsC13nIgzLvrREaGotc32FgxHx_Iuu1z2qCzJs1_4,65
|
|
7
|
+
mytunes_pro-2.1.1.dist-info/top_level.txt,sha256=KWzdFyNNG_sO7GT83-sN5fYArP4_DL5I8HYIwgazXyY,8
|
|
8
|
+
mytunes_pro-2.1.1.dist-info/RECORD,,
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
mytunes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
mytunes/app.py,sha256=bD54KYF1Y5_Ic-XoZfEij7tKGd_ZhwaJCpc43pZzgQw,82247
|
|
3
|
-
mytunes_pro-2.0.8.dist-info/licenses/LICENSE,sha256=lOrP0EIjxcgJia__W3f3PVDZkRd2oRzFkyH2g3LRRCg,1063
|
|
4
|
-
mytunes_pro-2.0.8.dist-info/METADATA,sha256=it24t91wwsXbqfyipIADChgHJy5fCHVsNzBZwC1DFWo,28977
|
|
5
|
-
mytunes_pro-2.0.8.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
6
|
-
mytunes_pro-2.0.8.dist-info/entry_points.txt,sha256=6-MsC13nIgzLvrREaGotc32FgxHx_Iuu1z2qCzJs1_4,65
|
|
7
|
-
mytunes_pro-2.0.8.dist-info/top_level.txt,sha256=KWzdFyNNG_sO7GT83-sN5fYArP4_DL5I8HYIwgazXyY,8
|
|
8
|
-
mytunes_pro-2.0.8.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|