combine the servers into one server without breaking it

'Server A'
import os
import torch
import logging
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import Optional
from audio_separator.separator import Separator

# Setup logging
logging.basicConfig(level=logging.INFO)

# Directories
models_dir = "./models"
out_dir = "./outputs"

# Device setup
device = "cuda" if torch.cuda.is_available() else "cpu"
use_autocast = device == "cuda"

# Fixed model for VR ARCH
VRARCH_MODEL = "UVR-DeEcho-DeReverb.pth"
MODEL_PATH = os.path.join(models_dir, VRARCH_MODEL)

if not os.path.exists(MODEL_PATH):
    raise FileNotFoundError(f"Model '{VRARCH_MODEL}' not found in '{models_dir}'")

# FastAPI app
app = FastAPI(title="UVR5 VR ARCH Separation Server")

class SeparationRequest(BaseModel):
    audio_path: str  # local file path to input audio
    output_format: str = "wav"
    window_size: int = 512
    aggression: int = 5
    tta: bool = True
    post_process: bool = False
    post_process_threshold: float = 0.2
    high_end_process: bool = False
    batch_size: int = 1
    normalization_threshold: float = 0.9
    amplification_threshold: float = 0.7
    single_stem: Optional[str] = ""  # empty or stem name


@app.post("/separate")
def separate_audio(req: SeparationRequest):
    """Perform music separation using VR ARCH model UVR-DeEcho-DeReverb.pth"""
    audio_path = req.audio_path

    if not os.path.isfile(audio_path):
        raise HTTPException(status_code=400, detail=f"Audio file '{audio_path}' does not exist")

    # Ensure output dir exists
    os.makedirs(out_dir, exist_ok=True)

    try:
        logging.info("Initializing Separator for VR ARCH model...")
        separator = Separator(
            log_level=logging.WARNING,
            model_file_dir=models_dir,
            output_dir=out_dir,
            output_format=req.output_format,
            use_autocast=use_autocast,
            normalization_threshold=req.normalization_threshold,
            amplification_threshold=req.amplification_threshold,
            output_single_stem=req.single_stem,
            vr_params={
                "batch_size": req.batch_size,
                "window_size": req.window_size,
                "aggression": req.aggression,
                "enable_tta": req.tta,
                "enable_post_process": req.post_process,
                "post_process_threshold": req.post_process_threshold,
                "high_end_process": req.high_end_process,
            }
        )

        logging.info(f"Loading model {VRARCH_MODEL}...")
        separator.load_model(model_filename=VRARCH_MODEL)

        logging.info(f"Starting separation on audio: {audio_path} ...")
        separation = separator.separate(audio_path)

        stems_paths = [os.path.abspath(os.path.join(out_dir, fname)) for fname in separation]

        if req.single_stem and req.single_stem.strip():
            # Output single stem only
            logging.info(f"Returning single stem at: {stems_paths[0]}")
            return {"stem1": stems_paths[0], "stem2": None}
        else:
            # Return first two stems if available
            stem1 = stems_paths[0] if len(stems_paths) > 0 else None
            stem2 = stems_paths[1] if len(stems_paths) > 1 else None
            logging.info(f"Returning stems at: {stem1}, {stem2}")
            return {"stem1": stem1, "stem2": stem2}

    except Exception as e:
        logging.error(f"Error during separation: {e}", exc_info=True)
        raise HTTPException(status_code=500, detail=f"Separation failed: {e}")


@app.get("/")
def root():
    return {"message": "UVR5 VR ARCH Separation Server - Use POST /separate to process audio files."}

'Server B'
import os
import sys
import torch
import logging
import yt_dlp
import json
import copy
import spaces
import gradio as gr
import urllib.parse
import assets.themes.loadThemes as loadThemes
from audio_separator.separator import Separator
from assets.i18n.i18n import I18nAuto
from assets.presence.discord_presence import RPCManager, track_presence

import uvicorn
from fastapi import FastAPI, UploadFile, File, Form, Request
from fastapi.responses import FileResponse, JSONResponse
from fastapi.staticfiles import StaticFiles   # <--- Added import for static files

i18n = I18nAuto()

now_dir = os.getcwd()
sys.path.append(now_dir)
config_file = os.path.join(now_dir, "assets", "config.json")
models_file = os.path.join(now_dir, "assets", "models.json")
default_settings_file = os.path.join(now_dir, "assets", "default_settings.json")
custom_settings_file = os.path.join(now_dir, "assets", "custom_settings.json")

device = "cuda" if torch.cuda.is_available() else "cpu"
use_autocast = device == "cuda"

out_dir = "./outputs"
models_dir = "./models"
extensions = (".wav", ".flac", ".mp3", ".ogg", ".opus", ".m4a", ".aiff", ".ac3")

mdx23c_models = [
    'MDX23C_D1581.ckpt',
    'MDX23C-8KFFT-InstVoc_HQ.ckpt',
    'MDX23C-8KFFT-InstVoc_HQ_2.ckpt',
    'MDX23C-De-Reverb-aufr33-jarredou.ckpt',
    'MDX23C-DrumSep-aufr33-jarredou.ckpt'
]

found_files = []
logs = []

def load_settings_from_file(filepath):
    try:
        with open(filepath, 'r', encoding='utf-8') as f:
            return json.load(f)
    except Exception:
        return None

def read_main_config():
    try:
        with open(config_file, "r", encoding="utf8") as f:
            return json.load(f)
    except Exception:
        return {}

def get_initial_settings():
    main_config = read_main_config()
    load_custom = main_config.get('load_custom_settings', False)

    settings_to_load = {}
    default_settings = load_settings_from_file(default_settings_file)

    if load_custom:
        custom_settings = load_settings_from_file(custom_settings_file)
        if custom_settings:
            settings_to_load = copy.deepcopy(default_settings)
            for section, params in custom_settings.items():
                if section in settings_to_load:
                    for key, value in params.items():
                        settings_to_load[section][key] = value
                else:
                    settings_to_load[section] = params
        else:
            settings_to_load = default_settings
    else:
        settings_to_load = default_settings

    return settings_to_load or {}

initial_settings = get_initial_settings()

@track_presence("Performing MDXC Separationn")
@spaces.GPU(duration=60)
def mdxc_separator(audio_path, model, out_format, segment_size, override_seg_size, overlap, batch_size, norm_thresh, amp_thresh, single_stem):
    model_path = os.path.join(models_dir, model)
    try:
        if not os.path.exists(model_path):
            # Here you can add logging or notifications about model download
            pass
        
        separator = Separator(
            log_level=logging.WARNING,
            model_file_dir=models_dir,
            output_dir=out_dir,
            output_format=out_format,
            use_autocast=use_autocast,
            normalization_threshold=norm_thresh,
            amplification_threshold=amp_thresh,
            output_single_stem=single_stem,
            mdxc_params={
                "segment_size": segment_size,
                "override_model_segment_size": override_seg_size,
                "batch_size": batch_size,
                "overlap": overlap,
            }
        )

        separator.load_model(model_filename=model)
        separation = separator.separate(audio_path)
        stems = [os.path.join(out_dir, file_name) for file_name in separation]
        if single_stem.strip():
            return stems[0], None
        return stems[0], stems[1]

    except Exception as e:
        raise RuntimeError(f"MDX23C separation failed: {e}") from e

def download_audio(url, output_dir="ytdl"):
    os.makedirs(output_dir, exist_ok=True)
    ydl_opts = {
        'format': 'bestaudio/best',
        'postprocessors': [{
            'key': 'FFmpegExtractAudio',
            'preferredcodec': 'wav',
            'preferredquality': '32',
        }],
        'outtmpl': os.path.join(output_dir, '%(title)s.%(ext)s'),
        'postprocessor_args': [
            '-acodec', 'pcm_f32le'
        ],
    }
    try:
        with yt_dlp.YoutubeDL(ydl_opts) as ydl:
            info = ydl.extract_info(url, download=False)
            video_title = info['title']
            ydl.download([url])
            file_path = os.path.join(output_dir, f"{video_title}.wav")
            if os.path.exists(file_path):
                return os.path.abspath(file_path)
            else:
                raise Exception("Something went wrong")
    except Exception as e:
        raise Exception(f"Error extracting audio with yt-dlp: {str(e)}")

app = FastAPI()

# <--- New: Serve the outputs folder as static files so client can download them by URL
app.mount("/outputs", StaticFiles(directory=out_dir), name="outputs")

@app.post("/mdx23c/separate")
async def separate_mdx23c(
    request: Request,  # <--- Added for base_url extraction
    audio: UploadFile = File(...),
    model: str = Form(...),
    output_format: str = Form(...),
    segment_size: int = Form(...),
    override_segment_size: bool = Form(...),
    overlap: int = Form(...),
    batch_size: int = Form(...),
    normalization_threshold: float = Form(...),
    amplification_threshold: float = Form(...),
    single_stem: str = Form("")
):
    try:
        # Save uploaded file temporarily
        input_path = os.path.join("temp", audio.filename)
        os.makedirs("temp", exist_ok=True)
        with open(input_path, "wb") as f:
            f.write(await audio.read())
        
        stem1, stem2 = mdxc_separator(
            audio_path=input_path,
            model=model,
            out_format=output_format,
            segment_size=segment_size,
            override_seg_size=override_segment_size,
            overlap=overlap,
            batch_size=batch_size,
            norm_thresh=normalization_threshold,
            amp_thresh=amplification_threshold,
            single_stem=single_stem,
        )
        
        # Construct URLs to the output files (instead of local paths)
        stem1_url = f"{request.base_url}outputs/{os.path.basename(stem1)}"
        
        response = {"stem1": stem1_url}
        
        if stem2:
            stem2_url = f"{request.base_url}outputs/{os.path.basename(stem2)}"
            response["stem2"] = stem2_url
        
        return JSONResponse(content=response)
    except Exception as e:
        return JSONResponse(content={"error": str(e)}, status_code=500)

@app.get("/mdx23c/models")
def list_models():
    return {"models": mdx23c_models}

@app.get("/mdx23c/download_audio")
def download_audio_endpoint(url: str):
    try:
        path = download_audio(url)
        return {"audio_path": path}
    except Exception as e:
        return JSONResponse(content={"error": str(e)}, status_code=500)

if __name__ == "__main__":
    uvicorn.run("main:app", host="0.0.0.0", port=8123, reload=False)