网上很多方案都是直接更换采用gradio-offline
的包。但这个包很久不更新了并且是直接修改源码,所以自己打包也比较麻烦。因此这里提供了一种非侵入式的方法,动态替换部分资源的使用并重定向到本地静态资源,根据需要自己下载资源到本地就可以了。
本方案在gradio 5.34.0上测试通过。理论上兼容一个大版本应该没问题,如果有其他需求可以自己改一下。
使用方法
- 把
gr_offline.py
放到你的项目中 - 导入补丁并在使用gradio之前调用
patch
函数 - 下载离线的静态资源
使用样例
from pathlib import Path
import gr_offline
import gradio as gr
import uvicorn
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
RESOURCES_PATH = Path(__file__).parent.joinpath("resources")
gr_offline.patch(resources_path=RESOURCES_PATH, url_prefix="./offline")
app = FastAPI()
RESOURCES_PATH.mkdir(exist_ok=True)
app.mount("/offline", StaticFiles(directory=RESOURCES_PATH), name="offline")
def greet(name: str, intensity: int) -> str:
return "Hello, " + name + "!" * int(intensity)
demo = gr.Interface(
fn=greet,
inputs=["text", "slider"],
outputs=["text"],
)
app = gr.mount_gradio_app(app, demo, pwa=True, path="")
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=7860)
资源替换
-
CDN
旧 URL:https://cdnjs.cloudflare.com/{...}
新 URL:./{url_prefix}/{...}
-
Google Font
旧 URL:https://fonts.googleapis.com/css2?family={name}:wght@{weight}&display=swap
新 本地文件:{resources_path}/css/{name}.css
比如说,如果原始在线的CSS字体文件如下:
@font-face { font-family: "Example Font"; font-style: normal; font-weight: 400; src: url(https://exmaple.com/example-font.woff2) format("woff2"); }
你应该修改并将其存储到本地文件中:
{resources_path}/css/Example Font.css
:@font-face { font-family: "Example Font"; font-style: normal; font-weight: 400; src: url({url_prefix}/font/example-font.woff2) format("woff2"); }
最后,下载在线的woff2字体到路径:
{resources_path}/font/example-font.woff2
原始资源下载链接
https://cdnjs.cloudflare.com/ajax/libs/iframe-resizer/4.3.1/iframeResizer.contentWindow.min.js
https://fonts.googleapis.com/css2?family=Source+Sans+Pro:wght@400;600&display=swap
gr_offline.py
import os
import re
import types
from pathlib import Path
import jinja2
from fastapi.templating import Jinja2Templates
from gradio import routes
from gradio.themes.utils.fonts import GoogleFont
def _patch_env() -> None:
os.environ["GRADIO_ANALYTICS_ENABLED"] = "False"
def _patch_font(resources_path: Path) -> None:
# Load css font from local file when server started
# If you want to provide it as an external url, please ensure url started with http:// or https://
# Otherwise external CSS fonts will not be loaded
def _patched_stylesheet(self: GoogleFont) -> dict:
file_path = resources_path.joinpath("css").joinpath(f"{self.name}.css")
if file_path.is_file():
return {
"url": None,
"css": file_path.read_text(),
}
else:
raise FileNotFoundError(f"Font css file '{file_path}' not found")
GoogleFont.stylesheet = _patched_stylesheet
def _patch_templates(url_prefix: str) -> None:
remove_regex: list[re.Pattern] = [
re.compile(
r"<meta\b[^>]*?property=[\"']og:[^\"'>]*[\"'][^>]*?>",
flags=re.IGNORECASE | re.DOTALL,
),
re.compile(
r"<meta\b[^>]*?name=[\"']twitter:[^\"'>]*[\"'][^>]*?>",
flags=re.IGNORECASE | re.DOTALL,
),
re.compile(
r"<link\b[^>]*?href=[\"']?[^\"'>]*fonts.googleapis.com[^\"'>]*[\"']?[^>]*?>",
flags=re.IGNORECASE | re.DOTALL,
),
re.compile(
r"<link\b[^>]*?href=[\"']?[^\"'>]*fonts.gstatic.com[^\"'>]*[\"']?[^>]*?>",
flags=re.IGNORECASE | re.DOTALL,
),
]
cdn_regex: list[re.Pattern] = [re.compile(r"https?://cdnjs.cloudflare.com.*?")]
def _do_patch(html: str) -> str:
for pattern in remove_regex:
html = re.sub(pattern, "", html)
for pattern in cdn_regex:
html = re.sub(pattern, url_prefix, html)
return html
def _patched_render(self: jinja2.Template, *args, **kwargs) -> str:
html = jinja2.Template.render(self, *args, **kwargs)
return _do_patch(html)
async def _patched_render_async(self: jinja2.Template, *args, **kwargs) -> str:
html = await jinja2.Template.render_async(self, *args, **kwargs)
return _do_patch(html)
class PatchedJinja2Templates(Jinja2Templates):
def get_template(self, name: str) -> jinja2.Template:
template: jinja2.Template = super().get_template(name)
template.render = types.MethodType(_patched_render, template)
template.render_async = types.MethodType(_patched_render_async, template)
return template
routes.templates = PatchedJinja2Templates(directory=routes.STATIC_TEMPLATE_LIB)
routes.templates.env.filters["toorjson"] = routes.toorjson
def patch(resources_path: Path, url_prefix: str) -> None:
_patch_env()
_patch_font(resources_path)
_patch_templates(url_prefix)
Github:Gist