mytunes-pro 1.8.3__py3-none-any.whl → 1.8.5__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 +55 -90
- {mytunes_pro-1.8.3.dist-info → mytunes_pro-1.8.5.dist-info}/METADATA +16 -4
- mytunes_pro-1.8.5.dist-info/RECORD +8 -0
- mytunes_pro-1.8.3.dist-info/RECORD +0 -8
- {mytunes_pro-1.8.3.dist-info → mytunes_pro-1.8.5.dist-info}/WHEEL +0 -0
- {mytunes_pro-1.8.3.dist-info → mytunes_pro-1.8.5.dist-info}/entry_points.txt +0 -0
- {mytunes_pro-1.8.3.dist-info → mytunes_pro-1.8.5.dist-info}/licenses/LICENSE +0 -0
- {mytunes_pro-1.8.3.dist-info → mytunes_pro-1.8.5.dist-info}/top_level.txt +0 -0
mytunes/app.py
CHANGED
|
@@ -35,7 +35,7 @@ MPV_SOCKET = "/tmp/mpv_socket"
|
|
|
35
35
|
LOG_FILE = "/tmp/mytunes_mpv.log"
|
|
36
36
|
PID_FILE = "/tmp/mytunes_mpv.pid"
|
|
37
37
|
APP_NAME = "MyTunes Pro"
|
|
38
|
-
APP_VERSION = "1.8.
|
|
38
|
+
APP_VERSION = "1.8.5"
|
|
39
39
|
|
|
40
40
|
# === [Strings & Localization] ===
|
|
41
41
|
STRINGS = {
|
|
@@ -245,10 +245,12 @@ class Player:
|
|
|
245
245
|
# self.cleanup_orphaned_mpv() # Moved to play() per user request
|
|
246
246
|
|
|
247
247
|
def cleanup_orphaned_mpv(self):
|
|
248
|
-
#
|
|
249
|
-
#
|
|
248
|
+
# Precise pkill to avoid matching the main TUI process
|
|
249
|
+
# Matches 'mpv ' (with space) or 'mpv' as exact process name
|
|
250
250
|
try:
|
|
251
|
-
subprocess.run(["pkill", "-
|
|
251
|
+
subprocess.run(["pkill", "-x", "mpv"], stderr=subprocess.DEVNULL)
|
|
252
|
+
# Second pass for variants or sub-arguments if needed
|
|
253
|
+
subprocess.run(["pkill", "-f", "mpv --video=no"], stderr=subprocess.DEVNULL)
|
|
252
254
|
except: pass
|
|
253
255
|
|
|
254
256
|
def play(self, url, start_pos=0):
|
|
@@ -331,7 +333,6 @@ class Player:
|
|
|
331
333
|
try:
|
|
332
334
|
self.current_proc.terminate()
|
|
333
335
|
self.current_proc.wait(timeout=1)
|
|
334
|
-
self.current_proc.wait(timeout=1)
|
|
335
336
|
except:
|
|
336
337
|
# If terminate fails, try socket quit
|
|
337
338
|
try: self.send_cmd(["quit"])
|
|
@@ -619,14 +620,24 @@ class MyTunesApp:
|
|
|
619
620
|
if self.selection_idx > 0:
|
|
620
621
|
self.selection_idx -= 1
|
|
621
622
|
if self.selection_idx < self.scroll_offset: self.scroll_offset = self.selection_idx
|
|
623
|
+
elif current_list:
|
|
624
|
+
# v1.8.5 - Wrapping: Top to Bottom
|
|
625
|
+
self.selection_idx = len(current_list) - 1
|
|
626
|
+
h, _ = self.stdscr.getmaxyx()
|
|
627
|
+
# Maintain scroll consistency (h - 10 matches draw() layout)
|
|
628
|
+
list_area_height = h - 10
|
|
629
|
+
self.scroll_offset = max(0, self.selection_idx - list_area_height + 1)
|
|
622
630
|
elif key == curses.KEY_DOWN or k_char in ['j', 'ㅓ']:
|
|
623
631
|
if self.selection_idx < len(current_list) - 1:
|
|
624
632
|
self.selection_idx += 1
|
|
625
633
|
h, _ = self.stdscr.getmaxyx()
|
|
626
|
-
# Use h - 10 to match inner_h in draw() (h - footer_h(5) - header_top(3) - borders(2))
|
|
627
634
|
list_area_height = h - 10
|
|
628
635
|
if self.selection_idx >= self.scroll_offset + list_area_height:
|
|
629
636
|
self.scroll_offset = self.selection_idx - list_area_height + 1
|
|
637
|
+
elif current_list:
|
|
638
|
+
# v1.8.5 - Wrapping: Bottom to Top
|
|
639
|
+
self.selection_idx = 0
|
|
640
|
+
self.scroll_offset = 0
|
|
630
641
|
|
|
631
642
|
# Enter / Select: Enter Only (L moved to Forward)
|
|
632
643
|
elif key == '\n' or key == 10 or key == 13:
|
|
@@ -743,21 +754,9 @@ class MyTunesApp:
|
|
|
743
754
|
if self.is_remote():
|
|
744
755
|
self.show_copy_dialog("YouTube", url)
|
|
745
756
|
else:
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
subprocess.Popen(["open", url], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
750
|
-
elif sys.platform == 'win32':
|
|
751
|
-
os.startfile(url)
|
|
752
|
-
elif self.is_wsl():
|
|
753
|
-
# In WSL, call the Windows shell to open the URL in Windows browser
|
|
754
|
-
subprocess.Popen(["cmd.exe", "/c", "start", url.replace("&", "^&")], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
755
|
-
else:
|
|
756
|
-
webbrowser.open(url)
|
|
757
|
-
self.status_msg = "🌐 Opening YouTube in Browser..."
|
|
758
|
-
except:
|
|
759
|
-
webbrowser.open(url)
|
|
760
|
-
self.status_msg = "🌐 Opening YouTube..."
|
|
757
|
+
# v1.8.4 - Use standard webbrowser library for maximum stability on F7
|
|
758
|
+
self.status_msg = "🌐 Opening YouTube in Browser..."
|
|
759
|
+
threading.Thread(target=webbrowser.open, args=(url,), daemon=True).start()
|
|
761
760
|
|
|
762
761
|
# Open Live Station: F8
|
|
763
762
|
elif key == curses.KEY_F8:
|
|
@@ -766,35 +765,24 @@ class MyTunesApp:
|
|
|
766
765
|
self.show_copy_dialog("Live Station", live_url)
|
|
767
766
|
return
|
|
768
767
|
|
|
769
|
-
#
|
|
770
|
-
# Using int(time.time() / 3600) to keep it stable within the same hour but fresh enough for new versions
|
|
771
|
-
temp_user_data = os.path.join(tempfile.gettempdir(), f"mytunes_v174_{int(time.time() / 10)}")
|
|
772
|
-
|
|
773
|
-
# Universal flags
|
|
768
|
+
# App Mode Flags
|
|
774
769
|
flags = [
|
|
775
770
|
f"--app={live_url}",
|
|
776
771
|
"--window-size=712,800",
|
|
777
772
|
"--window-position=100,100",
|
|
778
|
-
|
|
773
|
+
"--new-window",
|
|
779
774
|
"--no-first-run",
|
|
780
|
-
"--disable-extensions"
|
|
781
|
-
"--disable-default-apps",
|
|
782
|
-
"--disable-features=Translation",
|
|
783
|
-
"--disable-save-password-bubble",
|
|
784
|
-
"--disable-translate"
|
|
775
|
+
"--disable-extensions"
|
|
785
776
|
]
|
|
786
777
|
|
|
787
778
|
launched = False
|
|
788
|
-
#
|
|
779
|
+
# v1.8.4 - Subprocess Isolation (start_new_session) to prevent crashes on WSL/Linux
|
|
780
|
+
# 1. macOS
|
|
789
781
|
if sys.platform == 'darwin':
|
|
790
|
-
browsers = [
|
|
791
|
-
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
|
792
|
-
"/Applications/Brave Browser.app/Contents/MacOS/Brave Browser"
|
|
793
|
-
]
|
|
782
|
+
browsers = ["/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser"]
|
|
794
783
|
for b_path in browsers:
|
|
795
784
|
if os.path.exists(b_path):
|
|
796
785
|
try:
|
|
797
|
-
# Use 'open -na' but without AppleScript to stay 'standard' and avoid prompts
|
|
798
786
|
subprocess.Popen(["open", "-na", b_path, "--args"] + flags, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
799
787
|
launched = True; break
|
|
800
788
|
except: pass
|
|
@@ -802,71 +790,39 @@ class MyTunesApp:
|
|
|
802
790
|
# 2. Windows Native
|
|
803
791
|
elif sys.platform == 'win32':
|
|
804
792
|
win_paths = [
|
|
805
|
-
os.path.join(os.environ.get('PROGRAMFILES', '
|
|
806
|
-
os.path.join(os.environ.get('PROGRAMFILES(X86)', '
|
|
793
|
+
os.path.join(os.environ.get('PROGRAMFILES', ''), 'Google\\Chrome\\Application\\chrome.exe'),
|
|
794
|
+
os.path.join(os.environ.get('PROGRAMFILES(X86)', ''), 'Google\\Chrome\\Application\\chrome.exe'),
|
|
807
795
|
os.path.join(os.environ.get('LOCALAPPDATA', ''), 'Google\\Chrome\\Application\\chrome.exe'),
|
|
808
|
-
os.path.join(os.environ.get('PROGRAMFILES', 'C:\\Program Files'), 'BraveSoftware\\Brave-Browser\\Application\\brave.exe'),
|
|
809
|
-
os.path.join(os.environ.get('PROGRAMFILES(X86)', 'C:\\Program Files (x86)'), 'Microsoft\\Edge\\Application\\msedge.exe'),
|
|
810
|
-
os.path.join(os.environ.get('PROGRAMFILES', 'C:\\Program Files'), 'Microsoft\\Edge\\Application\\msedge.exe'),
|
|
811
|
-
]
|
|
812
|
-
# v1.8.2 - Maximum precision: --app flag MUST be exact and first for reliable popup mode
|
|
813
|
-
win_flags = [
|
|
814
|
-
f'--app={live_url}',
|
|
815
|
-
'--window-size=712,800',
|
|
816
|
-
'--window-position=100,100',
|
|
817
|
-
'--new-window',
|
|
818
|
-
'--no-first-run',
|
|
819
|
-
'--disable-extensions'
|
|
820
796
|
]
|
|
821
797
|
for p in win_paths:
|
|
822
|
-
if os.path.exists(p):
|
|
798
|
+
if p and os.path.exists(p):
|
|
823
799
|
try:
|
|
824
|
-
|
|
825
|
-
subprocess.Popen([p] + win_flags, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
800
|
+
subprocess.Popen([p] + flags, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
826
801
|
launched = True; break
|
|
827
802
|
except: pass
|
|
828
803
|
|
|
829
|
-
# 3. WSL
|
|
804
|
+
# 3. WSL / Linux (Direct path with session isolation)
|
|
830
805
|
elif self.is_wsl():
|
|
831
|
-
|
|
832
|
-
# We try standard Windows installation paths via /mnt/c/
|
|
833
|
-
wsl_win_paths = [
|
|
806
|
+
wsl_paths = [
|
|
834
807
|
"/mnt/c/Program Files/Google/Chrome/Application/chrome.exe",
|
|
835
808
|
"/mnt/c/Program Files (x86)/Google/Chrome/Application/chrome.exe",
|
|
836
809
|
"/mnt/c/Program Files/BraveSoftware/Brave-Browser/Application/brave.exe",
|
|
837
|
-
"/mnt/c/Program Files (x86)/Microsoft/Edge/Application/msedge.exe",
|
|
838
|
-
"/mnt/c/Program Files/Microsoft/Edge/Application/msedge.exe",
|
|
839
810
|
]
|
|
840
|
-
|
|
841
|
-
wsl_win_flags = [
|
|
842
|
-
f'--app={live_url}',
|
|
843
|
-
'--window-size=712,800',
|
|
844
|
-
'--window-position=100,100',
|
|
845
|
-
'--new-window',
|
|
846
|
-
'--no-first-run',
|
|
847
|
-
'--disable-extensions'
|
|
848
|
-
]
|
|
849
|
-
|
|
850
|
-
launched_direct = False
|
|
851
|
-
for p in wsl_win_paths:
|
|
811
|
+
for p in wsl_paths:
|
|
852
812
|
if os.path.exists(p):
|
|
853
813
|
try:
|
|
854
|
-
#
|
|
855
|
-
|
|
856
|
-
|
|
814
|
+
# CRITICAL: start_new_session=True isolates the browser from the TUI process group
|
|
815
|
+
# This prevents the TUI from dying when navigating or if the browser has shell issues.
|
|
816
|
+
subprocess.Popen([p] + flags, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, start_new_session=True)
|
|
817
|
+
launched = True; break
|
|
857
818
|
except: pass
|
|
858
819
|
|
|
859
|
-
if not
|
|
820
|
+
if not launched:
|
|
860
821
|
try:
|
|
861
|
-
#
|
|
862
|
-
|
|
863
|
-
subprocess.Popen(["cmd.exe", "/c", cmd_fallback], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
822
|
+
# Fallback for WSL to CMD
|
|
823
|
+
subprocess.Popen(["cmd.exe", "/c", f"start chrome --app={live_url}"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, start_new_session=True)
|
|
864
824
|
launched = True
|
|
865
|
-
except:
|
|
866
|
-
try:
|
|
867
|
-
subprocess.Popen(["cmd.exe", "/c", "start", live_url], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
868
|
-
launched = True
|
|
869
|
-
except: pass
|
|
825
|
+
except: pass
|
|
870
826
|
|
|
871
827
|
# 4. Native Linux
|
|
872
828
|
else:
|
|
@@ -1470,11 +1426,20 @@ class MyTunesApp:
|
|
|
1470
1426
|
|
|
1471
1427
|
def run(self):
|
|
1472
1428
|
while self.running:
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1429
|
+
try:
|
|
1430
|
+
self.loop_count = (self.loop_count + 1) % 1000
|
|
1431
|
+
self.update_playback_state()
|
|
1432
|
+
self.check_autoplay()
|
|
1433
|
+
self.draw()
|
|
1434
|
+
self.handle_input()
|
|
1435
|
+
except Exception as e:
|
|
1436
|
+
# v1.8.4 - Global resilience: Catch and log loop errors instead of crashing
|
|
1437
|
+
try:
|
|
1438
|
+
with open("/tmp/mytunes_error.log", "a") as f:
|
|
1439
|
+
f.write(f"[{time.ctime()}] Loop Error: {str(e)}\n")
|
|
1440
|
+
except: pass
|
|
1441
|
+
# Small sleep to prevent infinite tight loop on persistent error
|
|
1442
|
+
time.sleep(0.1)
|
|
1478
1443
|
|
|
1479
1444
|
if self.stop_on_exit:
|
|
1480
1445
|
self.player.stop()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mytunes-pro
|
|
3
|
-
Version: 1.8.
|
|
3
|
+
Version: 1.8.5
|
|
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
|
|
@@ -19,7 +19,7 @@ Dynamic: license-file
|
|
|
19
19
|
|
|
20
20
|
# 🎵 MyTunes Pro (Korean)
|
|
21
21
|
|
|
22
|
-
**현대적인 CLI 유튜브 뮤직 플레이어 (v1.8.
|
|
22
|
+
**현대적인 CLI 유튜브 뮤직 플레이어 (v1.8.5)**
|
|
23
23
|
터미널 환경에서 **YouTube 음악을 검색하여 듣는** 가볍고 빠른 키보드 중심의 플레이어입니다.
|
|
24
24
|
한국어 입력 환경에서도 **숫자 키(1~5)**를 통해 지연 없는 쾌적한 조작이 가능합니다.
|
|
25
25
|
|
|
@@ -208,7 +208,7 @@ Windows 환경에서 한글 검색이 안 되거나 설치가 어려운 분들
|
|
|
208
208
|
|
|
209
209
|
# 🎵 MyTunes Pro (English)
|
|
210
210
|
|
|
211
|
-
**Modern CLI YouTube Music Player (v1.8.
|
|
211
|
+
**Modern CLI YouTube Music Player (v1.8.4)**
|
|
212
212
|
A lightweight, keyboard-centric terminal player for streaming YouTube music.
|
|
213
213
|
|
|
214
214
|
---
|
|
@@ -296,7 +296,19 @@ sudo apt install mpv python3 python3-pip pipx python3-venv -y
|
|
|
296
296
|
|
|
297
297
|
## 🔄 Changelog
|
|
298
298
|
|
|
299
|
-
### v1.8.
|
|
299
|
+
### v1.8.5 (Latest)
|
|
300
|
+
|
|
301
|
+
- **Looping Navigation (Menu Wrapping)**: Pressing UP at the first item now wraps to the last item, and pressing DOWN at the last item wraps to the first.
|
|
302
|
+
- **Improved UI Flow**: Enhanced keyboard navigation experience across all list views (Main, Search, Favorites, History).
|
|
303
|
+
|
|
304
|
+
### v1.8.4
|
|
305
|
+
|
|
306
|
+
- **Python Crash Fix (WSL)**: Eliminated premature termination by implementing `start_new_session=True` for browser launches, isolating them from the TUI process group.
|
|
307
|
+
- **Hybrid Browser Strategy**: Switched to the standard `webbrowser` library for F7 (YouTube links) for maximum internal stability.
|
|
308
|
+
- **Global Error Protection**: Wrapped the main application loop in an exception guard to catch and log transient OS errors without crashing the entire app.
|
|
309
|
+
- **Refined Process Cleanup**: Specialized the `pkill` logic to prevent accidental self-termination while maintaining reliable MPV management.
|
|
310
|
+
|
|
311
|
+
### v1.8.3
|
|
300
312
|
|
|
301
313
|
- **Direct Binary Execution (WSL)**: Resolved shell parsing issues by bypassing `cmd.exe` and directly executing Windows browser binaries via `/mnt/c/` paths.
|
|
302
314
|
- **App Mode Reliability**: Guaranteed 712x800 popup mode by ensuring flags are delivered directly to the browser process without intermediate shell mangling.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
mytunes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
mytunes/app.py,sha256=HyaRwiTtall9kcCj9JOs-93QPk6Nr15HQC_3ENfvqgI,59636
|
|
3
|
+
mytunes_pro-1.8.5.dist-info/licenses/LICENSE,sha256=lOrP0EIjxcgJia__W3f3PVDZkRd2oRzFkyH2g3LRRCg,1063
|
|
4
|
+
mytunes_pro-1.8.5.dist-info/METADATA,sha256=uFEB7IuCMadyb02juYp_6sCkI6GAuWhJ4_Y67GGo7eI,17561
|
|
5
|
+
mytunes_pro-1.8.5.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
6
|
+
mytunes_pro-1.8.5.dist-info/entry_points.txt,sha256=6-MsC13nIgzLvrREaGotc32FgxHx_Iuu1z2qCzJs1_4,65
|
|
7
|
+
mytunes_pro-1.8.5.dist-info/top_level.txt,sha256=KWzdFyNNG_sO7GT83-sN5fYArP4_DL5I8HYIwgazXyY,8
|
|
8
|
+
mytunes_pro-1.8.5.dist-info/RECORD,,
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
mytunes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
mytunes/app.py,sha256=-bEfxmLNP_nSrvdoZNw3WQTgtlkn5dwUON7XgG_7ufw,61946
|
|
3
|
-
mytunes_pro-1.8.3.dist-info/licenses/LICENSE,sha256=lOrP0EIjxcgJia__W3f3PVDZkRd2oRzFkyH2g3LRRCg,1063
|
|
4
|
-
mytunes_pro-1.8.3.dist-info/METADATA,sha256=hY0oW58qdE_X9QE3vH-6LaX5R05XUljQ6IZCAfJbdbE,16657
|
|
5
|
-
mytunes_pro-1.8.3.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
6
|
-
mytunes_pro-1.8.3.dist-info/entry_points.txt,sha256=6-MsC13nIgzLvrREaGotc32FgxHx_Iuu1z2qCzJs1_4,65
|
|
7
|
-
mytunes_pro-1.8.3.dist-info/top_level.txt,sha256=KWzdFyNNG_sO7GT83-sN5fYArP4_DL5I8HYIwgazXyY,8
|
|
8
|
-
mytunes_pro-1.8.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|