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)