File size: 5,080 Bytes
5d022f9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c9e6d27
5d022f9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""
A robust wrapper for the Face/Off application that extracts a bundled zip file and
locates the real `app.py` file within it. This wrapper avoids executing the
embedded app's `if __name__ == '__main__'` block and supports multiple archive
layouts (with or without a top-level directory).

When deployed on Hugging Face Spaces, the repository root is `/home/user/app`.
This script will extract `faceoff-space.zip` into a dedicated directory and
search for `app.py` within the extracted contents. The first match is loaded
with `runpy.run_path` using a custom module name to prevent side effects.
"""

import os
import zipfile
import sys
import runpy

# Name of the bundled zip containing the full Face/Off project
ZIP_NAME = "faceoff-space-updated.zip"

# Determine absolute paths based on current file location
base_dir = os.path.dirname(__file__)
zip_path = os.path.join(base_dir, ZIP_NAME)

# Directory where the archive will be unpacked. Using a distinct name ensures we
# don't pollute the repo root and can detect if we've already extracted it.
extract_target = os.path.join(base_dir, "faceoff-space-extracted")

# Always (re)unpack the zip on startup. If the extraction directory exists from
# a previous run, remove it first to ensure that stale files are not served.
if os.path.isfile(zip_path):
    # Clean out any old extracted contents
    if os.path.isdir(extract_target):
        import shutil
        shutil.rmtree(extract_target, ignore_errors=True)
    os.makedirs(extract_target, exist_ok=True)
    with zipfile.ZipFile(zip_path, "r") as archive:
        archive.extractall(extract_target)

# Ensure extracted directories are in the module search path. We insert both
# the extraction root and any immediate subdirectories to ease module imports.
if os.path.isdir(extract_target) and extract_target not in sys.path:
    sys.path.insert(0, extract_target)
    # Also add one level deeper (e.g., faceoff-space) to sys.path if present
    for entry in os.listdir(extract_target):
        candidate = os.path.join(extract_target, entry)
        if os.path.isdir(candidate) and candidate not in sys.path:
            sys.path.insert(0, candidate)

def find_app_py(root: str) -> str:
    """Recursively search for an `app.py` file starting at `root` and return
    its path. Raises FileNotFoundError if none is found.
    """
    for current_dir, subdirs, files in os.walk(root):
        if "app.py" in files:
            return os.path.join(current_dir, "app.py")
    raise FileNotFoundError(f"`app.py` not found under {root}")

# Locate the actual application file within the extracted archive
try:
    app_path = find_app_py(extract_target)
except FileNotFoundError:
    # Fall back to the repository root in case extraction failed but files exist directly
    try:
        app_path = find_app_py(base_dir)
    except FileNotFoundError as exc:
        raise RuntimeError(
            "Unable to locate the Face/Off application (`app.py`). Ensure that "
            f"{ZIP_NAME} contains the app files."
        ) from exc

# Dynamically load the application while preventing execution of its main block.
app_globals = runpy.run_path(app_path, run_name="faceoff_app")

# Gradio Spaces expect either a `demo` object (Blocks/Interface) or, for FastAPI
# Spaces, an `app` object. The Face/Off project defines both. Surface both
# variables from the loaded module if they exist so that the runtime can
# initialize properly regardless of the selected SDK.
demo = app_globals.get("demo")
app = app_globals.get("app")

# If neither object was found, raise an informative error. This makes it clear
# that the wrapped project did not expose the expected variables.
if demo is None and app is None:
    raise RuntimeError(
        "Neither `demo` nor `app` was found in the extracted Face/Off project. "
        "Ensure that `demo` (Gradio Blocks) or `app` (FastAPI) is defined."
    )

# If running in a Hugging Face Space, automatically launch the Gradio demo. Many
# Gradio Spaces rely on an implicit call to `demo.launch()` provided by the
# platform. However, because we dynamically import the application, the
# automatic initialization does not occur. To ensure the server starts,
# explicitly launch the demo when this file is executed as the main module. We
# use `queue()` to enable concurrency and disable analytics endpoints with
# `show_api=False`. When running locally, you can still execute this file
# directly (e.g. `python app.py`) and the server will start.
if __name__ == "__main__":
    import os
    if demo is not None:
        # Determine the port Hugging Face spaces use (defaults to 7860 locally).
        port = int(os.getenv("PORT", "7860"))
        # Launch the Gradio app with queuing and without external share links.
        demo.queue().launch(server_name="0.0.0.0", server_port=port, show_api=False, share=False)
    elif app is not None:
        # Fallback: run FastAPI using uvicorn if no Gradio demo is available.
        import uvicorn
        port = int(os.getenv("PORT", "7860"))
        uvicorn.run(app, host="0.0.0.0", port=port)