commit 4bb91f7aa92887ad1f2e0523a7d0cf98317e6338 Author: 编码猿 Date: Fri Sep 27 01:10:21 2024 +0800 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c8b6b09 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +.idea +*.log* +logs +__pycache__ +问题.txt +helper.py +app.py \ No newline at end of file diff --git a/.pydio b/.pydio new file mode 100644 index 0000000..8fb5aaa --- /dev/null +++ b/.pydio @@ -0,0 +1 @@ +8ff2f803-d830-4ae6-af02-b18c8d3942a2 \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..0c1587d --- /dev/null +++ b/README.md @@ -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 \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..2d91a46 --- /dev/null +++ b/main.py @@ -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 + ) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..7100cf0 --- /dev/null +++ b/requirements.txt @@ -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 \ No newline at end of file diff --git a/requirements_back.txt b/requirements_back.txt new file mode 100644 index 0000000..5e9f7ea --- /dev/null +++ b/requirements_back.txt @@ -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 \ No newline at end of file diff --git a/run.bat b/run.bat new file mode 100644 index 0000000..4538de5 --- /dev/null +++ b/run.bat @@ -0,0 +1,59 @@ +@echo off + +:menu +cls +echo. +echo. AI ˵ +echo. +echo. 1: FastApi +echo. 2: Celery +echo. 3: Flower WebUi +echo. 4: Redis Server +echo. 5: ȫ +ECHO. + +set/p option="ѡ:" +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 FastApiԵ... && python -B main.py" +goto:eof + +:celery +start cmd /k "echo CeleryԵ... && celery -A src.celery.main.app worker -l info -P eventlet" +goto:eof + +:flower +start cmd /k "echo FlowerԵ... && celery --broker=redis://:lb714500@127.0.0.1:6379/0 flower --port=5551" +goto:eof + +:redis +start cmd /k "echo Redis ServerԵ... && 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 \ No newline at end of file diff --git a/run.sh b/run.sh new file mode 100755 index 0000000..a8610e1 --- /dev/null +++ b/run.sh @@ -0,0 +1,74 @@ +#!/bin/bash + + + +newTerminal() { + osascript 2>/dev/null <= 85 else False +# print("图文匹配概率:", result) \ No newline at end of file diff --git a/src/ai/imageCaptioning.py b/src/ai/imageCaptioning.py new file mode 100644 index 0000000..e2f631f --- /dev/null +++ b/src/ai/imageCaptioning.py @@ -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("。", "") diff --git a/src/ai/multimModaImageTextSearch.py b/src/ai/multimModaImageTextSearch.py new file mode 100644 index 0000000..8a7ede6 --- /dev/null +++ b/src/ai/multimModaImageTextSearch.py @@ -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 + \ No newline at end of file diff --git a/src/ai/test.py b/src/ai/test.py new file mode 100644 index 0000000..ac5098d --- /dev/null +++ b/src/ai/test.py @@ -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) + + diff --git a/src/celery/.pydio b/src/celery/.pydio new file mode 100644 index 0000000..f69b3a3 --- /dev/null +++ b/src/celery/.pydio @@ -0,0 +1 @@ +8743ab62-0f5d-4dea-8f07-40d2740a8f88 \ No newline at end of file diff --git a/src/celery/__init__.py b/src/celery/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/celery/ai_image/.pydio b/src/celery/ai_image/.pydio new file mode 100644 index 0000000..c0d1f3d --- /dev/null +++ b/src/celery/ai_image/.pydio @@ -0,0 +1 @@ +ef314510-8071-42fa-8309-fbfcc2040b93 \ No newline at end of file diff --git a/src/celery/ai_image/__init__.py b/src/celery/ai_image/__init__.py new file mode 100644 index 0000000..8bb9bb0 --- /dev/null +++ b/src/celery/ai_image/__init__.py @@ -0,0 +1 @@ +from .task import start_ai_img \ No newline at end of file diff --git a/src/celery/ai_image/task.py b/src/celery/ai_image/task.py new file mode 100644 index 0000000..014afaf --- /dev/null +++ b/src/celery/ai_image/task.py @@ -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) + + \ No newline at end of file diff --git a/src/celery/config.py b/src/celery/config.py new file mode 100644 index 0000000..95f57e4 --- /dev/null +++ b/src/celery/config.py @@ -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',) # 指定导入的任务模块 \ No newline at end of file diff --git a/src/celery/main.py b/src/celery/main.py new file mode 100644 index 0000000..a15e9ea --- /dev/null +++ b/src/celery/main.py @@ -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' +]) \ No newline at end of file diff --git a/src/config/.pydio b/src/config/.pydio new file mode 100644 index 0000000..8cb11bc --- /dev/null +++ b/src/config/.pydio @@ -0,0 +1 @@ +13b4bc8f-2ce7-468e-afff-c5db791c1c28 \ No newline at end of file diff --git a/src/config/__init__.py b/src/config/__init__.py new file mode 100644 index 0000000..5391ff7 --- /dev/null +++ b/src/config/__init__.py @@ -0,0 +1 @@ +from .setting import * diff --git a/src/config/setting.py b/src/config/setting.py new file mode 100644 index 0000000..a61b340 --- /dev/null +++ b/src/config/setting.py @@ -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() \ No newline at end of file diff --git a/src/controller/.pydio b/src/controller/.pydio new file mode 100644 index 0000000..bf528f5 --- /dev/null +++ b/src/controller/.pydio @@ -0,0 +1 @@ +92111a44-fa81-47ee-91d8-c1021373be45 \ No newline at end of file diff --git a/src/controller/__init__.py b/src/controller/__init__.py new file mode 100644 index 0000000..b55a900 --- /dev/null +++ b/src/controller/__init__.py @@ -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'] \ No newline at end of file diff --git a/src/controller/ai/.pydio b/src/controller/ai/.pydio new file mode 100644 index 0000000..732ea8d --- /dev/null +++ b/src/controller/ai/.pydio @@ -0,0 +1 @@ +da633ebd-90ac-4bea-80b9-b86147bdc5fd \ No newline at end of file diff --git a/src/controller/ai/__init__.py b/src/controller/ai/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/controller/ai/ai.py b/src/controller/ai/ai.py new file mode 100644 index 0000000..0b940b0 --- /dev/null +++ b/src/controller/ai/ai.py @@ -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) + } \ No newline at end of file diff --git a/src/controller/ai/schemas/.pydio b/src/controller/ai/schemas/.pydio new file mode 100644 index 0000000..233354d --- /dev/null +++ b/src/controller/ai/schemas/.pydio @@ -0,0 +1 @@ +f407fdd2-8a42-450d-9051-57c793797b75 \ No newline at end of file diff --git a/src/controller/ai/schemas/__init__.py b/src/controller/ai/schemas/__init__.py new file mode 100644 index 0000000..227bffa --- /dev/null +++ b/src/controller/ai/schemas/__init__.py @@ -0,0 +1 @@ +from src.controller.ai.schemas.ai_schemas import * \ No newline at end of file diff --git a/src/controller/ai/schemas/ai_schemas.py b/src/controller/ai/schemas/ai_schemas.py new file mode 100644 index 0000000..3cee786 --- /dev/null +++ b/src/controller/ai/schemas/ai_schemas.py @@ -0,0 +1,5 @@ +from pydantic import BaseModel, HttpUrl + +class textSearchVideoSchemas(BaseModel): + keyWord: str | None = None + imgList: list[str] \ No newline at end of file diff --git a/src/controller/kuaishou/.pydio b/src/controller/kuaishou/.pydio new file mode 100644 index 0000000..585d757 --- /dev/null +++ b/src/controller/kuaishou/.pydio @@ -0,0 +1 @@ +c9933e83-8290-4b3c-aaaf-da0611a06ac4 \ No newline at end of file diff --git a/src/controller/kuaishou/__init__.py b/src/controller/kuaishou/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/controller/kuaishou/kuaishou.py b/src/controller/kuaishou/kuaishou.py new file mode 100644 index 0000000..4fe7a4f --- /dev/null +++ b/src/controller/kuaishou/kuaishou.py @@ -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 \ No newline at end of file diff --git a/src/controller/kuaishou/schemas/.pydio b/src/controller/kuaishou/schemas/.pydio new file mode 100644 index 0000000..bcbbb17 --- /dev/null +++ b/src/controller/kuaishou/schemas/.pydio @@ -0,0 +1 @@ +63696c28-b9ab-493f-a1ef-f01fef0a0334 \ No newline at end of file diff --git a/src/controller/kuaishou/schemas/__init__.py b/src/controller/kuaishou/schemas/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/http/.pydio b/src/http/.pydio new file mode 100644 index 0000000..d03176d --- /dev/null +++ b/src/http/.pydio @@ -0,0 +1 @@ +43a32678-2504-4c0b-9355-bccef42511ca \ No newline at end of file diff --git a/src/http/__init__.py b/src/http/__init__.py new file mode 100644 index 0000000..1d94fa8 --- /dev/null +++ b/src/http/__init__.py @@ -0,0 +1,2 @@ +from ._config import httpConfig +from .http import http diff --git a/src/http/_config.py b/src/http/_config.py new file mode 100644 index 0000000..e897b59 --- /dev/null +++ b/src/http/_config.py @@ -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 \ No newline at end of file diff --git a/src/http/http.py b/src/http/http.py new file mode 100644 index 0000000..cb9405f --- /dev/null +++ b/src/http/http.py @@ -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 \ No newline at end of file diff --git a/src/utils/.pydio b/src/utils/.pydio new file mode 100644 index 0000000..7d8ec2f --- /dev/null +++ b/src/utils/.pydio @@ -0,0 +1 @@ +176e28e8-8250-4bc9-8589-5b9a605af8c6 \ No newline at end of file diff --git a/src/utils/__init__.py b/src/utils/__init__.py new file mode 100644 index 0000000..ec459e5 --- /dev/null +++ b/src/utils/__init__.py @@ -0,0 +1,3 @@ +# from .setInterval import * +from .tool import * +# from .socketio import * diff --git a/src/utils/screenshot.py b/src/utils/screenshot.py new file mode 100644 index 0000000..69e0e1f --- /dev/null +++ b/src/utils/screenshot.py @@ -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') \ No newline at end of file diff --git a/src/utils/socketio.py b/src/utils/socketio.py new file mode 100644 index 0000000..4b8e0a3 --- /dev/null +++ b/src/utils/socketio.py @@ -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() diff --git a/src/utils/tool.py b/src/utils/tool.py new file mode 100644 index 0000000..6343066 --- /dev/null +++ b/src/utils/tool.py @@ -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 diff --git a/static/.pydio b/static/.pydio new file mode 100644 index 0000000..5ada1f2 --- /dev/null +++ b/static/.pydio @@ -0,0 +1 @@ +9f9458e7-77f6-4706-bec7-91af6384b28b \ No newline at end of file diff --git a/static/dist/.pydio b/static/dist/.pydio new file mode 100644 index 0000000..93d76aa --- /dev/null +++ b/static/dist/.pydio @@ -0,0 +1 @@ +6f35b2a1-e5f9-483a-97b2-ddc726412f0f \ No newline at end of file diff --git a/static/dist/jk.mp4/.pydio b/static/dist/jk.mp4/.pydio new file mode 100644 index 0000000..847c3c6 --- /dev/null +++ b/static/dist/jk.mp4/.pydio @@ -0,0 +1 @@ +b8b52532-fea6-4df8-a7bd-3c3bffd863d5 \ No newline at end of file diff --git a/static/dist/jk.mp4/0.png b/static/dist/jk.mp4/0.png new file mode 100644 index 0000000..0ebdc01 Binary files /dev/null and b/static/dist/jk.mp4/0.png differ diff --git a/static/dist/jk.mp4/1.png b/static/dist/jk.mp4/1.png new file mode 100644 index 0000000..8057130 Binary files /dev/null and b/static/dist/jk.mp4/1.png differ diff --git a/static/dist/jk.mp4/2.png b/static/dist/jk.mp4/2.png new file mode 100644 index 0000000..e2a97ab Binary files /dev/null and b/static/dist/jk.mp4/2.png differ diff --git a/static/dist/jk.mp4/3.png b/static/dist/jk.mp4/3.png new file mode 100644 index 0000000..9d72829 Binary files /dev/null and b/static/dist/jk.mp4/3.png differ diff --git a/static/dist/linenz.mp4/.pydio b/static/dist/linenz.mp4/.pydio new file mode 100644 index 0000000..37adafd --- /dev/null +++ b/static/dist/linenz.mp4/.pydio @@ -0,0 +1 @@ +8ccd5d21-5849-4cfd-85cc-99125a20ba3d \ No newline at end of file diff --git a/static/dist/linenz.mp4/0.png b/static/dist/linenz.mp4/0.png new file mode 100644 index 0000000..b0c7750 Binary files /dev/null and b/static/dist/linenz.mp4/0.png differ diff --git a/static/dist/nz.mp4/.pydio b/static/dist/nz.mp4/.pydio new file mode 100644 index 0000000..bc023aa --- /dev/null +++ b/static/dist/nz.mp4/.pydio @@ -0,0 +1 @@ +12c149cd-72c0-43fc-bda3-a192e85a5519 \ No newline at end of file diff --git a/static/dist/nz.mp4/0.png b/static/dist/nz.mp4/0.png new file mode 100644 index 0000000..6514a26 Binary files /dev/null and b/static/dist/nz.mp4/0.png differ diff --git a/static/dist/nz.mp4/1.png b/static/dist/nz.mp4/1.png new file mode 100644 index 0000000..690b505 Binary files /dev/null and b/static/dist/nz.mp4/1.png differ diff --git a/static/dist/nz.mp4/2.png b/static/dist/nz.mp4/2.png new file mode 100644 index 0000000..0f05fd9 Binary files /dev/null and b/static/dist/nz.mp4/2.png differ diff --git a/static/dist/nz.mp4/3.png b/static/dist/nz.mp4/3.png new file mode 100644 index 0000000..252e973 Binary files /dev/null and b/static/dist/nz.mp4/3.png differ diff --git a/static/dist/nz.mp4/4.png b/static/dist/nz.mp4/4.png new file mode 100644 index 0000000..1ebc02e Binary files /dev/null and b/static/dist/nz.mp4/4.png differ diff --git a/static/dist/rf.mp4/.pydio b/static/dist/rf.mp4/.pydio new file mode 100644 index 0000000..f49b4d1 --- /dev/null +++ b/static/dist/rf.mp4/.pydio @@ -0,0 +1 @@ +d59c3e02-4216-44e8-a5ae-411d771ad1bb \ No newline at end of file diff --git a/static/dist/rf.mp4/1.png b/static/dist/rf.mp4/1.png new file mode 100644 index 0000000..8ba2909 Binary files /dev/null and b/static/dist/rf.mp4/1.png differ diff --git a/static/dist/rf.mp4/2.png b/static/dist/rf.mp4/2.png new file mode 100644 index 0000000..aa25709 Binary files /dev/null and b/static/dist/rf.mp4/2.png differ diff --git a/static/dist/rf.mp4/3.png b/static/dist/rf.mp4/3.png new file mode 100644 index 0000000..66795ad Binary files /dev/null and b/static/dist/rf.mp4/3.png differ diff --git a/static/dist/yj.mp4/.pydio b/static/dist/yj.mp4/.pydio new file mode 100644 index 0000000..8f97ec1 --- /dev/null +++ b/static/dist/yj.mp4/.pydio @@ -0,0 +1 @@ +95c87bb7-9825-45a5-bc6f-2031d2d96ec3 \ No newline at end of file diff --git a/static/dist/yj.mp4/0.png b/static/dist/yj.mp4/0.png new file mode 100644 index 0000000..b0c7750 Binary files /dev/null and b/static/dist/yj.mp4/0.png differ diff --git a/static/dist/yj.mp4/1.png b/static/dist/yj.mp4/1.png new file mode 100644 index 0000000..baa788e Binary files /dev/null and b/static/dist/yj.mp4/1.png differ diff --git a/static/dist/yj.mp4/2.png b/static/dist/yj.mp4/2.png new file mode 100644 index 0000000..282af94 Binary files /dev/null and b/static/dist/yj.mp4/2.png differ diff --git a/static/dist/yjkon.mp4/.pydio b/static/dist/yjkon.mp4/.pydio new file mode 100644 index 0000000..42cf77b --- /dev/null +++ b/static/dist/yjkon.mp4/.pydio @@ -0,0 +1 @@ +5f27e668-eea2-4a2d-9731-cd35a7233a85 \ No newline at end of file diff --git a/static/dist/yjkon.mp4/0.png b/static/dist/yjkon.mp4/0.png new file mode 100644 index 0000000..f8de606 Binary files /dev/null and b/static/dist/yjkon.mp4/0.png differ diff --git a/static/source/.pydio b/static/source/.pydio new file mode 100644 index 0000000..6417278 --- /dev/null +++ b/static/source/.pydio @@ -0,0 +1 @@ +6723c002-05dc-4ca3-9174-0a0e7fda82e8 \ No newline at end of file diff --git a/static/source/dn.mp4 b/static/source/dn.mp4 new file mode 100644 index 0000000..f66a69a Binary files /dev/null and b/static/source/dn.mp4 differ diff --git a/static/source/jk.mp4 b/static/source/jk.mp4 new file mode 100644 index 0000000..7d51921 Binary files /dev/null and b/static/source/jk.mp4 differ diff --git a/static/source/nz.mp4 b/static/source/nz.mp4 new file mode 100644 index 0000000..47db602 Binary files /dev/null and b/static/source/nz.mp4 differ diff --git a/static/source/rf.mp4 b/static/source/rf.mp4 new file mode 100644 index 0000000..c85561f Binary files /dev/null and b/static/source/rf.mp4 differ diff --git a/static/source/yj.mp4 b/static/source/yj.mp4 new file mode 100644 index 0000000..e9990b7 Binary files /dev/null and b/static/source/yj.mp4 differ diff --git a/test/.pydio b/test/.pydio new file mode 100644 index 0000000..7321101 --- /dev/null +++ b/test/.pydio @@ -0,0 +1 @@ +9f11425b-4d81-41f5-93d7-2609b17d0a1b \ No newline at end of file diff --git a/test/index.html b/test/index.html new file mode 100644 index 0000000..5a8d985 --- /dev/null +++ b/test/index.html @@ -0,0 +1,99 @@ + + + + + + + Document + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/wsc.py b/test/wsc.py new file mode 100644 index 0000000..ee0d13b --- /dev/null +++ b/test/wsc.py @@ -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()) \ No newline at end of file diff --git a/vs_BuildTools.exe b/vs_BuildTools.exe new file mode 100644 index 0000000..314c1e5 Binary files /dev/null and b/vs_BuildTools.exe differ diff --git a/vs_BuildTools.png b/vs_BuildTools.png new file mode 100644 index 0000000..b34ae07 Binary files /dev/null and b/vs_BuildTools.png differ