buy.py: automatically add purchased songs to /mix
Hunter
Tuesday, March 10th, 2026 at 10:27 AM
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 song 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())
68 # Use macOS 'open' command to bypass browser entirely
69 if platform.system() == 'Darwin': # macOS
70 subprocess.run(['open', itunes_url])
- return 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)
- return 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 in:\n {watch_dir}")
96 + print("(complete your purchase in iTunes — 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
143 else:
144 print("(Opening song.link with all platform options)")
145
- open_itunes_link(itunes_url, track_id)
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__":