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 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.3"
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
- # User requested revert to aggressive pkill for reliability
249
- # This ensures any previous background instances are killed
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", "-f", "mpv"], stderr=subprocess.DEVNULL)
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
- try:
747
- # Robust multi-platform open
748
- if sys.platform == 'darwin':
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
- # Add timestamp to user-data-dir to force size/position flags to be respected (prevents "remembering")
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
- f"--user-data-dir={temp_user_data}",
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
- # 1. macOS (Avoid AppleScript to prevent permission prompts)
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', 'C:\\Program Files'), 'Google\\Chrome\\Application\\chrome.exe'),
806
- os.path.join(os.environ.get('PROGRAMFILES(X86)', 'C:\\Program Files (x86)'), 'Google\\Chrome\\Application\\chrome.exe'),
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
- # Use list-based Popen for native Windows
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 (Run Windows Chrome directly if possible, fallback to cmd.exe)
804
+ # 3. WSL / Linux (Direct path with session isolation)
830
805
  elif self.is_wsl():
831
- # v1.8.3 - Direct binary execution for maximum precision (Avoids cmd.exe shell splitting)
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
- # Same precise flags for App Mode
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
- # Direct execution from WSL to Windows binary is extremely stable
855
- subprocess.Popen([p] + wsl_win_flags, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
856
- launched = True; launched_direct = True; break
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 launched_direct:
820
+ if not launched:
860
821
  try:
861
- # Final fallback: cmd.exe start (Literal strings, no title)
862
- cmd_fallback = f'start chrome --app={live_url} --window-size=712,800 --new-window'
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
- self.loop_count = (self.loop_count + 1) % 1000
1474
- self.update_playback_state()
1475
- self.check_autoplay()
1476
- self.draw()
1477
- self.handle_input()
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
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.3)**
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.3)**
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.3 (Latest)
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,,