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 |