|
@@ -0,0 +1,300 @@
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+import datetime
|
|
|
|
+import random
|
|
|
|
+import os
|
|
|
|
+import sys
|
|
|
|
+import time
|
|
|
|
+
|
|
|
|
+import requests
|
|
|
|
+import urllib.parse
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+sys.path.append(os.getcwd())
|
|
|
|
+from common.aliyun_oss_uploading import Oss
|
|
|
|
+from common.common import Common
|
|
|
|
+from common.db import MysqlHelper
|
|
|
|
+from common.material import Material
|
|
|
|
+from moviepy.editor import VideoFileClip, concatenate_videoclips
|
|
|
|
+from moviepy import editor
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+output_path = "video/new_video.mp4"
|
|
|
|
+class VideoStitching():
|
|
|
|
+ @classmethod
|
|
|
|
+ def split_text(cls, text, max_length):
|
|
|
|
+ words = text.split(' ')
|
|
|
|
+ lines = []
|
|
|
|
+ current_line = ''
|
|
|
|
+ for word in words:
|
|
|
|
+ if len(current_line) + len(word) <= max_length:
|
|
|
|
+ current_line += word + ' '
|
|
|
|
+ else:
|
|
|
|
+ lines.append(current_line.strip())
|
|
|
|
+ current_line = word + ' '
|
|
|
|
+ lines.append(current_line.strip())
|
|
|
|
+ result = ''.join(lines)
|
|
|
|
+ result = result[:11] + '\n' + result[11:]
|
|
|
|
+ return result
|
|
|
|
+
|
|
|
|
+ @classmethod
|
|
|
|
+ def srt_to_seconds(cls,srt_time):
|
|
|
|
+ hours, minutes, seconds = map(float, srt_time.replace(',', '.').split(':'))
|
|
|
|
+ return hours * 3600 + minutes * 60 + seconds
|
|
|
|
+
|
|
|
|
+ @classmethod
|
|
|
|
+ def insert_videoAudio(cls, audio_url, i):
|
|
|
|
+ for j in audio_url:
|
|
|
|
+ insert_sql = f"""INSERT INTO video_audio (audio, video_id, account_id, oss_object_key) values ("{i}", "{j[0]}", "{j[1]}", "{j[2]}")"""
|
|
|
|
+ MysqlHelper.update_values(
|
|
|
|
+ sql=insert_sql,
|
|
|
|
+ env="prod",
|
|
|
|
+ machine="",
|
|
|
|
+ )
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ @classmethod
|
|
|
|
+ def random_id(cls):
|
|
|
|
+ now = datetime.datetime.now()
|
|
|
|
+ rand_num = random.randint(10000, 99999)
|
|
|
|
+
|
|
|
|
+ id = "{}{}".format(now.strftime("%Y%m%d%H%M%S"), rand_num)
|
|
|
|
+ return id
|
|
|
|
+
|
|
|
|
+ @classmethod
|
|
|
|
+ def get_account_id(cls):
|
|
|
|
+ account_id = f"""select account_id from video_url group by account_id;"""
|
|
|
|
+ account_id = MysqlHelper.get_values(account_id, "prod")
|
|
|
|
+ return account_id
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ @classmethod
|
|
|
|
+ def get_url_list(cls, i, account):
|
|
|
|
+ url_list = f"""SELECT a.video_id,a.account_id,a.oss_object_key FROM video_url a WHERE NOT EXISTS (
|
|
|
|
+ SELECT video_id
|
|
|
|
+ FROM video_audio b
|
|
|
|
+ WHERE a.video_id = b.video_id AND b.audio = "{i}"
|
|
|
|
+ ) AND a.account_id = '{account}' ;"""
|
|
|
|
+ url_list = MysqlHelper.get_values(url_list, "prod")
|
|
|
|
+ return url_list
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ @classmethod
|
|
|
|
+ def insert_piaoquantv(cls, oss_object_key):
|
|
|
|
+ title = Material.get_title()
|
|
|
|
+ url = "https://vlogapi.piaoquantv.com/longvideoapi/crawler/video/send"
|
|
|
|
+ payload = dict(pageSource='vlog-pages/post/post-video-post', videoPath=oss_object_key, width='720',
|
|
|
|
+ height='1280', fileExtensions='mp4', viewStatus='1', title=title, careModelStatus='1',
|
|
|
|
+ token='f04f58d6e664cbc9902660a1e8d20ce6cd7fdb0f', loginUid='66425096', versionCode='719',
|
|
|
|
+ machineCode='weixin_openid_o0w175aZ4FJtqVsA1tcozJDJHdDU', appId='wx89e7eb06478361d7',
|
|
|
|
+ clientTimestamp='1703337579331',
|
|
|
|
+ machineInfo='{"sdkVersion":"3.2.5","brand":"iPhone","language":"zh_CN","model":"iPhone 12 Pro<iPhone13,3>","platform":"ios","system":"iOS 15.6.1","weChatVersion":"8.0.44","screenHeight":844,"screenWidth":390,"pixelRatio":3,"windowHeight":762,"windowWidth":390,"softVersion":"4.1.719"}',
|
|
|
|
+ sessionId='1703337560040-27bfe208-a389-f476-db1d-840681e04b32',
|
|
|
|
+ subSessionId='1703337569952-8f56d53c-b36d-760e-8abe-0b4a027cd5bd', senceType='1089',
|
|
|
|
+ hotSenceType='1089', id='1050', channel='pq')
|
|
|
|
+
|
|
|
|
+ payload['videoPath'] = oss_object_key
|
|
|
|
+ payload['title'] = title
|
|
|
|
+
|
|
|
|
+ data = urllib.parse.urlencode(payload)
|
|
|
|
+
|
|
|
|
+ headers = {
|
|
|
|
+ 'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 15_6_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 MicroMessenger/8.0.44(0x18002c2d) NetType/WIFI Language/zh_CN',
|
|
|
|
+ 'Accept-Encoding': 'gzip,compress,br,deflate',
|
|
|
|
+ 'Referer': 'https://servicewechat.com/wx89e7eb06478361d7/726/page-frame.html',
|
|
|
|
+ 'Content-Type': 'application/x-www-form-urlencoded',
|
|
|
|
+ 'Cookie': 'JSESSIONID=A60D96E7A300A25EA05425B069C8B459'
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ response = requests.post(url, data=data, headers=headers)
|
|
|
|
+ data = response.json()
|
|
|
|
+ if data["code"] == 0:
|
|
|
|
+ return True
|
|
|
|
+ else:
|
|
|
|
+ return False
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ @classmethod
|
|
|
|
+ def concatenate_videos(cls, videos, audio, srt):
|
|
|
|
+ clips = []
|
|
|
|
+ total_duration = 0
|
|
|
|
+ included_videos = []
|
|
|
|
+
|
|
|
|
+ video1 = VideoFileClip(audio)
|
|
|
|
+ mp3 = video1.audio
|
|
|
|
+
|
|
|
|
+ duration_limit = mp3.duration
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ for i, video in enumerate(videos):
|
|
|
|
+ clip = VideoFileClip(video[3])
|
|
|
|
+ clips.append(clip)
|
|
|
|
+ total_duration += clip.duration
|
|
|
|
+ if total_duration >= duration_limit:
|
|
|
|
+ break
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ if total_duration <= duration_limit:
|
|
|
|
+ return ""
|
|
|
|
+ else:
|
|
|
|
+ remaining_time = duration_limit
|
|
|
|
+ final_clips = []
|
|
|
|
+
|
|
|
|
+ for clip, video in zip(clips, videos):
|
|
|
|
+ if remaining_time - clip.duration >= 0:
|
|
|
|
+ final_clips.append(clip)
|
|
|
|
+ included_videos.append(video)
|
|
|
|
+ remaining_time -= clip.duration
|
|
|
|
+ else:
|
|
|
|
+
|
|
|
|
+ final_clips.append(clip.subclip(0, remaining_time))
|
|
|
|
+ included_videos.append(video)
|
|
|
|
+ break
|
|
|
|
+ final_clip = concatenate_videoclips(final_clips)
|
|
|
|
+ final_clip = final_clip.set_audio(mp3)
|
|
|
|
+
|
|
|
|
+ final_width = 480
|
|
|
|
+ final_height = 720
|
|
|
|
+ final_clip = final_clip.resize((final_width, final_height))
|
|
|
|
+
|
|
|
|
+ color_clip = editor.ColorClip(size=(final_width, 120),
|
|
|
|
+ color=(255, 255, 0)).set_duration(duration_limit)
|
|
|
|
+ final_clip = editor.CompositeVideoClip([final_clip, color_clip.set_position(("center", final_height - 100))])
|
|
|
|
+
|
|
|
|
+ subtitle_file = f"video/{srt}.srt"
|
|
|
|
+ if os.path.isfile(subtitle_file):
|
|
|
|
+ with open(subtitle_file, 'r') as file:
|
|
|
|
+ subtitles = file.read().strip().split('\n\n')
|
|
|
|
+
|
|
|
|
+ subtitle_clips = []
|
|
|
|
+ for subtitle in subtitles:
|
|
|
|
+
|
|
|
|
+ subtitle_lines = subtitle.strip().split('\n')
|
|
|
|
+
|
|
|
|
+ if len(subtitle_lines) >= 3:
|
|
|
|
+ times, text = subtitle_lines[1], '\n'.join(subtitle_lines[2:])
|
|
|
|
+ start, end = map(cls.srt_to_seconds, times.split(' --> '))
|
|
|
|
+ start = editor.cvsecs(start)
|
|
|
|
+ end = editor.cvsecs(end)
|
|
|
|
+ text = cls.split_text(text, 10)
|
|
|
|
+ sub = editor.TextClip(text, font="/System/Library/Fonts/Hiragino Sans GB.ttc",
|
|
|
|
+ fontsize=30, color="black").set_duration(end - start).set_start(
|
|
|
|
+ start).set_position(
|
|
|
|
+ ("center", final_height - 80)).set_opacity(0.8)
|
|
|
|
+ subtitle_clips.append(sub)
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ video_with_subtitles = editor.CompositeVideoClip([final_clip] + subtitle_clips)
|
|
|
|
+ else:
|
|
|
|
+ text_clip = (
|
|
|
|
+ editor.TextClip("分享、转发给群友", font="/System/Library/Fonts/Hiragino Sans GB.ttc",
|
|
|
|
+ fontsize=30, color="black").
|
|
|
|
+ set_position(("center", final_height - 80)).
|
|
|
|
+ set_duration(duration_limit).
|
|
|
|
+ set_opacity(0.8)
|
|
|
|
+ )
|
|
|
|
+
|
|
|
|
+ video_with_subtitles = editor.CompositeVideoClip([final_clip, text_clip])
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ video_with_subtitles.write_videofile(output_path, codec='libx264', fps=30, threads=4)
|
|
|
|
+ if os.path.isfile(output_path):
|
|
|
|
+ Common.logger().info("视频生成成功!生成路径为:", output_path)
|
|
|
|
+ return included_videos
|
|
|
|
+ else:
|
|
|
|
+ Common.logger().info("视频生成失败,请检查代码和文件路径。")
|
|
|
|
+ return ""
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ @classmethod
|
|
|
|
+ def get_audio_url(cls, i):
|
|
|
|
+ url = f"https://admin.piaoquantv.com/manager/video/detail/{i}"
|
|
|
|
+
|
|
|
|
+ payload = {}
|
|
|
|
+ headers = {
|
|
|
|
+ 'authority': 'admin.piaoquantv.com',
|
|
|
|
+ 'accept': 'application/json, text/plain, */*',
|
|
|
|
+ 'accept-language': 'zh-CN,zh;q=0.9',
|
|
|
|
+ 'cache-control': 'no-cache',
|
|
|
|
+ 'cookie': 'SESSION=MDJiNGM4YzgtY2RiMC00ZjRkLThlNzEtOThhZDJkMjE0Yzgx',
|
|
|
|
+ 'pragma': 'no-cache',
|
|
|
|
+ 'referer': f'https://admin.piaoquantv.com/cms/post-detail/{i}/detail',
|
|
|
|
+ 'sec-ch-ua': '"Not_A Brand";v="8", "Chromium";v="120", "Google Chrome";v="120"',
|
|
|
|
+ 'sec-ch-ua-mobile': '?0',
|
|
|
|
+ 'sec-ch-ua-platform': '"macOS"',
|
|
|
|
+ 'sec-fetch-dest': 'empty',
|
|
|
|
+ 'sec-fetch-mode': 'cors',
|
|
|
|
+ 'sec-fetch-site': 'same-origin',
|
|
|
|
+ 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ response = requests.request("GET", url, headers=headers, data=payload)
|
|
|
|
+ data = response.json()
|
|
|
|
+ audio_url = data["content"]["transedVideoPath"]
|
|
|
|
+ print(audio_url)
|
|
|
|
+ return audio_url
|
|
|
|
+
|
|
|
|
+ @classmethod
|
|
|
|
+ def video_stitching(cls):
|
|
|
|
+ count = 0
|
|
|
|
+
|
|
|
|
+ audio = Material.get_audio()
|
|
|
|
+ for i in audio:
|
|
|
|
+
|
|
|
|
+ account_id = cls.get_account_id()
|
|
|
|
+ for j in account_id:
|
|
|
|
+ account = j[0].replace('(', '').replace(')', '').replace(',', '')
|
|
|
|
+ Common.logger().info(f"获取用户ID:{account}")
|
|
|
|
+
|
|
|
|
+ url_list = cls.get_url_list(i, account)
|
|
|
|
+
|
|
|
|
+ audio = cls.get_audio_url(i)
|
|
|
|
+ Common.logger().info(f"获取音频地址:{audio}")
|
|
|
|
+ videos = [list(item) for item in url_list]
|
|
|
|
+ videos = Oss.get_oss_url(videos)
|
|
|
|
+
|
|
|
|
+ try:
|
|
|
|
+ audio_url = cls.concatenate_videos(videos, str(audio), i)
|
|
|
|
+ if len(audio_url) == 0:
|
|
|
|
+ Common.logger().info(f"视频生成失败")
|
|
|
|
+ continue
|
|
|
|
+
|
|
|
|
+ id = cls.random_id()
|
|
|
|
+ Common.logger().info(f"生成视频id为:{id}")
|
|
|
|
+
|
|
|
|
+ oss_object_key = Oss.stitching_sync_upload_oss(output_path, id)
|
|
|
|
+ status = oss_object_key.get("status")
|
|
|
|
+
|
|
|
|
+ oss_object_key = oss_object_key.get("oss_object_key")
|
|
|
|
+ Common.logger().info(f"新拼接视频,oss发送成功,oss地址:{oss_object_key}")
|
|
|
|
+ if status == 200:
|
|
|
|
+ time.sleep(10)
|
|
|
|
+
|
|
|
|
+ cls.insert_videoAudio(audio_url, i)
|
|
|
|
+ Common.logger().info(f"发送成功 已使用视频存入数据库完成")
|
|
|
|
+ if os.path.isfile(output_path):
|
|
|
|
+ os.remove(output_path)
|
|
|
|
+ Common.logger().info(f"文件删除成功{output_path}")
|
|
|
|
+ else:
|
|
|
|
+ Common.logger().info(f"文件不存在{output_path}")
|
|
|
|
+ piaoquantv = cls.insert_piaoquantv(oss_object_key)
|
|
|
|
+ if piaoquantv:
|
|
|
|
+ count += 1
|
|
|
|
+ Common.logger().info(f"视频添加到对应用户成功")
|
|
|
|
+ if count >= 20:
|
|
|
|
+ break
|
|
|
|
+ except Exception as e:
|
|
|
|
+ Common.logger().warning(f"新拼接视频发送oss失败:{e}\n")
|
|
|
|
+ continue
|
|
|
|
+ if count >= 20:
|
|
|
|
+ break
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+if __name__ == '__main__':
|
|
|
|
+ VideoStitching.video_stitching()
|