buy.py (4.7 KB)
1 #!/usr/bin/env python3 2 """ 3 Search for tracks and open purchase links. 4 5 Usage: 6 ./buy.py <search query> 7 ./buy.py beatles yesterday 8 ./buy.py "daft punk revolution 909" 9 """ 10 11 import sys 12 import urllib.parse 13 import urllib.request 14 import json 15 import platform 16 import webbrowser 17 import os 18 import shutil 19 import time 20 21 22 ITUNES_MUSIC_DIR = os.path.expanduser( 23 "~/Music/Music/Media.localized/Music" 24 ) 25 MIX_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "mix") 26 POLL_INTERVAL = 1 # seconds between directory scans 27 28 29 def search_itunes(query): 30 """Search iTunes for a track and return results.""" 31 encoded_query = urllib.parse.quote(query) 32 url = f"https://itunes.apple.com/search?term={encoded_query}&media=music&entity=song&limit=10" 33 34 try: 35 with urllib.request.urlopen(url) as response: 36 data = json.loads(response.read().decode()) 37 return data.get('results', []) 38 except Exception as e: 39 print(f"Error searching iTunes: {e}") 40 return [] 41 42 43 def open_itunes_link(itunes_url, track_id): 44 """Open the iTunes purchase link directly in iTunes app on macOS, or song.link on other platforms.""" 45 import subprocess 46 47 # Convert to geo-aware link and add app=itunes parameter to force iTunes Store 48 # This prevents opening in Apple Music (streaming) instead of iTunes Store (purchase) 49 50 # Replace music.apple.com with geo.itunes.apple.com for better compatibility 51 if 'music.apple.com' in itunes_url: 52 itunes_url = itunes_url.replace('music.apple.com', 'geo.itunes.apple.com') 53 elif 'itunes.apple.com' in itunes_url and 'geo.' not in itunes_url: 54 itunes_url = itunes_url.replace('itunes.apple.com', 'geo.itunes.apple.com') 55 56 # Add app=itunes parameter to force iTunes Store (not Apple Music) 57 if '?' in itunes_url: 58 # URL already has parameters, append with & 59 if 'app=' not in itunes_url: 60 itunes_url += '&app=itunes' 61 else: 62 # No parameters yet, add with ? 63 itunes_url += '?app=itunes' 64 65 # Convert https:// to itms:// to open directly in iTunes app 66 itunes_url = itunes_url.replace('https://', 'itms://') 67 68 # Use macOS 'open' command to bypass browser entirely 69 if platform.system() == 'Darwin': # macOS 70 subprocess.run(['open', itunes_url]) 71 return True # opened iTunes Store 72 else: 73 # For non-macOS systems, open song.link page with all platform options 74 songlink_url = f"https://song.link/i/{track_id}" 75 webbrowser.open(songlink_url) 76 return False # not iTunes Store 77 78 79 def snapshot_audio_files(root): 80 """Return a set of all audio file paths found recursively under root.""" 81 audio_exts = {'.mp3', '.m4a', '.ogg', '.flac', '.wav', '.aiff', '.aif'} 82 found = set() 83 for dirpath, _dirnames, filenames in os.walk(root): 84 for name in filenames: 85 if os.path.splitext(name)[1].lower() in audio_exts: 86 found.add(os.path.join(dirpath, name)) 87 return found 88 89 90 def wait_for_new_file(watch_dir, before): 91 """ 92 Poll watch_dir recursively until an audio file appears that wasn't in the 93 before snapshot. Returns the full path of the new file. 94 """ 95 print(f"Watching for new file...") 96 print("(complete your purchase in iTunes or press Ctrl+C to cancel)") 97 98 while True: 99 after = snapshot_audio_files(watch_dir) 100 new_files = after - before 101 if new_files: 102 return next(iter(new_files)) 103 time.sleep(POLL_INTERVAL) 104 105 def main(): 106 """Main function to run the music search CLI.""" 107 # Get search query from command line args or prompt user 108 if len(sys.argv) > 1: 109 query = ' '.join(sys.argv[1:]) 110 else: 111 query = input("Enter track name, artist, or both: ").strip() 112 113 if not query: 114 print("No search query provided.") 115 return 116 117 print(f"Searching for: {query}") 118 119 # Search iTunes 120 results = search_itunes(query) 121 122 if not results: 123 print("No results found. Try a different search term.") 124 return 125 126 # Get the best (first) result 127 best_track = results[0] 128 129 # Extract track info 130 artist = best_track.get('artistName', 'Unknown') 131 track = best_track.get('trackName', 'Unknown') 132 itunes_url = best_track.get('trackViewUrl') 133 track_id = best_track.get('trackId') 134 135 if not itunes_url or not track_id: 136 print("Error: Could not find iTunes URL") 137 return 138 139 # Open iTunes purchase page (macOS) or song.link (other platforms) 140 print(f"Opening: {artist} - {track}") 141 if platform.system() == 'Darwin': 142 print("Opening iTunes Store directly...") 143 else: 144 print("Opening song.link with all platform options...") 145 146 opened_itunes = open_itunes_link(itunes_url, track_id) 147 148 # Only watch for the downloaded file when we opened the iTunes Store 149 if not opened_itunes: 150 return 151 152 before = snapshot_audio_files(ITUNES_MUSIC_DIR) 153 new_file = wait_for_new_file(ITUNES_MUSIC_DIR, before) 154 155 filename = os.path.basename(new_file) 156 dest = os.path.join(MIX_DIR, filename) 157 shutil.copy2(new_file, dest) 158 print(f"Added to /mix: {filename}") 159 160 161 if __name__ == "__main__": 162 try: 163 main() 164 except KeyboardInterrupt: 165 print("\n\nExiting...") 166 sys.exit(0)