File size: 6,308 Bytes
2227f21
 
1e62ae6
 
8b8dae6
 
4c3ee6d
6f8785a
b6886e2
4c3ee6d
 
8b8dae6
6f8785a
c714721
12a5b0d
2227f21
4c3ee6d
 
8b8dae6
4c3ee6d
b6886e2
2227f21
 
 
 
14c3281
 
 
2462029
5249eb7
14c3281
9f372aa
14c3281
9f372aa
 
 
 
 
 
1e62ae6
4c3ee6d
 
 
2227f21
4c3ee6d
1e62ae6
4c3ee6d
 
 
 
 
1e62ae6
4c3ee6d
1e62ae6
 
2227f21
b6886e2
1e62ae6
2227f21
 
b6886e2
1e62ae6
2227f21
 
 
 
 
 
12a5b0d
518d0b5
8b8dae6
 
b6886e2
8b8dae6
 
 
 
b6886e2
8b8dae6
 
 
 
 
 
 
 
 
b6886e2
8b8dae6
6f8785a
8b8dae6
6f8785a
 
b6886e2
8b8dae6
6f8785a
 
 
 
b6886e2
 
 
8b8dae6
6f8785a
 
 
b6886e2
8b8dae6
b6886e2
 
 
 
c714721
b7c7178
 
 
 
 
 
 
 
 
 
 
 
 
c714721
 
 
0694f9d
b7c7178
0694f9d
 
 
 
c714721
0694f9d
 
 
c714721
 
0694f9d
 
 
 
 
 
 
 
 
 
 
12ed4ea
b7c7178
c714721
0694f9d
b7c7178
fd5a651
b7c7178
0694f9d
b7c7178
 
c714721
 
0694f9d
 
c714721
 
0694f9d
c714721
 
0694f9d
c714721
0694f9d
 
 
 
 
 
c714721
b6886e2
 
 
 
 
6f8785a
b6886e2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
import hashlib
import time
import re
import logging
import requests
import subprocess
from fastapi import FastAPI, Request, HTTPException
from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles
import tempfile
import os
import uuid
import gc  # Import the garbage collector module
import json


app = FastAPI()

# Temporary directory for file storage
global_download_dir = tempfile.mkdtemp()
logging.basicConfig(level=logging.DEBUG)

def md5(string):
    """Generate MD5 hash of a string."""
    return hashlib.md5(string.encode('utf-8')).hexdigest()
# Constants
BASE_URL = 'https://tecuts-request.hf.space'
BASE = 'https://www.qobuz.com/api.json/0.2/'
TOKEN = os.getenv('TOKEN')


APP_ID = '579939560'

# Custom Headers
HEADERS = {
    'X-App-Id': APP_ID,
    'X-User-Auth-Token': TOKEN,
    'User-Agent': 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.21) Gecko/20100312 Firefox/3.6'
}

@app.get('/')
def main():
    return {"appStatus": "running"}

@app.post('/track')
async def fetch_data_for_url(request: Request):
    try:
        data = await request.json()
    except Exception as e:
        logging.error(f"Error parsing JSON: {e}")
        raise HTTPException(status_code=400, detail="Invalid JSON")
    
    url = data.get('url')
    if not url:
        raise HTTPException(status_code=400, detail="URL is required")

    logging.info(f'Fetching data for: {url}')
    
    track_id_match = re.search(r'\d+$', url)
    if not track_id_match:
        logging.error('Track ID not found in your input.')
        raise HTTPException(status_code=400, detail="Track ID not found in URL")
    
    track_id = track_id_match.group(0)
    timestamp = int(time.time())
    rSigRaw = f'trackgetFileUrlformat_id27intentstreamtrack_id{track_id}{timestamp}fa31fc13e7a28e7d70bb61e91aa9e178'
    rSig = md5(rSigRaw)
    
    download_url = f'{BASE}track/getFileUrl?format_id=27&intent=stream&track_id={track_id}&request_ts={timestamp}&request_sig={rSig}'
    
    response = requests.get(download_url, headers=HEADERS)
    if response.status_code != 200:
        logging.error(f"Failed to fetch the track file URL: {response.status_code}")
        raise HTTPException(status_code=500, detail="Failed to fetch the track file URL")
    
    file_url = response.json().get('url')
    if not file_url:
        logging.error("No file URL returned from Qobuz")
        raise HTTPException(status_code=500, detail="No file URL returned")
    
    # Download the FLAC file
    flac_file_path = os.path.join(global_download_dir, f'{uuid.uuid4()}.flac')
    with requests.get(file_url, stream=True) as r:
        r.raise_for_status()
        with open(flac_file_path, 'wb') as f:
            for chunk in r.iter_content(chunk_size=8192):
                f.write(chunk)
    logging.info(f"FLAC file downloaded at {flac_file_path}")

    # Convert FLAC to ALAC using a context manager to ensure proper cleanup
    alac_file_path = os.path.join(global_download_dir, f'{uuid.uuid4()}.m4a')
    with subprocess.Popen(['ffmpeg', '-i', flac_file_path, '-c:a', 'alac', alac_file_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE) as process:
        process.communicate()
    logging.info(f"Converted to ALAC: {alac_file_path}")

    # Delete the FLAC file to free up space
    os.remove(flac_file_path)
    logging.info(f"Deleted FLAC file: {flac_file_path}")

    # Generate the file URL for ALAC
    alac_file_url = f'{BASE_URL}/file/{os.path.basename(alac_file_path)}'
    logging.info(f"Returning ALAC file URL: {alac_file_url}")

    # Force garbage collection to clear memory
    gc.collect()

    # Return the ALAC file URL to the client as JSON
    return {"url": alac_file_url}

# Mount StaticFiles to serve files from the global download directory
app.mount("/file", StaticFiles(directory=global_download_dir), name="downloads")


def extract_video_id(url):
    # 定义正则表达式模式
    pattern = r'(?:(?:youtube\.com\/(?:watch\?v=|embed\/|shorts\/)|youtu\.be\/)([a-zA-Z0-9_-]{11}))'
    
    # 进行匹配
    match = re.search(pattern, url)
    
    if match:
        return match.group(1)
    else:
        return None


@app.get("/yt-audio")
async def yt_audio(url: str):
    try:
        # 提取视频 ID
        videoId = extract_video_id(url)
        if videoId is None:
            raise ValueError("Video ID extraction failed")

        # 设置请求头
        headers = {
            "X-Goog-Authuser": "0",
            "X-Origin": "https://www.youtube.com",
            "X-goog-Visitor-Id": "CgtMWlVXOTFCcE5Edyj9p4i9BjIKCgJVUxIEGgAgZA%3D%3D"
        }

        # 构造请求体
        body = {
            "context": {
                "client": {
                    "clientName": "IOS",
                    "clientVersion": "19.14.3",
                    "deviceModel": "iPhone15,4"
                }
            },
            "videoId": videoId
        }
        Player_Url = "https://www.youtube.com/youtubei/v1/player?key=AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8&prettyPrint=false"

        # 发送请求
        logging.info(f"Sending request to URL: {url} with video ID: {videoId}")
        response = requests.post(
            Player_Url,
            timeout=20,
            json=body,
            headers=headers
        )
        response.raise_for_status()

        logging.info(f"Received response from URL: {url}, status code: {response.status_code}")
        return response.json()

    except requests.RequestException as e:
        logging.error(f"Request error: {str(e)}")
        return {"error": f"Request error: {str(e)}"}
    except json.JSONDecodeError as e:
        logging.error(f"JSON decoding error: {str(e)}")
        return {"error": f"JSON decoding error: {str(e)}"}
    except ValueError as e:
        logging.error(f"Value error: {str(e)}")
        return {"error": f"Value error: {str(e)}"}
    except Exception as e:
        logging.error(f"Unexpected error: {str(e)}")
        return {"error": f"Unexpected error: {str(e)}"}

# Middleware to set the correct MIME type
@app.middleware("http")
async def set_mime_type_middleware(request: Request, call_next):
    response = await call_next(request)
    if request.url.path.endswith(".m4a"):
        response.headers["Content-Type"] = "audio/m4a"
    return response