# -*- coding: utf-8 -*-
import sys
import requests
import re
import os
import time
import json
from datetime import datetime
from bs4 import BeautifulSoup
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from webdriver_manager.chrome import ChromeDriverManager
import requests

BASE_PATH = ""

def main():
    global BASE_PATH
    BASE_PATH = "./file/result/file_" + str(int(round(time.time() * 1000)))
    
    while True:
        r = get_dy_video()
        if r == 0:
            break

def get_dy_video():
    url = input("\n请输入抖音视频链接(输入0退出): ")
    if url == "0":
        return 0
    
    print(f"\n=== 开始处理 ===")
    print(f"输入链接: {url}")

    print("\n[1/4] 正在解析视频信息...")
    v_info = get_dy_video_info(url)
    if not v_info:
        print("❗ 获取视频信息失败")
        return 1
    
    try:
        print("\n[2/4] 构建下载地址...")
        v_id = v_info["video"]["play_addr"]["uri"]
        video_url = f'https://aweme.snssdk.com/aweme/v1/play/?video_id={v_id}&line=0&ratio=720p'
        print(f"原始下载地址: {video_url}")
    except KeyError as e:
        print(f"❗ 键错误: {str(e)}")
        return 1

    print("\n[3/4] 解析真实下载地址...")
    res_url = get_redirect_url(video_url)
    if not res_url:
        print("❗ 无法获取下载地址")
        return 1
    print(f"最终下载地址: {res_url[:80]}...")

    print("\n[4/4] 开始下载文件...")
    save_path = BASE_PATH + ".mp4"
    if download_media(res_url, save_path):
        print("\n------ 🎉 下载完成 ------")
        print(f"保存路径: {save_path}\n")
    return 1

def get_dy_video_info(url):
    print("\n[解析步骤] 处理短链接...")
    redirect_url = get_redirect_url(url)
    if not redirect_url:
        print("❗ 短链接解析失败")
        return None
    print(f"重定向地址: {redirect_url}")

    print("\n[解析步骤] 浏览器模拟访问...")
    chrome_options = webdriver.ChromeOptions()
    chrome_options.add_argument("--headless")
    chrome_options.add_argument("--disable-gpu")
    chrome_options.add_argument("user-agent=Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Mobile/15E148 Safari/604.1")

    service = Service(ChromeDriverManager().install())
    driver = webdriver.Chrome(service=service, options=chrome_options)
    try:
        driver.get(redirect_url)
        # 使用显式等待
        WebDriverWait(driver, 20).until(EC.presence_of_element_located((By.ID, "RENDER_DATA")))
        page_source = driver.page_source
    finally:
        driver.quit()

    print("\n[解析步骤] 提取数据标签...")
    soup = BeautifulSoup(page_source, 'html.parser')

    # 使用更精确的正则表达式提取JSON数据
    try:
        match = re.search(r'\{"aweme":\{.*\}\}', page_source)
        if match:
            try:
                video_data = json.loads(match.group(0))
                print("✅ JSON解析成功")
            except json.JSONDecodeError as e:
                print(f"❗ JSON解析失败: {str(e)}")
                return None
        else:
            print("❗ 未找到JSON数据")
            return None

        # 提取视频信息
        if video_data and video_data.get('aweme') and video_data.get('aweme').get('detail'):
            aweme_data = video_data['aweme']['detail']
            video_url = aweme_data.get('video', {}).get('playAddr', [{}])[0].get('src', '')
            video_cover = aweme_data.get('video', {}).get('cover', {}).get('urlList', [None])[0]
            author = aweme_data.get('author', {}).get('nickname')
            video_description = aweme_data.get('desc')
            audio_description = aweme_data.get('music', {}).get('title')
            # 提取html打开app的按钮
            open_app_text_tag = soup.find('div', class_='bottom-btn-con-new__text')
            open_app_text = open_app_text_tag.text if open_app_text_tag else "未找到打开APP文本"

            return {
                "data": {
                    "video_url": video_url,
                    "video_cover": video_cover,
                    "author": author,
                    "video_description": video_description,
                    "audio_description": audio_description,
                    "open_app_text": open_app_text
                }
            }
        else:
            print("❗ 无法从JSON中提取视频信息")
            return None

    except Exception as e:
        print(f"❗ 信息提取异常: {str(e)}")
        print("调试数据:", json.dumps(video_data, indent=2, ensure_ascii=False)[:500])
        return None
    
def download_media(url, save_path):
    try:
        print(f"\n开始下载: {url[:60]}...")
        headers = {
            "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",
            "Referer": "https://www.douyin.com/"
        }
        response = requests.get(url, headers=headers, stream=True, timeout=30)
        
        if response.status_code != 200:
            print(f"下载失败，状态码: {response.status_code}")
            return False
            
        total_size = int(response.headers.get('content-length', 0))
        print(f"文件大小: {total_size//1024} KB")
        
        with open(save_path, "wb") as f:
            downloaded = 0
            start = time.time()
            for chunk in response.iter_content(chunk_size=1024*1024):
                if chunk:
                    f.write(chunk)
                    downloaded += len(chunk)
                    speed = downloaded / (time.time() - start) / 1024
                    print(f"\r进度: {downloaded//1024}KB [{speed:.1f}KB/s]", end="")
            print()
        return True
    except Exception as e:
        print(f"\n下载错误: {str(e)}")
        return False

def get_redirect_url(url):
    try:
        headers = {
            "User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Mobile/15E148 Safari/604.1",
            "Referer": "https://www.douyin.com/"
        }
        response = requests.get(url, headers=headers, allow_redirects=True, timeout=15)
        print(f"重定向次数: {len(response.history)}")
        return response.url
    except Exception as e:
        print(f"重定向失败: {str(e)}")
        return None

if __name__ == "__main__":
    main()