Guest User

fuckaudiochan.py

a guest
Apr 19th, 2026
211
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 8.86 KB | Food | 0 0
  1. #!/usr/bin/env python3
  2. """
  3. AudioChan Stream Capture Tool (Manual Mode Only)
  4. Uses Playwright to capture stream URLs and full headers (including HttpOnly cookies).
  5. Generates the robust FFmpeg command for manual copy-pasting.
  6. """
  7. import argparse
  8. import asyncio
  9. from playwright.async_api import async_playwright
  10. import os
  11. import re
  12. import sys
  13. # Force UTF-8 output so emojis aren't mangled on Windows
  14. sys.stdout.reconfigure(encoding='utf-8')
  15. if sys.platform == 'win32':
  16. os.system('chcp 65001 > nul')
  17.  
  18. # Path to store browser session (cookies, etc.) so you don't have to login every time
  19. USER_DATA_DIR = os.path.join(os.getcwd(), 'playwright_data')
  20.  
  21. async def capture_stream_manual(url):
  22. print(f"šŸš€ Launching browser for: {url}")
  23. print(f"šŸ“‚ User Data Dir: {USER_DATA_DIR}")
  24.  
  25. async with async_playwright() as p:
  26. # Launch persistent context to save login state
  27. context = await p.chromium.launch_persistent_context(
  28. user_data_dir=USER_DATA_DIR,
  29. headless=True, # Change if cloudflare is an issue
  30. channel="chrome", # Try to use installed Chrome if available for better realism
  31. args=[
  32. '--disable-blink-features=AutomationControlled', # Hide automation flag
  33. ]
  34. )
  35.  
  36. page = await context.new_page()
  37.  
  38. # storage for captured info
  39. stream_info = {
  40. 'url': None,
  41. 'headers': None
  42. }
  43.  
  44. # Event listener for requests
  45. async def handle_request(request):
  46. try:
  47. # Print ALL requests so we can see what's happening
  48. print(f" [REQUEST] {request.url}")
  49.  
  50. # Filter strictly for the stream URL
  51. if 'stream.audiochan.com' in request.url and '&st=' in request.url:
  52. print(f"\nāœ… CAPTURED STREAM REQUEST! (Updating...)")
  53. stream_info['url'] = request.url
  54. try:
  55. # This can sometimes fail if the request is closed too quickly
  56. stream_info['headers'] = await request.all_headers()
  57. except Exception as e:
  58. print(f"āš ļø Warning: Could not fetch full headers: {e}")
  59. # Fallback to basic headers if available, or just use what we have
  60. if not stream_info['headers']: # Only overwrite if we don't have good headers yet
  61. stream_info['headers'] = request.headers
  62. except Exception as e:
  63. # Catch-all for any other issues in the handler to prevent loop crash
  64. pass
  65.  
  66. page.on("request", handle_request)
  67.  
  68. try:
  69. print("ā³ Navigating to page...")
  70. await page.goto(url)
  71.  
  72. print("šŸ‘€ Waiting for stream to start... (Press Play on the page if needed)")
  73.  
  74. # Wait until we capture the stream AND it stabilizes
  75. stability_count = 0
  76. required_stability = 3
  77.  
  78. for _ in range(60):
  79. if stream_info['url'] and stream_info['headers']:
  80. stability_count += 1
  81. print(f" Stream detected... stabilizing ({stability_count}/{required_stability})")
  82. if stability_count >= required_stability:
  83. break
  84. else:
  85. stability_count = 0 # Reset if lost (unlikely but good safety)
  86.  
  87. await asyncio.sleep(1)
  88.  
  89. if stream_info['url'] and stream_info['headers']:
  90.  
  91. # Sanitize title: Replace invalid Windows filename characters with underscore
  92. # Invalid characters for Windows filenames: < > : " / \ | ? *
  93. invalid_chars = r'[<>:"/\\|?*]'
  94.  
  95. # Wait for the h1 to have content
  96. await page.wait_for_selector('h1.text-foreground:not(:empty)')
  97.  
  98. h1_title = await page.evaluate('''() => {
  99. const el = document.querySelector('h1.text-foreground');
  100. return el ? el.innerText.trim() : null;
  101. }''')
  102.  
  103. title = h1_title or await page.title()
  104. safe_title = re.sub(invalid_chars, '_', title).strip()
  105. safe_title = re.sub(r'[ .]+$', '', safe_title)
  106. output_filename = f"{safe_title}.mp4"
  107.  
  108. # Grab artist from span, strip the "u/" prefix
  109. artist = await page.evaluate('''() => {
  110. const el = document.querySelector('span.truncate.text-muted-foreground.text-sm.no-underline');
  111. return el ? el.innerText.trim().replace(/^u\//, '') : null;
  112. }''')
  113. artist = artist or ""
  114.  
  115. # Grab description from <em> tag
  116. description = await page.evaluate('''() => {
  117. const el = document.querySelector('p');
  118. return el ? el.innerText.trim() : null;
  119. }''')
  120.  
  121. description = description.replace('"', '\\"') if description else ""
  122.  
  123. safe_title = re.sub(invalid_chars, '_', title).strip()
  124. # Also remove any trailing periods or spaces that Windows dislikes
  125. safe_title = re.sub(r'[ .]+$', '', safe_title)
  126.  
  127. print("\n" + "="*80)
  128. print("šŸŽ‰ SUCCESS! Stream info captured.")
  129. print("="*80)
  130.  
  131. # Construct FFmpeg Command
  132. headers_dict = stream_info['headers']
  133.  
  134. # Format headers for FFmpeg (-headers "Key: Value\r\nKey: Value\r\n")
  135. ffmpeg_headers = ""
  136.  
  137. # Critical headers
  138. keys_to_keep = ['user-agent', 'referer', 'cookie', 'accept', 'accept-language', 'origin']
  139.  
  140. for key, value in headers_dict.items():
  141. if key.lower() in keys_to_keep:
  142. # Escape double quotes inside header values with a backslash
  143. safe_val = value.replace('"', '\\"')
  144. # Capitalize keys nicely
  145. nice_key = key.title()
  146. ffmpeg_headers += f"{nice_key}: {safe_val}\r\n"
  147.  
  148. # Add connection keep-alive if missing
  149. if 'connection' not in headers_dict:
  150. ffmpeg_headers += "Connection: keep-alive\r\n"
  151.  
  152. # Construct the full command
  153. # Note: We use .mp4 container for better compatibility with streams
  154. output_filename = f"{safe_title}.m4a"
  155.  
  156. # String version for manual copy-pasting
  157. cmd_str = f'ffmpeg -reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5 -headers "{ffmpeg_headers}" -i "{stream_info["url"]}" -vn -c:a copy -metadata title="{h1_title or safe_title}" -metadata artist="{artist}" -metadata comment=" {description} - Captured from {url}" "{output_filename}"'
  158.  
  159. print("\nšŸ“‹ FFmpeg Command (Auto-Generated):")
  160. print("-" * 20)
  161. print(cmd_str.encode('utf-8').decode('utf-8'))
  162. print("-" * 20)
  163. print("\nāš ļø Copy the command above and run it in your terminal.")
  164. else:
  165. print("\nāŒ Timeout: Stream request not found.")
  166. print("Did you play the audio? Is the site asking for login?")
  167.  
  168. except Exception as e:
  169. print(f"\nāŒ Error: {e}")
  170.  
  171. finally:
  172. # Safely close the browser context
  173. try:
  174. if context.pages: # Simple check if it might still be open
  175. print("\nClosing browser...")
  176. await context.close()
  177. except Exception as e:
  178. # Ignore errors during shutdown (e.g., Target closed, Loop closed)
  179. pass
  180.  
  181. def main():
  182. if sys.platform == 'win32':
  183. asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy())
  184.  
  185. parser = argparse.ArgumentParser(description="AudioChan Stream Capture Tool (Manual Mode Only)")
  186. parser.add_argument('url', nargs='?', help='The URL of the AudioChan page (optional)')
  187. args = parser.parse_args()
  188.  
  189. target_url = args.url
  190.  
  191. # Interactive URL Input if not provided
  192. if not target_url:
  193. print("Enter the AudioChan URL:")
  194. target_url = input("> ").strip()
  195. if not target_url:
  196. print("Error: No URL provided. Exiting.")
  197. return
  198.  
  199. asyncio.run(capture_stream_manual(target_url))
  200.  
  201. if __name__ == "__main__":
  202. main()
  203.  
Advertisement
Add Comment
Please, Sign In to add comment