tecuts commited on
Commit
79960bc
Β·
verified Β·
1 Parent(s): fd7ae33

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +101 -52
app.py CHANGED
@@ -207,7 +207,12 @@ def parse_bt_manifest(manifest_data):
207
 
208
  @app.get("/download/{track_id}")
209
  async def download_track(track_id: int, quality: str = "LOSSLESS"):
210
- """Download a TIDAL track in specified quality"""
 
 
 
 
 
211
  try:
212
  print(f"πŸ” Download request: track_id={track_id}, quality={quality}")
213
 
@@ -254,7 +259,7 @@ async def download_track(track_id: int, quality: str = "LOSSLESS"):
254
  manifest_mime = data.get('manifestMimeType', '')
255
  manifest_data = base64.b64decode(data.get('manifest', ''))
256
 
257
- print(f"βœ… Got playback data: audioQuality={audio_quality}")
258
 
259
  if audio_quality != quality:
260
  print(f"⚠️ Requested {quality} but got {audio_quality}")
@@ -268,22 +273,29 @@ async def download_track(track_id: int, quality: str = "LOSSLESS"):
268
 
269
  title = track_info.get('title', f"track_{track_id}")
270
  artist = track_info.get('artist', {}).get('name', 'Unknown')
 
 
 
 
 
271
 
272
- security_token = data.get('securityToken')
273
- key = None
274
- nonce = None
275
-
276
- if security_token:
277
- print("πŸ” Decrypting security token...")
278
- key, nonce = decrypt_security_token(security_token)
279
-
280
- download_dir = "./downloads"
281
- os.makedirs(download_dir, exist_ok=True)
282
-
283
- tmpdir = tempfile.mkdtemp(prefix="tidal-api-")
284
 
285
- try:
286
- if 'dash+xml' in manifest_mime:
 
 
 
 
 
 
 
 
 
 
 
 
 
287
  print("πŸ“¦ Parsing DASH manifest...")
288
  base_url, segments = parse_dash_manifest(manifest_data)
289
  print(f"βœ… Found {len(segments)} segments")
@@ -315,7 +327,8 @@ async def download_track(track_id: int, quality: str = "LOSSLESS"):
315
  print(f" Segment {i+1}/{len(segments)} downloaded")
316
 
317
  print("πŸ” Decrypting and merging segments...")
318
- output_path = os.path.join(download_dir, f"{artist} - {title}.flac")
 
319
 
320
  with open(output_path, 'wb') as out:
321
  for i in range(len(segments)):
@@ -334,43 +347,57 @@ async def download_track(track_id: int, quality: str = "LOSSLESS"):
334
 
335
  print(f"βœ… Download complete: {output_path}")
336
 
337
- return FileResponse(
338
- output_path,
339
- media_type="audio/flac",
340
- filename=f"{artist} - {title}.flac"
341
- )
342
-
343
- elif 'tidal.bt' in manifest_mime:
344
- print("πŸ“¦ Parsing BT manifest...")
345
- base_url, segments = parse_bt_manifest(manifest_data)
346
- print(f"βœ… Found {len(segments)} URLs")
347
-
348
- if not segments:
349
- raise HTTPException(status_code=500, detail="No URLs found in BT manifest")
350
-
351
- output_path = os.path.join(download_dir, f"{artist} - {title}.m4a")
352
-
353
- with open(output_path, 'wb') as out:
354
- for i, seg_url in enumerate(segments):
355
- resp = requests.get(seg_url, headers=headers, timeout=60)
356
- if resp.status_code != 200:
357
- raise HTTPException(status_code=500, detail=f"Failed to download part {i}")
358
- out.write(resp.content)
359
- print(f" Downloaded part {i+1}/{len(segments)}")
360
-
361
- print(f"βœ… Download complete: {output_path}")
362
 
363
- return FileResponse(
364
- output_path,
365
- media_type="audio/mp4",
366
- filename=f"{artist} - {title}.m4a"
367
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
368
 
369
- else:
370
- raise HTTPException(status_code=500, detail=f"Unsupported manifest type: {manifest_mime}")
371
- finally:
372
- import shutil
373
- shutil.rmtree(tmpdir, ignore_errors=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
374
 
375
  except HTTPException:
376
  raise
@@ -378,6 +405,28 @@ async def download_track(track_id: int, quality: str = "LOSSLESS"):
378
  print(f"❌ Unexpected error: {type(e).__name__}: {str(e)}")
379
  raise HTTPException(status_code=500, detail=str(e))
380
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
381
  @app.get("/search")
382
  async def search_tracks(query: str = Query(..., description="Search query"), limit: int = 5):
383
  """Search for TIDAL tracks"""
 
207
 
208
  @app.get("/download/{track_id}")
209
  async def download_track(track_id: int, quality: str = "LOSSLESS"):
210
+ """Download a TIDAL track in specified quality.
211
+
212
+ Returns JSON with track info and download URL.
213
+ - HIGH/LOW: Returns direct TIDAL CDN URL (time-limited)
214
+ - LOSSLESS/HI_RES_LOSSLESS: Downloads, decrypts, merges, returns mounted file URL
215
+ """
216
  try:
217
  print(f"πŸ” Download request: track_id={track_id}, quality={quality}")
218
 
 
259
  manifest_mime = data.get('manifestMimeType', '')
260
  manifest_data = base64.b64decode(data.get('manifest', ''))
261
 
262
+ print(f"βœ… Got playback data: audioQuality={audio_quality}, manifestType={manifest_mime}")
263
 
264
  if audio_quality != quality:
265
  print(f"⚠️ Requested {quality} but got {audio_quality}")
 
273
 
274
  title = track_info.get('title', f"track_{track_id}")
275
  artist = track_info.get('artist', {}).get('name', 'Unknown')
276
+ duration = track_info.get('duration', 0)
277
+ album = track_info.get('album', {}).get('title', '')
278
+ album_artist = track_info.get('album', {}).get('artist', {}).get('name', '')
279
+ track_number = track_info.get('trackNumber', 1)
280
+ isrc = track_info.get('isrc', '')
281
 
282
+ is_lossless = 'dash+xml' in manifest_mime
 
 
 
 
 
 
 
 
 
 
 
283
 
284
+ if is_lossless:
285
+ security_token = data.get('securityToken')
286
+ key = None
287
+ nonce = None
288
+
289
+ if security_token:
290
+ print("πŸ” Decrypting security token...")
291
+ key, nonce = decrypt_security_token(security_token)
292
+
293
+ download_dir = "./downloads"
294
+ os.makedirs(download_dir, exist_ok=True)
295
+
296
+ tmpdir = tempfile.mkdtemp(prefix="tidal-api-")
297
+
298
+ try:
299
  print("πŸ“¦ Parsing DASH manifest...")
300
  base_url, segments = parse_dash_manifest(manifest_data)
301
  print(f"βœ… Found {len(segments)} segments")
 
327
  print(f" Segment {i+1}/{len(segments)} downloaded")
328
 
329
  print("πŸ” Decrypting and merging segments...")
330
+ output_filename = f"{artist} - {title}.flac"
331
+ output_path = os.path.join(download_dir, output_filename)
332
 
333
  with open(output_path, 'wb') as out:
334
  for i in range(len(segments)):
 
347
 
348
  print(f"βœ… Download complete: {output_path}")
349
 
350
+ file_size = os.path.getsize(output_path)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
351
 
352
+ return {
353
+ "success": True,
354
+ "track": {
355
+ "id": track_id,
356
+ "title": title,
357
+ "artist": artist,
358
+ "album": album,
359
+ "albumArtist": album_artist,
360
+ "trackNumber": track_number,
361
+ "duration": duration,
362
+ "isrc": isrc,
363
+ "quality": audio_quality,
364
+ "fileSize": file_size,
365
+ "format": "flac"
366
+ },
367
+ "downloadUrl": f"/download_file/{output_filename}",
368
+ "message": "File downloaded and ready for download"
369
+ }
370
+ finally:
371
+ import shutil
372
+ shutil.rmtree(tmpdir, ignore_errors=True)
373
+
374
+ else:
375
+ print("πŸ“¦ Parsing BT manifest...")
376
+ base_url, segments = parse_bt_manifest(manifest_data)
377
+ print(f"βœ… Found {len(segments)} URLs")
378
 
379
+ if not segments:
380
+ raise HTTPException(status_code=500, detail="No URLs found in BT manifest")
381
+
382
+ download_url = segments[0] if segments else None
383
+
384
+ return {
385
+ "success": True,
386
+ "track": {
387
+ "id": track_id,
388
+ "title": title,
389
+ "artist": artist,
390
+ "album": album,
391
+ "albumArtist": album_artist,
392
+ "trackNumber": track_number,
393
+ "duration": duration,
394
+ "isrc": isrc,
395
+ "quality": audio_quality,
396
+ "format": "m4a"
397
+ },
398
+ "downloadUrl": download_url,
399
+ "message": "Direct TIDAL CDN URL (time-limited)"
400
+ }
401
 
402
  except HTTPException:
403
  raise
 
405
  print(f"❌ Unexpected error: {type(e).__name__}: {str(e)}")
406
  raise HTTPException(status_code=500, detail=str(e))
407
 
408
+ @app.get("/download_file/{filename}")
409
+ async def download_file(filename: str):
410
+ """Serve downloaded file for LOSSLESS/HI_RES tracks"""
411
+ try:
412
+ file_path = os.path.join("./downloads", filename)
413
+
414
+ if not os.path.exists(file_path):
415
+ raise HTTPException(status_code=404, detail="File not found")
416
+
417
+ ext = os.path.splitext(filename)[1].lower()
418
+ media_type = "audio/flac" if ext == ".flac" else "audio/mp4"
419
+
420
+ return FileResponse(
421
+ file_path,
422
+ media_type=media_type,
423
+ filename=filename
424
+ )
425
+ except HTTPException:
426
+ raise
427
+ except Exception as e:
428
+ raise HTTPException(status_code=500, detail=str(e))
429
+
430
  @app.get("/search")
431
  async def search_tracks(query: str = Query(..., description="Search query"), limit: int = 5):
432
  """Search for TIDAL tracks"""