nts.py (4924B)
1 #!/usr/bin/env python3 2 import argparse 3 import requests 4 import sys 5 import json 6 from typing import Any, cast 7 from urllib.parse import urlparse, quote 8 9 10 class NTSTracklistFetcher: 11 def __init__(self) -> None: 12 self.session: requests.Session = requests.Session() 13 self.session.headers.update( 14 { 15 "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36", 16 "Accept": "application/json", 17 } 18 ) 19 20 def extract_episode_path(self, url: str) -> str | None: 21 """Extract show and episode path from URL.""" 22 try: 23 parsed = urlparse(url) 24 path_parts = [p for p in parsed.path.split("/") if p] 25 26 if ( 27 len(path_parts) >= 4 28 and path_parts[0] == "shows" 29 and path_parts[2] == "episodes" 30 ): 31 show_name = path_parts[1] 32 episode_name = path_parts[3] 33 return f"{show_name}/episodes/{episode_name}" 34 return None 35 except Exception as e: 36 print(f"Error extracting path: {e}", file=sys.stderr) 37 return None 38 39 def extract_episode_name(self, url: str) -> str | None: 40 """Extract episode name for filename from URL.""" 41 try: 42 parsed = urlparse(url) 43 path_parts = [p for p in parsed.path.split("/") if p] 44 45 if ( 46 len(path_parts) >= 4 47 and path_parts[0] == "shows" 48 and path_parts[2] == "episodes" 49 ): 50 return path_parts[3] 51 return None 52 except Exception as e: 53 print(f"Error extracting episode name: {e}", file=sys.stderr) 54 return None 55 56 def get_episode_data(self, episode_path: str) -> dict[str, Any]: 57 """Fetch episode data from the API.""" 58 encoded_path = "/".join(quote(part) for part in episode_path.split("/")) 59 url = f"https://www.nts.live/api/v2/shows/{encoded_path}" 60 61 if "--debug" in sys.argv: 62 print(f"DEBUG: Requesting URL: {url}", file=sys.stderr) 63 64 response = self.session.get(url) 65 response.raise_for_status() 66 return cast(dict[str, Any], response.json()) 67 68 def save_tracklist(self, episode_data: dict[str, Any], filename: str) -> None: 69 """Save tracks to a text file.""" 70 if "--debug" in sys.argv: 71 print( 72 f"DEBUG: Response data: {json.dumps(episode_data, indent=2)}", 73 file=sys.stderr, 74 ) 75 76 # Extract tracks from the embeds.tracklist.results path 77 embeds = cast(dict[str, Any], episode_data.get("embeds", {})) 78 tracklist_container = cast(dict[str, Any], embeds.get("tracklist", {})) 79 tracklist = cast(list[dict[str, Any]], tracklist_container.get("results", [])) 80 81 if not tracklist: 82 print("No tracks found in this episode", file=sys.stderr) 83 return 84 85 with open(filename, "w", encoding="utf-8") as f: 86 for track in tracklist: 87 artist = cast(str, track.get("artist", "Unknown Artist")) 88 title = cast(str, track.get("title", "Unknown Title")) 89 line = f"{artist} - {title}" 90 print(line) 91 f.write(line + "\n") 92 93 def fetch_and_save_tracklist(self, url: str) -> None: 94 """Main method to fetch and save tracklist.""" 95 episode_path = self.extract_episode_path(url) 96 episode_name = self.extract_episode_name(url) 97 98 if not episode_path or not episode_name: 99 raise ValueError("Could not extract episode information from URL") 100 101 filename = f"{episode_name}.txt" 102 103 if "--debug" in sys.argv: 104 print(f"DEBUG: Extracted path: {episode_path}", file=sys.stderr) 105 print(f"DEBUG: Output filename: {filename}", file=sys.stderr) 106 107 try: 108 episode_data = self.get_episode_data(episode_path) 109 self.save_tracklist(episode_data, filename) 110 except requests.exceptions.HTTPError: 111 raise 112 except Exception as e: 113 print(f"Error processing episode: {str(e)}", file=sys.stderr) 114 raise 115 116 117 def main(): 118 parser = argparse.ArgumentParser(description="Fetch tracklist from NTS Radio shows") 119 _ = parser.add_argument("url", help="URL of the NTS Radio show") 120 _ = parser.add_argument("--debug", action="store_true", help="Enable debug output") 121 122 args = parser.parse_args() 123 124 try: 125 fetcher = NTSTracklistFetcher() 126 fetcher.fetch_and_save_tracklist(args.url) 127 except requests.exceptions.RequestException: 128 sys.exit(1) 129 except ValueError as e: 130 print(f"Error: {e}", file=sys.stderr) 131 sys.exit(1) 132 except Exception: 133 sys.exit(1) 134 135 136 if __name__ == "__main__": 137 main()