first commit

This commit is contained in:
编码猿
2024-09-27 01:10:21 +08:00
commit 4bb91f7aa9
83 changed files with 952 additions and 0 deletions

8
.gitignore vendored Normal file
View File

@@ -0,0 +1,8 @@
.DS_Store
.idea
*.log*
logs
__pycache__
问题.txt
helper.py
app.py

1
.pydio Normal file
View File

@@ -0,0 +1 @@
8ff2f803-d830-4ae6-af02-b18c8d3942a2

35
README.md Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View File

@@ -0,0 +1 @@
3ac36a1c-7d73-47bf-af4e-1e05933b65bd

1
src/ai/.pydio Normal file
View File

@@ -0,0 +1 @@
0e6c5784-911f-49db-b759-70844e9d3d6e

0
src/ai/__init__.py Normal file
View File

26
src/ai/ai_test.py Normal file
View 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
View 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 预览图,或者使用 ffmpegdocarray这里推荐 使用 多种模态数据结构工具包 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("", "")

View 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 预览图,或者使用 ffmpegdocarray这里推荐
# 使用 多种模态数据结构工具包 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
View 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
View File

@@ -0,0 +1 @@
8743ab62-0f5d-4dea-8f07-40d2740a8f88

0
src/celery/__init__.py Normal file
View File

View File

@@ -0,0 +1 @@
ef314510-8071-42fa-8309-fbfcc2040b93

View File

@@ -0,0 +1 @@
from .task import start_ai_img

View 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
View 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
View 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
View File

@@ -0,0 +1 @@
13b4bc8f-2ce7-468e-afff-c5db791c1c28

1
src/config/__init__.py Normal file
View File

@@ -0,0 +1 @@
from .setting import *

41
src/config/setting.py Normal file
View 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
View File

@@ -0,0 +1 @@
92111a44-fa81-47ee-91d8-c1021373be45

View 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
View File

@@ -0,0 +1 @@
da633ebd-90ac-4bea-80b9-b86147bdc5fd

View File

46
src/controller/ai/ai.py Normal file
View 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)
}

View File

@@ -0,0 +1 @@
f407fdd2-8a42-450d-9051-57c793797b75

View File

@@ -0,0 +1 @@
from src.controller.ai.schemas.ai_schemas import *

View File

@@ -0,0 +1,5 @@
from pydantic import BaseModel, HttpUrl
class textSearchVideoSchemas(BaseModel):
keyWord: str | None = None
imgList: list[str]

View File

@@ -0,0 +1 @@
c9933e83-8290-4b3c-aaaf-da0611a06ac4

View File

View 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

View File

@@ -0,0 +1 @@
63696c28-b9ab-493f-a1ef-f01fef0a0334

1
src/http/.pydio Normal file
View File

@@ -0,0 +1 @@
43a32678-2504-4c0b-9355-bccef42511ca

2
src/http/__init__.py Normal file
View File

@@ -0,0 +1,2 @@
from ._config import httpConfig
from .http import http

54
src/http/_config.py Normal file
View 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
View 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
View File

@@ -0,0 +1 @@
176e28e8-8250-4bc9-8589-5b9a605af8c6

3
src/utils/__init__.py Normal file
View File

@@ -0,0 +1,3 @@
# from .setInterval import *
from .tool import *
# from .socketio import *

37
src/utils/screenshot.py Normal file
View 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
View 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
View 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
View File

@@ -0,0 +1 @@
9f9458e7-77f6-4706-bec7-91af6384b28b

1
static/dist/.pydio vendored Normal file
View File

@@ -0,0 +1 @@
6f35b2a1-e5f9-483a-97b2-ddc726412f0f

1
static/dist/jk.mp4/.pydio vendored Normal file
View File

@@ -0,0 +1 @@
b8b52532-fea6-4df8-a7bd-3c3bffd863d5

BIN
static/dist/jk.mp4/0.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 511 KiB

BIN
static/dist/jk.mp4/1.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 651 KiB

BIN
static/dist/jk.mp4/2.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 455 KiB

BIN
static/dist/jk.mp4/3.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 349 KiB

1
static/dist/linenz.mp4/.pydio vendored Normal file
View File

@@ -0,0 +1 @@
8ccd5d21-5849-4cfd-85cc-99125a20ba3d

BIN
static/dist/linenz.mp4/0.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 871 KiB

1
static/dist/nz.mp4/.pydio vendored Normal file
View File

@@ -0,0 +1 @@
12c149cd-72c0-43fc-bda3-a192e85a5519

BIN
static/dist/nz.mp4/0.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 431 KiB

BIN
static/dist/nz.mp4/1.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 407 KiB

BIN
static/dist/nz.mp4/2.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 415 KiB

BIN
static/dist/nz.mp4/3.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 427 KiB

BIN
static/dist/nz.mp4/4.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 409 KiB

1
static/dist/rf.mp4/.pydio vendored Normal file
View File

@@ -0,0 +1 @@
d59c3e02-4216-44e8-a5ae-411d771ad1bb

BIN
static/dist/rf.mp4/1.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 343 KiB

BIN
static/dist/rf.mp4/2.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 284 KiB

BIN
static/dist/rf.mp4/3.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 463 KiB

1
static/dist/yj.mp4/.pydio vendored Normal file
View File

@@ -0,0 +1 @@
95c87bb7-9825-45a5-bc6f-2031d2d96ec3

BIN
static/dist/yj.mp4/0.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 871 KiB

BIN
static/dist/yj.mp4/1.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 733 KiB

BIN
static/dist/yj.mp4/2.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 705 KiB

1
static/dist/yjkon.mp4/.pydio vendored Normal file
View File

@@ -0,0 +1 @@
5f27e668-eea2-4a2d-9731-cd35a7233a85

BIN
static/dist/yjkon.mp4/0.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 632 KiB

1
static/source/.pydio Normal file
View File

@@ -0,0 +1 @@
6723c002-05dc-4ca3-9174-0a0e7fda82e8

BIN
static/source/dn.mp4 Normal file

Binary file not shown.

BIN
static/source/jk.mp4 Normal file

Binary file not shown.

BIN
static/source/nz.mp4 Normal file

Binary file not shown.

BIN
static/source/rf.mp4 Normal file

Binary file not shown.

BIN
static/source/yj.mp4 Normal file

Binary file not shown.

1
test/.pydio Normal file
View File

@@ -0,0 +1 @@
9f11425b-4d81-41f5-93d7-2609b17d0a1b

99
test/index.html Normal file
View 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
View 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

Binary file not shown.

BIN
vs_BuildTools.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 283 KiB