zhangyong преди 1 година
ревизия
390669bdea

+ 3 - 0
README.md

@@ -0,0 +1,3 @@
+# video_editor_agc
+
+AGC-视频拼接

+ 48 - 0
agc_main.py

@@ -0,0 +1,48 @@
+from common import Feishu, Material
+from common.sql_help import sqlHelp
+from video_agc.agc_video_method import AgcVidoe
+
+import schedule
+import threading
+import time
+
+def video_start(user_data):
+    print(f"视频脚本参数{user_data}")
+    # 开始准备执行生成视频脚本
+    AgcVidoe.video_stitching(user_data)
+
+
+# 定义定时任务
+def video_task():
+    print("开始执行生成视频脚.")
+    data = Material.feishu_list()
+    threads = []
+    for _, user_data in data.iterrows():
+        thread = threading.Thread(target=video_start, args=(user_data,))
+        threads.append(thread)
+        thread.start()
+    for thread in threads:
+        thread.join()
+    print("执行生成视频脚结束")
+
+schedule.every(10).minutes.do(video_task)
+
+
+def job_feishu_bot():
+    name_list = Material.feishu_name()
+    count_list = sqlHelp.get_count_list(name_list)
+    Feishu.bot('recommend', 'AGC视频', f'{"".join(count_list)}', ' ')
+    print("机器人通知完成")
+
+
+# 每天下午1:30执行任务
+schedule.every().day.at("15:00").do(job_feishu_bot)
+
+while True:
+    schedule.run_pending()
+    time.sleep(1)
+
+# list = Material.feishu_list()
+# AgcVidoe.video_stitching(list)
+# print(list)
+

+ 5 - 0
common/__init__.py

@@ -0,0 +1,5 @@
+from .common import Common
+from .aliyun_oss_uploading import Oss
+from .material import Material
+from .feishu import Feishu
+from .db import MysqlHelper

+ 81 - 0
common/aliyun_oss_uploading.py

@@ -0,0 +1,81 @@
+# -*- coding: utf-8 -*-
+# @Time: 2023/12/26
+from datetime import datetime
+from typing import Dict, Any,  Optional
+
+import oss2
+import requests
+
+# OSS_BUCKET_PATH = "douyin"
+OSS_ACCESS_KEY_ID = "LTAIP6x1l3DXfSxm"
+OSS_ACCESS_KEY_SECRET = "KbTaM9ars4OX3PMS6Xm7rtxGr1FLon"
+# OSS_BUCKET_ENDPOINT = "oss-cn-hangzhou-internal.aliyuncs.com"# 内网地址
+OSS_BUCKET_ENDPOINT = "oss-cn-hangzhou.aliyuncs.com" # 外网地址
+OSS_BUCKET_NAME = "art-crawler"
+class Oss():
+    # 抓取视频上传到art-crawler
+    @classmethod
+    def video_sync_upload_oss(cls, src_url: str,
+                        video_id: str,
+                        account_id: str,
+                        OSS_BUCKET_PATH: str,
+                        referer: Optional[str] = None) -> Dict[str, Any]:
+        headers = {
+            'Accept': '*/*',
+            'Accept-Language': 'zh-CN,zh;q=0.9',
+            'Cache-Control': 'no-cache',
+            'Pragma': 'no-cache',
+            'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) '
+                          'Chrome/117.0.0.0 Safari/537.36',
+        }
+        if referer:
+            headers.update({'Referer': referer})
+        response = requests.request(url=src_url, method='GET', headers=headers)
+        file_content = response.content
+        content_type = response.headers.get('Content-Type', 'application/octet-stream')
+
+        oss_object_key = f'{OSS_BUCKET_PATH}/{account_id}/{video_id}'
+        auth = oss2.Auth(OSS_ACCESS_KEY_ID, OSS_ACCESS_KEY_SECRET)
+        bucket = oss2.Bucket(auth, OSS_BUCKET_ENDPOINT, OSS_BUCKET_NAME)
+        response = bucket.put_object(oss_object_key, file_content, headers={'Content-Type': content_type})
+
+        if 'Content-Length' in response.headers:
+            return {
+                'status': response.status,
+                'oss_object_key': oss_object_key}
+        raise AssertionError(f'OSS上传失败,请求ID: \n{response.headers["x-oss-request-id"]}')
+
+
+    #  视频发送到art-pubbucket
+    @classmethod
+    def stitching_sync_upload_oss(cls, src_url: str,
+                        video_id: str) -> Dict[str, Any]:
+        oss_object_key = f'agc_oss/agc_video/{video_id}'
+        auth = oss2.Auth(OSS_ACCESS_KEY_ID, OSS_ACCESS_KEY_SECRET)
+        bucket = oss2.Bucket(auth, OSS_BUCKET_ENDPOINT, "art-pubbucket")
+        response = bucket.put_object_from_file(oss_object_key, src_url)
+
+        if 'Content-Length' in response.headers:
+            return {
+                'status': response.status,
+                'oss_object_key': oss_object_key,
+                'save_oss_timestamp': int(datetime.now().timestamp() * 1000),
+            }
+        raise AssertionError(f'OSS上传失败,请求ID: \n{response.headers["x-oss-request-id"]}')
+
+
+    # 获取视频链接 将视频链接有效时间设置为1天
+    @classmethod
+    def get_oss_url(cls, videos, video_path):
+        auth = oss2.Auth(OSS_ACCESS_KEY_ID, OSS_ACCESS_KEY_SECRET)
+        bucket = oss2.Bucket(auth, OSS_BUCKET_ENDPOINT, OSS_BUCKET_NAME)
+        list = []
+        for i in videos:
+            try:
+                # 获取指定路径下的对象列表
+                filename = i[2].split("/")[-1]
+                bucket.get_object_to_file(i[2], f'{video_path}{filename}.mp4')
+                list.append([i[0], i[1], i[2], f'{video_path}{filename}.mp4'])
+            except Exception:
+                continue
+        return list

+ 48 - 0
common/common.py

@@ -0,0 +1,48 @@
+# -*- coding: utf-8 -*-
+# @Time: 2023/12/26
+"""
+公共方法,包含:生成log / 删除log / 下载方法 / 删除 weixinzhishu_chlsfiles / 过滤词库 / 保存视频信息至本地 txt / 翻译 / ffmpeg
+"""
+import os
+import sys
+
+sys.path.append(os.getcwd())
+from datetime import date, timedelta
+from datetime import datetime
+from loguru import logger
+
+proxies = {"http": None, "https": None}
+
+
+class Common:
+    # 统一获取当前时间 <class 'datetime.datetime'>  2022-04-14 20:13:51.244472
+    now = datetime.now()
+    # 昨天 <class 'str'>  2022-04-13
+    yesterday = (date.today() + timedelta(days=-1)).strftime("%Y-%m-%d")
+    # 今天 <class 'datetime.date'>  2022-04-14
+    today = date.today()
+    # 明天 <class 'str'>  2022-04-15
+    tomorrow = (date.today() + timedelta(days=1)).strftime("%Y-%m-%d")
+
+    # 使用 logger 模块生成日志
+    @staticmethod
+    def logger(log_type):
+        """
+        使用 logger 模块生成日志
+        """
+        # 日志路径
+        log_dir = f"./{log_type}/logs/"
+        log_path = os.getcwd() + os.sep + log_dir
+        if not os.path.isdir(log_path):
+            os.makedirs(log_path)
+        # 日志文件名
+        log_name = f"{log_type}-{datetime.now().date().strftime('%Y-%m-%d')}.log"
+
+        # 日志不打印到控制台
+        logger.remove(handler_id=None)
+        # 初始化日志
+        logger.add(os.path.join(log_dir, log_name), level="INFO", rotation="00:00", retention="10 days", enqueue=True)
+
+        return logger
+
+

+ 148 - 0
common/db.py

@@ -0,0 +1,148 @@
+# -*- coding: utf-8 -*-
+# @Time: 2023/12/26
+"""
+数据库连接及操作
+"""
+import redis
+import pymysql
+from common.common import Common
+# from common import Common
+
+class MysqlHelper:
+    @classmethod
+    def connect_mysql(cls, env, machine):
+        if machine == 'aliyun_hk':
+            # 创建一个 Connection 对象,代表了一个数据库连接
+            connection = pymysql.connect(
+                host="rm-j6cz4c6pt96000xi3.mysql.rds.aliyuncs.com",# 数据库IP地址,内网地址
+                # host="rm-j6cz4c6pt96000xi3lo.mysql.rds.aliyuncs.com",# 数据库IP地址,外网地址
+                port=3306,                      # 端口号
+                user="crawler",                 #  mysql用户名
+                passwd="crawler123456@",        # mysql用户登录密码
+                db="piaoquan-crawler" ,         # 数据库名
+                # 如果数据库里面的文本是utf8编码的,charset指定是utf8
+                charset = "utf8")
+        elif env == 'prod':
+            # 创建一个 Connection 对象,代表了一个数据库连接
+            connection = pymysql.connect(
+                host="rm-bp1159bu17li9hi94.mysql.rds.aliyuncs.com",# 数据库IP地址,内网地址
+                # host="rm-bp1159bu17li9hi94ro.mysql.rds.aliyuncs.com",# 数据库IP地址,外网地址
+                port=3306,                      # 端口号
+                user="crawler",                 #  mysql用户名
+                passwd="crawler123456@",        # mysql用户登录密码
+                db="piaoquan-crawler" ,         # 数据库名
+                # 如果数据库里面的文本是utf8编码的,charset指定是utf8
+                charset = "utf8")
+        else:
+            # 创建一个 Connection 对象,代表了一个数据库连接
+            connection = pymysql.connect(
+                host="rm-bp1k5853td1r25g3n690.mysql.rds.aliyuncs.com",# 数据库IP地址,内网地址
+                # host="rm-bp1k5853td1r25g3ndo.mysql.rds.aliyuncs.com",  # 数据库IP地址,外网地址
+                port=3306,  # 端口号
+                user="crawler",  # mysql用户名
+                passwd="crawler123456@",  # mysql用户登录密码
+                db="piaoquan-crawler",  # 数据库名
+                # 如果数据库里面的文本是utf8编码的,charset指定是utf8
+                charset="utf8")
+
+        return connection
+
+    @classmethod
+    def get_values(cls, sql, env):
+        try:
+            machine = ""
+            # 连接数据库
+            connect = cls.connect_mysql(env, machine)
+            # 返回一个 Cursor对象
+            mysql = connect.cursor()
+
+            # 执行 sql 语句
+            mysql.execute(sql)
+
+            # fetchall方法返回的是一个元组,里面每个元素也是元组,代表一行记录
+            data = mysql.fetchall()
+
+            # 关闭数据库连接
+            connect.close()
+
+            # 返回查询结果,元组
+            return data
+        except Exception as e:
+            print(f"get_values异常:{e}\n")
+            # Common.logger(log_type, crawler).error(f"get_values异常:{e}\n")
+
+    @classmethod
+    def update_values(cls, sql, env, machine):
+        # 连接数据库
+        connect = cls.connect_mysql(env, machine)
+        # 返回一个 Cursor对象
+        mysql = connect.cursor()
+
+        try:
+            # 执行 sql 语句
+            res = mysql.execute(sql)
+            # 注意 一定要commit,否则添加数据不生效
+            connect.commit()
+            return res
+        except Exception as e:
+            # print(f"update_values异常,进行回滚操作:{e}\n")
+            # 发生错误时回滚
+            connect.rollback()
+
+        # 关闭数据库连接
+        connect.close()
+
+class RedisHelper:
+    @classmethod
+    def connect_redis(cls, env, machine):
+        if machine == 'aliyun_hk':
+            redis_pool = redis.ConnectionPool(
+                # host='r-bp154bpw97gptefiqk.redis.rds.aliyuncs.com',  # 内网地址
+                host='r-bp154bpw97gptefiqkpd.redis.rds.aliyuncs.com',  # 外网地址
+                port=6379,
+                db=2,
+                password='Qingqu2019'
+            )
+            redis_conn = redis.Redis(connection_pool=redis_pool)
+        elif env == 'prod':
+            redis_pool = redis.ConnectionPool(
+                host='r-bp1mb0v08fqi4hjffu.redis.rds.aliyuncs.com',  # 内网地址
+                # host='r-bp1mb0v08fqi4hjffupd.redis.rds.aliyuncs.com',  # 外网地址
+                port=6379,
+                db=2,
+                password='Qingqu2019'
+            )
+            redis_conn = redis.Redis(connection_pool=redis_pool)
+        else:
+            redis_pool = redis.ConnectionPool(
+                # host='r-bp154bpw97gptefiqk.redis.rds.aliyuncs.com',  # 内网地址
+                host='r-bp154bpw97gptefiqkpd.redis.rds.aliyuncs.com',  # 外网地址
+                port=6379,
+                db=2,
+                password='Qingqu2019'
+            )
+            redis_conn = redis.Redis(connection_pool=redis_pool)
+        return redis_conn
+
+    @classmethod
+    def redis_push(cls, env, machine, data):
+        redis_conn = cls.connect_redis(env, machine)
+        redis_conn.lpush(machine, data)
+
+    @classmethod
+    def redis_pop(cls, env, machine):
+        redis_conn = cls.connect_redis(env, machine)
+        if redis_conn.llen(machine) == 0:
+            return None
+        else:
+            return redis_conn.rpop(machine)
+
+
+
+if __name__ == "__main__":
+
+    print(RedisHelper.connect_redis("prod", "aliyun"))
+
+
+    pass
+

+ 381 - 0
common/feishu.py

@@ -0,0 +1,381 @@
+# -*- coding: utf-8 -*-
+# @Time: 2023/12/26
+"""
+飞书表配置: token 鉴权 / 增删改查 / 机器人报警
+"""
+import json
+import os
+import sys
+import requests
+import urllib3
+
+sys.path.append(os.getcwd())
+from common import Common
+
+proxies = {"http": None, "https": None}
+
+
+class Feishu:
+    """
+    编辑飞书云文档
+    """
+    succinct_url = "https://w42nne6hzg.feishu.cn/sheets/"
+    # 飞书路径token
+    @classmethod
+    def spreadsheettoken(cls, crawler):
+        if crawler == "summary":
+            return "IbVVsKCpbhxhSJtwYOUc8S1jnWb"
+        else:
+            return crawler
+
+
+
+    # 获取飞书api token
+    @classmethod
+    def get_token(cls):
+        """
+        获取飞书api token
+        :return:
+        """
+        url = "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal/"
+        post_data = {"app_id": "cli_a13ad2afa438d00b",  # 这里账号密码是发布应用的后台账号及密码
+                     "app_secret": "4tK9LY9VbiQlY5umhE42dclBFo6t4p5O"}
+
+        try:
+            urllib3.disable_warnings()
+            response = requests.post(url=url, data=post_data, proxies=proxies, verify=False)
+            tenant_access_token = response.json()["tenant_access_token"]
+            return tenant_access_token
+        except Exception as e:
+            Common.logger("feishu").error("获取飞书 api token 异常:{}", e)
+
+    # 获取表格元数据
+    @classmethod
+    def get_metainfo(cls, crawler):
+        """
+        获取表格元数据
+        :return:
+        """
+        try:
+            get_metainfo_url = "https://open.feishu.cn/open-apis/sheets/v2/spreadsheets/" \
+                               + cls.spreadsheettoken(crawler) + "/metainfo"
+
+            headers = {
+                "Authorization": "Bearer " + cls.get_token(),
+                "Content-Type": "application/json; charset=utf-8"
+            }
+            params = {
+                "extFields": "protectedRange",  # 额外返回的字段,extFields=protectedRange时返回保护行列信息
+                "user_id_type": "open_id"  # 返回的用户id类型,可选open_id,union_id
+            }
+            urllib3.disable_warnings()
+            r = requests.get(url=get_metainfo_url, headers=headers, params=params, proxies=proxies, verify=False)
+            response = json.loads(r.content.decode("utf8"))
+            return response
+        except Exception as e:
+            Common.logger("feishu").error("获取表格元数据异常:{}", e)
+
+    # 读取工作表中所有数据
+    @classmethod
+    def get_values_batch(cls, crawler, sheetid):
+        """
+        读取工作表中所有数据
+        :param crawler: 哪个爬虫
+        :param sheetid: 哪张表
+        :return: 所有数据
+        """
+        try:
+            get_values_batch_url = "https://open.feishu.cn/open-apis/sheets/v2/spreadsheets/" \
+                                   + cls.spreadsheettoken(crawler) + "/values_batch_get"
+            headers = {
+                "Authorization": "Bearer " + cls.get_token(),
+                "Content-Type": "application/json; charset=utf-8"
+            }
+            params = {
+                "ranges": sheetid,
+                "valueRenderOption": "ToString",
+                "dateTimeRenderOption": "",
+                "user_id_type": "open_id"
+            }
+            urllib3.disable_warnings()
+            r = requests.get(url=get_values_batch_url, headers=headers, params=params, proxies=proxies, verify=False)
+            response = json.loads(r.content.decode("utf8"))
+            values = response["data"]["valueRanges"][0]["values"]
+            return values
+        except Exception as e:
+            Common.logger("feishu").error("读取工作表所有数据异常:{}", e)
+
+    # 工作表,插入行或列
+    @classmethod
+    def insert_columns(cls, log_type, crawler, sheetid, majordimension, startindex, endindex):
+        """
+        工作表插入行或列
+        :param log_type: 日志路径
+        :param crawler: 哪个爬虫的云文档
+        :param sheetid:哪张工作表
+        :param majordimension:行或者列, ROWS、COLUMNS
+        :param startindex:开始位置
+        :param endindex:结束位置
+        """
+        try:
+            insert_columns_url = "https://open.feishu.cn/open-apis/sheets/v2/spreadsheets/" \
+                                 + cls.spreadsheettoken(crawler) + "/insert_dimension_range"
+            headers = {
+                "Authorization": "Bearer " + cls.get_token(),
+                "Content-Type": "application/json; charset=utf-8"
+            }
+            body = {
+                "dimension": {
+                    "sheetId": sheetid,
+                    "majorDimension": majordimension,  # 默认 ROWS ,可选 ROWS、COLUMNS
+                    "startIndex": startindex,  # 开始的位置
+                    "endIndex": endindex  # 结束的位置
+                },
+                "inheritStyle": "AFTER"  # BEFORE 或 AFTER,不填为不继承 style
+            }
+
+            urllib3.disable_warnings()
+            r = requests.post(url=insert_columns_url, headers=headers, json=body, proxies=proxies, verify=False)
+            Common.logger("feishu").info("插入行或列:{}", r.json()["msg"])
+        except Exception as e:
+            Common.logger("feishu").error("插入行或列异常:{}", e)
+
+    # 写入数据
+    @classmethod
+    def update_values(cls, log_type, crawler, sheetid, ranges, values):
+        """
+        写入数据
+        :param log_type: 日志路径
+        :param crawler: 哪个爬虫的云文档
+        :param sheetid:哪张工作表
+        :param ranges:单元格范围
+        :param values:写入的具体数据,list
+        """
+        try:
+            update_values_url = "https://open.feishu.cn/open-apis/sheets/v2/spreadsheets/" \
+                                + cls.spreadsheettoken(crawler) + "/values_batch_update"
+            headers = {
+                "Authorization": "Bearer " + cls.get_token(),
+                "Content-Type": "application/json; charset=utf-8"
+            }
+            body = {
+                "valueRanges": [
+                    {
+                        "range": sheetid + "!" + ranges,
+                        "values": values
+                    },
+                ],
+            }
+            urllib3.disable_warnings()
+            r = requests.post(url=update_values_url, headers=headers, json=body, proxies=proxies, verify=False)
+            Common.logger("feishu").info("写入数据:{}", r.json()["msg"])
+        except Exception as e:
+            Common.logger("feishu").error("写入数据异常:{}", e)
+
+    # 合并单元格
+    @classmethod
+    def merge_cells(cls, log_type, crawler, sheetid, ranges):
+        """
+        合并单元格
+        :param log_type: 日志路径
+        :param crawler: 哪个爬虫
+        :param sheetid:哪张工作表
+        :param ranges:需要合并的单元格范围
+        """
+        try:
+            merge_cells_url = "https://open.feishu.cn/open-apis/sheets/v2/spreadsheets/" \
+                              + cls.spreadsheettoken(crawler) + "/merge_cells"
+            headers = {
+                "Authorization": "Bearer " + cls.get_token(),
+                "Content-Type": "application/json; charset=utf-8"
+            }
+
+            body = {
+                "range": sheetid + "!" + ranges,
+                "mergeType": "MERGE_ROWS"
+            }
+            urllib3.disable_warnings()
+            r = requests.post(url=merge_cells_url, headers=headers, json=body, proxies=proxies, verify=False)
+            Common.logger("feishu").info("合并单元格:{}", r.json()["msg"])
+        except Exception as e:
+            Common.logger("feishu").error("合并单元格异常:{}", e)
+
+    # 读取单元格数据
+    @classmethod
+    def get_range_value(cls, crawler, sheetid, cell):
+        """
+        读取单元格内容
+        :param log_type: 日志路径
+        :param crawler: 哪个爬虫
+        :param sheetid: 哪张工作表
+        :param cell: 哪个单元格
+        :return: 单元格内容
+        """
+        try:
+            get_range_value_url = "https://open.feishu.cn/open-apis/sheets/v2/spreadsheets/" \
+                                  + cls.spreadsheettoken(crawler) + "/values/" + sheetid + "!" + cell
+            headers = {
+                "Authorization": "Bearer " + cls.get_token(),
+                "Content-Type": "application/json; charset=utf-8"
+            }
+            params = {
+                "valueRenderOption": "FormattedValue",
+
+                # dateTimeRenderOption=FormattedString 计算并对时间日期按照其格式进行格式化,但不会对数字进行格式化,返回格式化后的字符串。
+                "dateTimeRenderOption": "",
+
+                # 返回的用户id类型,可选open_id,union_id
+                "user_id_type": "open_id"
+            }
+            urllib3.disable_warnings()
+            r = requests.get(url=get_range_value_url, headers=headers, params=params, proxies=proxies, verify=False)
+            # print(r.text)
+            return r.json()["data"]["valueRange"]["values"][0]
+        except Exception as e:
+            Common.logger("feishu").error("读取单元格数据异常:{}", e)
+    # 获取表内容
+    @classmethod
+    def get_sheet_content(cls, crawler, sheet_id):
+        try:
+            sheet = Feishu.get_values_batch(crawler, sheet_id)
+            content_list = []
+            for x in sheet:
+                for y in x:
+                    if y is None:
+                        pass
+                    else:
+                        content_list.append(y)
+            return content_list
+        except Exception as e:
+            Common.logger("feishu").error(f'get_sheet_content:{e}\n')
+
+    # 删除行或列,可选 ROWS、COLUMNS
+    @classmethod
+    def dimension_range(cls, log_type, crawler, sheetid, major_dimension, startindex, endindex):
+        """
+        删除行或列
+        :param log_type: 日志路径
+        :param crawler: 哪个爬虫
+        :param sheetid:工作表
+        :param major_dimension:默认 ROWS ,可选 ROWS、COLUMNS
+        :param startindex:开始的位置
+        :param endindex:结束的位置
+        :return:
+        """
+        try:
+            dimension_range_url = "https://open.feishu.cn/open-apis/sheets/v2/spreadsheets/" \
+                                  + cls.spreadsheettoken(crawler) + "/dimension_range"
+            headers = {
+                "Authorization": "Bearer " + cls.get_token(),
+                "Content-Type": "application/json; charset=utf-8"
+            }
+            body = {
+                "dimension": {
+                    "sheetId": sheetid,
+                    "majorDimension": major_dimension,
+                    "startIndex": startindex,
+                    "endIndex": endindex
+                }
+            }
+            urllib3.disable_warnings()
+            r = requests.delete(url=dimension_range_url, headers=headers, json=body, proxies=proxies, verify=False)
+            Common.logger("feishu").info("删除视频数据:{}", r.json()["msg"])
+        except Exception as e:
+            Common.logger("feishu").error("删除视频数据异常:{}", e)
+
+    # 获取用户 ID
+    @classmethod
+    def get_userid(cls, log_type, crawler, username):
+        try:
+            url = "https://open.feishu.cn/open-apis/user/v1/batch_get_id?"
+            headers = {
+                "Authorization": "Bearer " + cls.get_token(),
+                "Content-Type": "application/json; charset=utf-8"
+            }
+            name_phone_dict = {
+                "xinxin": "15546206651",
+                "muxinyi": "13699208058",
+                "wangxueke": "13513479926",
+                "yuzhuoyi": "18624010360",
+                "luojunhui": "18801281360",
+                "fanjun": "15200827642",
+                "zhangyong": "17600025055"
+            }
+            username = name_phone_dict.get(username)
+
+            data = {"mobiles": [username]}
+            urllib3.disable_warnings()
+            r = requests.get(url=url, headers=headers, params=data, verify=False, proxies=proxies)
+            open_id = r.json()["data"]["mobile_users"][username][0]["open_id"]
+
+            return open_id
+        except Exception as e:
+            Common.logger("feishu").error(f"get_userid异常:{e}\n")
+
+    # 飞书机器人
+    @classmethod
+    def bot(cls, log_type, crawler, text, mark):
+        try:
+            url = "https://open.feishu.cn/open-apis/bot/v2/hook/2b317db6-93ed-43b4-bf01-03c35cfa1d59"
+            headers = {'Content-Type': 'application/json'}
+            if crawler == "抖音":
+                content = "抖音cookie过期"
+                sheet_url = "https://w42nne6hzg.feishu.cn/sheets/IbVVsKCpbhxhSJtwYOUc8S1jnWb?sheet=n9xlLF"
+                users = "<at id=" + str(cls.get_userid(log_type, crawler, mark)) + "></at>"
+            elif crawler == "管理后台":
+                content = "管理后台cookie过期"
+                sheet_url = "https://w42nne6hzg.feishu.cn/sheets/IbVVsKCpbhxhSJtwYOUc8S1jnWb?sheet=n9xlLF"
+                users = "<at id=" + str(cls.get_userid(log_type, crawler, mark)) + "></at>"
+            elif crawler == "快手":
+                content = "快手cookie过期"
+                sheet_url = "https://w42nne6hzg.feishu.cn/sheets/IbVVsKCpbhxhSJtwYOUc8S1jnWb?sheet=n9xlLF"
+                users = "<at id=" + str(cls.get_userid(log_type, crawler, mark)) + "></at>"
+            elif crawler == "AGC视频":
+                content = 'AGC视频生成条数详情'
+                sheet_url = "https://w42nne6hzg.feishu.cn/sheets/IbVVsKCpbhxhSJtwYOUc8S1jnWb?sheet=n9xlLF"
+                users = "<at id=" + str(cls.get_userid(log_type, crawler, 'all')) + ">所有人</at> \n"
+
+            data = json.dumps({
+                "msg_type": "interactive",
+                "card": {
+                    "config": {
+                        "wide_screen_mode": True,
+                        "enable_forward": True
+                    },
+                    "elements": [{
+                        "tag": "div",
+                        "text": {
+                            "content": users + text,
+                            "tag": "lark_md"
+                        }
+                    }, {
+                        "actions": [{
+                            "tag": "button",
+                            "text": {
+                                "content": content,
+                                "tag": "lark_md"
+                            },
+                            "url": sheet_url,
+                            "type": "default",
+                            "value": {}
+                        }],
+                        "tag": "action"
+                    }],
+                    "header": {
+                        "title": {
+                            "content": "📣您有新的信息,请注意查收",
+                            "tag": "plain_text"
+                        }
+                    }
+                }
+            })
+            urllib3.disable_warnings()
+            r = requests.post(url, headers=headers, data=data, verify=False, proxies=proxies)
+            Common.logger("feishu").info(f'触发机器人消息:{r.status_code}, {text}')
+        except Exception as e:
+            Common.logger("feishu").error(f"bot异常:{e}\n")
+
+
+if __name__ == "__main__":
+    Feishu.bot('recommend', '抖音', '测试: 抖音cookie失效,请及时更换')
+

+ 138 - 0
common/material.py

@@ -0,0 +1,138 @@
+# -*- coding: utf-8 -*-
+# @Time: 2023/12/26
+import os
+import random
+import sys
+import datetime
+
+sys.path.append(os.getcwd())
+from datetime import datetime, timedelta
+from common.db import MysqlHelper
+from common.feishu import Feishu
+
+
+class Material():
+
+    # 获取所有用户名
+    @classmethod
+    def feishu_name(cls):
+        summary = Feishu.get_values_batch("summary", "n9xlLF")
+        list = []
+        for row in summary[2:]:
+            mark = row[0]
+            mark_name = row[1]
+            list.append({"mark": mark, "mark_name": mark_name})
+        return list
+
+    # 获取汇总表所有
+    @classmethod
+    def feishu_list(cls):
+        summary = Feishu.get_values_batch("summary", "n9xlLF")
+        list = []
+        for row in summary[2:]:
+            mark = row[0]
+            feishu_id = row[3]
+            video_call = row[4]
+            pq_id = row[7]
+            number = {"mark": mark, "feishu_id": feishu_id, "video_call": video_call, "pq_id": pq_id}
+            list.append(number)
+        return list
+
+    # 获取管理后台cookie
+    @classmethod
+    def get_houtai_cookie(cls):
+        douyin_token = Feishu.get_values_batch("MW7VsTb1vhctEot34g7ckrjdnIe", "pLWwBm")
+        for item in douyin_token:
+            if item[0] == '管理后台':
+                return item[1]
+
+
+
+    # 获取汇总表所有待抓取 用户
+    @classmethod
+    def get_all_user(cls, type):
+        summary = Feishu.get_values_batch("summary", "n9xlLF")
+        list = []
+        for row in summary[2:]:
+            mark = row[0]
+            feishu_id = row[3]
+            sheet = row[5]
+            token = row[6]
+
+            parts = sheet.split(',')
+            result = []
+            for part in parts:
+                sub_parts = part.split('--')
+                result.append(sub_parts)
+            douyin = result[0]
+            kuanshou = result[1]
+            if type == "douyin":
+                number = {"mark": mark, "feishu_id": feishu_id, "channel": douyin, "token": token}
+            elif type == "kuaishou":
+                number = {"mark": mark, "feishu_id": feishu_id, "channel": kuanshou, "token": token}
+            list.append(number)
+        return list
+
+    # 获取抖音 cookie
+    @classmethod
+    def get_cookie(cls, feishu_id, token, channel):
+        token = Feishu.get_values_batch(feishu_id, token)
+        for item in token:
+            if item[0] == channel:
+                return item[1]
+
+    # 获取抖音视频链接 存入数据库
+    @classmethod
+    def insert_user(cls, feishu_id, channel_id, mark, channel):
+        # 获取抖音视频链接
+        douyin = Feishu.get_values_batch(feishu_id, channel_id)
+        # 提取账号昵称和账号主页链接
+        for row in douyin[1:]:
+            uid = row[1]
+            insert_sql = f"""INSERT INTO agc_channel_data (user_id, channel, mark) values ('{uid}', '{channel}', '{mark}')"""
+            MysqlHelper.update_values(
+                sql=insert_sql,
+                env="prod",
+                machine="",
+            )
+
+    @classmethod
+    def get_uid(cls, uid, mark):
+        current_time = datetime.now()
+        formatted_time = current_time.strftime("%Y-%m-%d")
+        uid_list = f"""select account_id FROM agc_video_deposit where time = '{formatted_time}' AND audio = '{uid}' and mark = '{mark}' GROUP BY account_id """
+        id_list = MysqlHelper.get_values(uid_list, "prod")
+        return id_list
+
+    # 获取音频类型+字幕+标题
+    @classmethod
+    def get_all_data(cls, feishu_id, link, mark):
+        list = []
+        title_list = []
+        # 获取音频类型+字幕+标题
+        all_data = Feishu.get_values_batch(feishu_id, link)
+        print(all_data)
+        for row in all_data[1:]:
+            uid = row[1]
+            text = row[2]
+            title = row[3]
+            number = {"uid": uid, "text": text}
+            if uid:
+                list.append(number)
+            title_list.append(title)
+        while True:
+            list1 = random.choice(list)
+            uid1 = list1['uid']
+            srt = list1['text']
+            id_list = cls.get_uid(uid1, mark)
+            if len(id_list) < 1:
+                return uid1, srt, title_list
+
+
+
+
+
+
+
+
+

+ 106 - 0
common/scheduling_db.py

@@ -0,0 +1,106 @@
+# -*- coding: utf-8 -*-
+# @Time: 2023/12/26
+"""
+数据库连接及操作
+"""
+import pymysql
+from common.common import Common
+# from common import Common
+
+
+class MysqlHelper:
+    @classmethod
+    def connect_mysql(cls, env, action):
+        if env == 'hk':
+            if action == 'get_author_map':
+                # 创建一个 Connection 对象,代表了一个数据库连接
+                connection = pymysql.connect(
+                    host="rm-bp1159bu17li9hi94ro.mysql.rds.aliyuncs.com",  # 数据库IP地址,内网地址
+                    port=3306,  # 端口号
+                    user="crawler",  # mysql用户名
+                    passwd="crawler123456@",  # mysql用户登录密码
+                    db="piaoquan-crawler",  # 数据库名
+                    # 如果数据库里面的文本是utf8编码的,charset指定是utf8
+                    charset="utf8mb4")
+            else:
+                # 创建一个 Connection 对象,代表了一个数据库连接
+                connection = pymysql.connect(
+                    host="rm-j6cz4c6pt96000xi3.mysql.rds.aliyuncs.com",  # 数据库IP地址,内网地址
+                    # host="rm-j6cz4c6pt96000xi3lo.mysql.rds.aliyuncs.com",# 数据库IP地址,外网地址
+                    port=3306,  # 端口号
+                    user="crawler",  # mysql用户名
+                    passwd="crawler123456@",  # mysql用户登录密码
+                    db="piaoquan-crawler",  # 数据库名
+                    # 如果数据库里面的文本是utf8编码的,charset指定是utf8
+                    charset="utf8mb4")
+        elif env == 'prod':
+            # 创建一个 Connection 对象,代表了一个数据库连接
+            connection = pymysql.connect(
+                host="rm-bp1159bu17li9hi94.mysql.rds.aliyuncs.com",  # 数据库IP地址,内网地址
+                # host="rm-bp1159bu17li9hi94ro.mysql.rds.aliyuncs.com",# 数据库IP地址,外网地址
+                port=3306,  # 端口号
+                user="crawler",  # mysql用户名
+                passwd="crawler123456@",  # mysql用户登录密码
+                db="piaoquan-crawler",  # 数据库名
+                # 如果数据库里面的文本是utf8编码的,charset指定是utf8
+                charset="utf8mb4")
+        else:
+            # 创建一个 Connection 对象,代表了一个数据库连接
+            connection = pymysql.connect(
+                host="rm-bp1k5853td1r25g3n690.mysql.rds.aliyuncs.com",  # 数据库IP地址,内网地址
+                # host="rm-bp1k5853td1r25g3ndo.mysql.rds.aliyuncs.com",  # 数据库IP地址,外网地址
+                port=3306,  # 端口号
+                user="crawler",  # mysql用户名
+                passwd="crawler123456@",  # mysql用户登录密码
+                db="piaoquan-crawler",  # 数据库名
+                # 如果数据库里面的文本是utf8编码的,charset指定是utf8
+                charset="utf8mb4")
+
+        return connection
+
+    @classmethod
+    def get_values(cls, log_type, crawler, sql, env, action=''):
+        try:
+            # 连接数据库
+            connect = cls.connect_mysql(env, action)
+            # 返回一个 Cursor对象
+            mysql = connect.cursor(cursor=pymysql.cursors.DictCursor)
+
+            # 执行 sql 语句
+            mysql.execute(sql)
+
+            # fetchall方法返回的是一个元组,里面每个元素也是元组,代表一行记录
+            data = mysql.fetchall()
+
+            # 关闭数据库连接
+            connect.close()
+
+            # 返回查询结果,元组
+            return data
+        except Exception as e:
+            Common.logger(log_type).error(f"get_values异常:{e}\n")
+
+    @classmethod
+    def update_values(cls, log_type, crawler, sql, env, action=''):
+        # 连接数据库
+        connect = cls.connect_mysql(env, action)
+        # 返回一个 Cursor对象
+        mysql = connect.cursor()
+
+        try:
+            # 执行 sql 语句
+            res = mysql.execute(sql)
+            # 注意 一定要commit,否则添加数据不生效
+            connect.commit()
+            return res
+        except Exception as e:
+            Common.logger(log_type).error(f"update_values异常,进行回滚操作:{e}\n")
+            # 发生错误时回滚
+            connect.rollback()
+
+        # 关闭数据库连接
+        connect.close()
+
+
+if __name__ == "__main__":
+    pass

+ 26 - 0
common/sql_help.py

@@ -0,0 +1,26 @@
+import os
+import sys
+import datetime
+
+
+
+sys.path.append(os.getcwd())
+from datetime import datetime
+from common import MysqlHelper
+
+
+class sqlHelp():
+    @classmethod
+    def get_count_list(cls, name_list):
+        count_list = []
+        current_time = datetime.now()
+        formatted_time = current_time.strftime("%Y-%m-%d")
+        for name in name_list:
+
+            count = f"""SELECT COUNT(*) AS agc_video_deposit FROM ( SELECT audio, account_id FROM agc_video_deposit WHERE time = '{formatted_time}' AND mark = '{name["mark"]}' GROUP BY audio, account_id) AS subquery;"""
+            count = MysqlHelper.get_values(count, "prod")
+            if count == None:
+                count = 0
+            count = str(count).replace('(', '').replace(')', '').replace(',', '')
+            count_list.append(f"{name['mark_name']}生成条数为:{count}条 \n")
+        return count_list

+ 7 - 0
config.ini

@@ -0,0 +1,7 @@
+[PATHS]
+VIDEO_EDITOR_PATH = /Users/tzld/Desktop/video_editor_agc/video_agc/path/
+VIDEO_PATH = /Users/tzld/Desktop/video_editor_agc/video_agc/path/video/
+SRT_PATH = /Users/tzld/Desktop/video_editor_agc/video_agc/path/srt/
+OSS_PATH = /Users/tzld/Desktop/video_editor_agc/video_agc/path/oss_video/
+TXT_PATH = /Users/tzld/Desktop/video_editor_agc/video_agc/path/txt/
+

+ 57 - 0
data_main.py

@@ -0,0 +1,57 @@
+from common import Material, Feishu
+from extract_data.douyin.douyin_author import douyinAuthor
+from extract_data.kuaishou.kuaishou_author import kuaishouAuthor
+
+import schedule
+import threading
+import time
+
+# 定义读取表格的函数
+def douyin_data(user_data):
+    print(f"执行抖音数据抓取{user_data}")
+    douyinAuthor.get_videoList(user_data)
+
+def kuaishou_data(user_data):
+    print(f"执行快手数据抓取{user_data}")
+    kuaishouAuthor.get_kuaishou_videoList(user_data)
+
+
+# 定义定时任务
+def douyin_task():
+    data = Material.get_all_user("douyin")
+    threads = []
+    for _, user_data in data.iterrows():
+        thread = threading.Thread(target=douyin_data, args=(user_data,))
+        threads.append(thread)
+        thread.start()
+    for thread in threads:
+        thread.join()
+    print("抖音数据抓取定时任务执行完成")
+
+
+# 定义定时任务
+def kuanshou_task():
+    data = Material.get_all_user("kuanshou")
+    threads = []
+    for _, user_data in data.iterrows():
+        thread = threading.Thread(target=kuaishou_data, args=(user_data,))
+        threads.append(thread)
+        thread.start()
+    for thread in threads:
+        thread.join()
+    print("快手数据抓取定时任务执行完成.")
+
+schedule.every(2).minutes.do(douyin_task)
+
+schedule.every(2).minutes.do(kuanshou_task)
+
+# schedule.every(2).hours.do(douyin_task)
+#
+# schedule.every(3).hours.do(kuanshou_task)
+
+# 持续运行,直到手动终止
+while True:
+    schedule.run_pending()
+    time.sleep(1)
+
+

+ 0 - 0
extract_data/__init__.py


+ 0 - 0
extract_data/douyin/__init__.py


+ 175 - 0
extract_data/douyin/douyin_author.py

@@ -0,0 +1,175 @@
+# -*- coding: utf-8 -*-
+# @Time: 2023/12/22
+import datetime
+import os
+import random
+import sys
+import time
+from datetime import datetime
+import requests
+import json
+import urllib3
+
+from extract_data.douyin.douyin_author_help import DouYinHelper
+
+sys.path.append(os.getcwd())
+from common.aliyun_oss_uploading import Oss
+from common.common import Common
+from common.material import Material
+from common.feishu import Feishu
+from common.db import MysqlHelper
+from requests.adapters import HTTPAdapter
+
+class douyinAuthor():
+
+    """
+    获取抖音用户主页id
+    """
+    @classmethod
+    def get_videoUserId(cls, mark):
+        select_user_sql = f"""select user_id, channel from agc_channel_data where mark = '{mark}' and  channel  = '抖音' ORDER BY id DESC;"""
+        user_list = MysqlHelper.get_values(select_user_sql, "prod")
+        return user_list
+
+    """
+    oss视频地址 存入数据库
+    """
+    @classmethod
+    def insert_videoUrl(cls, video_id, account_id, oss_object_key, mark):
+        current_time = datetime.now()
+        formatted_time = current_time.strftime("%Y-%m-%d %H:%M")
+        insert_sql = f"""INSERT INTO agc_video_url (video_id, account_id, oss_object_key, time , status, mark) values ("{video_id}", "{account_id}", "{oss_object_key}", "{formatted_time}", 1, "{mark}")"""
+        MysqlHelper.update_values(
+            sql=insert_sql,
+            env="prod",
+            machine="",
+        )
+
+    """
+    查询该video_id是否在数据库存在
+    """
+    @classmethod
+    def select_videoUrl_id(cls, video_id, mark):
+        select_user_sql = f"""select video_id from agc_video_url where video_id='{video_id}'  and mark = '{mark}';"""
+        user_list = MysqlHelper.get_values(select_user_sql, "prod")
+        if user_list:
+            return True
+        else:
+            return False
+
+
+    """抖音读取数据 将数据存储到oss上"""
+    @classmethod
+    def get_videoList(cls, data):
+        try:
+            mark = data[0]['mark']
+            token = data[0]['token']
+            feishu_id = data[0]['feishu_id']
+            channel_id = data[0]['channel'][0]
+            channel = data[0]['channel'][1]
+            Material.insert_user(feishu_id, channel_id, mark, channel)
+            cookie = Material.get_cookie(feishu_id, token, channel)
+            # 获取 用户主页id
+            user_list = cls.get_videoUserId(mark)
+            if len(user_list) == 0:
+                return
+            for i in user_list:
+                account_id = i[0].replace('(', '').replace(')', '').replace(',', '')
+                Common.logger("douyin").info(f"用户主页ID:{account_id}")
+                next_cursor = 0
+                count = 0
+                while True:
+                    if next_cursor == None:
+                        break
+                    if count > 5:
+                        break
+                    time.sleep(random.randint(5, 10))
+
+                    url = 'https://www.douyin.com/aweme/v1/web/aweme/post/'
+                    headers = {
+                        'Accept': 'application/json, text/plain, */*',
+                        'Accept-Language': 'zh-CN,zh;q=0.9',
+                        'Cache-Control': 'no-cache',
+                        'Cookie': cookie,
+                        'Pragma': 'no-cache',
+                        'Referer': f'https://www.douyin.com/user/{account_id}',
+                        'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) '
+                                      'Chrome/118.0.0.0 Safari/537.36',
+                    }
+                    query = DouYinHelper.get_full_query(ua=headers['User-Agent'], extra_data={
+                        'sec_user_id': account_id,
+                        'max_cursor': next_cursor,
+                        'locate_query': 'false',
+                        'show_live_replay_strategy': '1',
+                        'need_time_list': '1',
+                        'time_list_query': '0',
+                        'whale_cut_token': '',
+                        'cut_version': '1',
+                        'count': '18',
+                        'publish_video_strategy_type': '2',
+                    })
+                    urllib3.disable_warnings()
+                    s = requests.session()
+                    s.mount('http://', HTTPAdapter(max_retries=3))
+                    s.mount('https://', HTTPAdapter(max_retries=3))
+                    response = requests.request(method='GET', url=url, headers=headers, params=query)
+                    text = response.text
+                    if len(text) == 0:
+                        time.sleep(60)
+                        continue
+                    body = response.content.decode()
+                    obj = json.loads(body)
+                    has_more = True if obj.get('has_more', 0) == 1 else False
+                    next_cursor = str(obj.get('max_cursor')) if has_more else None
+                    data = obj.get('aweme_list', [])
+                    response.close()
+                    if response.status_code != 200:
+                        Common.logger("douyin").info(
+                            f"接口请求失败,请更换cookie,{response.status_code}")
+                        Feishu.bot('recommend', '抖音', f'{mark}:抖音cookie失效,请及时更换~', mark)
+                        # 如果返回空信息,则随机睡眠 600, 1200 秒
+                        time.sleep(random.randint(600, 1200))
+                        continue
+                    elif len(data) == 0:
+                        Common.logger("douyin").info(
+                            f"接口请求失败,请更换cookie")
+                        continue
+
+                    for j in range(len(data)):
+                        try:
+                            entity_type = data[j].get('search_impr').get('entity_type')
+                            Common.logger("douyin").info(
+                                f"非视频:{entity_type}")
+                            if entity_type == 'GENERAL':
+
+                                video_id = data[j].get('aweme_id')  # 文章id
+                                id = cls.select_videoUrl_id(video_id, mark)
+                                if id:
+                                    count += 1
+                                    if count > 5:
+                                        Common.logger("douyin").info(
+                                            f"重复视频不在抓取该用户,用户主页id:{account_id}")
+                                        break
+                                    continue
+                                video_url = data[j].get('video').get('play_addr').get('url_list')[0]  # 视频链接
+
+                                channel_name = mark+'/douyin'
+                                oss_object_key = Oss.video_sync_upload_oss(video_url, video_id, account_id, channel_name)
+                                status = oss_object_key.get("status")
+                                # 发送 oss
+                                oss_object_key = oss_object_key.get("oss_object_key")
+                                Common.logger("douyin").info(f"抖音视频链接oss发送成功,oss地址:{oss_object_key}")
+                                if status == 200:
+                                    cls.insert_videoUrl(video_id, account_id, oss_object_key, mark)
+                                    Common.logger("douyin").info(
+                                        f"视频地址插入数据库成功,视频id:{video_id},用户主页id:{account_id},视频储存地址:{oss_object_key}")
+                                # 发送成功 存入数据库
+                        except Exception as e:
+                            Common.logger("douyin").warning(f"抓取单条视频异常:{e}\n")
+                            continue
+        except Exception as e:
+            Common.logger("douyin").warning(f"抓取异常:{e}\n")
+            return
+
+if __name__ == '__main__':
+    douyinAuthor.get_videoList()

+ 140 - 0
extract_data/douyin/douyin_author_help.py

@@ -0,0 +1,140 @@
+# -*- coding: utf-8 -*-
+# @Time: 2023/12/26
+import json
+import time
+from base64 import b64encode
+from functools import reduce
+from hashlib import md5
+from random import choice, randint
+from typing import Any, Dict, List, Optional
+from urllib.parse import urlencode
+
+
+class DouYinHelper(object):
+    ttwid_list = [
+        '1|G3wy_-RdLJnfG5P9zAcP54OM8_nTLZVrJxNi1lPzdmg|1693558867|5e43c47a424e939aaf7193b096e3c6f2274982ee64e9608c99c54d2a43982aca'
+    ]
+
+    @classmethod
+    def _0x30492c(cls, x: bytes, y: bytes, f: Optional[List[int]] = None) -> bytes:
+        """RC4加密, 可以用Crypto.Cipher.ARC4替代"""
+        c = 0
+        d = [i for i in range(256)]
+        for b in range(256):
+            c = (c + d[b] + x[b % len(x)]) % 256
+            e = d[b]
+            d[b] = d[c]
+            d[c] = e
+        t, c = 0, 0
+
+        if not f:
+            f = []
+        for i in range(len(y)):
+            t = (t + 1) % 256
+            c = (c + d[t]) % 256
+            e = d[t]
+            d[t] = d[c]
+            d[c] = e
+            f.append(y[i] ^ d[(d[t] + d[c]) % 256])
+        return bytes(f)
+
+    @classmethod
+    def _0x485470(cls, a: str) -> List[int]:
+        _0x583e81 = [0] * 103
+        for i in range(10):
+            _0x583e81[i + 48] = i
+        for j in range(10, 16):
+            _0x583e81[j + 87] = j
+
+        b = len(a) >> 1
+        e = b << 1
+        d = [0] * b
+        c = 0
+        for f in range(0, e, 2):
+            d[c] = _0x583e81[ord(a[f])] << 4 | _0x583e81[ord(a[f + 1])]
+            c += 1
+        return d
+
+    @classmethod
+    def calc_x_bogus(cls, ua: str, query: str, data: Optional[Dict[str, Any]] = None) -> str:
+        """计算X_Bogus参数"""
+        query = query.encode()
+        for _ in range(2):
+            query = md5(query).hexdigest()
+            query = bytes([int(query[i:i + 2], 16) for i in range(0, len(query), 2)])
+
+        data = json.dumps(data, separators=(',', ':'), ensure_ascii=False).encode() if data else b''
+        for _ in range(2):
+            data = md5(data).hexdigest()
+            data = bytes([int(data[i:i + 2], 16) for i in range(0, len(data), 2)])
+
+        a = b'\x00\x01\x0e'
+        ua = b64encode(cls._0x30492c(a, ua.encode())).decode()
+        ua = md5(ua.encode()).hexdigest()
+        ua = cls._0x485470(ua)
+
+        t = int(time.time())
+        fp = 2421646185  # 真实的canvas指纹
+        arr1 = [
+            64,
+            1 / 256,
+            1 % 256,
+            14,
+            query[14],
+            query[15],
+            data[14],
+            data[15],
+            ua[14],
+            ua[15],
+            t >> 24 & 255,
+            t >> 16 & 255,
+            t >> 8 & 255,
+            t >> 0 & 255,
+            fp >> 24 & 255,
+            fp >> 16 & 255,
+            fp >> 8 & 255,
+            fp >> 0 & 255,
+        ]
+        reduce_num = reduce(lambda x, y: int(x) ^ int(y), arr1)
+        arr1.append(reduce_num)
+        arr2 = [int(arr1[i]) for i in range(len(arr1))]
+
+        garble = cls._0x30492c(b'\xff', bytes(arr2), [2, 255])
+        m = 'Dkdpgh4ZKsQB80/Mfvw36XI1R25-WUAlEi7NLboqYTOPuzmFjJnryx9HVGcaStCe='
+        xb = ''
+        for i in range(0, len(garble), 3):
+            a, b, c = garble[i], garble[i + 1], garble[i + 2]
+            base_num = c | b << 8 | a << 16
+            c1 = m[(base_num & 16515072) >> 18]
+            c2 = m[(base_num & 258048) >> 12]
+            c3 = m[(base_num & 4032) >> 6]
+            c4 = m[(base_num & 63)]
+            xb += ''.join([c1, c2, c3, c4])
+        return xb
+
+    @classmethod
+    def get_full_query(cls, ua: str, extra_data: Dict[str, Any]) -> Dict[str, Any]:
+        ms_token = b64encode(bytes([randint(0, 255) for _ in range(94)])).decode()
+        ms_token = ms_token.replace('+', '-').replace('/', '_').rstrip('=')
+
+        data = {
+            'device_platform': 'webapp',
+            'aid': '6383',
+            'channel': 'channel_pc_web',
+            'pc_client_type': '1',
+            'version_code': '190500',
+            'version_name': '19.5.0',
+            'cookie_enabled': 'true',
+            'platform': 'PC',
+            'msToken': ms_token,
+        }
+        data.update(extra_data)
+        query = urlencode(data, safe='=')
+        x_bogus = cls.calc_x_bogus(ua=ua, query=query, data=None)
+        data.update({'X-Bogus': x_bogus})
+        return data
+
+    @classmethod
+    def get_cookie(cls):
+        ttwid = choice(cls.ttwid_list)
+        return f'ttwid={ttwid}'

+ 0 - 0
extract_data/kuaishou/__init__.py


+ 162 - 0
extract_data/kuaishou/kuaishou_author.py

@@ -0,0 +1,162 @@
+# -*- coding: utf-8 -*-
+# @Time: 2024/01/18
+import datetime
+import os
+import random
+import sys
+import time
+from datetime import datetime
+import requests
+import json
+import urllib3
+sys.path.append(os.getcwd())
+from common.aliyun_oss_uploading import Oss
+from common.common import Common
+from common.material import Material
+from common.feishu import Feishu
+from common.db import MysqlHelper
+from requests.adapters import HTTPAdapter
+
+
+class kuaishouAuthor():
+
+    """
+    oss视频地址 存入数据库
+    """
+    @classmethod
+    def insert_videoUrl(cls, video_id, account_id, oss_object_key, mark):
+        current_time = datetime.now()
+        formatted_time = current_time.strftime("%Y-%m-%d %H:%M")
+        insert_sql = f"""INSERT INTO agc_video_url (video_id, account_id, oss_object_key, time, status, mark) values ("{video_id}", "{account_id}", "{oss_object_key}", "{formatted_time}", 1, "{mark}")"""
+        MysqlHelper.update_values(
+            sql=insert_sql,
+            env="prod",
+            machine="",
+        )
+
+    """
+    获取快手用户主页id
+    """
+    @classmethod
+    def get_kuaishou_videoUserId(cls, mark):
+        select_user_sql = f"""select user_id, channel from agc_channel_data where mark = '{mark}' and  channel  = '快手' ORDER BY id DESC;"""
+        user_list = MysqlHelper.get_values(select_user_sql, "prod")
+        return user_list
+
+    """
+    查询该video_id是否在数据库存在
+    """
+    @classmethod
+    def select_videoUrl_id(cls, video_id):
+        select_user_sql = f"""select video_id from agc_video_url where video_id='{video_id}' ;"""
+        user_list = MysqlHelper.get_values(select_user_sql, "prod")
+        if user_list:
+            return True
+        else:
+            return False
+
+    """快手读取数据 将数据存储到oss上"""
+    @classmethod
+    def get_kuaishou_videoList(cls, data):
+        try:
+            mark = data[0]['mark']
+            token = data[0]['token']
+            feishu_id = data[0]['feishu_id']
+            channel_id = data[0]['channel'][0]
+            channel = data[0]['channel'][1]
+            Material.insert_user(feishu_id, channel_id, mark, channel)
+            cookie = Material.get_cookie(feishu_id, token, channel)
+            # 获取 用户主页id
+            user_list = cls.get_kuaishou_videoUserId(mark)
+            if len(user_list) == 0:
+                return
+            for i in user_list:
+                account_id = i[0].replace('(', '').replace(')', '').replace(',', '')
+                Common.logger("kuaishou").info(f"用户主页ID:{account_id}")
+                pcursor = ""
+                count = 0
+                while True:
+                    if count > 5:
+                        break
+                    time.sleep(random.randint(10, 50))
+                    url = "https://www.kuaishou.com/graphql"
+                    payload = json.dumps({
+                        "operationName": "visionProfilePhotoList",
+                        "variables": {
+                            "userId": account_id,
+                            "pcursor": pcursor,
+                            "page": "profile"
+                        },
+                        "query": "fragment photoContent on PhotoEntity {\n  id\n  duration\n  caption\n  originCaption\n  likeCount\n  viewCount\n  commentCount\n  realLikeCount\n  coverUrl\n  photoUrl\n  photoH265Url\n  manifest\n  manifestH265\n  videoResource\n  coverUrls {\n    url\n    __typename\n  }\n  timestamp\n  expTag\n  animatedCoverUrl\n  distance\n  videoRatio\n  liked\n  stereoType\n  profileUserTopPhoto\n  musicBlocked\n  __typename\n}\n\nfragment feedContent on Feed {\n  type\n  author {\n    id\n    name\n    headerUrl\n    following\n    headerUrls {\n      url\n      __typename\n    }\n    __typename\n  }\n  photo {\n    ...photoContent\n    __typename\n  }\n  canAddComment\n  llsid\n  status\n  currentPcursor\n  tags {\n    type\n    name\n    __typename\n  }\n  __typename\n}\n\nquery visionProfilePhotoList($pcursor: String, $userId: String, $page: String, $webPageArea: String) {\n  visionProfilePhotoList(pcursor: $pcursor, userId: $userId, page: $page, webPageArea: $webPageArea) {\n    result\n    llsid\n    webPageArea\n    feeds {\n      ...feedContent\n      __typename\n    }\n    hostName\n    pcursor\n    __typename\n  }\n}\n"
+                    })
+                    headers = {
+                        'Accept': '*/*',
+                        'Content-Type': 'application/json',
+                        'Origin': 'https://www.kuaishou.com',
+                        'Cookie': cookie,
+                        'Content-Length': '1260',
+                        'Accept-Language': 'zh-CN,zh-Hans;q=0.9',
+                        'Host': 'www.kuaishou.com',
+                        'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.6.1 Safari/605.1.15',
+                        'Referer': f'https://www.kuaishou.com/profile/{account_id}',
+                        'Accept-Encoding': 'gzip, deflate, br',
+                        'Connection': 'keep-alive'
+                    }
+                    urllib3.disable_warnings()
+                    s = requests.session()
+                    # max_retries=3 重试3次
+                    s.mount('http://', HTTPAdapter(max_retries=3))
+                    s.mount('https://', HTTPAdapter(max_retries=3))
+                    response = s.post(url=url, headers=headers, data=payload, verify=False,
+                                      timeout=10)
+                    response.close()
+                    if response.status_code != 200:
+                        Common.logger("kuaishou").info(
+                            f"接口请求失败,请更换cookie,{response.status_code}")
+                        Feishu.bot('recommend', '快手', f'{mark}:快手cookie失效,请及时更换~', mark)
+                        return
+                    elif "feeds" not in response.json()["data"]["visionProfilePhotoList"]:
+                        Common.logger("kuaishou").info(
+                            f'数据为空{response.json()["data"]["visionProfilePhotoList"]}')
+                        break
+                    elif len(response.json()["data"]["visionProfilePhotoList"]["feeds"]) == 0:
+                        Common.logger("kuaishou").info(
+                            f'数据为空{response.json()["data"]["visionProfilePhotoList"]["feeds"]}')
+                        break
+                    pcursor = response.json()['data']['visionProfilePhotoList']['pcursor']
+                    feeds = response.json()['data']['visionProfilePhotoList']['feeds']
+                    for j in range(len(feeds)):
+                        try:
+                            try:
+                                video_id = feeds[j].get("photo", {}).get("videoResource").get("h264", {}).get("videoId", "")
+                            except KeyError:
+                                video_id = feeds[j].get("photo", {}).get("videoResource").get("hevc", {}).get("videoId", "")
+                            video_url = feeds[j].get('photo', {}).get('photoUrl', "")
+
+                            id = cls.select_videoUrl_id(video_id)
+                            if id:
+                                if count > 5:
+                                    count += 1
+                                    Common.logger("kuaishou").info(
+                                        f"重复视频不在抓取该用户,用户主页id:{account_id}")
+                                    break
+                                continue
+                            channel_name = mark+'/kuaishou'
+                            oss_object_key = Oss.video_sync_upload_oss(video_url, video_id, account_id, channel_name)
+                            status = oss_object_key.get("status")
+                            # 发送 oss
+                            oss_object_key = oss_object_key.get("oss_object_key")
+                            Common.logger("kuaishou").info(f"抖音视频链接oss发送成功,oss地址:{oss_object_key}")
+                            if status == 200:
+                                cls.insert_videoUrl(video_id, account_id, oss_object_key, mark)
+                                Common.logger("kuaishou").info(
+                                    f"视频地址插入数据库成功,视频id:{video_id},用户主页id:{account_id},视频储存地址:{oss_object_key}")
+                        except Exception as e:
+                            Common.logger("kuaishou").warning(f"抓取单条视频异常:{e}\n")
+                            continue
+        except Exception as e:
+            Common.logger("kuaishou").warning(f"抓取异常:{e}\n")
+            return
+
+if __name__ == '__main__':
+    kuaishouAuthor.get_kuaishou_videoList()

+ 25 - 0
requirements.txt

@@ -0,0 +1,25 @@
+loguru==0.6.0
+lxml==4.9.1
+oss2==2.15.0
+psutil==5.9.2
+PyMySQL==1.0.2
+requests==2.27.1
+urllib3==1.26.9
+workalendar==17.0.0
+opencv-python~=4.8.0.74
+crontab~=1.0.1
+mitmproxy~=9.0.1
+bs4~=0.0.1
+beautifulsoup4~=4.11.1
+scikit-learn~=1.3.0
+decorator
+tqdm
+numpy
+proglog
+certifi
+idna
+chardet
+pillow
+imageio
+configparser
+subprocess

+ 0 - 0
video_agc/__init__.py


+ 382 - 0
video_agc/agc_video_method.py

@@ -0,0 +1,382 @@
+import configparser
+import os
+import random
+import subprocess
+import sys
+import time
+import urllib.parse
+
+import requests
+from datetime import datetime
+
+sys.path.append(os.getcwd())
+from common.db import MysqlHelper
+from common.material import Material
+from common import Common, Oss, Feishu
+
+config = configparser.ConfigParser()
+config.read('./config.ini')  # 替换为您的配置文件路径
+video_path = config['PATHS']['VIDEO_PATH']
+srt_path = config['PATHS']['SRT_PATH']
+oss_path = config['PATHS']['OSS_PATH']
+txt_path = config['PATHS']['TXT_PATH']
+
+
+class AgcVidoe():
+
+    # 获取未使用的视频链接
+    @classmethod
+    def get_url_list(cls, user, mark, limit_count):
+        current_time = datetime.now()
+        formatted_time = current_time.strftime("%Y-%m-%d")
+        url_list = f"""SELECT a.video_id,a.account_id,a.oss_object_key FROM agc_video_url a WHERE NOT EXISTS (
+                    SELECT video_id
+                    FROM agc_video_deposit b
+                    WHERE a.oss_object_key = b.oss_object_key AND b.time = '{formatted_time}'
+                ) AND a.account_id = {user} and a.`status` = 1 and a.mark = '{mark}'  limit  {limit_count};"""
+        url_list = MysqlHelper.get_values(url_list, "prod")
+        return url_list
+
+
+    # 随机生成id
+    @classmethod
+    def random_id(cls):
+        now = datetime.now()
+        rand_num = random.randint(10000, 99999)
+        oss_id = "{}{}".format(now.strftime("%Y%m%d%H%M%S"), rand_num)
+        return oss_id
+
+    # 获取已入库的用户id
+    @classmethod
+    def get_user_id(cls, channel_type, mark):
+        account_id = f"""select account_id from agc_video_url where mark = '{mark}' and  oss_object_key LIKE '%{channel_type}%' group by account_id ;"""
+        account_id = MysqlHelper.get_values(account_id, "prod")
+        return account_id
+
+    # 获取已入库数量
+    @classmethod
+    def get_link_count(cls, mark, platform):
+        current_time = datetime.now()
+        formatted_time = current_time.strftime("%Y-%m-%d")
+        count = f"""SELECT COUNT(*) AS total_count FROM ( SELECT audio, account_id FROM agc_video_deposit WHERE time = '{formatted_time}' AND platform = '{platform}' and mark = '{mark}' GROUP BY audio, account_id) AS subquery;"""
+        count = MysqlHelper.get_values(count, "prod")
+        if count == None:
+            count = 0
+        count = str(count).replace('(', '').replace(')', '').replace(',', '')
+        return int(count)
+
+    @classmethod
+    def create_subtitle_file(cls, srt, s_path):
+        # 创建临时字幕文件
+        with open(s_path, 'w') as f:
+            f.write(srt)
+
+    @classmethod
+    def convert_srt_to_ass(cls, s_path, a_path):
+        # 使用 FFmpeg 将 SRT 转换为 ASS
+        subprocess.run(["ffmpeg", "-i", s_path, a_path])
+
+    # 新生成视频上传到对应账号下
+    @classmethod
+    def insert_piaoquantv(cls, oss_object_key, title_list, pq_ids_list):
+        for pq_ids in pq_ids_list:
+            title_list = [item for item in title_list if item is not None]
+            title = random.choice(title_list)
+            url = "https://vlogapi.piaoquantv.com/longvideoapi/crawler/video/send"
+            payload = dict(pageSource='vlog-pages/post/post-video-post', videoPath=oss_object_key, width='720',
+                           height='1280', fileExtensions='mp4', viewStatus='1', title=title,
+                           careModelStatus='1',
+                           token='f04f58d6e664cbc9902660a1e8d20ce6cd7fdb0f', loginUid=pq_ids,
+                           versionCode='719',
+                           machineCode='weixin_openid_o0w175aZ4FJtqVsA1tcozJDJHdDU', appId='wx89e7eb06478361d7',
+                           clientTimestamp='1703337579331',
+                           machineInfo='{"sdkVersion":"3.2.5","brand":"iPhone","language":"zh_CN","model":"iPhone 12 Pro<iPhone13,3>","platform":"ios","system":"iOS 15.6.1","weChatVersion":"8.0.44","screenHeight":844,"screenWidth":390,"pixelRatio":3,"windowHeight":762,"windowWidth":390,"softVersion":"4.1.719"}',
+                           sessionId='1703337560040-27bfe208-a389-f476-db1d-840681e04b32',
+                           subSessionId='1703337569952-8f56d53c-b36d-760e-8abe-0b4a027cd5bd', senceType='1089',
+                           hotSenceType='1089', id='1050', channel='pq')
+
+            payload['videoPath'] = oss_object_key
+            payload['title'] = title
+            data = urllib.parse.urlencode(payload)
+            headers = {
+                'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 15_6_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 MicroMessenger/8.0.44(0x18002c2d) NetType/WIFI Language/zh_CN',
+                'Accept-Encoding': 'gzip,compress,br,deflate',
+                'Referer': 'https://servicewechat.com/wx89e7eb06478361d7/726/page-frame.html',
+                'Content-Type': 'application/x-www-form-urlencoded',
+                'Cookie': 'JSESSIONID=A60D96E7A300A25EA05425B069C8B459'
+            }
+            response = requests.post(url, data=data, headers=headers)
+            data = response.json()
+            code = data["code"]
+            if code == 0:
+                return True
+            else:
+                return False
+
+    # 获取视频链接
+    @classmethod
+    def get_audio_url(cls, uid, mark):
+        cookie = Material.get_houtai_cookie()
+        url = f"https://admin.piaoquantv.com/manager/video/detail/{uid}"
+
+        payload = {}
+        headers = {
+            'authority': 'admin.piaoquantv.com',
+            'accept': 'application/json, text/plain, */*',
+            'accept-language': 'zh-CN,zh;q=0.9',
+            'cache-control': 'no-cache',
+            'cookie': cookie,
+            'pragma': 'no-cache',
+            'referer': f'https://admin.piaoquantv.com/cms/post-detail/{uid}/detail',
+            'sec-ch-ua': '"Not_A Brand";v="8", "Chromium";v="120", "Google Chrome";v="120"',
+            'sec-ch-ua-mobile': '?0',
+            'sec-ch-ua-platform': '"macOS"',
+            'sec-fetch-dest': 'empty',
+            'sec-fetch-mode': 'cors',
+            'sec-fetch-site': 'same-origin',
+            'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
+        }
+
+        response = requests.request("GET", url, headers=headers, data=payload)
+        data = response.json()
+        try:
+            code = data["code"]
+            if code != 0:
+                Common.logger("video").info(
+                    f"未登录,请更换cookie,{data}")
+                Feishu.bot('recommend', '管理后台', '管理后台cookie失效,请及时更换~', mark)
+                return ""
+            audio_url = data["content"]["transedVideoPath"]
+            return audio_url
+        except Exception as e:
+            Common.logger("video").warning(f"获取音频视频链接失败:{e}\n")
+            return ""
+
+
+    # 获取视频时长
+    @classmethod
+    def get_audio_duration(cls, video_url):
+        ffprobe_cmd = [
+            "ffprobe",
+            "-i", video_url,
+            "-show_entries", "format=duration",
+            "-v", "quiet",
+            "-of", "csv=p=0"
+        ]
+        output = subprocess.check_output(ffprobe_cmd).decode("utf-8").strip()
+        return float(output)
+
+
+    #  获取视频文件的时长(秒)
+    @classmethod
+    def get_video_duration(cls, video_file):
+        result = subprocess.run(
+            ["ffprobe", "-v", "error", "-show_entries", "format=duration",
+             "-of", "default=noprint_wrappers=1:nokey=1", video_file],
+            capture_output=True, text=True)
+        return float(result.stdout)
+
+
+    # 计算需要拼接的视频
+    @classmethod
+    def concat_videos_with_subtitles(cls, videos, audio_duration, platform):
+        # 计算视频文件列表总时长
+        total_video_duration = sum(cls.get_video_duration(video_file[3]) for video_file in videos)
+        if platform == "koubo":
+            # 视频时长大于音频时长
+            if total_video_duration > audio_duration:
+                return videos
+            # 计算音频秒数与视频秒数的比率,然后加一得到需要的视频数量
+            video_audio_ratio = audio_duration / total_video_duration
+            videos_needed = int(video_audio_ratio) + 2
+            trimmed_video_list = videos * videos_needed
+            return trimmed_video_list
+        else:
+            # 如果视频总时长小于音频时长,则不做拼接
+            if total_video_duration < audio_duration:
+                Common.logger("video").info(f"时长小于等于目标时长,不做视频拼接")
+                return ""
+            # 如果视频总时长大于音频时长,则截断视频
+            trimmed_video_list = []
+            remaining_duration = audio_duration
+            for video_file in videos:
+                video_duration = cls.get_video_duration(video_file[3])
+                if video_duration <= remaining_duration:
+                    # 如果视频时长小于或等于剩余时长,则将整个视频添加到列表中
+                    trimmed_video_list.append(video_file)
+                    remaining_duration -= video_duration
+                else:
+                    trimmed_video_list.append(video_file)
+                    break
+            return trimmed_video_list
+
+
+    # 已使用视频链接存表
+    @classmethod
+    def insert_videoAudio(cls, video_files, uid, platform, mark):
+        current_time = datetime.now()
+        formatted_time = current_time.strftime("%Y-%m-%d")
+        for j in video_files:
+            insert_sql = f"""INSERT INTO agc_video_deposit (audio, video_id, account_id, oss_object_key, time, platform, mark) values ('{uid}', '{j[0]}', '{j[1]}', '{j[2]}', '{formatted_time}', {platform}, {mark})"""
+            MysqlHelper.update_values(
+                sql=insert_sql,
+                env="prod",
+                machine="",
+            )
+
+    # 视频拼接
+    @classmethod
+    def concatenate_videos(cls, videos, audio_duration, audio_video, platform, s_path, v_path, mark, t_path):
+        video_files = cls.concat_videos_with_subtitles(videos, audio_duration, platform)
+        if video_files == "":
+            return ""
+        print(f"{mark}的{platform}:开始拼接视频喽~~~")
+        Common.logger("video").info(f"{mark}的{platform}:开始拼接视频喽~~~")
+        # 将使用视频写入文件中
+        with open(f'{t_path}', 'w') as f:
+            for video_file in video_files:
+                f.write(f"file '{video_file[3]}'\n")
+
+        # 字幕参数
+        if os.path.exists(s_path):
+            subtitle_cmd = f"subtitles={s_path}:force_style='Fontsize=11,Fontname=Hiragino Sans GB,Outline=0,PrimaryColour=&H000000,SecondaryColour=&H000000'"
+        else:
+            subtitle_cmd = "drawtext=text='分享、转发给群友':fontsize=50:fontcolor=black:x=(w-text_w)/2:y=h-text_h-50"
+        # 背景色参数
+        background_cmd = "drawbox=y=ih-158:color=yellow@1.0:width=iw:height=0:t=fill"
+        # 分辨率参数
+        resolution_cmd = "-s", "480x854"
+        # 多线程数
+        num_threads = 4
+        # 构建 FFmpeg 命令
+        ffmpeg_cmd = [
+            "ffmpeg",
+            "-f", "concat",
+            "-safe", "0",
+            "-i", f"{t_path}",  # 视频文件列表
+            "-i", audio_video,  # 音频文件
+            "-c:v", "libx264",
+            "-c:a", "aac",
+            "-threads", str(num_threads),
+            "-vf", f"{background_cmd},{subtitle_cmd}",  # 添加背景色和字幕
+            "-b:v", "9997K",
+            "-shortest",  # 保持与音频时长一致
+            "-map", "0:v:0",  # 映射第一个输入的视频流
+            "-map", "1:a:0",  # 映射第二个输入的音频流
+            *resolution_cmd,  # 添加分辨率参数
+            "-y",  # 覆盖输出文件
+            v_path
+        ]
+        # 执行 FFmpeg 命令
+        subprocess.run(ffmpeg_cmd)
+        print(f"{mark}的{platform}:视频拼接成功啦~~~")
+        Common.logger("video").info(f"{mark}的{platform}:视频拼接成功啦~~~")
+        return video_files
+
+    @classmethod
+    def video_stitching(cls, ex_list):
+        try:
+            pq_ids = ex_list[0]["pq_id"]
+            pq_ids_list = pq_ids.split(',')
+
+            mark = ex_list[0]["mark"]
+            feishu_id = ex_list[0]["feishu_id"]
+            video_call = ex_list[0]["video_call"]
+            parts = video_call.split(',')
+            result = []
+            for part in parts:
+                sub_parts = part.split('--')
+                result.append(sub_parts)
+            link = result[0][0]
+            yhmw_all_count = result[0][1]
+            if int(yhmw_all_count) == 0:
+                yhmw_count = 0
+            else:
+                yhmw_count = int(int(yhmw_all_count)/2)
+            # kb_link = result[1][0]
+            kb_count = int(result[1][1])
+            channel = ['douyin', 'kuaishou', 'koubo']
+            for platform in channel:
+                limit_count = 35
+                count = cls.get_link_count(mark, platform)
+                if platform == "douyin" and count >= yhmw_count:
+                    continue
+                elif platform == "kuaishou" and count >= yhmw_count:
+                    continue
+                elif platform == "koubo":
+                    link = result[1][0]
+                    limit_count = 1
+                    if kb_count >= count or kb_count == 0:
+                        break
+                # 获取音频类型+字幕+标题
+                uid, srt, title_list = Material.get_all_data(feishu_id, link, mark)
+                # 获取已入库的用户id
+                user_id = cls.get_user_id(platform, mark)
+                user = random.choice(user_id)
+                user = str(user).replace('(', '').replace(')', '').replace(',', '')
+                Common.logger("video").info(f"{mark}的{platform}渠道获取的用户ID:{user}")
+                # 获取 未使用的视频链接
+                url_list = cls.get_url_list(user, mark, limit_count)
+                if url_list == None:
+                    Common.logger("video").info(f"未使用视频链接为空:{url_list}")
+                    return
+                videos = [list(item) for item in url_list]
+                # 下载视频
+                videos = Oss.get_oss_url(videos, video_path)
+                # srt 文件地址
+                s_path = srt_path + mark + ".srt"
+                # ass 文件地址
+                t_path = txt_path + mark + ".txt"
+                # 最终生成视频地址
+                v_path = oss_path + mark + ".mp4"
+                if srt:
+                    # 创建临时字幕文件
+                    cls.create_subtitle_file(srt, s_path)
+                    Common.logger("video").info(f"SRT 文件目录创建成功")
+                # 获取音频
+                audio_video = cls.get_audio_url(uid, mark)
+                Common.logger("video").info(f"获取需要拼接的音频成功")
+                # 获取音频秒数
+                audio_duration = cls.get_audio_duration(audio_video)
+                Common.logger("video").info(f"获取需要拼接的音频秒数为:{audio_duration}")
+                video_files = cls.concatenate_videos(videos, audio_duration, audio_video, platform, s_path, v_path, mark, t_path)
+                if video_files == "":
+                    break
+                # 随机生成视频oss_id
+                oss_id = cls.random_id()
+                Common.logger("video").info(f"上传到 OSS 生成视频id为:{oss_id}")
+                # 上传 oss
+                oss_object_key = Oss.stitching_sync_upload_oss(v_path, oss_id)
+                status = oss_object_key.get("status")
+                if status == 200:
+                    # 获取 oss 视频地址
+                    oss_object_key = oss_object_key.get("oss_object_key")
+                    Common.logger("video").info(f"拼接视频发送成功,OSS 地址:{oss_object_key}")
+                    time.sleep(10)
+                    # 已使用视频存入数据库
+                    Common.logger("video").info(f"开始已使用视频存入数据库")
+                    cls.insert_videoAudio(video_files, uid, platform, mark)
+                    Common.logger("video").info(f"完成已使用视频存入数据库")
+                    Common.logger("video").info(f"开始视频添加到对应用户")
+                    piaoquantv = cls.insert_piaoquantv(oss_object_key, title_list, pq_ids_list)
+                    if piaoquantv:
+                        Common.logger("video").info(f"视频添加到对应用户成功")
+                if os.path.isfile(s_path):
+                    os.remove(s_path)
+                    os.remove(t_path)
+                else:
+                    Common.logger("video").info(f"文件不存在{s_path}")
+                if os.path.isfile(v_path):
+                    os.remove(v_path)
+                else:
+                    Common.logger("video").info(f"文件不存在{v_path}")
+                for video in videos:
+                    filename = video[2].split("/")[-1]
+                    os.remove(f'{video_path}{filename}.mp4')
+                Common.logger("video").info(f"{mark}的临时文件删除成功")
+                break
+        except Exception as e:
+            Common.logger("video").warning(f"拼接视频失败了:{e}\n")
+            return
+
+

+ 0 - 0
video_agc/path/__init__.py


+ 0 - 0
video_agc/path/oss_video/__init__.py


+ 0 - 0
video_agc/path/srt/__init__.py


+ 0 - 0
video_agc/path/txt/__init__.py


+ 0 - 0
video_agc/path/video/__init__.py