瀏覽代碼

爆款视频功能上线

zhangyong 11 月之前
父節點
當前提交
7e0e1dd066
共有 4 個文件被更改,包括 303 次插入3 次删除
  1. 64 0
      agc_bk_main.py
  2. 17 0
      common/aliyun_oss_uploading.py
  3. 38 0
      common/material.py
  4. 184 3
      video_agc/agc_video_method.py

+ 64 - 0
agc_bk_main.py

@@ -0,0 +1,64 @@
+import re
+
+from common import Material, Common, Feishu
+from video_agc.agc_video_method import AgcVidoe
+import concurrent.futures
+import schedule
+import time
+
+
+# 记录今天已经返回的用户名
+returned_usernames_today = []
+def video_start(user_data):
+    global returned_usernames_today
+    user_data_mark = user_data["mark"]
+    mark_name = user_data['mark_name']
+
+    # 开始准备执行生成视频脚本
+    if user_data_mark is not None and user_data_mark in returned_usernames_today:
+        Common.logger("video").info(f"视频脚本参数中的用户名 {user_data_mark} 今天已经返回过,不再启动线程。今天已经返回的用户名:{returned_usernames_today}")
+        print(f"视频脚本参数中的用户名 {user_data_mark} 今天已经返回过,不再启动线程。")
+        return  # 如果返回了某个用户名,并且今天已经返回过,则不启动线程
+    else:
+        print(f"视频脚本参数{user_data}")
+        mark = AgcVidoe.video_bk_stitching(user_data)
+        print(f"返回用户名{mark}")
+        if mark:
+            returned_usernames_today.append(mark)
+            Common.logger("video").info(f"返回用户名{mark}")
+
+
+# gs_name_list = Material.feishu_bk_list()
+# video_start(gs_name_list[0])
+
+def clear_returned_usernames():
+    returned_usernames_today.clear()
+    print("returned_usernames_today 已清空")
+
+# 定义定时任务
+def video_task():
+    print("开始执行生成视频脚.")
+    data = Material.feishu_bk_list()
+    # 创建一个线程池
+    with concurrent.futures.ThreadPoolExecutor() as executor:
+        futures = [executor.submit(video_start, user_data) for user_data in data]
+        # 等待所有任务执行完成
+        for future in concurrent.futures.as_completed(futures):
+            try:
+                # 获取每个任务的执行结果
+                result = future.result()
+                print("处理结果:", result)
+            except Exception as e:
+                print("处理任务时出现异常:", e)
+    print("执行生成视频脚结束")
+
+# 每天12点30清空集合
+schedule.every().day.at("12:30").do(clear_returned_usernames)
+
+#每10分钟执行次脚本
+schedule.every(10).minutes.do(video_task)
+
+
+while True:
+    schedule.run_pending()
+    time.sleep(1)

+ 17 - 0
common/aliyun_oss_uploading.py

@@ -79,3 +79,20 @@ class Oss():
             except Exception:
                 continue
         return list
+
+    @classmethod
+    def get_bk_url(cls, videos, video_path, video):
+        for i in range(3):
+            payload = {}
+            headers = {}
+            response = requests.request("GET", videos, headers=headers, data=payload)
+            if response.status_code == 200:
+                video_url = []
+                # 以二进制写入模式打开文件
+                video = video_path+video+'.mp4'
+                with open(f"{video}", "wb") as file:
+                    # 将响应内容写入文件
+                    file.write(response.content)
+                video_url.append(video)
+                return video_url
+        return ''

+ 38 - 0
common/material.py

@@ -73,6 +73,27 @@ class Material():
             return ''
         return list
 
+    # 获取爆款汇总表所有
+    @classmethod
+    def feishu_bk_list(cls):
+        try:
+            summary = Feishu.get_values_batch("summary", "xJxEUH")
+            list = []
+            for row in summary[1:]:
+                mark = row[0]
+                mark_name = row[1]
+                feishu_id = row[3]
+                video_call = row[4]
+                pq_id = row[5]
+                if pq_id:
+                    number = {"mark": mark, "feishu_id": feishu_id, "video_call": video_call, "pq_id": pq_id,
+                              "mark_name": mark_name}
+                    list.append(number)
+        except Exception as e:
+            Common.logger("gensui").warning(f"抓取异常:{e}\n")
+            return ''
+        return list
+
     # 获取管理后台cookie
     @classmethod
     def get_houtai_cookie(cls):
@@ -216,6 +237,23 @@ class Material():
             if len(id_list) < 2:
                 return uid1, srt, video_list
 
+        # 获取音频类型+字幕+标题
+
+    @classmethod
+    def get_allbk_data(cls, feishu_id, link, mark):
+        list_data = []
+        # 获取音频类型+字幕
+        all_data = Feishu.get_values_batch(feishu_id, link)
+        for row in all_data[1:]:
+            uid = row[0]
+            text = row[1]
+            video = row[2]
+            number = {"uid": uid, "text": text, "video": video}
+            if uid:
+                list_data.append(number)
+        return list_data
+
+
     # 获取站内视频id
     @classmethod
     def get_zn_user(cls, feishu_id, link):

+ 184 - 3
video_agc/agc_video_method.py

@@ -296,8 +296,11 @@ class AgcVidoe():
     @classmethod
     def concat_videos_with_subtitles(cls, videos, audio_duration, platform, mark):
         # 计算视频文件列表总时长
-        total_video_duration = sum(cls.get_video_duration(video_file[3]) for video_file in videos)
-        if platform == "koubo" or platform == "zhannei":
+        if platform == "baokuai":
+            total_video_duration = sum(cls.get_video_duration(video_file) for video_file in videos)
+        else:
+            total_video_duration = sum(cls.get_video_duration(video_file[3]) for video_file in videos)
+        if platform == "koubo" or platform == "zhannei" or platform == "baokuai":
             # 视频时长大于音频时长
             if total_video_duration > audio_duration:
                 return videos
@@ -664,4 +667,182 @@ class AgcVidoe():
             return ''
         except Exception as e:
             Common.logger("gs_video").warning(f"{mark}的视频拼接失败:{e}\n")
-            return ''
+            return ''
+
+    # 爆款跟随任务
+    @classmethod
+    def video_bk_stitching(cls, ex_list):
+        pq_ids = ex_list["pq_id"]
+        pq_ids_list = pq_ids.split(',')  # 账号ID
+        mark_name = ex_list['mark_name']  # 负责人
+        mark = ex_list["mark"]  # 标示
+        feishu_id = ex_list["feishu_id"]  # 飞书文档ID
+        video_call = ex_list["video_call"] #脚本sheet
+        platform = 'baokuai'
+        list_data = Material.get_allbk_data(feishu_id, video_call, mark)
+        # 如果没有该文件目录则创建,有文件目录的话 则删除文件
+        s_path, v_path, video_path_url, v_oss_path = cls.create_folders(mark)
+        for data in list_data:
+            try:
+                uid = data['uid']  # 音频id
+                srt = data['text']  # srt
+                videos = data['video'].split(',')
+                if srt:
+                    # 创建临时字幕文件
+                    cls.create_subtitle_file(srt, s_path)
+                    Common.logger("bk_video").info(f"S{mark} 文件目录创建成功")
+                    # 获取音频
+                audio_video, audio_title = cls.get_audio_url(uid, mark, mark_name)
+                Common.logger("bk_video").info(f"{mark}获取需要拼接的音频成功")
+                # 获取音频秒数
+                audio_duration = cls.get_audio_duration(audio_video)
+                video = random.choice(videos)
+                video_url = cls.get_zn_video(video, mark, mark_name)
+                download_video = Oss.get_bk_url(video_url, video_path_url, video)
+                if download_video:
+                    video_files = cls.bk_concatenate_videos(download_video, audio_duration, audio_video, platform, s_path, v_path, mark, v_oss_path)
+                    if video_files == "":
+                        Common.logger("bk_video").info(f"{mark}的{platform}渠道使用拼接视频为空")
+                        continue
+                    if os.path.isfile(v_oss_path):
+                        Common.logger("bk_video").info(f"{mark}的{platform}渠道新视频生成成功")
+                    else:
+                        Common.logger("bk_video").info(f"{mark}的{platform}渠道新视频生成失败")
+                        continue
+                    # 随机生成视频oss_id
+                    oss_id = cls.random_id()
+                    # 获取新生成视频时长
+                    v_path_duration = cls.get_audio_duration(v_oss_path)
+                    if v_path_duration > audio_duration + 3 or v_path_duration < audio_duration - 3:
+                        print(f"{mark}最终生成视频秒数错误,生成了:{v_path_duration}秒,实际秒数{audio_duration}")
+                        Common.logger("gs_video").info(
+                            f"{mark}最终生成视频秒数错误,生成了:{v_path_duration}秒,实际秒数{audio_duration}")
+                        continue
+                    # 上传 oss
+                    Common.logger("bk_video").info(f"{mark}上传到 OSS 生成视频id为:{oss_id}")
+                    oss_object_key = Oss.stitching_sync_upload_oss(v_oss_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("bk_video").info(f"{mark}拼接视频发送成功,OSS 地址:{oss_object_key}")
+                        time.sleep(10)
+                        Common.logger("bk_video").info(f"{mark}开始视频添加到对应用户")
+                        piaoquantv = cls.insert_piaoquantv(oss_object_key, audio_title, pq_ids_list)
+                        if piaoquantv:
+                            Common.logger("bk_video").info(f"{mark}视频添加到对应用户成功")
+                        if os.path.isfile(v_oss_path):
+                            os.remove(v_oss_path)
+                        if os.path.isfile(v_path):
+                            os.remove(v_path)
+                        if os.path.isfile(s_path):
+                            os.remove(s_path)
+                            # 清空所有mp4数据
+                        for file_path in download_video:
+                            os.remove(file_path)
+                            print(f"已删除文件:{file_path}")
+                else:
+                    Common.logger("bk_video").info(f"{mark}的视频下载视频")
+                    continue
+            except Exception as e:
+                Common.logger("bk_video").warning(f"{mark}的视频拼接失败:{e}\n")
+                continue
+        Feishu.bot('recommend', 'AGC完成通知', f'今日脚本爆款视频拼接任务完成,共{len(list_data)}条', mark.split("-")[0], mark_name)
+        return mark
+
+    @classmethod
+    def get_zn_video(cls, video, mark, mark_name):
+        cookie = Material.get_houtai_cookie()
+        url = f"https://admin.piaoquantv.com/manager/video/detail/{video}"
+        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/{video}/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()
+        code = data["code"]
+        if code != 0:
+            if "-" in mark:
+                mark1 = mark.split("-")[0]
+                Common.logger("video").info(
+                    f"未登录,请更换cookie,{data}")
+                Feishu.bot('recommend', '管理后台', '管理后台cookie失效,请及时更换~', mark1, mark_name)
+                return
+        video_url = data["content"]["transedVideoPath"]
+        return video_url
+
+    # text文件没有则创建目录
+    @classmethod
+    def bk_text_folders(cls, mark):
+        oss_id = cls.random_id()
+        v_text_url = config['PATHS']['VIDEO_PATH'] + mark + "/text/"
+        if not os.path.exists(v_text_url):
+            os.makedirs(v_text_url)
+        # srt 文件地址
+        text_path = v_text_url + mark + f"{str(oss_id)}.text"
+        return text_path
+
+    # 爆款视频拼接
+    @classmethod
+    def bk_concatenate_videos(cls, videos, audio_duration, audio_video, platform, s_path, v_path, mark, v_oss_path):
+        text_ptah = cls.bk_text_folders(mark)
+        video_files = cls.concat_videos_with_subtitles(videos, audio_duration, platform, mark)
+        with open(text_ptah, 'w') as f:
+            for file in video_files:
+                f.write(f"file '{file}'\n")
+        Common.logger("video").info(f"{mark}的{platform}视频文件:{video_files}")
+        if video_files == "":
+            return ""
+        print(f"{mark}的{platform}:开始拼接视频喽~~~")
+        Common.logger("video").info(f"{mark}的{platform}:开始拼接视频喽~~~")
+        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'"
+            subtitle_cmd = f"subtitles={s_path}:force_style='Fontsize=12,Fontname=wqy-zenhei,Bold=1,Outline=0,PrimaryColour=&H000000,SecondaryColour=&H000000'"
+        else:
+            # subtitle_cmd = "drawtext=text='分享、转发给群友':fontsize=28:fontcolor=black:x=(w-text_w)/2:y=h-text_h-15"
+            subtitle_cmd = "drawtext=text='分享、转发给群友':x=(w-text_w)/2:y=h-text_h-15:fontsize=28:fontcolor=black:fontfile=/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc"
+        # 背景色参数
+        background_cmd = "drawbox=y=ih-65:color=yellow@1.0:width=iw:height=0:t=fill"
+        # 多线程数
+        num_threads = 4
+        # 构建 FFmpeg 命令,生成视频
+        ffmpeg_cmd_oss = [
+            "ffmpeg",
+            "-f", "concat",
+            "-safe", "0",
+            "-i", f"{text_ptah}",  # 视频文件列表
+            "-i", audio_video,  # 音频文件
+            "-c:v", "libx264",
+            "-c:a", "aac",
+            "-threads", str(num_threads),
+            "-vf", f"scale=320x480,{background_cmd},{subtitle_cmd}",  # 添加背景色和字幕
+            "-t", str(int(audio_duration)),  # 保持与音频时长一致
+            "-map", "0:v:0",  # 映射第一个输入的视频流
+            "-map", "1:a:0",  # 映射第二个输入的音频流
+            "-y",  # 覆盖输出文件
+            v_oss_path
+        ]
+        try:
+            subprocess.run(ffmpeg_cmd_oss)
+            print("视频处理完成!")
+            if os.path.isfile(text_ptah):
+                os.remove(text_ptah)
+        except subprocess.CalledProcessError as e:
+            print(f"视频处理失败:{e}")
+        print(f"{mark}:视频拼接成功啦~~~")
+        Common.logger("video").info(f"{mark}:视频拼接成功啦~~~")
+        return video_files