zhangyong 4 月之前
父節點
當前提交
83e8ce7125
共有 5 個文件被更改,包括 1486 次插入538 次删除
  1. 110 156
      common/ffmpeg.py
  2. 351 0
      common/ffmpeg_test.py
  3. 0 1
      job_fj.py
  4. 303 381
      video_rewriting/video_processor.py
  5. 722 0
      video_rewriting/video_processor1.py

+ 110 - 156
common/ffmpeg.py

@@ -1,10 +1,25 @@
 
 
+import asyncio
+import os
 import subprocess
 import subprocess
 import time
 import time
+from typing import List
+
+import cv2
+from loguru import logger
+from mutagen.mp3 import MP3
 
 
 
 
 
 
 class FFmpeg():
 class FFmpeg():
+    """获取mp3时长"""
+    @classmethod
+    def get_mp3_duration(cls, file_path):
+        audio = MP3(file_path)
+        duration = audio.info.length
+        if duration:
+            return int(duration)
+        return 0
 
 
     """
     """
     时间转换
     时间转换
@@ -22,58 +37,24 @@ class FFmpeg():
     """
     """
     @classmethod
     @classmethod
     def get_video_duration(cls, video_url):
     def get_video_duration(cls, video_url):
-        try:
-            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)
-        except Exception as e:
-            return 0
+        cap = cv2.VideoCapture(video_url)
+        if cap.isOpened():
+            rate = cap.get(5)
+            frame_num = cap.get(7)
+            duration = int(frame_num / rate)
+            return duration
+        return 0
 
 
+    """视频转为640像素"""
 
 
-    """
-    获取视频文件的时长(秒)
-    """
     @classmethod
     @classmethod
-    def get_videos_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 video_tailor(cls, video_url):
-        output_video_path = ''
+    def video_640(cls, video_path, file_path):
+        video_url = file_path + 'pixelvideo.mp4'
         try:
         try:
-            # 获取视频的原始宽高信息
-            width, height = cls.get_w_h_size(video_url)
-            # 计算裁剪后的高度
-            new_height = int(height * 0.8)
-            # 构建 FFmpeg 命令,裁剪视频高度为原始高度的70%,并将宽度缩放为320x480
-            ffmpeg_cmd = [
-                "ffmpeg",
-                "-i", video_url,
-                "-vf", f"crop={width}:{new_height},scale=320:480",
-                "-c:v", "libx264",
-                "-c:a", "aac",
-                "-y",
-                output_video_path
-            ]
-            # 执行 FFmpeg 命令
-            subprocess.run(ffmpeg_cmd, check=True)
-            return output_video_path
+            cls.asyncio_run_subprocess(["ffmpeg", "-i", video_path, "-vf", "scale=360:640", video_url], timeout=420)
+            return video_url
         except Exception as e:
         except Exception as e:
-            return None
-
+            return video_url
     """
     """
     获取视频宽高
     获取视频宽高
     """
     """
@@ -81,10 +62,10 @@ class FFmpeg():
     def get_w_h_size(cls, new_video_path):
     def get_w_h_size(cls, new_video_path):
         try:
         try:
             # 获取视频的原始宽高信息
             # 获取视频的原始宽高信息
-            ffprobe_cmd = f"ffprobe -v error -select_streams v:0 -show_entries stream=width,height -of csv=p=0 {new_video_path}"
-            ffprobe_process = subprocess.Popen(ffprobe_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
-            output, _ = ffprobe_process.communicate()
-            output_decoded = output.decode().strip()
+            ffprobe_cmd = cls.asyncio_run_subprocess(
+                ["ffprobe", "-v", "error", "-select_streams", "v:0", "-show_entries", "stream=width,height", "-of",
+                 "csv=p=0", new_video_path], timeout=10)
+            output_decoded = ffprobe_cmd.strip()
             split_output = [value for value in output_decoded.split(',') if value.strip()]
             split_output = [value for value in output_decoded.split(',') if value.strip()]
             height, width = map(int, split_output)
             height, width = map(int, split_output)
             return width, height
             return width, height
@@ -98,25 +79,24 @@ class FFmpeg():
     def video_crop(cls, new_video_path, video_path_url, pw_random_id):
     def video_crop(cls, new_video_path, video_path_url, pw_random_id):
         crop_url = video_path_url + str(pw_random_id) + 'crop.mp4'
         crop_url = video_path_url + str(pw_random_id) + 'crop.mp4'
         # 获取视频的原始宽高信息
         # 获取视频的原始宽高信息
-        ffprobe_cmd = f"ffprobe -v error -select_streams v:0 -show_entries stream=width,height -of csv=p=0 {new_video_path}"
-        ffprobe_process = subprocess.Popen(ffprobe_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
-        output, _ = ffprobe_process.communicate()
-        width, height = map(int, output.decode().strip().split(','))
-
+        ffprobe_cmd = cls.asyncio_run_subprocess(
+            ["ffprobe", "-v", "error", "-select_streams", "v:0", "-show_entries", "stream=width,height", "-of",
+             "csv=p=0", new_video_path], timeout=10)
+        width, height = map(int, ffprobe_cmd.strip().split(','))
         # 计算裁剪后的高度
         # 计算裁剪后的高度
         new_height = int(height * 0.8)
         new_height = int(height * 0.8)
 
 
         # 构建 FFmpeg 命令,裁剪视频高度为原始高度的80%
         # 构建 FFmpeg 命令,裁剪视频高度为原始高度的80%
-        ffmpeg_cmd = [
-            "ffmpeg",
-            "-i", new_video_path,
-            "-vf", f"crop={width}:{new_height}",
-            "-c:v", "libx264",
-            "-c:a", "aac",
-            "-y",
-            crop_url
-        ]
-        subprocess.run(ffmpeg_cmd)
+        cls.asyncio_run_subprocess(
+            [
+                "ffmpeg",
+                "-i", new_video_path,
+                "-vf", f"crop={width}:{new_height}",
+                "-c:v", "libx264",
+                "-c:a", "aac",
+                "-y",
+                crop_url
+            ], timeout=240)
         return crop_url
         return crop_url
 
 
     """
     """
@@ -128,11 +108,11 @@ class FFmpeg():
         # 获取视频时长
         # 获取视频时长
         total_duration = cls.get_video_duration(new_video_path)
         total_duration = cls.get_video_duration(new_video_path)
         if total_duration == 0:
         if total_duration == 0:
-            return new_video_path
+            return gg_duration_url
         duration = int(total_duration) - int(gg_duration_total)
         duration = int(total_duration) - int(gg_duration_total)
         if int(total_duration) < int(gg_duration_total):
         if int(total_duration) < int(gg_duration_total):
-            return new_video_path
-        ffmpeg_cmd = [
+            return gg_duration_url
+        cls.asyncio_run_subprocess([
             "ffmpeg",
             "ffmpeg",
             "-i", new_video_path,
             "-i", new_video_path,
             "-c:v", "libx264",
             "-c:v", "libx264",
@@ -140,8 +120,7 @@ class FFmpeg():
             "-t", str(duration),
             "-t", str(duration),
             "-y",
             "-y",
             gg_duration_url
             gg_duration_url
-        ]
-        subprocess.run(ffmpeg_cmd)
+        ], timeout=360)
         return gg_duration_url
         return gg_duration_url
 
 
     """
     """
@@ -157,13 +136,8 @@ class FFmpeg():
         # 获取视频的原始宽高信息
         # 获取视频的原始宽高信息
         jpg_url = video_path_url + str(pw_random_id) + 'png.jpg'
         jpg_url = video_path_url + str(pw_random_id) + 'png.jpg'
         # 获取视频时长
         # 获取视频时长
-        total_duration = cls.get_video_duration(new_video_path)
-        if total_duration == 0:
-            return new_video_path
-        time_offset = total_duration - 1  # 提取倒数第一秒的帧
-        # 获取视频最后一秒,生成.jpg
-        subprocess.run(
-            ['ffmpeg', '-ss', str(time_offset), '-i', new_video_path, '-t', str(total_duration), '-vf', 'fps=1,scale=360:640', "-y", jpg_url])
+        cls.asyncio_run_subprocess(
+            ["ffmpeg", "-sseof", "-1", '-i', new_video_path, '-frames:v', '1', "-y", jpg_url], timeout=120)
         return jpg_url
         return jpg_url
 
 
     """
     """
@@ -172,15 +146,13 @@ class FFmpeg():
     @classmethod
     @classmethod
     def get_video_mp3(cls, video_file, video_path_url, pw_random_id):
     def get_video_mp3(cls, video_file, video_path_url, pw_random_id):
         pw_mp3_path = video_path_url + str(pw_random_id) +'pw_video.mp3'
         pw_mp3_path = video_path_url + str(pw_random_id) +'pw_video.mp3'
-        command = [
+        cls.asyncio_run_subprocess([
             'ffmpeg',
             'ffmpeg',
             '-i', video_file,
             '-i', video_file,
             '-q:a', '0',
             '-q:a', '0',
             '-map', 'a',
             '-map', 'a',
-            # '-codec:a', 'libmp3lame',  # 指定 MP3 编码器
             pw_mp3_path
             pw_mp3_path
-        ]
-        subprocess.run(command)
+        ], timeout=120)
         time.sleep(1)
         time.sleep(1)
         return pw_mp3_path
         return pw_mp3_path
 
 
@@ -188,9 +160,9 @@ class FFmpeg():
     @classmethod
     @classmethod
     def update_video_h_w(cls, new_video_path, video_path_url, pw_random_id):
     def update_video_h_w(cls, new_video_path, video_path_url, pw_random_id):
         video_h_w_path = video_path_url + str(pw_random_id) +'video_h_w_video.mp4'
         video_h_w_path = video_path_url + str(pw_random_id) +'video_h_w_video.mp4'
-
-        ffmpeg_cmd = f"ffmpeg -i {new_video_path} -vf 'scale=640:ih*640/iw,pad=iw:iw*16/9:(ow-iw)/2:(oh-ih)/2' {video_h_w_path}"
-        subprocess.run(ffmpeg_cmd, shell=True)
+        cls.asyncio_run_subprocess(
+            ["ffmpeg", "-i", new_video_path, "-vf", "scale=640:ih*640/iw,pad=iw:iw*16/9:(ow-iw)/2:(oh-ih)/2",
+             video_h_w_path], timeout=420)
         return video_h_w_path
         return video_h_w_path
 
 
     """横屏视频顶部增加字幕"""
     """横屏视频顶部增加字幕"""
@@ -211,19 +183,17 @@ class FFmpeg():
             f.write(f"1\n{start_time} --> {end_time}\n{new_text}\n\n")
             f.write(f"1\n{start_time} --> {end_time}\n{new_text}\n\n")
         subtitle_cmd = f"subtitles={single_video_srt}:force_style='Fontsize=12,Fontname=wqy-zenhei,Outline=2,PrimaryColour=&H00FFFF,SecondaryColour=&H000000,Bold=1,MarginV=225'"
         subtitle_cmd = f"subtitles={single_video_srt}:force_style='Fontsize=12,Fontname=wqy-zenhei,Outline=2,PrimaryColour=&H00FFFF,SecondaryColour=&H000000,Bold=1,MarginV=225'"
         draw = f"{subtitle_cmd}"
         draw = f"{subtitle_cmd}"
-        ffmpeg_cmd = [
+        cls.asyncio_run_subprocess([
             "ffmpeg",
             "ffmpeg",
             "-f", "concat",
             "-f", "concat",
             "-safe", "0",
             "-safe", "0",
             "-i", single_video_txt,
             "-i", single_video_txt,
             "-c:v", "libx264",
             "-c:v", "libx264",
             "-c:a", "aac",
             "-c:a", "aac",
-            # '-vf', f"scale=640x360",
             "-vf", draw,
             "-vf", draw,
             "-y",
             "-y",
             single_video
             single_video
-        ]
-        subprocess.run(ffmpeg_cmd)
+        ], timeout=500)
         return single_video
         return single_video
 
 
 
 
@@ -231,7 +201,7 @@ class FFmpeg():
      生成片尾视频
      生成片尾视频
     """
     """
     @classmethod
     @classmethod
-    def pw_video(cls, jpg_url, video_path_url, pw_url, pw_srt, pw_random_id, pw_mp3_path):
+    def pw_video(cls, jpg_url, video_path_url, pw_srt, pw_random_id, pw_mp3_path):
         # 添加音频到图片
         # 添加音频到图片
         """
         """
         jpg_url 图片地址
         jpg_url 图片地址
@@ -249,7 +219,7 @@ class FFmpeg():
         # 片尾位置
         # 片尾位置
         pw_url_path = video_path_url + str(pw_random_id) + 'pw_video.mp4'
         pw_url_path = video_path_url + str(pw_random_id) + 'pw_video.mp4'
         # 获取视频时长
         # 获取视频时长
-        pw_duration = cls.get_video_duration(pw_url)
+        pw_duration = cls.get_mp3_duration(pw_mp3_path)
         if pw_duration == 0:
         if pw_duration == 0:
             return pw_url_path
             return pw_url_path
         time.sleep(2)
         time.sleep(2)
@@ -260,7 +230,7 @@ class FFmpeg():
         bg_position_offset = (int(360) - 360//8) / 1.75
         bg_position_offset = (int(360) - 360//8) / 1.75
         background_cmd = f"drawbox=y=(ih-{int(360)}/2-{bg_position_offset}):color=yellow@1.0:width=iw:height={int(360)}/4:t=fill"
         background_cmd = f"drawbox=y=(ih-{int(360)}/2-{bg_position_offset}):color=yellow@1.0:width=iw:height={int(360)}/4:t=fill"
 
 
-        ffmpeg_cmd = [
+        cls.asyncio_run_subprocess([
             'ffmpeg',
             'ffmpeg',
             '-loop', '1',
             '-loop', '1',
             '-i', jpg_url,  # 输入的图片文件
             '-i', jpg_url,  # 输入的图片文件
@@ -273,49 +243,22 @@ class FFmpeg():
             '-shortest',  # 确保输出视频的长度与音频一致
             '-shortest',  # 确保输出视频的长度与音频一致
             '-vf', f"{background_cmd},{subtitle_cmd}",  # 视频过滤器,设置分辨率和其他过滤器
             '-vf', f"{background_cmd},{subtitle_cmd}",  # 视频过滤器,设置分辨率和其他过滤器
             pw_url_path  # 输出的视频文件路径
             pw_url_path  # 输出的视频文件路径
-        ]
-        subprocess.run(ffmpeg_cmd)
+        ], timeout=500)
+        if os.path.exists(pw_srt_path):
+            os.remove(pw_srt_path)
         return pw_url_path
         return pw_url_path
 
 
-    """
-    设置统一格式拼接视频
-    """
+    """视频拼接到一起"""
     @classmethod
     @classmethod
-    def concatenate_videos(cls, video_list, video_path_url):
-        concatenate_videos_url = video_path_url + 'concatenate_videos.mp4'
-        # 拼接视频
-        VIDEO_COUNTER = 0
-        FF_INPUT = ""
-        FF_SCALE = ""
-        FF_FILTER = ""
-        ffmpeg_cmd = ["ffmpeg"]
-        for videos in video_list:
-            # 添加输入文件
-            FF_INPUT += f" -i {videos}"
-            # 为每个视频文件统一长宽,并设置SAR(采样宽高比)
-            FF_SCALE += f"[{VIDEO_COUNTER}:v]scale=360x640,setsar=1[v{VIDEO_COUNTER}];"
-            # 为每个视频文件创建一个输入流,并添加到-filter_complex参数中
-            FF_FILTER += f"[v{VIDEO_COUNTER}][{VIDEO_COUNTER}:a]"
-            # 增加视频计数器
-            VIDEO_COUNTER += 1
-        # 构建最终的FFmpeg命令
-        ffmpeg_cmd.extend(FF_INPUT.split())
-        ffmpeg_cmd.extend(["-filter_complex", f"{FF_SCALE}{FF_FILTER}concat=n={VIDEO_COUNTER}:v=1:a=1[v][a]",
-                           "-map", "[v]", "-map", "[a]", "-y", concatenate_videos_url])
-        # subprocess.run(ffmpeg_cmd)
+    def h_b_video(cls, video_path, pw_path, file_path):
+        video_url = file_path + 'hbvideo.mp4'
         try:
         try:
-            result = subprocess.run(ffmpeg_cmd, capture_output=True, text=True)
-            if result.returncode != 0:
-                # 打印错误信息并返回 None
-                print("ffmpeg 错误信息:", result.stderr)
-                return concatenate_videos_url
-            else:
-                return concatenate_videos_url
+            cls.asyncio_run_subprocess(["ffmpeg", "-i", video_path, "-i", pw_path, "-filter_complex",
+                                        "[0:v]scale=360:640[v1]; [1:v]scale=360:640[v2]; [v1][0:a][v2][1:a]concat=n=2:v=1:a=1[outv][outa]",
+                                        "-map", "[outv]", "-map", "[outa]", video_url], timeout=500)
+            return video_url
         except Exception as e:
         except Exception as e:
-            # 捕获其他异常并返回 None
-            print("处理视频时出现异常:", e)
-            return concatenate_videos_url
-
+            return video_url
     """
     """
     单个视频拼接
     单个视频拼接
     """
     """
@@ -332,43 +275,54 @@ class FFmpeg():
         single_video_txt = video_path_url + 'single_video.txt'
         single_video_txt = video_path_url + 'single_video.txt'
         with open(single_video_txt, 'w') as f:
         with open(single_video_txt, 'w') as f:
             f.write(f"file '{new_video_path}'\n")
             f.write(f"file '{new_video_path}'\n")
-        height = 1080
-        box_height = int(int(height) / 4)  # 框的高度为视频高度的四分之一
-        # background_cmd = f"drawbox=y=ih-{70 + box_height}-{int(box_height / 20)}:color=yellow@1.0:width=iw:height={box_height}:t=fill"
         if zm:
         if zm:
-            # with open(single_video_srt, 'w') as f:
-            #     # f.write(f"1\n{start_time} --> {end_time}\n{zm}\n\n")
-            #     f.write(f"1\n{start_time} --> {end_time}\n<font color=\"red\">\u2764\uFE0F</font>{zm}\n\n")
-            # subtitle_cmd = f"subtitles={single_video_srt}:force_style='Fontsize=12,Fontname=wqy-zenhei,Outline=2,PrimaryColour=&H00FFFF,SecondaryColour=&H000000,Bold=1,MarginV=10'"
             with open(single_video_srt, 'w') as f:
             with open(single_video_srt, 'w') as f:
-                # f.write(f"1\n{start_time} --> {end_time}\n\u2764\uFE0F{zm}\n\n")
                 f.write(f"1\n{start_time} --> {end_time}\n<font color=\"red\">\u2764\uFE0F</font>{zm}\n\n")
                 f.write(f"1\n{start_time} --> {end_time}\n<font color=\"red\">\u2764\uFE0F</font>{zm}\n\n")
             subtitle_cmd = f"subtitles={single_video_srt}:force_style='Fontsize=14,Fontname=wqy-zenhei,Outline=2,PrimaryColour=&H00FFFF,SecondaryColour=&H000000,Bold=1,MarginV=20'"
             subtitle_cmd = f"subtitles={single_video_srt}:force_style='Fontsize=14,Fontname=wqy-zenhei,Outline=2,PrimaryColour=&H00FFFF,SecondaryColour=&H000000,Bold=1,MarginV=20'"
         else:
         else:
             subtitle_cmd = f"force_style='Fontsize=14,Fontname=wqy-zenhei,Outline=2,PrimaryColour=&H00FFFF,SecondaryColour=&H000000,Bold=1,MarginV=20'"
             subtitle_cmd = f"force_style='Fontsize=14,Fontname=wqy-zenhei,Outline=2,PrimaryColour=&H00FFFF,SecondaryColour=&H000000,Bold=1,MarginV=20'"
-        draw = f"{subtitle_cmd}"
         # 多线程数
         # 多线程数
         num_threads = 5
         num_threads = 5
         # 构建 FFmpeg 命令,生成视频
         # 构建 FFmpeg 命令,生成视频
-        ffmpeg_cmd_oss = [
-                "ffmpeg",
-                "-f", "concat",
-                "-safe", "0",
-                "-i",  f"{single_video_txt}",
-                "-c:v", "libx264",
-                "-c:a", "aac",
-                '-b:v', '260k',
-                "-b:a", "96k",
-                "-threads", str(num_threads),
-                "-vf", f"{draw}",
-                # '-fs', '15M',
-                "-y",
-                 single_video_url
-
-        ]
-        subprocess.run(ffmpeg_cmd_oss)
+        cls.asyncio_run_subprocess([
+            "ffmpeg",
+            "-f", "concat",
+            "-safe", "0",
+            "-i", f"{single_video_txt}",
+            "-c:v", "libx264",
+            "-c:a", "aac",
+            '-b:v', '260k',
+            "-b:a", "96k",
+            "-threads", str(num_threads),
+            "-vf", subtitle_cmd,
+            "-y",
+            single_video_url
+        ], timeout=400)
+        if os.path.exists(single_video_srt):
+            os.remove(single_video_srt)
         return single_video_url
         return single_video_url
 
 
+    @classmethod
+    def asyncio_run_subprocess(cls, params: List[str], timeout: int = 30) -> str:
+        async def run_subprocess():
+            process = await asyncio.create_subprocess_exec(
+                params[0],
+                *params[1:],
+                stdout=asyncio.subprocess.PIPE,
+                stderr=asyncio.subprocess.PIPE,
+            )
+            try:
+                out, err = await asyncio.wait_for(process.communicate(), timeout=timeout)
+                if process.returncode != 0:
+                    raise IOError(err)
+                return out.decode()
+            except asyncio.TimeoutError:
+                process.kill()
+                out, err = await process.communicate()
+                raise IOError(err)
+
+        return asyncio.run(run_subprocess())
+
 
 
 
 
 
 

+ 351 - 0
common/ffmpeg_test.py

@@ -0,0 +1,351 @@
+import asyncio
+import os
+import subprocess
+import time
+from typing import List
+
+import cv2
+from loguru import logger
+from mutagen.mp3 import MP3
+
+
+
+class FFmpeg():
+
+    """
+    时间转换
+    """
+    @classmethod
+    def seconds_to_srt_time(cls, seconds):
+        hours = int(seconds // 3600)
+        minutes = int((seconds % 3600) // 60)
+        seconds = seconds % 60
+        milliseconds = int((seconds - int(seconds)) * 1000)
+        return f"{hours:02d}:{minutes:02d}:{int(seconds):02d},{milliseconds:03d}"
+
+    """
+    获取单个视频时长
+    """
+    @classmethod
+    def get_video_duration(cls, video_url):
+        cap = cv2.VideoCapture(video_url)
+        if cap.isOpened():
+            rate = cap.get(5)
+            frame_num = cap.get(7)
+            duration = int(frame_num / rate)
+            return duration
+        return 0
+
+
+    # """
+    # 获取视频文件的时长(秒)
+    # """
+    # @classmethod
+    # def get_videos_duration(cls, video_file):
+    #     result = cls.asyncio_run_subprocess(["ffprobe", "-v", "error", "-show_entries", "format=duration",
+    #          "-of", "default=noprint_wrappers=1:nokey=1", video_file], timeout=10)
+    #     return float(result)
+
+    """
+    获取视频宽高
+    """
+    @classmethod
+    def get_w_h_size(cls, new_video_path):
+        try:
+            # 获取视频的原始宽高信息
+            ffprobe_cmd = cls.asyncio_run_subprocess(["ffprobe", "-v" ,"error" ,"-select_streams" ,"v:0" ,"-show_entries", "stream=width,height" ,"-of" ,"csv=p=0" ,new_video_path],timeout=10)
+            output_decoded = ffprobe_cmd.strip()
+            split_output = [value for value in output_decoded.split(',') if value.strip()]
+            height, width = map(int, split_output)
+            return width, height
+        except ValueError as e:
+            return 1920, 1080
+
+
+    """
+    视频裁剪
+    """
+    @classmethod
+    def video_crop(cls, video_path, file_path):
+        crop_url = file_path + 'crop.mp4'
+        try:
+            # 获取视频的原始宽高信息
+            ffprobe_cmd = cls.asyncio_run_subprocess(
+                ["ffprobe", "-v", "error", "-select_streams", "v:0", "-show_entries", "stream=width,height", "-of",
+                 "csv=p=0", video_path], timeout=10)
+            width, height = map(int, ffprobe_cmd.strip().split(','))
+            # 计算裁剪后的高度
+            new_height = int(height * 0.8)
+
+            # 构建 FFmpeg 命令,裁剪视频高度为原始高度的80%
+            cls.asyncio_run_subprocess(
+                [
+                "ffmpeg",
+                "-i", video_path,
+                "-vf", f"crop={width}:{new_height}",
+                "-c:v", "libx264",
+                "-c:a", "aac",
+                "-y",
+                crop_url
+                ],timeout=240)
+            return crop_url
+        except Exception as e:
+            return crop_url
+
+    """
+    视频截断
+    """
+    @classmethod
+    def video_ggduration(cls, video_path, file_path, gg_duration_total):
+        gg_duration_url = file_path + 'gg_duration.mp4'
+        # 获取视频时长
+        try:
+            total_duration = cls.get_video_duration(video_path)
+            if total_duration == 0:
+                return gg_duration_url
+            duration = int(total_duration) - int(gg_duration_total)
+            if int(total_duration) < int(gg_duration_total):
+                return gg_duration_url
+            cls.asyncio_run_subprocess([
+                "ffmpeg",
+                "-i", video_path,
+                "-c:v", "libx264",
+                "-c:a", "aac",
+                "-t", str(duration),
+                "-y",
+                gg_duration_url
+            ], timeout= 360)
+            return gg_duration_url
+        except Exception as e:
+            return gg_duration_url
+
+    """
+     截取原视频最后一帧
+    """
+    @classmethod
+    def video_png(cls, video_path, file_path):
+        # 获取视频的原始宽高信息
+        jpg_url = file_path + 'png.jpg'
+        try:
+            cls.asyncio_run_subprocess(
+                ["ffmpeg", "-sseof", "-1", '-i', video_path, '-frames:v', '1',  "-y", jpg_url], timeout=120)
+            return jpg_url
+        except Exception as e:
+            return jpg_url
+
+    """
+    获取视频音频
+    """
+    @classmethod
+    def get_video_mp3(cls, video_file, video_path_url, pw_random_id):
+        pw_mp3_path = video_path_url + str(pw_random_id) +'pw_video.mp3'
+        try:
+            cls.asyncio_run_subprocess([
+                'ffmpeg',
+                '-i', video_file,
+                '-q:a', '0',
+                '-map', 'a',
+                pw_mp3_path
+            ], timeout=120)
+            time.sleep(1)
+            return pw_mp3_path
+        except Exception as e:
+            return pw_mp3_path
+
+    """横屏视频改为竖屏"""
+    @classmethod
+    def update_video_h_w(cls, video_path, file_path):
+        video_h_w_path = file_path +'video_h_w_video.mp4'
+        try:
+            cls.asyncio_run_subprocess(["ffmpeg" ,"-i" ,video_path ,"-vf" ,"scale=640:ih*640/iw,pad=iw:iw*16/9:(ow-iw)/2:(oh-ih)/2" ,video_h_w_path],timeout=420)
+            return video_h_w_path
+        except Exception as e:
+            return video_h_w_path
+
+    """视频转为640像素"""
+    @classmethod
+    def video_640(cls, video_path, file_path):
+        video_url = file_path + 'pixelvideo.mp4'
+        try:
+            cls.asyncio_run_subprocess(["ffmpeg" ,"-i" ,video_path ,"-vf" ,"scale=360:640" ,video_url],timeout=420)
+            return video_url
+        except Exception as e:
+            return video_url
+
+    """视频拼接到一起"""
+    @classmethod
+    def h_b_video(cls, video_path, pw_path, file_path):
+        video_url = file_path + 'hbvideo.mp4'
+        try:
+            cls.asyncio_run_subprocess(["ffmpeg","-i", video_path, "-i", pw_path, "-filter_complex" ,"[0:v]scale=360:640[v1]; [1:v]scale=360:640[v2]; [v1][0:a][v2][1:a]concat=n=2:v=1:a=1[outv][outa]" ,"-map" ,"[outv]" ,"-map" ,"[outa]" ,video_url],timeout=500)
+            return video_url
+        except Exception as e:
+            return video_url
+
+    """横屏视频顶部增加字幕"""
+    @classmethod
+    def add_video_zm(cls, new_video_path, video_path_url, pw_random_id, new_text):
+        single_video_srt = video_path_url + str(pw_random_id) +'video_zm.srt'
+        single_video_txt = video_path_url + str(pw_random_id) +'video_zm.txt'
+        single_video = video_path_url + str(pw_random_id) +'video_zm.mp4'
+        try:
+            duration = cls.get_video_duration(new_video_path)
+            if duration == 0:
+                return new_video_path
+            start_time = cls.seconds_to_srt_time(0)
+            end_time = cls.seconds_to_srt_time(duration)
+            # zm = '致敬伟大的教员,为整个民族\n感谢老人家历史向一代伟人'
+            with open(single_video_txt, 'w') as f:
+                f.write(f"file '{new_video_path}'\n")
+            with open(single_video_srt, 'w') as f:
+                f.write(f"1\n{start_time} --> {end_time}\n{new_text}\n\n")
+            subtitle_cmd = f"subtitles={single_video_srt}:force_style='Fontsize=12,Fontname=wqy-zenhei,Outline=2,PrimaryColour=&H00FFFF,SecondaryColour=&H000000,Bold=1,MarginV=225'"
+            draw = f"{subtitle_cmd}"
+            cls.asyncio_run_subprocess([
+                "ffmpeg",
+                "-f", "concat",
+                "-safe", "0",
+                "-i", single_video_txt,
+                "-c:v", "libx264",
+                "-c:a", "aac",
+                "-vf", draw,
+                "-y",
+                single_video
+            ],timeout=500)
+            # subprocess.run(ffmpeg_cmd)
+            return single_video
+        except Exception as e:
+            return single_video
+
+    """获取mp3时长"""
+    @classmethod
+    def get_mp3_duration(cls, file_path):
+        audio = MP3(file_path)
+        duration = audio.info.length
+        if duration:
+            return int(duration)
+        return 0
+
+    """
+     生成片尾视频
+    """
+    @classmethod
+    def pw_video(cls, jpg_path, file_path, pw_mp3_path, pw_srt):
+        # 添加音频到图片
+        """
+        jpg_url 图片地址
+        pw_video 提供的片尾视频
+        pw_duration  提供的片尾视频时长
+        new_video_path 视频位置
+        subtitle_cmd 字幕
+        pw_url 生成视频地址
+        :return:
+        """
+        pw_srt_path = file_path +'pw_video.srt'
+        with open(pw_srt_path, 'w') as f:
+            f.write(pw_srt)
+        pw_url_path = file_path + 'pw_video.mp4'
+        try:
+            pw_duration = cls.get_mp3_duration(pw_mp3_path)
+            if pw_duration == 0:
+                return pw_url_path
+            time.sleep(2)
+            # 添加字幕 wqy-zenhei  Hiragino Sans GB
+            height = 1080
+            margin_v = int(height) // 8  # 可根据需要调整字幕和背景之间的距离
+            subtitle_cmd = f"subtitles={pw_srt_path}:force_style='Fontsize=13,Fontname=wqy-zenhei,Outline=0,PrimaryColour=&H000000,SecondaryColour=&H000000,Bold=1,MarginV={margin_v}'"
+            bg_position_offset = (int(360) - 360//8) / 1.75
+            background_cmd = f"drawbox=y=(ih-{int(360)}/2-{bg_position_offset}):color=yellow@1.0:width=iw:height={int(360)}/4:t=fill"
+
+            cls.asyncio_run_subprocess([
+                'ffmpeg',
+                '-loop', '1',
+                '-i', jpg_path,  # 输入的图片文件
+                '-i', pw_mp3_path,  # 输入的音频文件
+                '-c:v', 'libx264',  # 视频编码格式
+                '-t', str(pw_duration),  # 输出视频的持续时间,与音频持续时间相同
+                '-pix_fmt', 'yuv420p',  # 像素格式
+                '-c:a', 'aac',  # 音频编码格式
+                '-strict', 'experimental',  # 使用实验性编码器
+                '-shortest',  # 确保输出视频的长度与音频一致
+                '-vf', f"{background_cmd},{subtitle_cmd}",  # 视频过滤器,设置分辨率和其他过滤器
+                pw_url_path  # 输出的视频文件路径
+            ], timeout=500)
+            if os.path.exists(pw_srt_path):
+                os.remove(pw_srt_path)
+            return pw_url_path
+        except Exception as e:
+            return pw_url_path
+
+
+    """
+    单个视频拼接
+    """
+    @classmethod
+    def single_video(cls, video_path, file_path, zm):
+        single_video_url = file_path + 'single_video.mp4'
+        single_video_srt = file_path + 'single_video.srt'
+        # 获取时长
+        try:
+            duration = cls.get_video_duration(video_path)
+            if duration == 0:
+                return single_video_url
+            start_time = cls.seconds_to_srt_time(2)
+            end_time = cls.seconds_to_srt_time(duration)
+            single_video_txt = file_path + 'single_video.txt'
+            with open(single_video_txt, 'w') as f:
+                f.write(f"file '{video_path}'\n")
+            if zm:
+                with open(single_video_srt, 'w') as f:
+                    f.write(f"1\n{start_time} --> {end_time}\n<font color=\"red\">\u2764\uFE0F</font>{zm}\n\n")
+                subtitle_cmd = f"subtitles={single_video_srt}:force_style='Fontsize=14,Fontname=wqy-zenhei,Outline=2,PrimaryColour=&H00FFFF,SecondaryColour=&H000000,Bold=1,MarginV=20'"
+            else:
+                subtitle_cmd = f"force_style='Fontsize=14,Fontname=wqy-zenhei,Outline=2,PrimaryColour=&H00FFFF,SecondaryColour=&H000000,Bold=1,MarginV=20'"
+            # 多线程数
+            num_threads = 5
+            # 构建 FFmpeg 命令,生成视频
+            cls.asyncio_run_subprocess([
+                    "ffmpeg",
+                    "-f", "concat",
+                    "-safe", "0",
+                    "-i",  f"{single_video_txt}",
+                    "-c:v", "libx264",
+                    "-c:a", "aac",
+                    '-b:v', '260k',
+                    "-b:a", "96k",
+                    "-threads", str(num_threads),
+                    "-vf", subtitle_cmd,
+                    "-y",
+                     single_video_url
+            ], timeout=400)
+            if os.path.exists(single_video_srt):
+                os.remove(single_video_srt)
+            return single_video_url
+        except Exception as e:
+            return single_video_url
+
+    @classmethod
+    def asyncio_run_subprocess(cls, params: List[str], timeout: int = 30) -> str:
+        async def run_subprocess():
+            process = await asyncio.create_subprocess_exec(
+                params[0],
+                *params[1:],
+                stdout=asyncio.subprocess.PIPE,
+                stderr=asyncio.subprocess.PIPE,
+            )
+            try:
+                out, err = await asyncio.wait_for(process.communicate(), timeout=timeout)
+                if process.returncode != 0:
+                    raise IOError(err)
+                return out.decode()
+            except asyncio.TimeoutError:
+                process.kill()
+                out, err = await process.communicate()
+                raise IOError(err)
+        return asyncio.run(run_subprocess())
+
+
+if __name__ == '__main__':
+    FFmpeg.video_png("/Users/z/Downloads/c2ce4ba6-fdb3-4e1b-bdba-dc05cb292abfvideo.mp4", "/Users/z/Downloads/")
+
+

+ 0 - 1
job_fj.py

@@ -1,5 +1,4 @@
 import os
 import os
-import concurrent.futures
 import time
 import time
 import threading
 import threading
 from common import Material
 from common import Material

+ 303 - 381
video_rewriting/video_processor.py

@@ -5,6 +5,7 @@ import random
 import re
 import re
 import shutil
 import shutil
 import time
 import time
+import traceback
 from datetime import datetime
 from datetime import datetime
 
 
 from common.gpt4o_mini_help import GPT4oMini
 from common.gpt4o_mini_help import GPT4oMini
@@ -89,19 +90,21 @@ class VideoProcessor:
         """
         """
         处理单个任务
         处理单个任务
         """
         """
+        task_mark = task["task_mark"]
+        channel_id = str(task["channel_id"])
+        url = str(task["channel_url"])
+        piaoquan_id = str(task["piaoquan_id"])
+        number = task["number"]
+        title = task["title"]
+        video_share = task["video_share"]
+        video_ending = task["video_ending"]
+        crop_total = task["crop_total"]
+        gg_duration_total = task["gg_duration_total"]
+        voice = task['voice']
+        tags = task['tags']
+        v_id = None
         try:
         try:
-            task_mark = task["task_mark"]
-            channel_id = str(task["channel_id"])
-            url = str(task["channel_url"])
-            piaoquan_id = str(task["piaoquan_id"])
-            number = task["number"]
-            title = task["title"]
-            video_share = task["video_share"]
-            video_ending = task["video_ending"]
-            crop_total = task["crop_total"]
-            gg_duration_total = task["gg_duration_total"]
-            voice = task['voice']
-            tags = task['tags']
+
             if voice:
             if voice:
                 if ',' in voice:
                 if ',' in voice:
                     voices = voice.split(',')
                     voices = voice.split(',')
@@ -112,9 +115,6 @@ class VideoProcessor:
                 voice = "zhifeng_emo"
                 voice = "zhifeng_emo"
             zm = Material.get_pzsrt_data("summary", "500Oe0", video_share)
             zm = Material.get_pzsrt_data("summary", "500Oe0", video_share)
             Common.logger(mark).info(f"{name}的{task_mark}下{channel_id}的用户:{url}开始获取视频")
             Common.logger(mark).info(f"{name}的{task_mark}下{channel_id}的用户:{url}开始获取视频")
-            new_count = None
-            # if name in ['快手品类账号', '抖音品类账号', '抖音品类账号-1', '视频号品类账号']:
-            #     new_count = OdpsDataCount.main(channel_id, name, url)
             data_list = cls.get_data_list(
             data_list = cls.get_data_list(
                 channel_id, task_mark, url,
                 channel_id, task_mark, url,
                 number,
                 number,
@@ -132,66 +132,109 @@ class VideoProcessor:
                 Feishu.finish_bot(text, "https://open.feishu.cn/open-apis/bot/v2/hook/e7697dc6-5254-4411-8b59-3cd0742bf703",
                 Feishu.finish_bot(text, "https://open.feishu.cn/open-apis/bot/v2/hook/e7697dc6-5254-4411-8b59-3cd0742bf703",
                                   "【 机器改造通知 】")
                                   "【 机器改造通知 】")
                 return
                 return
-            if new_count:
-                sqlCollect.insert_spider_supply_targetcnt(channel_id, name, url, number, new_count, str(len(data_list)))
-                current_time = datetime.now()
-                formatted_time = current_time.strftime("%Y-%m-%d %H:%M:%S")
-                values = [
-                    [
-                        name,
-                        channel_id,
-                        url,
-                        str(number),
-                        str(new_count),
-                        str(len(data_list)),
-                        formatted_time
-                    ]
-                ]
-                Feishu.insert_columns("Z5xLsdyyxh3abntTTvUc9zw8nYd", "099da8", "ROWS", 1, 2)
-                time.sleep(0.5)
-                Feishu.update_values("Z5xLsdyyxh3abntTTvUc9zw8nYd", "099da8", "A2:Z2", values)
             Common.logger(mark).info(f"{name}的{task_mark}下的ID{url} 获取视频完成,共{len(data_list)}条")
             Common.logger(mark).info(f"{name}的{task_mark}下的ID{url} 获取视频完成,共{len(data_list)}条")
-            try:
-                for video in data_list:
-                    # limit_number = task["limit_number"]
-                    # if limit_number:
-                    #     task_mark = task["task_mark"]
-                    #     makr_count = sqlCollect.get_mark_count(task_mark)
-                    #     if int(limit_number) <= int(makr_count[0][0]):
-                    #         AliyunLogger.logging((task["channel_id"]), name, task["channel_url"], '',
-                    #                              f"{task_mark}标识任务每日指定条数已足够,指定条数{limit_number},实际生成条数{int(makr_count[0][0])}",
-                    #                              "1111")
-                    #         return
-                    cls.remove_files(mark)
-                    video_path_url = cls.create_folders(mark)
-                    new_title = cls.generate_title(video, title)
-                    v_id = video["video_id"]
-                    cover = video["cover"]
-                    video_url = video["video_url"]
-                    old_title = video['old_title']
-                    rule = video['rule']
-                    if not old_title:
-                        old_title = '这个视频,分享给我的老友,祝愿您能幸福安康'
+            for video in data_list:
+                cls.remove_files(mark)
+                file_path = cls.create_folders(mark)
+                new_title = cls.generate_title(video, title)
+                v_id = video["video_id"]
+                cover = video["cover"]
+                video_url = video["video_url"]
+                old_title = video['old_title']
+                rule = video['rule']
+                if not old_title:
+                    old_title = '这个视频,分享给我的老友,祝愿您能幸福安康'
+                    text = (
+                        f"**通知类型**: 标题为空,使用兜底标题生成片尾\n"
+                        f"**负责人**: {name}\n"
+                        f"**渠道**: {channel_id}\n"
+                        f"**视频主页ID**: {url}\n"
+                        f"**视频Video_id**: {v_id}\n"
+                    )
+                    Feishu.finish_bot(text,
+                                      "https://open.feishu.cn/open-apis/bot/v2/hook/e7697dc6-5254-4411-8b59-3cd0742bf703",
+                                      "【 机器改造通知 】")
+                    Common.logger(mark).info(f"{name}的{task_mark}下的视频{url},标题为空,使用兜底标题生成片尾")
+                time.sleep(1)
+                pw_random_id = cls.random_id()
+                Common.logger(mark).info(f"{name}的{task_mark}下的ID{url} 开始下载视频")
+                video_path = cls.download_and_process_video( channel_id, video_url, file_path, v_id, video)
+                if not os.path.isfile(video_path) or os.path.getsize(video_path) == 0:
+                    if name == "单点视频":
+                        sqlCollect.update_shp_dd_vid_4(v_id)
+                        from_user_name = video['from_user_name']  # 来源用户
+                        from_group_name = video['from_group_name']  # 来源群组
+                        source = video['source']  # 渠道
                         text = (
                         text = (
-                            f"**通知类型**: 标题为空,使用兜底标题生成片尾\n"
-                            f"**负责人**: {name}\n"
-                            f"**渠道**: {channel_id}\n"
-                            f"**视频主页ID**: {url}\n"
-                            f"**视频Video_id**: {v_id}\n"
+                            f"**渠道**: {source}\n"
+                            f"**来源用户**: {from_user_name}\n"
+                            f"**来源群组**: {from_group_name}\n"
+                            f"**原视频链接**: {video['video_url']}\n"
+                            f"**原视频封面**: {video['cover']}\n"
+                            f"**原视频标题**: {video['old_title']}\n"
                         )
                         )
                         Feishu.finish_bot(text,
                         Feishu.finish_bot(text,
-                                          "https://open.feishu.cn/open-apis/bot/v2/hook/e7697dc6-5254-4411-8b59-3cd0742bf703",
-                                          "【 机器改造通知 】")
-                        Common.logger(mark).info(f"{name}的{task_mark}下的视频{url},标题为空,使用兜底标题生成片尾")
-                    time.sleep(1)
-                    pw_random_id = cls.random_id()
-                    Common.logger(mark).info(f"{name}的{task_mark}下的ID{url} 开始下载视频")
-                    new_video_path = cls.download_and_process_video(channel_id, video_url, video_path_url, v_id,
-                                                                    crop_total, gg_duration_total, pw_random_id, new_title, mark, video)
-                    if not os.path.isfile(new_video_path) or os.path.getsize(new_video_path) == 0:
-                        AliyunLogger.logging(channel_id, name, url, v_id, "视频下载失败", "3002", f"video_url:{video_url}")
+                                          "https://open.feishu.cn/open-apis/bot/v2/hook/493b3d4c-5fae-4a9d-980b-1dd86636524e",
+                                          "【 视频下载失败,跳过该视频 】")
+                    text = (
+                        f"**通知类型**: 视频下载失败\n"
+                        f"**负责人**: {name}\n"
+                        f"**渠道**: {channel_id}\n"
+                        f"**视频主页ID**: {url}\n"
+                        f"**视频Video_id**: {v_id}\n"
+                    )
+                    if name == "快手推荐流" or name == "视频号推荐流":
+                        sqlCollect.update_feed_vid_2(v_id)
+                        Feishu.finish_bot(text,
+                                          "https://open.feishu.cn/open-apis/bot/v2/hook/493b3d4c-5fae-4a9d-980b-1dd86636524e",
+                                          "【 视频下载失败,跳过该视频 】")
+                    AliyunLogger.logging(channel_id, name, url, v_id, "视频下载失败", "3002", f"video_url:{video_url}")
+
+                    Feishu.finish_bot(text,
+                                      "https://open.feishu.cn/open-apis/bot/v2/hook/e7697dc6-5254-4411-8b59-3cd0742bf703",
+                                      "【 机器改造通知 】")
+                    continue
+
+                width, height = FFmpeg.get_w_h_size(video_path)
+                if width < height:  # 判断是否需要修改为竖屏
+                    video_path = FFmpeg.update_video_h_w(video_path, file_path, pw_random_id)
+                video_path = FFmpeg.video_640(video_path, file_path)
+                if not os.path.exists(video_path) or os.path.getsize(video_path) == 0:
+                    AliyunLogger.logging(channel_id, name, url, v_id, "视频更改分辨率失败", "3002")
+                    continue
+                if crop_total and crop_total != 'None':  # 判断是否需要裁剪
+                    video_path = FFmpeg.video_crop(video_path, file_path, pw_random_id)
+                if gg_duration_total and gg_duration_total != 'None':  # 判断是否需要指定视频时长
+                    video_path = FFmpeg.video_ggduration(video_path, file_path, pw_random_id,gg_duration_total)
+                if video_ending and video_ending != 'None':
+                    if video_ending == "AI片尾引导":
+                        pw_srt_text = GPT4oMini.get_ai_mini_pw(old_title)
+                        pw_url = TTS.get_pw_zm(pw_srt_text, voice)
+                        pw_mp3_path = TTS.download_mp3(pw_url, file_path, pw_random_id)
+                        pw_srt = TTS.getSrt(pw_url)
+                    else:
+                        if ',' in video_ending:
+                            video_ending_list = video_ending.split(',')
+                        else:
+                            video_ending_list = [video_ending]
+                        ending = random.choice(video_ending_list)
+                        pw_list = Material.get_pwsrt_data("summary", "DgX7vC", ending)  # 获取srt
+                        pw_id = pw_list["pw_id"]
+                        pw_srt = pw_list["pw_srt"]
+                        pw_url = PQ.get_pw_url(pw_id)
+                        pw_mp3_path = FFmpeg.get_video_mp3(pw_url, file_path, pw_random_id)
+                    if not pw_url:
+                        AliyunLogger.logging(channel_id, name, url, v_id, "获取片尾失败", "3002")
+                        continue
+                    jpg_path = FFmpeg.video_png(video_path, file_path, pw_random_id)  # 生成视频最后一帧jpg
+                    if not os.path.exists(jpg_path) or os.path.getsize(jpg_path) == 0:
+                        AliyunLogger.logging(channel_id, name, url, v_id, "片尾获取最后一帧失败", "3002")
+                        continue
+                    pw_path = FFmpeg.pw_video(jpg_path, file_path,  pw_srt, pw_random_id, pw_mp3_path)  # 生成片尾视频
+                    if not os.path.exists(pw_path) or os.path.getsize(pw_path) == 0:
+                        AliyunLogger.logging(channel_id, name, url, v_id, "生成片尾失败", "3002")
                         text = (
                         text = (
-                            f"**通知类型**: 视频下载失败\n"
+                            f"**通知类型**: 生成片尾失败\n"
                             f"**负责人**: {name}\n"
                             f"**负责人**: {name}\n"
                             f"**渠道**: {channel_id}\n"
                             f"**渠道**: {channel_id}\n"
                             f"**视频主页ID**: {url}\n"
                             f"**视频主页ID**: {url}\n"
@@ -201,37 +244,10 @@ class VideoProcessor:
                                           "https://open.feishu.cn/open-apis/bot/v2/hook/e7697dc6-5254-4411-8b59-3cd0742bf703",
                                           "https://open.feishu.cn/open-apis/bot/v2/hook/e7697dc6-5254-4411-8b59-3cd0742bf703",
                                           "【 机器改造通知 】")
                                           "【 机器改造通知 】")
                         continue
                         continue
-                    if video_ending and video_ending != 'None':
-                        new_video_path = cls.handle_video_ending(new_video_path, video_ending, old_title, pw_random_id, video_path_url, mark, task_mark, url, name, video_share, zm, voice)
-                        if new_video_path == None:
-                            if name == "单点视频":
-                                sqlCollect.update_shp_dd_vid_4(v_id)
-                                from_user_name = video['from_user_name']  # 来源用户
-                                from_group_name = video['from_group_name']  # 来源群组
-                                source = video['source']  # 渠道
-                                text = (
-                                    f"**渠道**: {source}\n"
-                                    f"**来源用户**: {from_user_name}\n"
-                                    f"**来源群组**: {from_group_name}\n"
-                                    f"**原视频链接**: {video['video_url']}\n"
-                                    f"**原视频封面**: {video['cover']}\n"
-                                    f"**原视频标题**: {video['old_title']}\n"
-                                )
-                                AliyunLogger.logging(channel_id, name, url, v_id, "视频下载失败", "3002")
-                                Feishu.finish_bot(text,
-                                                  "https://open.feishu.cn/open-apis/bot/v2/hook/493b3d4c-5fae-4a9d-980b-1dd86636524e",
-                                                  "【 视频下载失败,跳过该视频 】")
-                            if name == "快手推荐流" or name == "视频号推荐流":
-                                sqlCollect.update_feed_vid_2(v_id)
-                                Feishu.finish_bot(text,
-                                                  "https://open.feishu.cn/open-apis/bot/v2/hook/493b3d4c-5fae-4a9d-980b-1dd86636524e",
-                                                  "【 视频下载失败,跳过该视频 】")
-                            continue
-                    else:
-                        if video_share and video_share != 'None':
-                            new_video_path = FFmpeg.single_video(new_video_path, video_path_url, zm)
-                    if not os.path.isfile(new_video_path) or os.path.getsize(new_video_path) == 0:
-                        AliyunLogger.logging(channel_id, name, url, v_id, "视频改造失败", "3001")
+                    video_path = FFmpeg.h_b_video(video_path, pw_path, file_path)
+                    video_path = FFmpeg.single_video(video_path, file_path, zm)
+                    if not os.path.exists(video_path) or os.path.getsize(video_path) == 0:
+                        AliyunLogger.logging(channel_id, name, url, v_id, "拼接失败", "3002")
                         text = (
                         text = (
                             f"**通知类型**: 视频改造失败\n"
                             f"**通知类型**: 视频改造失败\n"
                             f"**负责人**: {name}\n"
                             f"**负责人**: {name}\n"
@@ -243,195 +259,195 @@ class VideoProcessor:
                                           "https://open.feishu.cn/open-apis/bot/v2/hook/e7697dc6-5254-4411-8b59-3cd0742bf703",
                                           "https://open.feishu.cn/open-apis/bot/v2/hook/e7697dc6-5254-4411-8b59-3cd0742bf703",
                                           "【 机器改造通知 】")
                                           "【 机器改造通知 】")
                         continue
                         continue
-                    # 上传视频和封面,并更新数据库
-                    code = cls.upload_video_and_thumbnail(new_video_path, cover, v_id, new_title, task_mark, name, piaoquan_id,
-                                                   video_path_url, mark, channel_id, url, old_title, title, rule, video)
-                    # 更新已使用的视频号状态
-                    pq_url = f'https://admin.piaoquantv.com/cms/post-detail/{code}/detail'  # 站内视频链接
-                    if name == "单点视频":
-                        sphdd_status = sqlCollect.update_shp_dd_vid(v_id)
-                        if sphdd_status == 1:
-                            Common.logger(mark).info(f"{name}的{task_mark}下的ID{url} 视频修改已使用,状态已修改")
-                            from_user_name = video['from_user_name']  # 来源用户
-                            from_group_name = video['from_group_name']  # 来源群组
-                            source = video['source']  # 渠道
-                            channel_id = source
-                            text = (
-                                f"**站内视频链接**: {pq_url}\n"
-                                f"**渠道**: {source}\n"
-                                f"**来源用户**: {from_user_name}\n"
-                                f"**来源群组**: {from_group_name}\n"
-                                f"**原视频链接**: {video['video_url']}\n"
-                                f"**原视频封面**: {video['cover']}\n"
-                                f"**原视频标题**: {video['old_title']}\n"
-                            )
-                            Feishu.finish_bot(text, "https://open.feishu.cn/open-apis/bot/v2/hook/d2f751a8-5b0a-49ca-a306-1fda142707a9", "【 有一条新的内容改造成功 】")
-                    if name == "快手推荐流" or name == "视频号推荐流":
-                        feed_status = sqlCollect.update_feed_vid(v_id)
-                        if feed_status == 1:
-                            Common.logger(mark).info(f"{name}的{task_mark}下的ID{url} 视频修改已使用,状态已修改")
-                    if channel_id == "快手历史" or channel_id == "抖音历史" or channel_id == "视频号历史":
-                        explain = "历史爆款"
-                    else:
-                        explain = "新供给"
-                    current_time = datetime.now()
-                    formatted_time = current_time.strftime("%Y-%m-%d %H:%M:%S")
-                    if name == "品类关键词搜索":
-                        first_category = task["first_category"]
-                        keyword_principal = task["keyword_name"]
-                        tag_first = f"一级品类_{first_category}"
-                        tag_keyword = f"关键词_{url}"
-                        if channel_id == "抖音搜索":
-                            tag_channel = "来源_抖音关键词"
-                        elif channel_id == "快手搜索":
-                            tag_channel = "来源_快手关键词"
-                        elif channel_id == "视频号搜索":
-                            tag_channel = "来源_视频号关键词"
-                        tag = f"{tag_first},{tag_keyword},{tag_channel}"
-                        tag_status = Tag.video_tag(code, tag)
-                        if tag_status == 0:
-                            Common.logger(mark).info(f"{name}的{task_mark}下的ID{url}下的票圈视频{code},写入标签成功")
-                        secondary_category = task["secondary_category"]
-                        log_data = f"user:{url},,video_id:{v_id},,video_url:{video_url},,ai_title:{new_title},,voice:{voice},,first_category:{first_category},,secondary_category:{secondary_category},,keyword_principal:{keyword_principal},,tag:{tag}"
-                        values = [
-                            [
-                                name,
-                                task_mark,
-                                channel_id,
-                                url,
-                                str(v_id),
-                                piaoquan_id,
-                                old_title,
-                                title if title in ["原标题", "AI标题"] else "",
-                                new_title,
-                                str(code),
-                                formatted_time,
-                                str(rule),
-                                explain,
-                                voice,
-                                first_category,
-                                secondary_category,
-                                keyword_principal,
-                                pq_url
-                            ]
+
+
+                # 上传视频和封面,并更新数据库
+                code = cls.upload_video_and_thumbnail(video_path, cover, v_id, new_title, task_mark, name, piaoquan_id,
+                                               file_path, mark, channel_id, url, old_title, title, rule, video)
+                # 更新已使用的视频号状态
+                pq_url = f'https://admin.piaoquantv.com/cms/post-detail/{code}/detail'  # 站内视频链接
+                if name == "单点视频":
+                    sphdd_status = sqlCollect.update_shp_dd_vid(v_id)
+                    if sphdd_status == 1:
+                        Common.logger(mark).info(f"{name}的{task_mark}下的ID{url} 视频修改已使用,状态已修改")
+                        from_user_name = video['from_user_name']  # 来源用户
+                        from_group_name = video['from_group_name']  # 来源群组
+                        source = video['source']  # 渠道
+                        channel_id = source
+                        text = (
+                            f"**站内视频链接**: {pq_url}\n"
+                            f"**渠道**: {source}\n"
+                            f"**来源用户**: {from_user_name}\n"
+                            f"**来源群组**: {from_group_name}\n"
+                            f"**原视频链接**: {video['video_url']}\n"
+                            f"**原视频封面**: {video['cover']}\n"
+                            f"**原视频标题**: {video['old_title']}\n"
+                        )
+                        Feishu.finish_bot(text, "https://open.feishu.cn/open-apis/bot/v2/hook/d2f751a8-5b0a-49ca-a306-1fda142707a9", "【 有一条新的内容改造成功 】")
+                if name == "快手推荐流" or name == "视频号推荐流":
+                    feed_status = sqlCollect.update_feed_vid(v_id)
+                    if feed_status == 1:
+                        Common.logger(mark).info(f"{name}的{task_mark}下的ID{url} 视频修改已使用,状态已修改")
+                if channel_id == "快手历史" or channel_id == "抖音历史" or channel_id == "视频号历史":
+                    explain = "历史爆款"
+                else:
+                    explain = "新供给"
+                current_time = datetime.now()
+                formatted_time = current_time.strftime("%Y-%m-%d %H:%M:%S")
+                if name == "品类关键词搜索":
+                    first_category = task["first_category"]
+                    keyword_principal = task["keyword_name"]
+                    tag_first = f"一级品类_{first_category}"
+                    tag_keyword = f"关键词_{url}"
+                    if channel_id == "抖音搜索":
+                        tag_channel = "来源_抖音关键词"
+                    elif channel_id == "快手搜索":
+                        tag_channel = "来源_快手关键词"
+                    elif channel_id == "视频号搜索":
+                        tag_channel = "来源_视频号关键词"
+                    tag = f"{tag_first},{tag_keyword},{tag_channel}"
+                    tag_status = Tag.video_tag(code, tag)
+                    if tag_status == 0:
+                        Common.logger(mark).info(f"{name}的{task_mark}下的ID{url}下的票圈视频{code},写入标签成功")
+                    secondary_category = task["secondary_category"]
+                    log_data = f"user:{url},,video_id:{v_id},,video_url:{video_url},,ai_title:{new_title},,voice:{voice},,first_category:{first_category},,secondary_category:{secondary_category},,keyword_principal:{keyword_principal},,tag:{tag}"
+                    values = [
+                        [
+                            name,
+                            task_mark,
+                            channel_id,
+                            url,
+                            str(v_id),
+                            piaoquan_id,
+                            old_title,
+                            title if title in ["原标题", "AI标题"] else "",
+                            new_title,
+                            str(code),
+                            formatted_time,
+                            str(rule),
+                            explain,
+                            voice,
+                            first_category,
+                            secondary_category,
+                            keyword_principal,
+                            pq_url
                         ]
                         ]
-                    elif name == "抖音品类账号-1" or name == "抖音品类账号" or name == "视频号品类账号" or name == "快手品类账号":
-                        first_category = task["first_category"]
-                        tag_first = f"一级品类_{first_category}"
-                        if channel_id == "抖音" or channel_id == "抖音历史":
-                            tag_channel = "来源_抖音品类账号"
-                        elif channel_id == "快手" or channel_id == "快手历史":
-                            tag_channel = "来源_快手品类账号"
-                        elif channel_id == "视频号" or channel_id == "视频号历史":
-                            tag_channel = "来源_视频号品类账号"
-                        tag = f"{tag_first},{tag_channel}"
-                        tag_status = Tag.video_tag( code, tag )
-                        if tag_status == 0:
-                            Common.logger(mark).info(f"{name}的{task_mark}下的ID{url}下的票圈视频{code},写入标签成功")
-                        log_data = f"user:{url},,video_id:{v_id},,video_url:{video_url},,ai_title:{new_title},,voice:{voice},,tag:{tag}"
-                        # log_data = f"user:{url},,video_id:{v_id},,video_url:{video_url},,ai_title:{new_title},,voice:{voice},,first_category:{first_category},,tag:{tag}"
-                        values = [
-                            [
-                                name,
-                                task_mark,
-                                channel_id,
-                                url,
-                                str( v_id ),
-                                piaoquan_id,
-                                old_title,
-                                title if title in ["原标题", "AI标题"] else "",
-                                new_title,
-                                str( code ),
-                                formatted_time,
-                                str( rule ),
-                                explain,
-                                voice,
-                                first_category,
-                                pq_url
-                            ]
+                    ]
+                elif name == "抖音品类账号-1" or name == "抖音品类账号" or name == "视频号品类账号" or name == "快手品类账号":
+                    first_category = task["first_category"]
+                    tag_first = f"一级品类_{first_category}"
+                    if channel_id == "抖音" or channel_id == "抖音历史":
+                        tag_channel = "来源_抖音品类账号"
+                    elif channel_id == "快手" or channel_id == "快手历史":
+                        tag_channel = "来源_快手品类账号"
+                    elif channel_id == "视频号" or channel_id == "视频号历史":
+                        tag_channel = "来源_视频号品类账号"
+                    tag = f"{tag_first},{tag_channel}"
+                    tag_status = Tag.video_tag( code, tag )
+                    if tag_status == 0:
+                        Common.logger(mark).info(f"{name}的{task_mark}下的ID{url}下的票圈视频{code},写入标签成功")
+                    log_data = f"user:{url},,video_id:{v_id},,video_url:{video_url},,ai_title:{new_title},,voice:{voice},,tag:{tag}"
+                    # log_data = f"user:{url},,video_id:{v_id},,video_url:{video_url},,ai_title:{new_title},,voice:{voice},,first_category:{first_category},,tag:{tag}"
+                    values = [
+                        [
+                            name,
+                            task_mark,
+                            channel_id,
+                            url,
+                            str( v_id ),
+                            piaoquan_id,
+                            old_title,
+                            title if title in ["原标题", "AI标题"] else "",
+                            new_title,
+                            str( code ),
+                            formatted_time,
+                            str( rule ),
+                            explain,
+                            voice,
+                            first_category,
+                            pq_url
                         ]
                         ]
+                    ]
 
 
-                    else:
-                        log_data = f"user:{url},,video_id:{v_id},,video_url:{video_url},,ai_title:{new_title},,voice:{voice}"
-                        values = [
-                            [
-                                name,
-                                task_mark,
-                                channel_id,
-                                url,
-                                str(v_id),
-                                piaoquan_id,
-                                old_title,
-                                title if title in ["原标题", "AI标题"] else "",
-                                new_title,
-                                str(code),
-                                formatted_time,
-                                str(rule),
-                                explain,
-                                voice
-                            ]
+                else:
+                    log_data = f"user:{url},,video_id:{v_id},,video_url:{video_url},,ai_title:{new_title},,voice:{voice}"
+                    values = [
+                        [
+                            name,
+                            task_mark,
+                            channel_id,
+                            url,
+                            str(v_id),
+                            piaoquan_id,
+                            old_title,
+                            title if title in ["原标题", "AI标题"] else "",
+                            new_title,
+                            str(code),
+                            formatted_time,
+                            str(rule),
+                            explain,
+                            voice
                         ]
                         ]
-                    AliyunLogger.logging(channel_id, name, url, v_id, "视频改造成功", "1000", log_data, str(code))
-                    text = (
-                        f"**通知类型**: 视频改造成功\n"
-                        f"**站内视频链接**: {pq_url}\n"
-                        f"**负责人**: {name}\n"
-                        f"**渠道**: {channel_id}\n"
-                        f"**视频主页ID**: {url}\n"
-                        f"**视频Video_id**: {v_id}\n"
-                        f"**使用音频音色**: {voice}\n"
-                    )
-                    Feishu.finish_bot(text,
-                                      "https://open.feishu.cn/open-apis/bot/v2/hook/e7697dc6-5254-4411-8b59-3cd0742bf703",
-                                      "【 机器改造通知 】")
-                    if tags:
-                        Tag.video_tag(code, tags)
-                    if values:
-                        if name == "王雪珂":
-                            sheet = "vfhHwj"
-                        elif name == "鲁涛":
-                            sheet = "FhewlS"
-                        elif name == "范军":
-                            sheet = "B6dCfS"
-                        elif name == "余海涛":
-                            sheet = "mfBrNT"
-                        elif name == "罗情":
-                            sheet = "2J3PwN"
-                        elif name == "王玉婷":
-                            sheet = "bBHFwC"
-                        elif name == "刘诗雨":
-                            sheet = "fBdxIQ"
-                        elif name == "信欣":
-                            sheet = "lPe1eT"
-                        elif name == "快手创作者版品类推荐流":
-                            sheet = "k7l7nQ"
-                        elif name == "抖音品类账号":
-                            sheet = "ZixHmf"
-                        elif name == "抖音品类账号-1":
-                            sheet = "61kvW7"
-                        elif name == "视频号品类账号":
-                            sheet = "b0uLWw"
-                        elif name == "单点视频":
-                            sheet = "ptgCXW"
-                        elif name == "快手品类账号":
-                            sheet = "ibjoMx"
-                        elif name == "品类关键词搜索":
-                            sheet = "rBAJT8"
-                        elif name == "快手推荐流":
-                            sheet = "9Ii8lw"
-                        elif name == "视频号推荐流":
-                            sheet = "hMBv7T"
-                        elif name == "快手小程序":
-                            sheet = "GeDT6Q"
-                        Feishu.insert_columns("ILb4sa0LahddRktnRipcu2vQnLb", sheet, "ROWS", 1, 2)
-                        time.sleep(0.5)
-                        Feishu.update_values("ILb4sa0LahddRktnRipcu2vQnLb", sheet, "A2:Z2", values)
-            except Exception as e:
-                AliyunLogger.logging(channel_id, name, url, video["video_id"], f"改造失败{e}", "3001", log_data)
-                Common.logger(mark).error(f"{name}的{task_mark}任务处理失败:{e}")
+                    ]
+                AliyunLogger.logging(channel_id, name, url, v_id, "视频改造成功", "1000", log_data, str(code))
+                text = (
+                    f"**通知类型**: 视频改造成功\n"
+                    f"**站内视频链接**: {pq_url}\n"
+                    f"**负责人**: {name}\n"
+                    f"**渠道**: {channel_id}\n"
+                    f"**视频主页ID**: {url}\n"
+                    f"**视频Video_id**: {v_id}\n"
+                    f"**使用音频音色**: {voice}\n"
+                )
+                Feishu.finish_bot(text,
+                                  "https://open.feishu.cn/open-apis/bot/v2/hook/e7697dc6-5254-4411-8b59-3cd0742bf703",
+                                  "【 机器改造通知 】")
+                if tags:
+                    Tag.video_tag(code, tags)
+                if values:
+                    if name == "王雪珂":
+                        sheet = "vfhHwj"
+                    elif name == "鲁涛":
+                        sheet = "FhewlS"
+                    elif name == "范军":
+                        sheet = "B6dCfS"
+                    elif name == "余海涛":
+                        sheet = "mfBrNT"
+                    elif name == "罗情":
+                        sheet = "2J3PwN"
+                    elif name == "王玉婷":
+                        sheet = "bBHFwC"
+                    elif name == "刘诗雨":
+                        sheet = "fBdxIQ"
+                    elif name == "信欣":
+                        sheet = "lPe1eT"
+                    elif name == "快手创作者版品类推荐流":
+                        sheet = "k7l7nQ"
+                    elif name == "抖音品类账号":
+                        sheet = "ZixHmf"
+                    elif name == "抖音品类账号-1":
+                        sheet = "61kvW7"
+                    elif name == "视频号品类账号":
+                        sheet = "b0uLWw"
+                    elif name == "单点视频":
+                        sheet = "ptgCXW"
+                    elif name == "快手品类账号":
+                        sheet = "ibjoMx"
+                    elif name == "品类关键词搜索":
+                        sheet = "rBAJT8"
+                    elif name == "快手推荐流":
+                        sheet = "9Ii8lw"
+                    elif name == "视频号推荐流":
+                        sheet = "hMBv7T"
+                    elif name == "快手小程序":
+                        sheet = "GeDT6Q"
+                    Feishu.insert_columns("ILb4sa0LahddRktnRipcu2vQnLb", sheet, "ROWS", 1, 2)
+                    time.sleep(0.5)
+                    Feishu.update_values("ILb4sa0LahddRktnRipcu2vQnLb", sheet, "A2:Z2", values)
+
         except Exception as e:
         except Exception as e:
-            AliyunLogger.logging(channel_id, name, url, video["video_id"], f"改造失败{e}", "3001", log_data)
-            Common.logger(mark).error(f"{name}的{task_mark}任务处理失败:{e}")
+            AliyunLogger.logging(channel_id, name, url, v_id, f"改造失败{traceback.format_exc()}", "3001")
+            Common.logger(mark).error(f"{name}的{task_mark}任务处理失败:{traceback.format_exc()}")
 
 
 
 
     @classmethod
     @classmethod
@@ -500,8 +516,7 @@ class VideoProcessor:
         return new_title
         return new_title
 
 
     @classmethod
     @classmethod
-    def download_and_process_video(cls, channel_id, video_url, video_path_url, v_id, crop_total, gg_duration_total,
-                                   pw_random_id, new_title, mark, video):
+    def download_and_process_video(cls, channel_id, video_url, video_path_url, v_id, video):
         """
         """
         下载并处理视频
         下载并处理视频
         """
         """
@@ -509,116 +524,23 @@ class VideoProcessor:
             new_video_path = PQ.dd_sph_download_video(video_url, video_path_url, v_id, video, channel_id)
             new_video_path = PQ.dd_sph_download_video(video_url, video_path_url, v_id, video, channel_id)
         elif channel_id == "视频号":
         elif channel_id == "视频号":
             new_video_path = PQ.sph_download_video(video_url, video_path_url, v_id, video)
             new_video_path = PQ.sph_download_video(video_url, video_path_url, v_id, video)
-            if new_video_path == None:
-                return None
-            Common.logger(mark).info(f"{channel_id}视频下载成功: {new_video_path}")
         elif channel_id == "票圈" or channel_id == "快手创作者版" or channel_id == '视频号搜索' or channel_id == "快手推荐流":
         elif channel_id == "票圈" or channel_id == "快手创作者版" or channel_id == '视频号搜索' or channel_id == "快手推荐流":
             new_video_path = PQ.download_video(video_url, video_path_url, v_id)
             new_video_path = PQ.download_video(video_url, video_path_url, v_id)
-            if new_video_path == None:
-                return None
-            Common.logger(mark).info(f"{channel_id}视频下载成功: {new_video_path}")
         elif channel_id == "抖音" or channel_id == "抖音历史" or channel_id == "抖音搜索":
         elif channel_id == "抖音" or channel_id == "抖音历史" or channel_id == "抖音搜索":
             new_video_path = PQ.download_dy_video(video_url, video_path_url, v_id)
             new_video_path = PQ.download_dy_video(video_url, video_path_url, v_id)
-            if new_video_path == None:
-                return None
-            Common.logger(mark).info(f"{channel_id}视频下载成功: {new_video_path}")
         elif channel_id == "视频号历史":
         elif channel_id == "视频号历史":
             new_video_path = Oss.download_sph_ls(video_url, video_path_url, v_id)
             new_video_path = Oss.download_sph_ls(video_url, video_path_url, v_id)
         else:
         else:
-            Common.logger(mark).info(f"视频准备下载")
             new_video_path = Oss.download_video_oss(video_url, video_path_url, v_id)
             new_video_path = Oss.download_video_oss(video_url, video_path_url, v_id)
-        if not os.path.isfile(new_video_path)  or os.path.getsize(new_video_path) == 0:
-            return None
-        Common.logger(mark).info(f"视频下载成功: {new_video_path}")
-        if crop_total and crop_total != 'None':  # 判断是否需要裁剪
-            new_video_path = FFmpeg.video_crop(new_video_path, video_path_url, pw_random_id)
-        if gg_duration_total and gg_duration_total != 'None':  # 判断是否需要指定视频时长
-            new_video_path = FFmpeg.video_ggduration(new_video_path, video_path_url, pw_random_id,
-                                                     gg_duration_total)
-        width, height = FFmpeg.get_w_h_size(new_video_path)
-        if width < height:  # 判断是否需要修改为竖屏
-            new_video_path = FFmpeg.update_video_h_w(new_video_path, video_path_url, pw_random_id)
-            new_title_re = re.sub(r'[^\w\s\u4e00-\u9fff,。!?]', '', new_title)
-            if len(new_title_re) > 12:
-                new_title_re = '\n'.join(
-                    [new_title_re[i:i + 12] for i in range(0, len(new_title_re), 12)])
-            new_video_path = FFmpeg.add_video_zm(new_video_path, video_path_url, pw_random_id, new_title_re)
         return new_video_path
         return new_video_path
 
 
-    @classmethod
-    def handle_video_ending(cls, new_video_path, video_ending, old_title, pw_random_id, video_path_url, mark, task_mark, url, name, video_share, zm, voice):
-        """
-        处理视频片尾
-        """
-        if video_ending == "AI片尾引导":
-            pw_srt_text = GPT4oMini.get_ai_mini_pw(old_title)
-            if pw_srt_text:
-
-                pw_url = TTS.get_pw_zm(pw_srt_text, voice)
-                if pw_url:
-                    pw_mp3_path = TTS.download_mp3(pw_url, video_path_url, pw_random_id)
-                    # oss_mp3_key = Oss.mp3_upload_oss(pw_mp3_path, pw_random_id)
-                    # oss_mp3_key = oss_mp3_key.get("oss_object_key")
-                    # new_pw_path = f"http://art-crawler.oss-cn-hangzhou.aliyuncs.com/{oss_mp3_key}"
-                    # print(f"mp3地址:{new_pw_path}")
-                    # pw_url_sec = FFmpeg.get_video_duration(pw_mp3_path)
-                    pw_srt = TTS.getSrt(pw_url)
-                    Common.logger(mark).info(f"{name}的{task_mark}下的视频{url},获取AI片尾srt成功")
-                else:
-                    Common.logger(mark).info(f"{name}的{task_mark}下的视频{url},获取AI片尾失败")
-                    return None
-            else:
-                Common.logger(mark).info(f"{name}的{task_mark}下的视频{url},获取AI片尾失败")
-
-                return None
-        else:
-            if ',' in video_ending:
-                video_ending_list = video_ending.split(',')
-            else:
-                video_ending_list = [video_ending]
-            ending = random.choice(video_ending_list)
-            pw_list = Material.get_pwsrt_data("summary", "DgX7vC", ending)  # 获取srt
-            if pw_list:
-                pw_id = pw_list["pw_id"]
-                pw_srt = pw_list["pw_srt"]
-                pw_url = PQ.get_pw_url(pw_id)
-                pw_mp3_path = FFmpeg.get_video_mp3(pw_url, video_path_url, pw_random_id)
-            else:
-                Feishu.bot(mark, '机器自动改造消息通知', f'{task_mark}任务下片尾标示错误,请关注!!!!', name)
-        for attempt in range(3):
-            jpg_path = FFmpeg.video_png(new_video_path, video_path_url, pw_random_id)  # 生成视频最后一帧jpg
-            if os.path.isfile(jpg_path):
-                Common.logger(mark).info(f"{name}的{task_mark}下的视频{url},生成视频最后一帧成功")
-                break
-            time.sleep(1)
-        for attempt in range(3):
-            Common.logger(mark).info(f"{name}的{task_mark}下的视频{url},获取mp3成功")
-            pw_path = FFmpeg.pw_video(jpg_path, video_path_url, pw_mp3_path, pw_srt, pw_random_id,
-                                      pw_mp3_path)  # 生成片尾视频
-            if os.path.isfile(pw_path):
-                Common.logger(mark).info(f"{task_mark}下的视频{url},生成片尾视频成功")
-                break
-            time.sleep(1)
-        pw_video_list = [new_video_path, pw_path]
-        Common.logger(mark).info(f"{task_mark}下的视频{url},视频与片尾开始拼接")
-        video_path = FFmpeg.concatenate_videos(pw_video_list, video_path_url)  # 视频与片尾拼接到一起
-        if os.path.exists(video_path) or os.path.getsize(video_path) != 0:
-            Common.logger(mark).info(f"{name}的{task_mark}下的视频{url},视频与片尾拼接成功")
-            time.sleep(1)
-            if video_share and video_share != 'None':
-                new_video_path = FFmpeg.single_video(video_path, video_path_url, zm)
-            else:
-                new_video_path = video_path
-            return new_video_path
-        else:
-            return new_video_path
 
 
 
 
 
 
 
 
     @classmethod
     @classmethod
     def upload_video_and_thumbnail(cls, new_video_path: str, cover: str, v_id, new_title: str, task_mark: str, name: str, piaoquan_id,
     def upload_video_and_thumbnail(cls, new_video_path: str, cover: str, v_id, new_title: str, task_mark: str, name: str, piaoquan_id,
-                                   video_path_url: str, mark: str, channel_id: str, url: str, old_title: str, title, rule: str, video):
+                                   video_path_url: str, mark: str, channel_id: str, url: str, old_title: str, rule: str, video):
         """
         """
         上传视频和封面到OSS,并更新数据库
         上传视频和封面到OSS,并更新数据库
         """
         """

+ 722 - 0
video_rewriting/video_processor1.py

@@ -0,0 +1,722 @@
+import configparser
+import json
+import os
+import random
+import re
+import shutil
+import time
+from datetime import datetime
+
+from common.gpt4o_mini_help import GPT4oMini
+from common.redis import get_data, get_first_value_with_prefix, increment_key
+from common.tag_video import Tag
+from common.tts_help import TTS
+from common import Material, Feishu, Common, Oss, AliyunLogger
+from common.ffmpeg import FFmpeg
+from data_channel.douyin import DY
+from data_channel.dy_keyword import DyKeyword
+from data_channel.dy_ls import DYLS
+from data_channel.ks_feed import KSFeed
+from data_channel.ks_keyword import KsKeyword
+from data_channel.ks_ls import KSLS
+from data_channel.ks_xcx import KSXCX
+from data_channel.ks_xcx_keyword import KsXCXKeyword
+from data_channel.kuaishou import KS
+from data_channel.kuaishouchuangzuozhe import KsFeedVideo
+from data_channel.piaoquan import PQ
+from common.sql_help import sqlCollect
+from data_channel.shipinhao import SPH
+
+# 读取配置文件
+from data_channel.shipinhaodandian import SPHDD
+from data_channel.sph_feed import SPHFeed
+from data_channel.sph_keyword import SphKeyword
+from data_channel.sph_ls import SPHLS
+
+config = configparser.ConfigParser()
+config.read('./config.ini')
+
+
+class VideoProcessor:
+
+    """
+    视频处理类,包含创建文件夹、生成随机ID、删除文件和处理视频任务等方法。
+    """
+
+    @classmethod
+    def create_folders(cls, mark):
+        """
+        根据标示和任务标示创建目录
+        """
+        id = cls.random_id()
+        video_path_url = config['PATHS']['VIDEO_PATH'] + mark + "/" + str(id) + "/"
+        if not os.path.exists(video_path_url):
+            os.makedirs(video_path_url)
+        return video_path_url
+
+    @classmethod
+    def random_id(cls):
+        """
+        随机生成ID
+        """
+        now = datetime.now()
+        rand_num = random.randint(10000, 99999)
+        return f"{now.strftime('%Y%m%d%H%M%S')}{rand_num}"
+
+    @classmethod
+    def remove_files(cls, mark):
+        """
+        删除指定目录下的所有文件和子目录
+        """
+        path = config['PATHS']['VIDEO_PATH'] + mark + "/"
+        # 删除目录下的所有内容
+        if os.path.exists(path):
+            # 遍历目录下的所有文件和子目录
+            for filename in os.listdir(path):
+                file_path = os.path.join(path, filename)
+                try:
+                    if os.path.isfile(file_path) or os.path.islink(file_path):
+                        os.unlink(file_path)  # 删除文件或符号链接
+                    elif os.path.isdir(file_path):
+                        shutil.rmtree(file_path)  # 删除子目录及其所有内容
+                except Exception as e:
+                    print(f'Failed to delete {file_path}. Reason: {e}')
+
+
+
+    @classmethod
+    def process_task(cls, task, mark, name, feishu_id, cookie_sheet):
+        """
+        处理单个任务
+        """
+        try:
+            task_mark = task["task_mark"]
+            channel_id = str(task["channel_id"])
+            url = str(task["channel_url"])
+            piaoquan_id = str(task["piaoquan_id"])
+            number = task["number"]
+            title = task["title"]
+            video_share = task["video_share"]
+            video_ending = task["video_ending"]
+            crop_total = task["crop_total"]
+            gg_duration_total = task["gg_duration_total"]
+            voice = task['voice']
+            tags = task['tags']
+            if voice:
+                if ',' in voice:
+                    voices = voice.split(',')
+                else:
+                    voices = [voice]
+                voice = random.choice(voices)
+            else:
+                voice = "zhifeng_emo"
+            zm = Material.get_pzsrt_data("summary", "500Oe0", video_share)
+            Common.logger(mark).info(f"{name}的{task_mark}下{channel_id}的用户:{url}开始获取视频")
+            new_count = None
+            # if name in ['快手品类账号', '抖音品类账号', '抖音品类账号-1', '视频号品类账号']:
+            #     new_count = OdpsDataCount.main(channel_id, name, url)
+            data_list = cls.get_data_list(
+                channel_id, task_mark, url,
+                number,
+                mark, feishu_id, cookie_sheet, name, task
+            )
+            if not data_list:
+                AliyunLogger.logging(channel_id, name, url, "", "无改造视频", "4000")
+                Common.logger(mark).info(f"{name}的{task_mark}下{channel_id}的视频ID{url} 无改造视频")
+                text = (
+                    f"**通知类型**: 没有改造的视频\n"
+                    f"**负责人**: {name}\n"
+                    f"**渠道**: {channel_id}\n"
+                    f"**视频主页ID**: {url}\n"
+                )
+                Feishu.finish_bot(text, "https://open.feishu.cn/open-apis/bot/v2/hook/e7697dc6-5254-4411-8b59-3cd0742bf703",
+                                  "【 机器改造通知 】")
+                return
+            if new_count:
+                sqlCollect.insert_spider_supply_targetcnt(channel_id, name, url, number, new_count, str(len(data_list)))
+                current_time = datetime.now()
+                formatted_time = current_time.strftime("%Y-%m-%d %H:%M:%S")
+                values = [
+                    [
+                        name,
+                        channel_id,
+                        url,
+                        str(number),
+                        str(new_count),
+                        str(len(data_list)),
+                        formatted_time
+                    ]
+                ]
+                Feishu.insert_columns("Z5xLsdyyxh3abntTTvUc9zw8nYd", "099da8", "ROWS", 1, 2)
+                time.sleep(0.5)
+                Feishu.update_values("Z5xLsdyyxh3abntTTvUc9zw8nYd", "099da8", "A2:Z2", values)
+            Common.logger(mark).info(f"{name}的{task_mark}下的ID{url} 获取视频完成,共{len(data_list)}条")
+            try:
+                for video in data_list:
+                    # limit_number = task["limit_number"]
+                    # if limit_number:
+                    #     task_mark = task["task_mark"]
+                    #     makr_count = sqlCollect.get_mark_count(task_mark)
+                    #     if int(limit_number) <= int(makr_count[0][0]):
+                    #         AliyunLogger.logging((task["channel_id"]), name, task["channel_url"], '',
+                    #                              f"{task_mark}标识任务每日指定条数已足够,指定条数{limit_number},实际生成条数{int(makr_count[0][0])}",
+                    #                              "1111")
+                    #         return
+                    cls.remove_files(mark)
+                    video_path_url = cls.create_folders(mark)
+                    new_title = cls.generate_title(video, title)
+                    v_id = video["video_id"]
+                    cover = video["cover"]
+                    video_url = video["video_url"]
+                    old_title = video['old_title']
+                    rule = video['rule']
+                    if not old_title:
+                        old_title = '这个视频,分享给我的老友,祝愿您能幸福安康'
+                        text = (
+                            f"**通知类型**: 标题为空,使用兜底标题生成片尾\n"
+                            f"**负责人**: {name}\n"
+                            f"**渠道**: {channel_id}\n"
+                            f"**视频主页ID**: {url}\n"
+                            f"**视频Video_id**: {v_id}\n"
+                        )
+                        Feishu.finish_bot(text,
+                                          "https://open.feishu.cn/open-apis/bot/v2/hook/e7697dc6-5254-4411-8b59-3cd0742bf703",
+                                          "【 机器改造通知 】")
+                        Common.logger(mark).info(f"{name}的{task_mark}下的视频{url},标题为空,使用兜底标题生成片尾")
+                    time.sleep(1)
+                    pw_random_id = cls.random_id()
+                    Common.logger(mark).info(f"{name}的{task_mark}下的ID{url} 开始下载视频")
+                    new_video_path = cls.download_and_process_video(channel_id, video_url, video_path_url, v_id,
+                                                                    crop_total, gg_duration_total, pw_random_id, new_title, mark, video)
+                    if not os.path.isfile(new_video_path) or os.path.getsize(new_video_path) == 0:
+                        AliyunLogger.logging(channel_id, name, url, v_id, "视频下载失败", "3002", f"video_url:{video_url}")
+                        text = (
+                            f"**通知类型**: 视频下载失败\n"
+                            f"**负责人**: {name}\n"
+                            f"**渠道**: {channel_id}\n"
+                            f"**视频主页ID**: {url}\n"
+                            f"**视频Video_id**: {v_id}\n"
+                        )
+                        Feishu.finish_bot(text,
+                                          "https://open.feishu.cn/open-apis/bot/v2/hook/e7697dc6-5254-4411-8b59-3cd0742bf703",
+                                          "【 机器改造通知 】")
+                        continue
+                    if video_ending and video_ending != 'None':
+                        new_video_path = cls.handle_video_ending(new_video_path, video_ending, old_title, pw_random_id, video_path_url, mark, task_mark, url, name, video_share, zm, voice)
+                        if new_video_path == None:
+                            if name == "单点视频":
+                                sqlCollect.update_shp_dd_vid_4(v_id)
+                                from_user_name = video['from_user_name']  # 来源用户
+                                from_group_name = video['from_group_name']  # 来源群组
+                                source = video['source']  # 渠道
+                                text = (
+                                    f"**渠道**: {source}\n"
+                                    f"**来源用户**: {from_user_name}\n"
+                                    f"**来源群组**: {from_group_name}\n"
+                                    f"**原视频链接**: {video['video_url']}\n"
+                                    f"**原视频封面**: {video['cover']}\n"
+                                    f"**原视频标题**: {video['old_title']}\n"
+                                )
+                                AliyunLogger.logging(channel_id, name, url, v_id, "视频下载失败", "3002")
+                                Feishu.finish_bot(text,
+                                                  "https://open.feishu.cn/open-apis/bot/v2/hook/493b3d4c-5fae-4a9d-980b-1dd86636524e",
+                                                  "【 视频下载失败,跳过该视频 】")
+                            if name == "快手推荐流" or name == "视频号推荐流":
+                                sqlCollect.update_feed_vid_2(v_id)
+                                Feishu.finish_bot(text,
+                                                  "https://open.feishu.cn/open-apis/bot/v2/hook/493b3d4c-5fae-4a9d-980b-1dd86636524e",
+                                                  "【 视频下载失败,跳过该视频 】")
+                            continue
+                    else:
+                        if video_share and video_share != 'None':
+                            new_video_path = FFmpeg.single_video(new_video_path, video_path_url, zm)
+                    if not os.path.isfile(new_video_path) or os.path.getsize(new_video_path) == 0:
+                        AliyunLogger.logging(channel_id, name, url, v_id, "视频改造失败", "3001")
+                        text = (
+                            f"**通知类型**: 视频改造失败\n"
+                            f"**负责人**: {name}\n"
+                            f"**渠道**: {channel_id}\n"
+                            f"**视频主页ID**: {url}\n"
+                            f"**视频Video_id**: {v_id}\n"
+                        )
+                        Feishu.finish_bot(text,
+                                          "https://open.feishu.cn/open-apis/bot/v2/hook/e7697dc6-5254-4411-8b59-3cd0742bf703",
+                                          "【 机器改造通知 】")
+                        continue
+                    # 上传视频和封面,并更新数据库
+                    code = cls.upload_video_and_thumbnail(new_video_path, cover, v_id, new_title, task_mark, name, piaoquan_id,
+                                                   video_path_url, mark, channel_id, url, old_title, title, rule, video)
+                    # 更新已使用的视频号状态
+                    pq_url = f'https://admin.piaoquantv.com/cms/post-detail/{code}/detail'  # 站内视频链接
+                    if name == "单点视频":
+                        sphdd_status = sqlCollect.update_shp_dd_vid(v_id)
+                        if sphdd_status == 1:
+                            Common.logger(mark).info(f"{name}的{task_mark}下的ID{url} 视频修改已使用,状态已修改")
+                            from_user_name = video['from_user_name']  # 来源用户
+                            from_group_name = video['from_group_name']  # 来源群组
+                            source = video['source']  # 渠道
+                            channel_id = source
+                            text = (
+                                f"**站内视频链接**: {pq_url}\n"
+                                f"**渠道**: {source}\n"
+                                f"**来源用户**: {from_user_name}\n"
+                                f"**来源群组**: {from_group_name}\n"
+                                f"**原视频链接**: {video['video_url']}\n"
+                                f"**原视频封面**: {video['cover']}\n"
+                                f"**原视频标题**: {video['old_title']}\n"
+                            )
+                            Feishu.finish_bot(text, "https://open.feishu.cn/open-apis/bot/v2/hook/d2f751a8-5b0a-49ca-a306-1fda142707a9", "【 有一条新的内容改造成功 】")
+                    if name == "快手推荐流" or name == "视频号推荐流":
+                        feed_status = sqlCollect.update_feed_vid(v_id)
+                        if feed_status == 1:
+                            Common.logger(mark).info(f"{name}的{task_mark}下的ID{url} 视频修改已使用,状态已修改")
+                    if channel_id == "快手历史" or channel_id == "抖音历史" or channel_id == "视频号历史":
+                        explain = "历史爆款"
+                    else:
+                        explain = "新供给"
+                    current_time = datetime.now()
+                    formatted_time = current_time.strftime("%Y-%m-%d %H:%M:%S")
+                    if name == "品类关键词搜索":
+                        first_category = task["first_category"]
+                        keyword_principal = task["keyword_name"]
+                        tag_first = f"一级品类_{first_category}"
+                        tag_keyword = f"关键词_{url}"
+                        if channel_id == "抖音搜索":
+                            tag_channel = "来源_抖音关键词"
+                        elif channel_id == "快手搜索":
+                            tag_channel = "来源_快手关键词"
+                        elif channel_id == "视频号搜索":
+                            tag_channel = "来源_视频号关键词"
+                        tag = f"{tag_first},{tag_keyword},{tag_channel}"
+                        tag_status = Tag.video_tag(code, tag)
+                        if tag_status == 0:
+                            Common.logger(mark).info(f"{name}的{task_mark}下的ID{url}下的票圈视频{code},写入标签成功")
+                        secondary_category = task["secondary_category"]
+                        log_data = f"user:{url},,video_id:{v_id},,video_url:{video_url},,ai_title:{new_title},,voice:{voice},,first_category:{first_category},,secondary_category:{secondary_category},,keyword_principal:{keyword_principal},,tag:{tag}"
+                        values = [
+                            [
+                                name,
+                                task_mark,
+                                channel_id,
+                                url,
+                                str(v_id),
+                                piaoquan_id,
+                                old_title,
+                                title if title in ["原标题", "AI标题"] else "",
+                                new_title,
+                                str(code),
+                                formatted_time,
+                                str(rule),
+                                explain,
+                                voice,
+                                first_category,
+                                secondary_category,
+                                keyword_principal,
+                                pq_url
+                            ]
+                        ]
+                    elif name == "抖音品类账号-1" or name == "抖音品类账号" or name == "视频号品类账号" or name == "快手品类账号":
+                        first_category = task["first_category"]
+                        tag_first = f"一级品类_{first_category}"
+                        if channel_id == "抖音" or channel_id == "抖音历史":
+                            tag_channel = "来源_抖音品类账号"
+                        elif channel_id == "快手" or channel_id == "快手历史":
+                            tag_channel = "来源_快手品类账号"
+                        elif channel_id == "视频号" or channel_id == "视频号历史":
+                            tag_channel = "来源_视频号品类账号"
+                        tag = f"{tag_first},{tag_channel}"
+                        tag_status = Tag.video_tag( code, tag )
+                        if tag_status == 0:
+                            Common.logger(mark).info(f"{name}的{task_mark}下的ID{url}下的票圈视频{code},写入标签成功")
+                        log_data = f"user:{url},,video_id:{v_id},,video_url:{video_url},,ai_title:{new_title},,voice:{voice},,tag:{tag}"
+                        # log_data = f"user:{url},,video_id:{v_id},,video_url:{video_url},,ai_title:{new_title},,voice:{voice},,first_category:{first_category},,tag:{tag}"
+                        values = [
+                            [
+                                name,
+                                task_mark,
+                                channel_id,
+                                url,
+                                str( v_id ),
+                                piaoquan_id,
+                                old_title,
+                                title if title in ["原标题", "AI标题"] else "",
+                                new_title,
+                                str( code ),
+                                formatted_time,
+                                str( rule ),
+                                explain,
+                                voice,
+                                first_category,
+                                pq_url
+                            ]
+                        ]
+
+                    else:
+                        log_data = f"user:{url},,video_id:{v_id},,video_url:{video_url},,ai_title:{new_title},,voice:{voice}"
+                        values = [
+                            [
+                                name,
+                                task_mark,
+                                channel_id,
+                                url,
+                                str(v_id),
+                                piaoquan_id,
+                                old_title,
+                                title if title in ["原标题", "AI标题"] else "",
+                                new_title,
+                                str(code),
+                                formatted_time,
+                                str(rule),
+                                explain,
+                                voice
+                            ]
+                        ]
+                    AliyunLogger.logging(channel_id, name, url, v_id, "视频改造成功", "1000", log_data, str(code))
+                    text = (
+                        f"**通知类型**: 视频改造成功\n"
+                        f"**站内视频链接**: {pq_url}\n"
+                        f"**负责人**: {name}\n"
+                        f"**渠道**: {channel_id}\n"
+                        f"**视频主页ID**: {url}\n"
+                        f"**视频Video_id**: {v_id}\n"
+                        f"**使用音频音色**: {voice}\n"
+                    )
+                    Feishu.finish_bot(text,
+                                      "https://open.feishu.cn/open-apis/bot/v2/hook/e7697dc6-5254-4411-8b59-3cd0742bf703",
+                                      "【 机器改造通知 】")
+                    if tags:
+                        Tag.video_tag(code, tags)
+                    if values:
+                        if name == "王雪珂":
+                            sheet = "vfhHwj"
+                        elif name == "鲁涛":
+                            sheet = "FhewlS"
+                        elif name == "范军":
+                            sheet = "B6dCfS"
+                        elif name == "余海涛":
+                            sheet = "mfBrNT"
+                        elif name == "罗情":
+                            sheet = "2J3PwN"
+                        elif name == "王玉婷":
+                            sheet = "bBHFwC"
+                        elif name == "刘诗雨":
+                            sheet = "fBdxIQ"
+                        elif name == "信欣":
+                            sheet = "lPe1eT"
+                        elif name == "快手创作者版品类推荐流":
+                            sheet = "k7l7nQ"
+                        elif name == "抖音品类账号":
+                            sheet = "ZixHmf"
+                        elif name == "抖音品类账号-1":
+                            sheet = "61kvW7"
+                        elif name == "视频号品类账号":
+                            sheet = "b0uLWw"
+                        elif name == "单点视频":
+                            sheet = "ptgCXW"
+                        elif name == "快手品类账号":
+                            sheet = "ibjoMx"
+                        elif name == "品类关键词搜索":
+                            sheet = "rBAJT8"
+                        elif name == "快手推荐流":
+                            sheet = "9Ii8lw"
+                        elif name == "视频号推荐流":
+                            sheet = "hMBv7T"
+                        elif name == "快手小程序":
+                            sheet = "GeDT6Q"
+                        Feishu.insert_columns("ILb4sa0LahddRktnRipcu2vQnLb", sheet, "ROWS", 1, 2)
+                        time.sleep(0.5)
+                        Feishu.update_values("ILb4sa0LahddRktnRipcu2vQnLb", sheet, "A2:Z2", values)
+            except Exception as e:
+                AliyunLogger.logging(channel_id, name, url, video["video_id"], f"改造失败{e}", "3001", log_data)
+                Common.logger(mark).error(f"{name}的{task_mark}任务处理失败:{e}")
+        except Exception as e:
+            AliyunLogger.logging(channel_id, name, url, video["video_id"], f"改造失败{e}", "3001", log_data)
+            Common.logger(mark).error(f"{name}的{task_mark}任务处理失败:{e}")
+
+
+    @classmethod
+    def get_data_list(cls, channel_id, task_mark, url, number, mark, feishu_id, cookie_sheet, name, task):
+        """
+        根据渠道ID获取数据列表
+        """
+        if channel_id == "抖音":
+            return DY.get_dy_url(task_mark, url, number, mark, feishu_id, cookie_sheet, channel_id, name)
+        elif channel_id == "票圈":
+            return PQ.get_pq_url(task_mark, url, number, mark, channel_id, name)
+        elif channel_id == "视频号":
+            return SPH.get_sph_url(task_mark, url, number, mark, channel_id, name)
+        elif channel_id == "快手":
+            return KS.get_ks_url(task_mark, url, number, mark, feishu_id, cookie_sheet, channel_id, name)
+        elif channel_id == "快手创作者版":
+            return KsFeedVideo.get_data(channel_id, name)
+        elif channel_id == "单点视频":
+            return SPHDD.get_sphdd_data(url, channel_id, name)
+        elif channel_id == "抖音历史":
+            return DYLS.get_dy_zr_list(task_mark, url, number, mark, channel_id, name)
+        elif channel_id == "快手历史":
+            return KSLS.get_ksls_list(task_mark, url, number, mark, channel_id, name)
+        elif channel_id == "视频号历史":
+            return SPHLS.get_sphls_data(task_mark, url, number, mark, channel_id, name)
+        elif channel_id == '抖音搜索':
+            return DyKeyword.get_key_word(url, task_mark, mark, channel_id, name, task)
+        elif channel_id == '快手搜索':
+            return KsXCXKeyword.get_key_word(url, task_mark, mark, channel_id, name, task)
+        elif channel_id == '视频号搜索':
+            return SphKeyword.get_key_word(url, task_mark, mark, channel_id, name)
+        elif channel_id == '快手推荐流':
+            return KSFeed.get_feed_date()
+        elif channel_id == '视频号推荐流':
+            return SPHFeed.get_feed_date()
+        elif channel_id == '快手小程序':
+            return KSXCX.get_xcx_date()
+
+
+    @classmethod
+    def generate_title(cls, video, title):
+        """
+        生成新标题
+        """
+        if video['old_title']:
+            new_title = video['old_title'].strip().replace("\n", "") \
+                .replace("/", "").replace("\\", "").replace("\r", "") \
+                .replace(":", "").replace("*", "").replace("?", "") \
+                .replace("?", "").replace('"', "").replace("<", "") \
+                .replace(">", "").replace("|", "").replace(" ", "") \
+                .replace("&NBSP", "").replace(".", "。").replace(" ", "") \
+                .replace("'", "").replace("#", "").replace("Merge", "")
+        else:
+            return '这个视频,分享给我的老友,祝愿您能幸福安康'
+        if title == "原标题":
+            if not new_title:
+                new_title = '这个视频,分享给我的老友,祝愿您能幸福安康'
+        elif title == "AI标题":
+            if not new_title:
+                new_title = '这个视频,分享给我的老友,祝愿您能幸福安康'
+            else:
+                new_title = GPT4oMini.get_ai_mini_title(new_title)
+        else:
+            titles = title.split('/') if '/' in title else [title]
+            new_title = random.choice(titles)
+        return new_title
+
+    @classmethod
+    def download_and_process_video(cls, channel_id, video_url, video_path_url, v_id, crop_total, gg_duration_total,
+                                   pw_random_id, new_title, mark, video):
+        """
+        下载并处理视频
+        """
+        if channel_id == "单点视频":
+            new_video_path = PQ.dd_sph_download_video(video_url, video_path_url, v_id, video, channel_id)
+        elif channel_id == "视频号":
+            new_video_path = PQ.sph_download_video(video_url, video_path_url, v_id, video)
+            if new_video_path == None:
+                return None
+            Common.logger(mark).info(f"{channel_id}视频下载成功: {new_video_path}")
+        elif channel_id == "票圈" or channel_id == "快手创作者版" or channel_id == '视频号搜索' or channel_id == "快手推荐流":
+            new_video_path = PQ.download_video(video_url, video_path_url, v_id)
+            if new_video_path == None:
+                return None
+            Common.logger(mark).info(f"{channel_id}视频下载成功: {new_video_path}")
+        elif channel_id == "抖音" or channel_id == "抖音历史" or channel_id == "抖音搜索":
+            new_video_path = PQ.download_dy_video(video_url, video_path_url, v_id)
+            if new_video_path == None:
+                return None
+            Common.logger(mark).info(f"{channel_id}视频下载成功: {new_video_path}")
+        elif channel_id == "视频号历史":
+            new_video_path = Oss.download_sph_ls(video_url, video_path_url, v_id)
+        else:
+            Common.logger(mark).info(f"视频准备下载")
+            new_video_path = Oss.download_video_oss(video_url, video_path_url, v_id)
+        if not os.path.isfile(new_video_path)  or os.path.getsize(new_video_path) == 0:
+            return None
+        Common.logger(mark).info(f"视频下载成功: {new_video_path}")
+        if crop_total and crop_total != 'None':  # 判断是否需要裁剪
+            new_video_path = FFmpeg.video_crop(new_video_path, video_path_url, pw_random_id)
+        if gg_duration_total and gg_duration_total != 'None':  # 判断是否需要指定视频时长
+            new_video_path = FFmpeg.video_ggduration(new_video_path, video_path_url, pw_random_id,
+                                                     gg_duration_total)
+        width, height = FFmpeg.get_w_h_size(new_video_path)
+        if width < height:  # 判断是否需要修改为竖屏
+            new_video_path = FFmpeg.update_video_h_w(new_video_path, video_path_url, pw_random_id)
+            new_title_re = re.sub(r'[^\w\s\u4e00-\u9fff,。!?]', '', new_title)
+            if len(new_title_re) > 12:
+                new_title_re = '\n'.join(
+                    [new_title_re[i:i + 12] for i in range(0, len(new_title_re), 12)])
+            new_video_path = FFmpeg.add_video_zm(new_video_path, video_path_url, pw_random_id, new_title_re)
+        return new_video_path
+
+    @classmethod
+    def handle_video_ending(cls, new_video_path, video_ending, old_title, pw_random_id, video_path_url, mark, task_mark, url, name, video_share, zm, voice):
+        """
+        处理视频片尾
+        """
+        if video_ending == "AI片尾引导":
+            pw_srt_text = GPT4oMini.get_ai_mini_pw(old_title)
+            if pw_srt_text:
+
+                pw_url = TTS.get_pw_zm(pw_srt_text, voice)
+                if pw_url:
+                    pw_mp3_path = TTS.download_mp3(pw_url, video_path_url, pw_random_id)
+                    # oss_mp3_key = Oss.mp3_upload_oss(pw_mp3_path, pw_random_id)
+                    # oss_mp3_key = oss_mp3_key.get("oss_object_key")
+                    # new_pw_path = f"http://art-crawler.oss-cn-hangzhou.aliyuncs.com/{oss_mp3_key}"
+                    # print(f"mp3地址:{new_pw_path}")
+                    # pw_url_sec = FFmpeg.get_video_duration(pw_mp3_path)
+                    pw_srt = TTS.getSrt(pw_url)
+                    Common.logger(mark).info(f"{name}的{task_mark}下的视频{url},获取AI片尾srt成功")
+                else:
+                    Common.logger(mark).info(f"{name}的{task_mark}下的视频{url},获取AI片尾失败")
+                    return None
+            else:
+                Common.logger(mark).info(f"{name}的{task_mark}下的视频{url},获取AI片尾失败")
+
+                return None
+        else:
+            if ',' in video_ending:
+                video_ending_list = video_ending.split(',')
+            else:
+                video_ending_list = [video_ending]
+            ending = random.choice(video_ending_list)
+            pw_list = Material.get_pwsrt_data("summary", "DgX7vC", ending)  # 获取srt
+            if pw_list:
+                pw_id = pw_list["pw_id"]
+                pw_srt = pw_list["pw_srt"]
+                pw_url = PQ.get_pw_url(pw_id)
+                pw_mp3_path = FFmpeg.get_video_mp3(pw_url, video_path_url, pw_random_id)
+            else:
+                Feishu.bot(mark, '机器自动改造消息通知', f'{task_mark}任务下片尾标示错误,请关注!!!!', name)
+        for attempt in range(3):
+            jpg_path = FFmpeg.video_png(new_video_path, video_path_url, pw_random_id)  # 生成视频最后一帧jpg
+            if os.path.isfile(jpg_path):
+                Common.logger(mark).info(f"{name}的{task_mark}下的视频{url},生成视频最后一帧成功")
+                break
+            time.sleep(1)
+        for attempt in range(3):
+            Common.logger(mark).info(f"{name}的{task_mark}下的视频{url},获取mp3成功")
+            pw_path = FFmpeg.pw_video(jpg_path, video_path_url, pw_mp3_path, pw_srt, pw_random_id,
+                                      pw_mp3_path)  # 生成片尾视频
+            if os.path.isfile(pw_path):
+                Common.logger(mark).info(f"{task_mark}下的视频{url},生成片尾视频成功")
+                break
+            time.sleep(1)
+        pw_video_list = [new_video_path, pw_path]
+        Common.logger(mark).info(f"{task_mark}下的视频{url},视频与片尾开始拼接")
+        video_path = FFmpeg.concatenate_videos(pw_video_list, video_path_url)  # 视频与片尾拼接到一起
+        if os.path.exists(video_path) or os.path.getsize(video_path) != 0:
+            Common.logger(mark).info(f"{name}的{task_mark}下的视频{url},视频与片尾拼接成功")
+            time.sleep(1)
+            if video_share and video_share != 'None':
+                new_video_path = FFmpeg.single_video(video_path, video_path_url, zm)
+            else:
+                new_video_path = video_path
+            return new_video_path
+        else:
+            return new_video_path
+
+
+
+
+    @classmethod
+    def upload_video_and_thumbnail(cls, new_video_path: str, cover: str, v_id, new_title: str, task_mark: str, name: str, piaoquan_id,
+                                   video_path_url: str, mark: str, channel_id: str, url: str, old_title: str, title, rule: str, video):
+        """
+        上传视频和封面到OSS,并更新数据库
+        """
+        try:
+            oss_id = cls.random_id()
+            Common.logger(mark).info(f"{name}的{task_mark},开始发送oss")
+            oss_object_key = Oss.stitching_sync_upload_oss(new_video_path, oss_id)  # 视频发送OSS
+            Common.logger(mark).info(f"{name}的{task_mark},发送oss成功{oss_object_key}")
+            status = oss_object_key.get("status")
+            if status == 200:
+                oss_object_key = oss_object_key.get("oss_object_key")
+                time.sleep(1)
+                jpg_path = None
+                if channel_id == "快手历史" or channel_id == "快手" or channel_id == '快手搜索' or channel_id == '视频号':
+                    jpg = None
+                elif channel_id == "视频号历史":
+                    jpg_path = Oss.download_sph_ls( cover, video_path_url, v_id )
+                elif channel_id == '单点视频':
+                    if video['source'] != "快手":
+                        jpg_path = PQ.download_video_jpg( cover, video_path_url, v_id )  # 下载视频封面
+                if jpg_path and os.path.isfile( jpg_path ):
+                    oss_jpg_key = Oss.stitching_fm_upload_oss( jpg_path, oss_id )  # 封面上传OSS
+                    jpg = oss_jpg_key.get( "oss_object_key" )
+                else:
+                    jpg = None
+                code = PQ.insert_piaoquantv(oss_object_key, new_title, jpg, piaoquan_id)
+                Common.logger(mark).info(f"{name}的{task_mark}下的视频ID{v_id}发送成功")
+                sqlCollect.insert_task(task_mark, v_id, mark, channel_id)  # 插入数据库
+                current_time = datetime.now()
+                formatted_time = current_time.strftime("%Y-%m-%d %H:%M:%S")
+                if name == "单点视频":
+                    url = str(rule)
+                sqlCollect.insert_machine_making_data(name, task_mark, channel_id, url, v_id, piaoquan_id, new_title, code,
+                                                      formatted_time, old_title, oss_object_key)
+
+                return code
+        except Exception as e:
+            Common.logger(mark).error(f"{name}的{task_mark}上传视频和封面到OSS,并更新数据库失败:{e}\n")
+            AliyunLogger.logging(channel_id, name, url, video["video_id"], "改造失败-上传视频和封面到OSS", "3001")
+            return
+
+
+    @classmethod
+    def main(cls, data):
+        """
+        主函数,初始化任务并使用线程池处理任务。
+        """
+        mark = data["mark"]
+        name = data["name"]
+        feishu_id = data["feishu_id"]
+        feishu_sheet = data["feishu_sheet"]
+        cookie_sheet = data["cookie_sheet"]
+        data = get_data(mark, feishu_id, feishu_sheet)
+        if not data:
+            Common.logger("redis").error(f"{mark}任务开始新的一轮\n")
+            return
+        task = json.loads(data)
+        try:
+            limit_number = task["limit_number"]
+            if limit_number:
+                task_mark = task["task_mark"]
+                makr_count = sqlCollect.get_mark_count(task_mark)
+                if int(limit_number) <= int(makr_count[0][0]):
+                    AliyunLogger.logging((task["channel_id"]), name, task["channel_url"], '', f"{task_mark}标识任务每日指定条数已足够,指定条数{limit_number},实际生成条数{int(makr_count[0][0])}",
+                                         "1111")
+                    return
+            if mark == 'dy-pl-gjc' and task['channel_id'] == '抖音搜索':
+                mark_count = 'dyss-count'
+                count = get_first_value_with_prefix(mark_count)
+                increment_key(mark_count)
+                if int(count) >= 300:
+                    return "抖音搜索上限"
+            if mark == 'ks-pl-gjc':
+                mark_count = 'ksss-count'
+                count = get_first_value_with_prefix(mark_count)
+                increment_key(mark_count)
+                if int(count) >= 300:
+                    return "快手搜索上限"
+            if mark == 'sph-pl-gjc':
+                mark_count = 'ss-sph-count'
+                count = get_first_value_with_prefix(mark_count)
+                increment_key(mark_count)
+                if int(count) >= 300:
+                    time.sleep(10)
+                    return "视频号搜索上限"
+            if mark == 'sph-plzh'and task['channel_id'] == '视频号':
+                mark_count = 'sph-count'
+                count = get_first_value_with_prefix(mark_count)
+                increment_key(mark_count)
+                if int(count) >= 400:
+                    time.sleep(10)
+                    return "视频号获取用户主页视频上限"
+            VideoProcessor.process_task(task, mark, name, feishu_id, cookie_sheet)
+            return mark
+        except Exception as e:
+            AliyunLogger.logging((task["channel_id"]), name, task["channel_url"],'', f"用户抓取异常:{e}", "3001")
+            return mark
+
+
+# if __name__ == "__main__":
+#     main()