first commit
8
.gitignore
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
.DS_Store
|
||||
.idea
|
||||
*.log*
|
||||
logs
|
||||
__pycache__
|
||||
问题.txt
|
||||
helper.py
|
||||
app.py
|
||||
35
README.md
Normal file
@@ -0,0 +1,35 @@
|
||||
## 详细使用文档写完再写!
|
||||
|
||||
接口文档:http://127.0.0.1:8000/docs。
|
||||
|
||||
fairseq 依赖 需要 windows 先安装 vs_BuildTools.exe
|
||||
|
||||
decord 用 eva-decord 替代
|
||||
https://github.com/dmlc/decord/issues/213
|
||||
|
||||
python -B main.py
|
||||
|
||||
uvicorn main:app --reload
|
||||
|
||||
celery -A src.celery.main.app worker -l info -P eventlet
|
||||
|
||||
celery --broker=redis://:lb714500@127.0.0.1:6379/0 flower --port=5551
|
||||
|
||||
python -mvenv venv
|
||||
source venv/bin/activate
|
||||
|
||||
windows redis 配置
|
||||
redis-server.exe redis.windows.conf
|
||||
# 卸载服务:
|
||||
redis-server --service-uninstall
|
||||
# 开启服务:
|
||||
redis-server --service-start
|
||||
# 停止服务:
|
||||
redis-server --service-stop
|
||||
# 重命名服务:
|
||||
redis-server --service-name name
|
||||
|
||||
mac redis 配置
|
||||
软件 /usr/local/Cellar/redis/7.2.4
|
||||
redis的配置文件 /usr/local/etc/redis.conf
|
||||
启动 redis-server /usr/local/etc/redis.conf
|
||||
44
main.py
Normal file
@@ -0,0 +1,44 @@
|
||||
# encoding: utf-8
|
||||
from fastapi import FastAPI
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from src.controller import api_router
|
||||
from src.config import *
|
||||
from src.utils import *
|
||||
|
||||
|
||||
def startApp():
|
||||
# 接口文档配置
|
||||
app: FastAPI = FastAPI(
|
||||
title=settings.PROJECT_NAME,
|
||||
version=settings.PROJECT_VERSION,
|
||||
description=settings.PROJECT_DESC
|
||||
)
|
||||
|
||||
# 配置路由
|
||||
app.include_router(api_router, prefix=settings.API_PREFIX)
|
||||
# 配置静态资源目录
|
||||
app.mount("/" + settings.STATIC_DIR, StaticFiles(directory=settings.STATIC_DIR), name=settings.STATIC_DIR)
|
||||
# 跨域配置
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=settings.CORS_ORIGINS,
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
return app
|
||||
|
||||
|
||||
app = startApp()
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
|
||||
uvicorn.run(
|
||||
app=settings.APP_NAME,
|
||||
host=settings.HOST,
|
||||
port=settings.PORT,
|
||||
reload=settings.RELOAD
|
||||
)
|
||||
21
requirements.txt
Normal file
@@ -0,0 +1,21 @@
|
||||
fastapi==0.109.2
|
||||
uvicorn==0.27.1
|
||||
pydantic==2.6.1
|
||||
pydantic-settings==2.2.0
|
||||
requests==2.31.0
|
||||
|
||||
torch==2.2.0
|
||||
opencv-python==4.9.0.80
|
||||
open-clip-torch==2.24.0
|
||||
transformers==4.37.2
|
||||
librosa==0.10.1
|
||||
fairseq==0.12.2
|
||||
unicodedata2==15.1.0
|
||||
zhconv==1.4.3
|
||||
modelscope==1.12.0
|
||||
rapidfuzz==3.6.1
|
||||
eva-decord==0.6.1
|
||||
flower==2.0.1
|
||||
redis==5.0.1
|
||||
celery==5.3.6
|
||||
eventlet==0.35.1
|
||||
28
requirements_back.txt
Normal file
@@ -0,0 +1,28 @@
|
||||
clip_client>=0.8.3
|
||||
clip_server>=0.8.3
|
||||
docarray>=0.19.0
|
||||
ImageHash==4.2.1
|
||||
numpy==1.26.4
|
||||
Pillow==9.2.0
|
||||
streamlit==1.11.1
|
||||
av
|
||||
altair<5
|
||||
|
||||
fastapi==0.109.2
|
||||
uvicorn==0.27.1
|
||||
pydantic==2.6.1
|
||||
pydantic-settings==2.2.0
|
||||
requests==2.31.0
|
||||
fastapi-socketio==0.0.10
|
||||
|
||||
torch==2.2.0
|
||||
opencv-python==4.9.0.80
|
||||
open-clip-torch==2.24.0
|
||||
transformers==4.37.2
|
||||
librosa==0.10.1
|
||||
fairseq==0.12.2
|
||||
unicodedata2==15.1.0
|
||||
zhconv==1.4.3
|
||||
modelscope==1.12.0
|
||||
rapidfuzz==3.6.1
|
||||
decord>=0.6.0
|
||||
59
run.bat
Normal file
@@ -0,0 +1,59 @@
|
||||
@echo off
|
||||
|
||||
:menu
|
||||
cls
|
||||
echo.
|
||||
echo. AI<41><49><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>˵<EFBFBD>
|
||||
echo.
|
||||
echo. 1: <20><><EFBFBD><EFBFBD> FastApi
|
||||
echo. 2: <20><><EFBFBD><EFBFBD> Celery
|
||||
echo. 3: <20><><EFBFBD><EFBFBD> Flower WebUi
|
||||
echo. 4: <20><><EFBFBD><EFBFBD> Redis Server
|
||||
echo. 5: ȫ<><C8AB> <20><><EFBFBD><EFBFBD>
|
||||
ECHO.
|
||||
|
||||
set/p option="<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ѡ<EFBFBD><EFBFBD>:"
|
||||
if "%option%"=="1" (
|
||||
call:fastapi
|
||||
goto menu
|
||||
)
|
||||
if "%option%"=="2" (
|
||||
call:celery
|
||||
goto menu
|
||||
)
|
||||
if "%option%"=="3" (
|
||||
call:flower
|
||||
goto menu
|
||||
)
|
||||
if "%option%"=="4" (
|
||||
call:redis
|
||||
goto menu
|
||||
)
|
||||
if "%option%"=="5" (
|
||||
call:all
|
||||
goto menu
|
||||
)
|
||||
|
||||
:fastapi
|
||||
start cmd /k "echo <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> FastApi<70><69><EFBFBD><EFBFBD><EFBFBD>Ե<EFBFBD>... && python -B main.py"
|
||||
goto:eof
|
||||
|
||||
:celery
|
||||
start cmd /k "echo <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> Celery<72><79><EFBFBD><EFBFBD><EFBFBD>Ե<EFBFBD>... && celery -A src.celery.main.app worker -l info -P eventlet"
|
||||
goto:eof
|
||||
|
||||
:flower
|
||||
start cmd /k "echo <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> Flower<65><72><EFBFBD><EFBFBD><EFBFBD>Ե<EFBFBD>... && celery --broker=redis://:lb714500@127.0.0.1:6379/0 flower --port=5551"
|
||||
goto:eof
|
||||
|
||||
:redis
|
||||
start cmd /k "echo <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> Redis Server<65><72><EFBFBD><EFBFBD><EFBFBD>Ե<EFBFBD>... && cd C:\Program Files\Redis && redis-server.exe redis.windows.conf"
|
||||
goto:eof
|
||||
|
||||
:all
|
||||
call:redis
|
||||
call:fastapi
|
||||
call:celery
|
||||
call:flower
|
||||
goto menu
|
||||
goto:eof
|
||||
74
run.sh
Executable file
@@ -0,0 +1,74 @@
|
||||
#!/bin/bash
|
||||
|
||||
|
||||
|
||||
newTerminal() {
|
||||
osascript 2>/dev/null <<EOF
|
||||
tell application "System Events"
|
||||
tell process "Terminal" to keystroke "t" using command down
|
||||
end
|
||||
tell application "Terminal"
|
||||
activate
|
||||
do script with command "cd \"$PWD\"; $*" in window 1
|
||||
end tell
|
||||
EOF
|
||||
}
|
||||
|
||||
fastapi(){
|
||||
newTerminal "echo 正在启动 FastApi,请稍等... && $PWD/venv/bin/python -B main.py"
|
||||
}
|
||||
|
||||
celery(){
|
||||
echo "echo 正在启动 Celery,请稍等... && celery -A src.celery.main.app worker -l info -P eventlet"
|
||||
}
|
||||
|
||||
flower(){
|
||||
echo "echo 正在启动 Flower,请稍等... && celery --broker=redis://:lb714500@127.0.0.1:6379/0 flower --port=5551"
|
||||
}
|
||||
|
||||
redis(){
|
||||
echo "echo 正在启动 Redis Server,请稍等... && cd C:\Program Files\Redis && redis-server.exe redis.windows.conf"
|
||||
}
|
||||
|
||||
all(){
|
||||
redis
|
||||
fastapi
|
||||
celery
|
||||
flower
|
||||
}
|
||||
|
||||
name='0'
|
||||
menu() {
|
||||
echo
|
||||
echo AI爬虫 批处理启动菜单
|
||||
echo
|
||||
echo 1: 启动 FastApi
|
||||
echo 2: 启动 Celery
|
||||
echo 3: 启动 Flower WebUi
|
||||
echo 4: 启动 Redis Server
|
||||
echo 5: 全部 启动
|
||||
echo
|
||||
read -p "请输入你的选择:" name
|
||||
}
|
||||
|
||||
menu
|
||||
|
||||
if [ $name == "1" ]
|
||||
then
|
||||
fastapi
|
||||
elif [ $name == "2" ]
|
||||
then
|
||||
celery
|
||||
elif [ $name == "3" ]
|
||||
then
|
||||
flower
|
||||
elif [ $name == "4" ]
|
||||
then
|
||||
redis
|
||||
elif [ $name == "5" ]
|
||||
then
|
||||
all
|
||||
else
|
||||
echo "没有符合的条件"
|
||||
fi
|
||||
|
||||
1
src/.pydio
Normal file
@@ -0,0 +1 @@
|
||||
3ac36a1c-7d73-47bf-af4e-1e05933b65bd
|
||||
1
src/ai/.pydio
Normal file
@@ -0,0 +1 @@
|
||||
0e6c5784-911f-49db-b759-70844e9d3d6e
|
||||
0
src/ai/__init__.py
Normal file
26
src/ai/ai_test.py
Normal file
@@ -0,0 +1,26 @@
|
||||
import torch
|
||||
from modelscope.utils.constant import Tasks
|
||||
from modelscope.pipelines import pipeline
|
||||
from modelscope.preprocessors.image import load_image
|
||||
|
||||
pipeline = pipeline(task=Tasks.multi_modal_embedding, model='damo/multi-modal_clip-vit-large-patch14_336_zh', model_revision='v1.0.1', device='cpu')
|
||||
input_img = load_image('https://p1.a.yximgs.com/upic/2023/12/27/13/BMjAyMzEyMjcxMzAwMzdfMjkwOTEwNDc5N18xMjA2NTYyODIyNDRfMl8z_Be33b730fcb8aaf000ba5e58bf8c3681e.jpg?tag=1-1708460644-unknown-0-huswbkiuiq-0ac88680ace16101&clientCacheKey=3x9ghy3dj8y2z5a.jpg&di=70208a94&bp=14764') # 支持皮卡丘示例图片路径/本地图片 返回PIL.Image
|
||||
input_texts = ["小火龙", "白色衬衫"]
|
||||
|
||||
# 支持一张图片(PIL.Image)或多张图片(List[PIL.Image])输入,输出归一化特征向量
|
||||
img_embedding = pipeline.forward({'img': input_img})['img_embedding'] # 2D Tensor, [图片数, 特征维度]
|
||||
|
||||
# 支持一条文本(str)或多条文本(List[str])输入,输出归一化特征向量
|
||||
text_embedding = pipeline.forward({'text': input_texts})['text_embedding'] # 2D Tensor, [文本数, 特征维度]
|
||||
|
||||
# 计算图文相似度
|
||||
with torch.no_grad():
|
||||
# 计算内积得到logit,考虑模型temperature
|
||||
logits_per_image = (img_embedding / pipeline.model.temperature) @ text_embedding.t()
|
||||
# 根据logit计算概率分布
|
||||
probs = logits_per_image.softmax(dim=-1).cpu().numpy()
|
||||
|
||||
# similarityNum = probs[0][1] * 100
|
||||
print("图文匹配概率:", probs[0])
|
||||
# result = True if similarityNum >= 85 else False
|
||||
# print("图文匹配概率:", result)
|
||||
53
src/ai/imageCaptioning.py
Normal file
@@ -0,0 +1,53 @@
|
||||
from modelscope.pipelines import pipeline
|
||||
from modelscope.utils.constant import Tasks
|
||||
|
||||
class imageCaptioning:
|
||||
# AI模型的本地地址
|
||||
modelName = 'damo/mplug_image-captioning_coco_base_zh'
|
||||
# 使用 cpu 还是 GPU 计算,GPU 比 CPU快,但是 GPU 只支持 英伟达 显卡,至少4G显存以上
|
||||
deviceType = 'cpu' # cpu, gpu
|
||||
# 外部传入 需要检索的关键词数组
|
||||
keyword = []
|
||||
# 外部传入 的视频帧 截图数据,[ 'http地址 或者 本地地址', '' ]
|
||||
# 地址可以用 video标签 poster 预览图,或者使用 ffmpeg,docarray,这里推荐 使用 多种模态数据结构工具包 docarray
|
||||
# 具体 docarray 封装看 utils/screenshot.py
|
||||
imgArr = []
|
||||
# 符合 条件 数据的计数
|
||||
count = 0
|
||||
# 符合 条件 数据的list下标
|
||||
haveIndex = []
|
||||
|
||||
def __init__(self, key, imgList):
|
||||
self.keyword = key
|
||||
self.imgArr = imgList
|
||||
|
||||
def start(self):
|
||||
self.count = 0
|
||||
self.haveIndex = []
|
||||
|
||||
for index, item in enumerate(self.imgArr):
|
||||
print()
|
||||
print("ai 得到的图片为:", item)
|
||||
res = self.mplugImage(item)
|
||||
print("ai 解析图片内容为:", res)
|
||||
for v in self.keyword:
|
||||
if v in res:
|
||||
self.count += 1
|
||||
self.haveIndex.append(index)
|
||||
|
||||
|
||||
if self.count != 0 and len(self.haveIndex) != 0:
|
||||
# print('包含次数:', self.count)
|
||||
return {'count': self.count, 'index': self.haveIndex}
|
||||
else:
|
||||
return {'count': 0, 'index': []}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def mplugImage(self, url):
|
||||
pipeline_caption = pipeline(Tasks.image_captioning, model=self.modelName, device=self.deviceType)
|
||||
result = pipeline_caption(url)
|
||||
str = ''.join(result['caption'].split())
|
||||
return str.replace("。", "")
|
||||
75
src/ai/multimModaImageTextSearch.py
Normal file
@@ -0,0 +1,75 @@
|
||||
import torch
|
||||
from modelscope.utils.constant import Tasks
|
||||
from modelscope.pipelines import pipeline
|
||||
from modelscope.preprocessors.image import load_image
|
||||
from fastapi import WebSocket
|
||||
|
||||
class multimModaImageTextSearch:
|
||||
# AI模型的本地地址
|
||||
modelName: str = 'damo/multi-modal_clip-vit-large-patch14_336_zh'
|
||||
# 使用 cpu 还是 GPU 计算,GPU 比 CPU快,但是 GPU 只支持 英伟达 显卡,至少4G显存以上
|
||||
deviceType: str = 'cpu' # cpu, gpu
|
||||
# 干扰词,干扰词越少见,搜索结果的准确度越高
|
||||
# 但不是越少见越多就越好,需要用数据测试,找到合适的
|
||||
noiseWord: list[str] = ['小火龙']
|
||||
# 阈值,数值越大,搜索结果的准确度越高,
|
||||
# 但不是越大越好,需要用数据测试,找到合适的
|
||||
thresholdValue: int = 85
|
||||
# 外部传入 需要检索的关键词数组
|
||||
keyword: str = ""
|
||||
# 外部传入 的视频帧 截图数据,[ 'http地址 或者 本地地址', '' ]
|
||||
# 地址可以用 video标签 poster 预览图,或者使用 ffmpeg,docarray,这里推荐
|
||||
# 使用 多种模态数据结构工具包 docarray,具体 docarray 封装看 utils/screenshot.py
|
||||
imgArr: list[str] = []
|
||||
# 符合 条件 数据的计数
|
||||
count: int = 0
|
||||
# 符合 条件 数据的list下标
|
||||
haveIndex = []
|
||||
|
||||
def __init__(self, key, imgList):
|
||||
self.keyword = key
|
||||
self.imgArr = imgList
|
||||
|
||||
def start(self):
|
||||
self.count = 0
|
||||
self.haveIndex = []
|
||||
|
||||
for index, item in enumerate(self.imgArr):
|
||||
print()
|
||||
print("关键词: ", self.keyword)
|
||||
print(f"下标:{index},图片地址为:{item}")
|
||||
res = self.imageTextSearch(item)
|
||||
print(f"下标:{index},图片&文字是否相似:{res}")
|
||||
if res:
|
||||
self.count += 1
|
||||
self.haveIndex.append(index)
|
||||
|
||||
if self.count != 0 and len(self.haveIndex) != 0:
|
||||
return {'count': self.count, 'index': self.haveIndex}
|
||||
else:
|
||||
return {'count': 0, 'index': []}
|
||||
|
||||
def imageTextSearch(self,url):
|
||||
pipeline_caption = pipeline(
|
||||
task=Tasks.multi_modal_embedding,
|
||||
model=self.modelName,
|
||||
model_revision='v1.0.1',
|
||||
device=self.deviceType
|
||||
)
|
||||
# 支持网络地址 & 本地地址
|
||||
input_img = load_image(url)
|
||||
# input_texts = [self.noiseWord, self.keyword]
|
||||
input_texts = [self.keyword]+[item for item in self.noiseWord]
|
||||
|
||||
img_embedding = pipeline_caption.forward({'img': input_img})['img_embedding']
|
||||
text_embedding = pipeline_caption.forward({'text': input_texts})['text_embedding']
|
||||
|
||||
with torch.no_grad():
|
||||
logits_per_image = (img_embedding / pipeline_caption.model.temperature) @ text_embedding.t()
|
||||
probs = logits_per_image.softmax(dim=-1).cpu().numpy()
|
||||
|
||||
similarityNum = probs[0][0] * 100
|
||||
print(f"图片&文字相似度数值:{similarityNum}")
|
||||
result = True if similarityNum >= self.thresholdValue else False
|
||||
return result
|
||||
|
||||
22
src/ai/test.py
Normal file
@@ -0,0 +1,22 @@
|
||||
from multimModaImageTextSearch import multimModaImageTextSearch
|
||||
|
||||
imgList=[
|
||||
'https://p3.a.yximgs.com/upic/2024/02/17/17/BMjAyNDAyMTcxNzUwMjVfMTIxMjEyNTM0OF8xMjUyMDI5MzE4NjlfMV8z_B0817bda8480e6981ee36e3e03e430b0e.jpg?tag=1-1708294678-unknown-0-cixqi9cbxk-c689c31c32f2ffdc&clientCacheKey=3xq7x3n3qn37ebk.jpg&di=70208a94&bp=14764',
|
||||
'https://p3.a.yximgs.com/upic/2024/01/06/07/BMjAyNDAxMDYwNzMwNTFfMjc2MjcyOTk5N18xMjE0NDE2NDk4MDlfMl8z_B05a11d8cf566915c63399ed4e5344807.jpg?tag=1-1708294678-unknown-0-gwntq0bmmi-e2f16df83fcd9f1d&clientCacheKey=3xncgpqst6dcquk.jpg&di=70208a94&bp=14764',
|
||||
'https://p2.a.yximgs.com/upic/2022/09/07/10/BMjAyMjA5MDcxMDI3MzJfMTQzMjQ3NzA2XzgzNjQzMjMwNzA3XzJfMw==_B010362f63eebfbf8bf83c119e56adc32.jpg?tag=1-1708300460-unknown-0-eevoqueweh-a6cf9d3f78363d23&clientCacheKey=3x9455m5a6kijdu.jpg&di=70208a94&bp=14764',
|
||||
|
||||
'https://p1.a.yximgs.com/upic/2023/11/25/16/BMjAyMzExMjUxNjEyMjhfMTkwMDU1MjI2Ml8xMTgwMzUwNjE5OTdfMV8z_B3b6eaa07a54295924840b2676e4fcf5c.jpg?tag=1-1708300460-unknown-0-xmixtw3cxl-725ff520d02821b3&clientCacheKey=3xfave2rujw6mhk.jpg&di=70208a94&bp=14764',
|
||||
'https://p1.a.yximgs.com/upic/2023/10/23/19/BMjAyMzEwMjMxOTM0NDVfMzQ3NTg0NzAyMV8xMTU2MjM4NDU3MjFfMV8z_B6cda66f76f7c07bafcae503472960ecf.jpg?tag=1-1708300527-unknown-0-dnxzlo1pni-409704b08667fd22&clientCacheKey=3xwa8mv9hcr53xc.jpg&di=70208a94&bp=14764',
|
||||
]
|
||||
|
||||
image = multimModaImageTextSearch(
|
||||
key="牛仔裤",
|
||||
imgList=imgList
|
||||
)
|
||||
|
||||
res = image.start()
|
||||
|
||||
print("输入AI的总视频数据长度: ", len(imgList))
|
||||
print("AI匹配关键词后得到数据为: ", res)
|
||||
|
||||
|
||||
1
src/celery/.pydio
Normal file
@@ -0,0 +1 @@
|
||||
8743ab62-0f5d-4dea-8f07-40d2740a8f88
|
||||
0
src/celery/__init__.py
Normal file
1
src/celery/ai_image/.pydio
Normal file
@@ -0,0 +1 @@
|
||||
ef314510-8071-42fa-8309-fbfcc2040b93
|
||||
1
src/celery/ai_image/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .task import start_ai_img
|
||||
36
src/celery/ai_image/task.py
Normal file
@@ -0,0 +1,36 @@
|
||||
from src.celery.main import app
|
||||
from src.ai.multimModaImageTextSearch import multimModaImageTextSearch
|
||||
import celery
|
||||
from src.utils import *
|
||||
|
||||
class MyTask(celery.Task):
|
||||
# 任务失败时执行
|
||||
def on_failure(self, exc, task_id, args, kwargs, einfo):
|
||||
print('{0!r} failed: {1!r}'.format(task_id, exc))
|
||||
# 任务成功时执行
|
||||
def on_success(self, retval, task_id, args, kwargs):
|
||||
# print("--------------------------: ",socketio.app())
|
||||
print("task_id: ", task_id)
|
||||
print("retval: ", retval)
|
||||
print("args: ", args)
|
||||
print("kwargs: ", kwargs)
|
||||
print("任务成功时执行")
|
||||
pass
|
||||
# 任务重试时执行
|
||||
def on_retry(self, exc, task_id, args, kwargs, einfo):
|
||||
pass
|
||||
|
||||
@app.task(base=MyTask, bind=True)
|
||||
def start_ai_img(self, keyWord: str, imgList: list[str]):
|
||||
try:
|
||||
image = multimModaImageTextSearch(
|
||||
key=keyWord,
|
||||
imgList=imgList
|
||||
)
|
||||
res = image.start()
|
||||
return res
|
||||
|
||||
except Exception as exc:
|
||||
raise self.retry(exc=exc)
|
||||
|
||||
|
||||
10
src/celery/config.py
Normal file
@@ -0,0 +1,10 @@
|
||||
# config.py => 配置文件
|
||||
# 配置中的CELERY_为特定前缀
|
||||
# https://docs.celeryproject.org/en/latest/genindex.html
|
||||
BROKER_URL = 'redis://:lb714500@127.0.0.1:6379/0' # Broker配置
|
||||
CELERY_RESULT_BACKEND = 'redis://:lb714500@127.0.0.1:6379/0' # BACKEND配置
|
||||
CELERY_RESULT_SERIALIZER = 'json' # 结果序列化方案
|
||||
CELERY_TASK_RESULT_EXPIRES = 60 * 60 * 24 # 任务过期时间
|
||||
CELERY_TIMEZONE='Asia/Shanghai' # 时区配置
|
||||
|
||||
# CELERY_IMPORTS = ('src.celery.ai_image.task',) # 指定导入的任务模块
|
||||
10
src/celery/main.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from celery import Celery
|
||||
|
||||
# 创建Celery实例且名称为 ai_image
|
||||
app = Celery('ai_image')
|
||||
# 从配置文件config.py中加载配置参数
|
||||
app.config_from_object('src.celery.config')
|
||||
# 注册任务
|
||||
app.autodiscover_tasks([
|
||||
'src.celery.ai_image.task'
|
||||
])
|
||||
1
src/config/.pydio
Normal file
@@ -0,0 +1 @@
|
||||
13b4bc8f-2ce7-468e-afff-c5db791c1c28
|
||||
1
src/config/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .setting import *
|
||||
41
src/config/setting.py
Normal file
@@ -0,0 +1,41 @@
|
||||
from pydantic import AnyHttpUrl
|
||||
from fastapi import FastAPI
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
IS_DEV = True # 是否开发环境
|
||||
|
||||
class Settings(BaseSettings):
|
||||
|
||||
PROJECT_NAME: str = "AiReptile" # 项目名称 必填
|
||||
PROJECT_DESC: str = "🎉 接口汇总 🎉" # 描述
|
||||
PROJECT_VERSION: int | str = 1.0 # 版本
|
||||
API_PREFIX: str = "/api/v1" # 接口前缀
|
||||
|
||||
APP_NAME: str = "main:app"
|
||||
HOST: str = "0.0.0.0" # 允许访问程序的ip, 只允许本地访问使用 127.0.0.1, 只在直接允许程序时候生效
|
||||
PORT: int = 8000 # 程序端口,只在直接运行程序的时候生效
|
||||
RELOAD: bool = True # 是否自动重启,只在直接运行程序时候生效
|
||||
|
||||
CORS_ORIGINS: list[str] = ['*'] # 跨域请求(务必指定精确ip, 不要用localhost)
|
||||
MOUNT_LOCATION: str = '/ws'
|
||||
|
||||
STATIC_DIR: str = "static" # 静态文件目录
|
||||
BASE_URL: AnyHttpUrl = "http://127.0.0.1:8000" # 开发环境(为了存放图片全路径)
|
||||
|
||||
|
||||
class DevelopmentConfig(Settings):
|
||||
pass
|
||||
|
||||
class ProductionConfig(Settings):
|
||||
BASE_URL: AnyHttpUrl = "http://114.115.143.81:8000" # 生产环境(为了存放图片全路径)
|
||||
CORS_ORIGINS: list[AnyHttpUrl] = ["http://114.115.143.81"] # 跨域请求
|
||||
|
||||
# REDIS_URI: str = "redis://:123456@redis:6379/0" # Redis
|
||||
# DATABASE_URI: str = "mysql://root:123456@mysql:3306/demo?charset=utf8" # MySQL
|
||||
# DATABASE_ECHO: bool = True # 是否打印数据库日志 (可看到创建表、表数据增删改查的信息)
|
||||
|
||||
# LOGGER_LEVEL: str = 'INFO' # 日志等级: ['DEBUG' | 'INFO']
|
||||
|
||||
|
||||
appInatanc: FastAPI
|
||||
settings = DevelopmentConfig() if IS_DEV else ProductionConfig()
|
||||
1
src/controller/.pydio
Normal file
@@ -0,0 +1 @@
|
||||
92111a44-fa81-47ee-91d8-c1021373be45
|
||||
12
src/controller/__init__.py
Normal file
@@ -0,0 +1,12 @@
|
||||
from fastapi import APIRouter
|
||||
|
||||
from .kuaishou.kuaishou import router as kuaishou_api
|
||||
from .ai.ai import router as ai_api
|
||||
|
||||
|
||||
api_router = APIRouter()
|
||||
|
||||
api_router.include_router(kuaishou_api, prefix="/ks")
|
||||
api_router.include_router(ai_api, prefix="/ai")
|
||||
|
||||
__all__ = ['api_router']
|
||||
1
src/controller/ai/.pydio
Normal file
@@ -0,0 +1 @@
|
||||
da633ebd-90ac-4bea-80b9-b86147bdc5fd
|
||||
0
src/controller/ai/__init__.py
Normal file
46
src/controller/ai/ai.py
Normal file
@@ -0,0 +1,46 @@
|
||||
from fastapi import APIRouter
|
||||
from src.celery.ai_image import start_ai_img
|
||||
from src.controller.ai.schemas import textSearchVideoSchemas
|
||||
from src.utils import *
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
# http://127.0.0.1:8000/api/v1/ai/text_search_video
|
||||
@router.post("/text_search_video")
|
||||
async def textSearchVideo(params: textSearchVideoSchemas):
|
||||
task = start_ai_img.delay(params.keyWord, params.imgList)
|
||||
return {
|
||||
'task_id': task.id,
|
||||
'message': 'ai任务已添加后台'
|
||||
}
|
||||
|
||||
# @router.websocket("/ws")
|
||||
# async def task_status(websocket: WebSocket):
|
||||
# await websocket.accept()
|
||||
# while True:
|
||||
# data = await websocket.receive_json()
|
||||
# while True:
|
||||
# res = getTaskStaus(data['task_id'])
|
||||
# print("2 循环查找任务 res:", res)
|
||||
# if res['status']:
|
||||
# await websocket.send_json(res)
|
||||
# break;
|
||||
# time.sleep(3)
|
||||
|
||||
|
||||
# http://127.0.0.1:8000/api/v1/ai/task_status?task_id=c4b57ff1-0ca4-448e-ad95-e3d549841842
|
||||
@router.get("/task_status")
|
||||
def taskStatus(task_id: str):
|
||||
return getTaskStaus(task_id)
|
||||
|
||||
|
||||
def getTaskStaus(id: str):
|
||||
async_result = start_ai_img.AsyncResult(id)
|
||||
if async_result.successful():
|
||||
return async_result.get() | { 'status': True }
|
||||
else:
|
||||
return {
|
||||
'status': False,
|
||||
'message': tool.getState(async_result.status)
|
||||
}
|
||||
1
src/controller/ai/schemas/.pydio
Normal file
@@ -0,0 +1 @@
|
||||
f407fdd2-8a42-450d-9051-57c793797b75
|
||||
1
src/controller/ai/schemas/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from src.controller.ai.schemas.ai_schemas import *
|
||||
5
src/controller/ai/schemas/ai_schemas.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from pydantic import BaseModel, HttpUrl
|
||||
|
||||
class textSearchVideoSchemas(BaseModel):
|
||||
keyWord: str | None = None
|
||||
imgList: list[str]
|
||||
1
src/controller/kuaishou/.pydio
Normal file
@@ -0,0 +1 @@
|
||||
c9933e83-8290-4b3c-aaaf-da0611a06ac4
|
||||
0
src/controller/kuaishou/__init__.py
Normal file
37
src/controller/kuaishou/kuaishou.py
Normal file
@@ -0,0 +1,37 @@
|
||||
from fastapi import APIRouter
|
||||
from ...http import *
|
||||
from src.celery.ai_image import start_ai_img
|
||||
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
# http://127.0.0.1:8000/api/v1/ks/like
|
||||
@router.get("/like")
|
||||
async def LikeVideo(pcursor: str = ''):
|
||||
"""
|
||||
获取账号下点过赞的所有视频
|
||||
|
||||
- **pcursor**: 下一页的页标
|
||||
\f
|
||||
:param item: User input.
|
||||
"""
|
||||
res = await http.post(
|
||||
{ 'page': "profile", 'pcursor': pcursor },
|
||||
httpConfig.visionProfileLikePhotoList
|
||||
)
|
||||
|
||||
# feeds = res['feeds']
|
||||
|
||||
# 处理原始数据,构造视频 & 图片
|
||||
# imgSourceUrl = []
|
||||
# for index, item in enumerate(feeds):
|
||||
# imgSourceUrl.append(item['photo']['coverUrl'])
|
||||
# imgSourceUrl.append({
|
||||
# 'id': item['photo']['id'],
|
||||
# 'imgSrc': item['photo']['coverUrl'],
|
||||
# 'videoSrc': item['photo']['photoUrl']
|
||||
# })
|
||||
|
||||
# task = start_ai_img.delay(keyWord,imgSourceUrl)
|
||||
|
||||
return res
|
||||
1
src/controller/kuaishou/schemas/.pydio
Normal file
@@ -0,0 +1 @@
|
||||
63696c28-b9ab-493f-a1ef-f01fef0a0334
|
||||
0
src/controller/kuaishou/schemas/__init__.py
Normal file
1
src/http/.pydio
Normal file
@@ -0,0 +1 @@
|
||||
43a32678-2504-4c0b-9355-bccef42511ca
|
||||
2
src/http/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from ._config import httpConfig
|
||||
from .http import http
|
||||
54
src/http/_config.py
Normal file
@@ -0,0 +1,54 @@
|
||||
class HttpConfig:
|
||||
url: str = "https://www.kuaishou.com/graphql"
|
||||
header: list[str,str] = {
|
||||
'Content-Type': 'application/json',
|
||||
'Origin': 'https://www.kuaishou.com',
|
||||
'Host': 'www.kuaishou.com',
|
||||
'Referer': 'https://www.kuaishou.com/profile/3x2347wkfukzx4g',
|
||||
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36',
|
||||
'Cookie': 'kpf=PC_WEB; clientid=3; did=web_fca770657d0a24abb53ddc92bb42e612; didv=1700407978000; userId=58344290; kuaishou.server.web_st=ChZrdWFpc2hvdS5zZXJ2ZXIud2ViLnN0EqABFs7urbq3vvcrvB-IRBSKHXfdVRzblSFx7udepTzeAtgCompa6-aZiv1f_Bb66SubMkCbLODJQ3cn_GxlWWWbPgbQEyul9f7SDKsGphyvK1Pk3aj-yn899D9gR-xj1wiqT_0nA9x3XcPmvuJ8qQzIeLl3236npeMV0i9uYEAeIHDp9RtvPlQ4o0piktgGjMZH13Uhk7-ZfqDuUPQ-mEH7axoSS2UWrTnDahVDKzRYjzjLpJM-IiDXKnEX31rxM9oVovhBLMPSocxSvtN7wyo1oS1nNdvjSCgFMAE; kuaishou.server.web_ph=5b6a8c20ee92347d220cbc98f705f92042fb; kpn=KUAISHOU_VISION'
|
||||
}
|
||||
visionProfileLikePhotoList: str = ''' fragment photoContent on PhotoEntity {
|
||||
__typename
|
||||
id duration
|
||||
caption originCaption
|
||||
likeCount viewCount commentCount realLikeCount
|
||||
coverUrl photoUrl photoH265Url manifest
|
||||
manifestH265 videoResource coverUrls
|
||||
{
|
||||
url __typename
|
||||
}
|
||||
timestamp expTag animatedCoverUrl distance
|
||||
videoRatio liked stereoType profileUserTopPhoto
|
||||
musicBlocked riskTagContent riskTagUrl
|
||||
}
|
||||
fragment recoPhotoFragment on recoPhotoEntity {
|
||||
__typename id duration caption originCaption likeCount
|
||||
viewCount commentCount realLikeCount coverUrl photoUrl photoH265Url manifest
|
||||
manifestH265 videoResource coverUrls
|
||||
{ url __typename }
|
||||
timestamp expTag animatedCoverUrl distance videoRatio liked stereoType
|
||||
profileUserTopPhoto musicBlocked riskTagContent riskTagUrl
|
||||
}
|
||||
fragment feedContent on Feed {
|
||||
type author
|
||||
{
|
||||
id name headerUrl following headerUrls
|
||||
{
|
||||
url __typename
|
||||
} __typename
|
||||
} photo {
|
||||
...photoContent ...recoPhotoFragment __typename
|
||||
} canAddComment llsid status currentPcursor tags
|
||||
{ type name __typename }
|
||||
__typename
|
||||
}
|
||||
query visionProfileLikePhotoList($pcursor: String, $page: String, $webPageArea: String) {
|
||||
visionProfileLikePhotoList(pcursor: $pcursor, page: $page, webPageArea: $webPageArea) {
|
||||
result llsid webPageArea feeds {
|
||||
...feedContent __typename
|
||||
} hostName pcursor __typename
|
||||
}
|
||||
}'''
|
||||
|
||||
httpConfig = HttpConfig
|
||||
17
src/http/http.py
Normal file
@@ -0,0 +1,17 @@
|
||||
import requests
|
||||
from . import *
|
||||
|
||||
class Http:
|
||||
|
||||
async def post(variables, query):
|
||||
res = requests.post(
|
||||
httpConfig.url,
|
||||
json={
|
||||
'query': query,
|
||||
'variables': variables
|
||||
},
|
||||
headers=httpConfig.header,
|
||||
)
|
||||
return res.json()['data']['visionProfileLikePhotoList']
|
||||
|
||||
http = Http
|
||||
1
src/utils/.pydio
Normal file
@@ -0,0 +1 @@
|
||||
176e28e8-8250-4bc9-8589-5b9a605af8c6
|
||||
3
src/utils/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
# from .setInterval import *
|
||||
from .tool import *
|
||||
# from .socketio import *
|
||||
37
src/utils/screenshot.py
Normal file
@@ -0,0 +1,37 @@
|
||||
import os, shutil
|
||||
from docarray import Document, DocumentArray
|
||||
|
||||
# https://blog.csdn.net/Jina_AI/article/details/128475707
|
||||
|
||||
class Screenshot:
|
||||
# 视频名称
|
||||
videoName = ''
|
||||
# 视频所在的 父级路径
|
||||
videoPath = '../../source/'
|
||||
# 视频帧截图后输出的 父级路径
|
||||
outputKeyframes = '../../dist/'
|
||||
d = ''
|
||||
keyframes = DocumentArray()
|
||||
|
||||
def __init__(self, video_name):
|
||||
self.videoName = video_name
|
||||
self.d = Document(uri=self.videoPath + self.videoName).load_uri_to_video_tensor(only_keyframes=False)
|
||||
self.createdDir()
|
||||
self.start()
|
||||
|
||||
def start(self):
|
||||
for i in range(len(self.d.tensor)):
|
||||
if i in self.d.tags['keyframe_indices']:
|
||||
keyframe = Document(
|
||||
tensor=self.d.tensor[i], tags={'index': len(self.keyframes)}
|
||||
)
|
||||
keyframe.save_image_tensor_to_file(file=f'{self.outputKeyframes}/{self.videoName}/{len(self.keyframes)}.png')
|
||||
self.keyframes.append(keyframe)
|
||||
|
||||
|
||||
def createdDir(self):
|
||||
if not os.path.isdir(self.outputKeyframes + self.videoName):
|
||||
os.makedirs(self.outputKeyframes + self.videoName, exist_ok=True)
|
||||
|
||||
|
||||
Screenshot('yjkon.mp4')
|
||||
34
src/utils/socketio.py
Normal file
@@ -0,0 +1,34 @@
|
||||
from fastapi import FastAPI
|
||||
from fastapi_socketio import SocketManager
|
||||
|
||||
class Socketio:
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def init(self, app: FastAPI):
|
||||
print("++++++++++++++++++++++++++++++++++: ", app)
|
||||
self._app = app
|
||||
self._manager = SocketManager(app=app, mount_location="/")
|
||||
|
||||
self._manager._sio.on('task_status', self.task_status)
|
||||
|
||||
async def task_status(sid, args, msg):
|
||||
print("task_status sid ++++++++++++++ ", sid)
|
||||
print("task_status args ++++++++++++++ ", args)
|
||||
print("task_status kwargs ++++++++++++++ ", msg)
|
||||
|
||||
|
||||
|
||||
def app(self):
|
||||
return self._app
|
||||
|
||||
def manager(self):
|
||||
return self._manager
|
||||
|
||||
@property
|
||||
def emit(self):
|
||||
return self._app.emit
|
||||
|
||||
|
||||
socketio = Socketio()
|
||||
14
src/utils/tool.py
Normal file
@@ -0,0 +1,14 @@
|
||||
class Tool:
|
||||
def getState(state):
|
||||
if state == "PENDING":
|
||||
return "任务正在等待执行"
|
||||
elif state == "STARTED":
|
||||
return "任务已启动."
|
||||
elif state == "RETRY":
|
||||
return "将重试该任务,可能是因为失败"
|
||||
elif state == "FAILURE":
|
||||
return "该任务引发异常,或已超过重试限制"
|
||||
elif state == "SUCCESS":
|
||||
return "任务执行成功"
|
||||
|
||||
tool = Tool
|
||||
1
static/.pydio
Normal file
@@ -0,0 +1 @@
|
||||
9f9458e7-77f6-4706-bec7-91af6384b28b
|
||||
1
static/dist/.pydio
vendored
Normal file
@@ -0,0 +1 @@
|
||||
6f35b2a1-e5f9-483a-97b2-ddc726412f0f
|
||||
1
static/dist/jk.mp4/.pydio
vendored
Normal file
@@ -0,0 +1 @@
|
||||
b8b52532-fea6-4df8-a7bd-3c3bffd863d5
|
||||
BIN
static/dist/jk.mp4/0.png
vendored
Normal file
|
After Width: | Height: | Size: 511 KiB |
BIN
static/dist/jk.mp4/1.png
vendored
Normal file
|
After Width: | Height: | Size: 651 KiB |
BIN
static/dist/jk.mp4/2.png
vendored
Normal file
|
After Width: | Height: | Size: 455 KiB |
BIN
static/dist/jk.mp4/3.png
vendored
Normal file
|
After Width: | Height: | Size: 349 KiB |
1
static/dist/linenz.mp4/.pydio
vendored
Normal file
@@ -0,0 +1 @@
|
||||
8ccd5d21-5849-4cfd-85cc-99125a20ba3d
|
||||
BIN
static/dist/linenz.mp4/0.png
vendored
Normal file
|
After Width: | Height: | Size: 871 KiB |
1
static/dist/nz.mp4/.pydio
vendored
Normal file
@@ -0,0 +1 @@
|
||||
12c149cd-72c0-43fc-bda3-a192e85a5519
|
||||
BIN
static/dist/nz.mp4/0.png
vendored
Normal file
|
After Width: | Height: | Size: 431 KiB |
BIN
static/dist/nz.mp4/1.png
vendored
Normal file
|
After Width: | Height: | Size: 407 KiB |
BIN
static/dist/nz.mp4/2.png
vendored
Normal file
|
After Width: | Height: | Size: 415 KiB |
BIN
static/dist/nz.mp4/3.png
vendored
Normal file
|
After Width: | Height: | Size: 427 KiB |
BIN
static/dist/nz.mp4/4.png
vendored
Normal file
|
After Width: | Height: | Size: 409 KiB |
1
static/dist/rf.mp4/.pydio
vendored
Normal file
@@ -0,0 +1 @@
|
||||
d59c3e02-4216-44e8-a5ae-411d771ad1bb
|
||||
BIN
static/dist/rf.mp4/1.png
vendored
Normal file
|
After Width: | Height: | Size: 343 KiB |
BIN
static/dist/rf.mp4/2.png
vendored
Normal file
|
After Width: | Height: | Size: 284 KiB |
BIN
static/dist/rf.mp4/3.png
vendored
Normal file
|
After Width: | Height: | Size: 463 KiB |
1
static/dist/yj.mp4/.pydio
vendored
Normal file
@@ -0,0 +1 @@
|
||||
95c87bb7-9825-45a5-bc6f-2031d2d96ec3
|
||||
BIN
static/dist/yj.mp4/0.png
vendored
Normal file
|
After Width: | Height: | Size: 871 KiB |
BIN
static/dist/yj.mp4/1.png
vendored
Normal file
|
After Width: | Height: | Size: 733 KiB |
BIN
static/dist/yj.mp4/2.png
vendored
Normal file
|
After Width: | Height: | Size: 705 KiB |
1
static/dist/yjkon.mp4/.pydio
vendored
Normal file
@@ -0,0 +1 @@
|
||||
5f27e668-eea2-4a2d-9731-cd35a7233a85
|
||||
BIN
static/dist/yjkon.mp4/0.png
vendored
Normal file
|
After Width: | Height: | Size: 632 KiB |
1
static/source/.pydio
Normal file
@@ -0,0 +1 @@
|
||||
6723c002-05dc-4ca3-9174-0a0e7fda82e8
|
||||
BIN
static/source/dn.mp4
Normal file
BIN
static/source/jk.mp4
Normal file
BIN
static/source/nz.mp4
Normal file
BIN
static/source/rf.mp4
Normal file
BIN
static/source/yj.mp4
Normal file
1
test/.pydio
Normal file
@@ -0,0 +1 @@
|
||||
9f11425b-4d81-41f5-93d7-2609b17d0a1b
|
||||
99
test/index.html
Normal file
@@ -0,0 +1,99 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Document</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.list {
|
||||
column-count: 5;
|
||||
column-gap: 10px;
|
||||
}
|
||||
|
||||
.list li {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.list li img {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<input class="search" type="text" value="牛仔裤" placeholder="输入关键词" />
|
||||
<button class="submit">搜索</button>
|
||||
|
||||
<ul class="list">
|
||||
|
||||
</ul>
|
||||
|
||||
</body>
|
||||
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
|
||||
<script src="https://cdn.bootcdn.net/ajax/libs/axios/1.5.0/axios.js"></script>
|
||||
<script>
|
||||
let pcursorNum = '1703970033000', feedsList = [], filterList = [], intervalId = 0;
|
||||
|
||||
axios.defaults.baseURL = 'http://127.0.0.1:8000/api/v1';
|
||||
axios.interceptors.response.use(response => response.data, error => Promise.reject(error));
|
||||
|
||||
// 点击 搜索按钮 使用【视频预览图地址】进行 视频&文字的多模态搜索,
|
||||
// 会在后端进行 ai批量任务计算,接口返回 task_id
|
||||
$(".submit").click(async () => {
|
||||
let { task_id } = await axios.post('/ai/text_search_video', {
|
||||
keyWord: $(".search").val(),
|
||||
imgList: feedsList.map(v => v.photo.coverUrl)
|
||||
})
|
||||
checkTaskStatus(task_id)
|
||||
})
|
||||
|
||||
// 使用 task_id 轮询,查询 任务计算 结果,如果为 true
|
||||
// 结束 轮询,根据后端返回的 搜索结果 index 下标集合从原始数据中,
|
||||
// 取出数据,存储 filterList 并渲染页面
|
||||
async function checkTaskStatus(task_id) {
|
||||
intervalId = setInterval(async () => {
|
||||
let { status, index } = await axios.get('/ai/task_status', {
|
||||
params: { task_id }
|
||||
});
|
||||
if (status) {
|
||||
clearInterval(intervalId)
|
||||
renderItem(index.map(v => feedsList[v]))
|
||||
}
|
||||
}, 3000)
|
||||
}
|
||||
|
||||
async function getIndex() {
|
||||
let { feeds, pcursor } = await axios.get('/ks/like', {
|
||||
params: { pcursor: pcursorNum }
|
||||
})
|
||||
|
||||
pcursorNum = pcursor
|
||||
feedsList = feeds
|
||||
renderItem(feedsList)
|
||||
}
|
||||
|
||||
function renderItem(data) {
|
||||
$(".list").empty()
|
||||
data.forEach((v, i) => {
|
||||
$(".list").append(`
|
||||
<li>
|
||||
<img src="${v.photo.coverUrl}" alt="">
|
||||
</li>
|
||||
`)
|
||||
});
|
||||
}
|
||||
|
||||
getIndex()
|
||||
|
||||
</script>
|
||||
|
||||
</html>
|
||||
24
test/wsc.py
Normal file
@@ -0,0 +1,24 @@
|
||||
import asyncio
|
||||
import socketio
|
||||
|
||||
sio = socketio.AsyncClient()
|
||||
|
||||
@sio.event
|
||||
async def connect():
|
||||
print('connection established')
|
||||
|
||||
@sio.event
|
||||
async def my_message(data):
|
||||
print('message received with ', data)
|
||||
await sio.emit('my response', {'response': 'my response'})
|
||||
|
||||
@sio.event
|
||||
async def disconnect():
|
||||
print('disconnected from server')
|
||||
|
||||
async def main():
|
||||
await sio.connect('http://localhost:8000')
|
||||
await sio.wait()
|
||||
|
||||
if __name__ == '__main__':
|
||||
asyncio.run(main())
|
||||
BIN
vs_BuildTools.exe
Normal file
BIN
vs_BuildTools.png
Normal file
|
After Width: | Height: | Size: 283 KiB |