superbrain-server 1.0.2-beta.0

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.
Files changed (39) hide show
  1. package/bin/superbrain.js +196 -0
  2. package/package.json +23 -0
  3. package/payload/.dockerignore +45 -0
  4. package/payload/.env.example +58 -0
  5. package/payload/Dockerfile +73 -0
  6. package/payload/analyzers/__init__.py +0 -0
  7. package/payload/analyzers/audio_transcribe.py +225 -0
  8. package/payload/analyzers/caption.py +244 -0
  9. package/payload/analyzers/music_identifier.py +346 -0
  10. package/payload/analyzers/text_analyzer.py +117 -0
  11. package/payload/analyzers/visual_analyze.py +218 -0
  12. package/payload/analyzers/webpage_analyzer.py +789 -0
  13. package/payload/analyzers/youtube_analyzer.py +320 -0
  14. package/payload/api.py +1676 -0
  15. package/payload/config/.api_keys.example +22 -0
  16. package/payload/config/model_rankings.json +492 -0
  17. package/payload/config/openrouter_free_models.json +1364 -0
  18. package/payload/config/whisper_model.txt +1 -0
  19. package/payload/config_settings.py +185 -0
  20. package/payload/core/__init__.py +0 -0
  21. package/payload/core/category_manager.py +219 -0
  22. package/payload/core/database.py +811 -0
  23. package/payload/core/link_checker.py +300 -0
  24. package/payload/core/model_router.py +1253 -0
  25. package/payload/docker-compose.yml +120 -0
  26. package/payload/instagram/__init__.py +0 -0
  27. package/payload/instagram/instagram_downloader.py +253 -0
  28. package/payload/instagram/instagram_login.py +190 -0
  29. package/payload/main.py +912 -0
  30. package/payload/requirements.txt +39 -0
  31. package/payload/reset.py +311 -0
  32. package/payload/start-docker-prod.sh +125 -0
  33. package/payload/start-docker.sh +56 -0
  34. package/payload/start.py +1302 -0
  35. package/payload/static/favicon.ico +0 -0
  36. package/payload/stop-docker.sh +16 -0
  37. package/payload/utils/__init__.py +0 -0
  38. package/payload/utils/db_stats.py +108 -0
  39. package/payload/utils/manage_token.py +91 -0
@@ -0,0 +1,244 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Simple Instagram Caption Extractor
5
+ Fast, reliable, no rate limiting using direct HTML parsing.
6
+ """
7
+
8
+ import requests
9
+ import re
10
+ import sys
11
+ import json
12
+ import html
13
+ import io
14
+
15
+ # Force UTF-8 encoding for stdout on Windows
16
+ if sys.platform == 'win32':
17
+ sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
18
+ sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8')
19
+
20
+
21
+ def is_valid_instagram_url(url):
22
+ """
23
+ Check if the URL is a valid Instagram post/reel URL.
24
+
25
+ Args:
26
+ url: Instagram URL to validate
27
+
28
+ Returns:
29
+ Boolean indicating if URL is valid
30
+ """
31
+ patterns = [
32
+ r'instagram\.com/p/[A-Za-z0-9_-]+', # Regular posts
33
+ r'instagram\.com/reel/[A-Za-z0-9_-]+', # Reels
34
+ r'instagram\.com/tv/[A-Za-z0-9_-]+', # IGTV
35
+ ]
36
+
37
+ return any(re.search(pattern, url) for pattern in patterns)
38
+
39
+
40
+ def clean_caption(caption):
41
+ """
42
+ Clean the caption by removing metadata, hashtags, and decoding HTML entities.
43
+
44
+ Args:
45
+ caption: Raw caption text
46
+
47
+ Returns:
48
+ Cleaned caption text
49
+ """
50
+ if not caption:
51
+ return caption
52
+
53
+ # Decode HTML entities (e.g., " -> ", ❤ -> ❤)
54
+ caption = html.unescape(caption)
55
+
56
+ # Remove Instagram metadata patterns - multiple variations
57
+ # Pattern 1: "123 likes, 45 comments - username on Date: "
58
+ # Pattern 2: "12K likes, 50 comments - username on Date: "
59
+ # Pattern 3: "1,277 likes, 34 comments - username on Date: "
60
+ caption = re.sub(r'^\s*[\d,\.]+[KMB]?\s*(likes?|comments?)[^:]*?:\s*["\']?', '', caption, flags=re.IGNORECASE)
61
+
62
+ # Remove trailing quotes
63
+ caption = re.sub(r'["\']\.?\s*$', '', caption)
64
+
65
+ # Remove trailing metadata like "- See photos and videos"
66
+ caption = re.sub(r'\s*-\s*See\s+(photos?|videos?).*$', '', caption, flags=re.IGNORECASE)
67
+
68
+ # Remove "X likes, Y comments" patterns at the end
69
+ caption = re.sub(r'\s*[\d,\.]+[KMB]?\s*(likes?|comments?).*$', '', caption, flags=re.IGNORECASE)
70
+
71
+ # Remove hashtags (including the # symbol and the tag text)
72
+ caption = re.sub(r'#\w+', '', caption)
73
+
74
+ # Clean up extra quotes at the beginning and end
75
+ caption = caption.strip('"\'')
76
+
77
+ # Clean up extra whitespace and newlines
78
+ caption = re.sub(r'\n\s*\n+', '\n', caption) # Remove multiple blank lines
79
+ caption = re.sub(r'[ \t]+', ' ', caption) # Normalize spaces
80
+ caption = caption.strip()
81
+
82
+ # Remove lines that only contain dots or whitespace
83
+ lines = caption.split('\n')
84
+ lines = [line.strip() for line in lines if line.strip() and line.strip() != '.']
85
+ caption = '\n'.join(lines)
86
+
87
+ return caption
88
+
89
+
90
+
91
+ def get_caption(url):
92
+ """
93
+ Get the caption from an Instagram post or reel by parsing HTML.
94
+
95
+ Args:
96
+ url: Instagram post or reel URL
97
+
98
+ Returns:
99
+ Caption text or error message
100
+ """
101
+ # Validate URL
102
+ if not is_valid_instagram_url(url):
103
+ return "❌ Invalid Instagram URL. Please provide a valid post or reel link."
104
+
105
+ # Clean URL - remove query parameters and trailing slashes
106
+ url = url.split('?')[0].rstrip('/') + '/'
107
+
108
+ try:
109
+ # Request headers to mimic a browser
110
+ headers = {
111
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
112
+ 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
113
+ 'Accept-Language': 'en-US,en;q=0.5',
114
+ 'Accept-Encoding': 'gzip, deflate, br',
115
+ 'DNT': '1',
116
+ 'Connection': 'keep-alive',
117
+ 'Upgrade-Insecure-Requests': '1',
118
+ 'Sec-Fetch-Dest': 'document',
119
+ 'Sec-Fetch-Mode': 'navigate',
120
+ 'Sec-Fetch-Site': 'none',
121
+ 'Cache-Control': 'max-age=0',
122
+ }
123
+
124
+ # Make request
125
+ response = requests.get(url, headers=headers, timeout=15)
126
+
127
+ if response.status_code != 200:
128
+ return f"❌ Error: Unable to fetch post (Status code: {response.status_code})"
129
+
130
+ # Get text - let requests handle any decompression
131
+ html = response.text
132
+
133
+ # Method 1: Try to extract from JSON-LD structured data
134
+ json_ld_pattern = r'<script type="application/ld\+json">(.*?)</script>'
135
+ json_ld_matches = re.findall(json_ld_pattern, html, re.DOTALL)
136
+
137
+ for json_str in json_ld_matches:
138
+ try:
139
+ data = json.loads(json_str)
140
+ if isinstance(data, dict):
141
+ # Check for caption in various fields
142
+ caption = data.get('caption') or data.get('description') or data.get('articleBody')
143
+ if caption:
144
+ return clean_caption(caption)
145
+ except:
146
+ continue
147
+
148
+ # Method 2: Extract from meta tags
149
+ meta_patterns = [
150
+ r'<meta property="og:description" content="([^"]*)"',
151
+ r'<meta name="description" content="([^"]*)"',
152
+ r'<meta property="og:title" content="([^"]*)"',
153
+ ]
154
+
155
+ for pattern in meta_patterns:
156
+ match = re.search(pattern, html)
157
+ if match:
158
+ caption = match.group(1)
159
+ caption = clean_caption(caption)
160
+ if caption and len(caption) > 10: # Make sure it's not just metadata
161
+ return caption
162
+
163
+ # Method 3: Try to find in embedded JSON data
164
+ shared_data_pattern = r'window\._sharedData\s*=\s*({.*?});'
165
+ match = re.search(shared_data_pattern, html)
166
+ if match:
167
+ try:
168
+ shared_data = json.loads(match.group(1))
169
+ # Navigate through the nested structure
170
+ entry_data = shared_data.get('entry_data', {})
171
+
172
+ # Try PostPage
173
+ if 'PostPage' in entry_data:
174
+ media = entry_data['PostPage'][0]['graphql']['shortcode_media']
175
+ caption_edges = media.get('edge_media_to_caption', {}).get('edges', [])
176
+ if caption_edges:
177
+ return clean_caption(caption_edges[0]['node']['text'])
178
+
179
+ except:
180
+ pass
181
+
182
+ # Method 4: Try additional_data pattern
183
+ additional_pattern = r'"caption":\s*"([^"]*)"'
184
+ matches = re.findall(additional_pattern, html)
185
+ if matches:
186
+ # Get the longest caption (likely the actual post caption)
187
+ caption = max(matches, key=len)
188
+ if caption:
189
+ # Decode unicode escapes
190
+ caption = caption.encode().decode('unicode_escape')
191
+ return clean_caption(caption)
192
+
193
+ return "ℹ️ Could not extract caption. The post may have no caption or Instagram's HTML structure has changed."
194
+
195
+ except requests.exceptions.Timeout:
196
+ return "❌ Request timed out. Please check your internet connection and try again."
197
+
198
+ except requests.exceptions.ConnectionError:
199
+ return "❌ Connection error. Please check your internet connection."
200
+
201
+ except requests.exceptions.RequestException as e:
202
+ return f"❌ Request error: {str(e)}"
203
+
204
+ except Exception as e:
205
+ return f"❌ Unexpected error: {str(e)}"
206
+
207
+
208
+ def main():
209
+ """Main function to run the caption extractor."""
210
+ # Check if URL was provided as command line argument
211
+ if len(sys.argv) > 1:
212
+ url = sys.argv[1]
213
+ # When called from API, just print the caption
214
+ caption = get_caption(url)
215
+ print(caption)
216
+ else:
217
+ # Interactive mode
218
+ print("=" * 60)
219
+ print("📸 Instagram Caption Extractor")
220
+ print("=" * 60)
221
+ print()
222
+
223
+ # Prompt for URL
224
+ url = input("Enter Instagram post or reel URL: ").strip()
225
+
226
+ if not url:
227
+ print("❌ No URL provided. Exiting.")
228
+ return
229
+
230
+ print()
231
+ print("🔍 Fetching caption...")
232
+ print()
233
+
234
+ # Get and display caption
235
+ caption = get_caption(url)
236
+
237
+ print("📝 Caption:")
238
+ print("-" * 60)
239
+ print(caption)
240
+ print("-" * 60)
241
+
242
+
243
+ if __name__ == "__main__":
244
+ main()
@@ -0,0 +1,346 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Music Identifier – Optimized Shazam multi-segment recognition
4
+ ==============================================================
5
+ Strategy:
6
+ • Quick probe — tries a 12 s fingerprint from several evenly-spaced
7
+ positions first (fast, low-bandwidth)
8
+ • Deep scan — if nothing found, retries the same positions with
9
+ full 20 s segments for trickier tracks
10
+ • Positions — up to 5 evenly-spread offsets scaled to audio length
11
+ so songs that start after speech/ambient sound are caught
12
+
13
+ Output format is identical to the original so that main.py's parser
14
+ requires no changes.
15
+ """
16
+
17
+ import sys
18
+ import os
19
+ import asyncio
20
+ import subprocess
21
+ import tempfile
22
+ from pathlib import Path
23
+
24
+ # Ensure backend root is on sys.path when called as a subprocess
25
+ sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
26
+
27
+ try:
28
+ from shazamio import Shazam
29
+ _HAS_SHAZAM = True
30
+ except ImportError:
31
+ _HAS_SHAZAM = False
32
+
33
+
34
+ # ─────────────────────────────────────────────────────────────────────────────
35
+ # Audio helpers
36
+ # ─────────────────────────────────────────────────────────────────────────────
37
+
38
+ def _get_duration(audio_path: str) -> float:
39
+ """Return audio duration in seconds via ffprobe. Falls back to 60 s."""
40
+ try:
41
+ result = subprocess.run(
42
+ ["ffprobe", "-v", "error",
43
+ "-show_entries", "format=duration",
44
+ "-of", "default=noprint_wrappers=1:nokey=1",
45
+ audio_path],
46
+ capture_output=True, text=True, timeout=10,
47
+ )
48
+ return float(result.stdout.strip())
49
+ except Exception:
50
+ return 60.0
51
+
52
+
53
+ def _extract_segment(audio_path: str, start_sec: float,
54
+ duration: float = 20.0) -> str | None:
55
+ """Cut a slice from *audio_path* starting at *start_sec*.
56
+ Returns path to a temp MP3 file (caller must delete it), or None.
57
+ """
58
+ try:
59
+ fd, seg_path = tempfile.mkstemp(suffix=".mp3")
60
+ os.close(fd)
61
+ subprocess.run(
62
+ ["ffmpeg", "-y",
63
+ "-ss", str(int(start_sec)),
64
+ "-t", str(int(duration)),
65
+ "-i", audio_path,
66
+ "-acodec", "libmp3lame", "-q:a", "3",
67
+ seg_path],
68
+ capture_output=True, timeout=30,
69
+ )
70
+ if os.path.getsize(seg_path) > 1024:
71
+ return seg_path
72
+ os.remove(seg_path)
73
+ return None
74
+ except Exception:
75
+ return None
76
+
77
+
78
+ def _segment_positions(duration: float) -> list[float]:
79
+ """Return evenly-spread start-second offsets to probe.
80
+
81
+ Rules:
82
+ <= 20 s -> [0] (short clip, try as-is)
83
+ <= 50 s -> [0, ~50%] (2 positions)
84
+ <= 90 s -> [0, ~33%, ~66%] (3 positions)
85
+ <= 180 s -> [0, ~25%, ~50%, ~75%] (4 positions)
86
+ > 180 s -> [0, ~20%, ~40%, ~60%, ~80%] (5 positions)
87
+ """
88
+ if duration <= 20:
89
+ return [0.0]
90
+ if duration <= 50:
91
+ return [0.0, duration * 0.50]
92
+ if duration <= 90:
93
+ return [0.0, duration * 0.33, duration * 0.66]
94
+ if duration <= 180:
95
+ return [0.0, duration * 0.25, duration * 0.50, duration * 0.75]
96
+ return [0.0, duration * 0.20, duration * 0.40,
97
+ duration * 0.60, duration * 0.80]
98
+
99
+
100
+ # ─────────────────────────────────────────────────────────────────────────────
101
+ # Shazam core
102
+ # ─────────────────────────────────────────────────────────────────────────────
103
+
104
+ async def _shazam_recognize_file(shazam, path: str) -> dict | None:
105
+ try:
106
+ result = await shazam.recognize(path)
107
+ if result and "track" in result:
108
+ return result
109
+ except Exception:
110
+ pass
111
+ return None
112
+
113
+
114
+ async def _shazam_multi_segment(audio_path: str) -> dict | None:
115
+ """Two-pass Shazam scan.
116
+
117
+ Pass 1 — Quick probe (12 s segments): fast fingerprint; catches most hits
118
+ Pass 2 — Deep scan (20 s segments): longer window catches harder tracks
119
+ Both passes share the same evenly-distributed position list.
120
+ """
121
+ if not _HAS_SHAZAM:
122
+ return None
123
+
124
+ shazam = Shazam()
125
+ duration = _get_duration(audio_path)
126
+ positions = _segment_positions(duration)
127
+ total = len(positions)
128
+
129
+ # ── Pass 1: quick 12 s probe ─────────────────────────────────────────────
130
+ print(f" ⚡ [Pass 1 – quick probe, 12 s x {total} position{'s' if total > 1 else ''}]")
131
+ for i, start in enumerate(positions, start=1):
132
+ label = f"@{int(start)}s" if start > 0 else "start"
133
+ print(f" [Shazam] {i}/{total} {label}...", end=" ", flush=True)
134
+
135
+ if start == 0:
136
+ # Try the original file first (no re-encoding overhead)
137
+ result = await _shazam_recognize_file(shazam, audio_path)
138
+ else:
139
+ seg = _extract_segment(audio_path, start, duration=12)
140
+ if not seg:
141
+ print("(extract failed)")
142
+ continue
143
+ try:
144
+ result = await _shazam_recognize_file(shazam, seg)
145
+ finally:
146
+ try:
147
+ os.remove(seg)
148
+ except Exception:
149
+ pass
150
+
151
+ if result:
152
+ print("match!")
153
+ return result
154
+ print("no match")
155
+
156
+ # ── Pass 2: deep 20 s scan ───────────────────────────────────────────────
157
+ if total == 1:
158
+ # Audio is <= 20 s — pass 2 adds nothing new
159
+ return None
160
+
161
+ print()
162
+ print(f" [Shazam] Pass 2 – deep scan, 20 s x {total} positions")
163
+ for i, start in enumerate(positions, start=1):
164
+ label = f"@{int(start)}s" if start > 0 else "start"
165
+ print(f" [Shazam] {i}/{total} {label} (20 s)...", end=" ", flush=True)
166
+
167
+ seg = _extract_segment(audio_path, start, duration=20)
168
+ if not seg:
169
+ print("(extract failed)")
170
+ continue
171
+ try:
172
+ result = await _shazam_recognize_file(shazam, seg)
173
+ finally:
174
+ try:
175
+ os.remove(seg)
176
+ except Exception:
177
+ pass
178
+
179
+ if result:
180
+ print("match!")
181
+ return result
182
+ print("no match")
183
+
184
+ return None
185
+
186
+
187
+ # ─────────────────────────────────────────────────────────────────────────────
188
+ # Result formatter
189
+ # ─────────────────────────────────────────────────────────────────────────────
190
+
191
+ def _format_shazam(result: dict) -> dict:
192
+ track = result["track"]
193
+
194
+ # ── Artist ────────────────────────────────────────────────────────────────
195
+ artist = track.get("subtitle", "").strip()
196
+ if not artist and track.get("artists"):
197
+ aliases = [a.get("alias", "").replace("-", " ").title()
198
+ for a in track["artists"] if a.get("alias")]
199
+ artist = ", ".join(aliases)
200
+ if not artist:
201
+ for section in track.get("sections", []):
202
+ if section.get("type") == "SONG":
203
+ for meta in section.get("metadata", []):
204
+ if meta.get("title", "").lower() in ("artist", "artists"):
205
+ artist = meta.get("text", "").strip()
206
+ if not artist:
207
+ artist = section.get("tabname", "").strip()
208
+ if not artist and "hub" in track:
209
+ hub_text = track["hub"].get("actions", [{}])[0].get("name", "")
210
+ if " - " in hub_text:
211
+ artist = hub_text.split(" - ")[0].strip()
212
+
213
+ # ── Metadata ──────────────────────────────────────────────────────────────
214
+ album = released = label = genre = ""
215
+ for section in track.get("sections", []):
216
+ if section.get("type") == "SONG":
217
+ for meta in section.get("metadata", []):
218
+ t, v = meta.get("title", "").lower(), meta.get("text", "")
219
+ if t == "album": album = v
220
+ elif t == "released": released = v
221
+ elif t == "label": label = v
222
+ if "genres" in track:
223
+ genre = track["genres"].get("primary", "")
224
+
225
+ # ── Links ─────────────────────────────────────────────────────────────────
226
+ spotify = ""
227
+ if "hub" in track:
228
+ for p in track["hub"].get("providers", []):
229
+ if p.get("type") == "SPOTIFY":
230
+ spotify = p["actions"][0].get("uri", "")
231
+
232
+ return {
233
+ "title": track.get("title", ""),
234
+ "artist": artist or "Unknown",
235
+ "album": album,
236
+ "released": released,
237
+ "label": label,
238
+ "genre": genre,
239
+ "shazam_count": track.get("shazamcount", 0),
240
+ "spotify": spotify,
241
+ "apple": track.get("url", ""),
242
+ "source": "Shazam",
243
+ }
244
+
245
+
246
+ # ─────────────────────────────────────────────────────────────────────────────
247
+ # Output printer
248
+ # ─────────────────────────────────────────────────────────────────────────────
249
+
250
+ def _print_result(info: dict) -> None:
251
+ print()
252
+ print("=" * 70)
253
+ print(f"\u2705 MUSIC IDENTIFIED [{info['source']}]")
254
+ print("=" * 70)
255
+ print()
256
+ print(f"\U0001f3b5 Song: {info['title']}")
257
+ print(f"\U0001f464 Artist: {info['artist']}")
258
+ if info["album"]:
259
+ print(f"\U0001f4bf Album: {info['album']}")
260
+ if info["released"]:
261
+ print(f"\U0001f4c5 Released: {info['released']}")
262
+ if info["label"]:
263
+ print(f"\U0001f3f7\ufe0f Label: {info['label']}")
264
+ if info["genre"]:
265
+ print(f"\U0001f3b8 Genre: {info['genre']}")
266
+ if info["shazam_count"]:
267
+ c = info["shazam_count"]
268
+ fmt = (f"{c / 1_000_000:.1f}M" if c >= 1_000_000
269
+ else (f"{c / 1_000:.1f}K" if c >= 1_000 else str(c)))
270
+ print(f"\U0001f525 Shazams: {fmt}")
271
+ print()
272
+ print("\U0001f517 LINKS:")
273
+ if info["spotify"]:
274
+ print(f" Spotify: {info['spotify']}")
275
+ if info["apple"]:
276
+ print(f" Apple Music: {info['apple']}")
277
+ print()
278
+ print("=" * 70)
279
+
280
+
281
+ # ─────────────────────────────────────────────────────────────────────────────
282
+ # Public API
283
+ # ─────────────────────────────────────────────────────────────────────────────
284
+
285
+ async def identify_music(audio_path: str) -> None:
286
+ """Identify music from *audio_path* using optimized Shazam multi-segment."""
287
+
288
+ print("=" * 70)
289
+ print("MUSIC IDENTIFIER (Shazam – optimized multi-segment)")
290
+ print("=" * 70)
291
+ print()
292
+
293
+ path = Path(audio_path)
294
+ if not path.exists():
295
+ print(f"File not found: {audio_path}")
296
+ return
297
+
298
+ valid_exts = {".mp3", ".wav", ".m4a", ".ogg", ".flac",
299
+ ".aac", ".mp4", ".avi", ".mov"}
300
+ if path.suffix.lower() not in valid_exts:
301
+ print(f"Unsupported file type: {path.suffix}")
302
+ return
303
+
304
+ if not _HAS_SHAZAM:
305
+ print("shazamio is not installed. Run: pip install shazamio")
306
+ return
307
+
308
+ print(f"Analyzing: {path.name}")
309
+ print()
310
+
311
+ result = await _shazam_multi_segment(str(path))
312
+ if result:
313
+ _print_result(_format_shazam(result))
314
+ return
315
+
316
+ print()
317
+ print("No match found. The audio might be:")
318
+ print(" - Original / unreleased / user-created music")
319
+ print(" - Too short or poor audio quality")
320
+ print(" - Background / ambient sound without a clear melody")
321
+ print(" - In a niche regional catalogue not yet in Shazam's DB")
322
+
323
+
324
+ # ─────────────────────────────────────────────────────────────────────────────
325
+ # CLI entry point
326
+ # ─────────────────────────────────────────────────────────────────────────────
327
+
328
+ def main() -> None:
329
+ if len(sys.argv) > 1:
330
+ audio_path = sys.argv[1].strip("\"'").strip()
331
+ else:
332
+ print("=" * 70)
333
+ print("MUSIC IDENTIFIER (Shazam – optimized multi-segment)")
334
+ print("=" * 70)
335
+ print()
336
+ audio_path = input("Enter audio/video file path: ").strip()
337
+
338
+ if not audio_path:
339
+ print("No path provided!")
340
+ return
341
+
342
+ asyncio.run(identify_music(audio_path))
343
+
344
+
345
+ if __name__ == "__main__":
346
+ main()