video_stitching.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  1. # -*- coding: utf-8 -*-
  2. # @Time: 2023/12/26
  3. import datetime
  4. import random
  5. import os
  6. import sys
  7. import time
  8. import subprocess
  9. import requests
  10. import urllib.parse
  11. sys.path.append(os.getcwd())
  12. from common.aliyun_oss_uploading import Oss
  13. from common.common import Common
  14. from common.db import MysqlHelper
  15. from common.material import Material
  16. from moviepy.editor import VideoFileClip, concatenate_videoclips
  17. from moviepy import editor
  18. output_path = "./video_stitchingvideo/video/new_video.mp4"
  19. class VideoStitching():
  20. @classmethod
  21. def split_text(cls, text, max_length):
  22. words = text.split(' ')
  23. lines = []
  24. current_line = ''
  25. for word in words:
  26. if len(current_line) + len(word) <= max_length:
  27. current_line += word + ' '
  28. else:
  29. lines.append(current_line.strip())
  30. current_line = word + ' '
  31. lines.append(current_line.strip())
  32. result = ''.join(lines)
  33. result = result[:11] + '\n' + result[11:] # 在第10个字后面增加换行
  34. return result
  35. @classmethod
  36. def srt_to_seconds(cls,srt_time):
  37. hours, minutes, seconds = map(float, srt_time.replace(',', '.').split(':'))
  38. return hours * 3600 + minutes * 60 + seconds
  39. @classmethod
  40. def insert_videoAudio(cls, audio_url, i):
  41. for j in audio_url:
  42. insert_sql = f"""INSERT INTO video_audio (audio, video_id, account_id, oss_object_key) values ("{i}", "{j[0]}", "{j[1]}", "{j[2]}")"""
  43. MysqlHelper.update_values(
  44. sql=insert_sql,
  45. env="prod",
  46. machine="",
  47. )
  48. # 随机生成id
  49. @classmethod
  50. def random_id(cls):
  51. now = datetime.datetime.now()
  52. rand_num = random.randint(10000, 99999)
  53. id = "{}{}".format(now.strftime("%Y%m%d%H%M%S"), rand_num)
  54. return id
  55. @classmethod
  56. def get_account_id(cls):
  57. account_id = f"""select account_id from video_url group by account_id;"""
  58. account_id = MysqlHelper.get_values(account_id, "prod")
  59. return account_id
  60. @classmethod
  61. def get_url_list(cls, i, account):
  62. url_list = f"""SELECT a.video_id,a.account_id,a.oss_object_key FROM video_url a WHERE NOT EXISTS (
  63. SELECT video_id
  64. FROM video_audio b
  65. WHERE a.video_id = b.video_id AND b.audio = "{i}"
  66. ) AND a.account_id = '{account}' ;"""
  67. url_list = MysqlHelper.get_values(url_list, "prod")
  68. return url_list
  69. # 新生成视频上传到对应账号下
  70. @classmethod
  71. def insert_piaoquantv(cls, oss_object_key):
  72. title = Material.get_title()
  73. url = "https://vlogapi.piaoquantv.com/longvideoapi/crawler/video/send"
  74. payload = dict(pageSource='vlog-pages/post/post-video-post', videoPath=oss_object_key, width='720',
  75. height='1280', fileExtensions='mp4', viewStatus='1', title=title, careModelStatus='1',
  76. token='f04f58d6e664cbc9902660a1e8d20ce6cd7fdb0f', loginUid='66425096', versionCode='719',
  77. machineCode='weixin_openid_o0w175aZ4FJtqVsA1tcozJDJHdDU', appId='wx89e7eb06478361d7',
  78. clientTimestamp='1703337579331',
  79. 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"}',
  80. sessionId='1703337560040-27bfe208-a389-f476-db1d-840681e04b32',
  81. subSessionId='1703337569952-8f56d53c-b36d-760e-8abe-0b4a027cd5bd', senceType='1089',
  82. hotSenceType='1089', id='1050', channel='pq')
  83. payload['videoPath'] = oss_object_key
  84. payload['title'] = title
  85. data = urllib.parse.urlencode(payload)
  86. headers = {
  87. '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',
  88. 'Accept-Encoding': 'gzip,compress,br,deflate',
  89. 'Referer': 'https://servicewechat.com/wx89e7eb06478361d7/726/page-frame.html',
  90. 'Content-Type': 'application/x-www-form-urlencoded',
  91. 'Cookie': 'JSESSIONID=A60D96E7A300A25EA05425B069C8B459'
  92. }
  93. response = requests.post(url, data=data, headers=headers)
  94. data = response.json()
  95. if data["code"] == 0:
  96. return True
  97. else:
  98. return False
  99. # 视频拼接
  100. @classmethod
  101. def concatenate_videos(cls, videos, audio, srt):
  102. clips = []
  103. total_duration = 0
  104. included_videos = []
  105. # 提取视频的音频
  106. filename = "./video_stitching/video.mp4"
  107. response = requests.get(audio)
  108. with open(filename, "wb") as f:
  109. f.write(response.content)
  110. time.sleep(10)
  111. Common.logger().info(f"开始提取视频的音频{audio}")
  112. video1 = VideoFileClip(filename)
  113. mp3 = video1.audio
  114. Common.logger().info(f"提取视频的音频成功")
  115. # 获取音频时长(以秒为单位)
  116. duration_limit = mp3.duration
  117. # 遍历每个视频并计算总时长
  118. for i, video in enumerate(videos):
  119. clip = VideoFileClip(video[3])
  120. clips.append(clip)
  121. total_duration += clip.duration
  122. if total_duration >= duration_limit:
  123. break
  124. # 如果总时长小于等于目标时长,则不做视频拼接
  125. if total_duration <= duration_limit:
  126. Common.logger().info(f"时长小于等于目标时长,不做视频拼接")
  127. return ""
  128. else:
  129. remaining_time = duration_limit
  130. final_clips = []
  131. for clip, video in zip(clips, videos):
  132. if remaining_time - clip.duration >= 0:
  133. final_clips.append(clip)
  134. included_videos.append(video)
  135. remaining_time -= clip.duration
  136. else:
  137. # 如果剩余时间不足以加入下一个视频,则截断当前视频并返回已包含的URL
  138. final_clips.append(clip.subclip(0, remaining_time))
  139. included_videos.append(video)
  140. break
  141. final_clip = concatenate_videoclips(final_clips)
  142. final_clip = final_clip.set_audio(mp3)
  143. # 统一设置视频分辨率
  144. final_width = 480
  145. final_height = 720
  146. final_clip = final_clip.resize((final_width, final_height))
  147. # 设置背景色
  148. color_clip = editor.ColorClip(size=(final_width, 120),
  149. color=(255, 255, 0)).set_duration(duration_limit)
  150. final_clip = editor.CompositeVideoClip([final_clip, color_clip.set_position(("center", final_height - 100))])
  151. # 处理SRT字幕文件
  152. subtitle_file = f"./video_stitching/video/{srt}.srt"
  153. Common.logger().info(f"处理SRT字幕文件")
  154. if os.path.isfile(subtitle_file):
  155. with open(subtitle_file, 'r') as file:
  156. subtitles = file.read().strip().split('\n\n')
  157. # 从SRT字幕文件中获取字幕
  158. subtitle_clips = []
  159. for subtitle in subtitles:
  160. # 按行分割字幕内容
  161. subtitle_lines = subtitle.strip().split('\n')
  162. # 提取时间轴信息和字幕文本
  163. if len(subtitle_lines) >= 3:
  164. times, text = subtitle_lines[1], '\n'.join(subtitle_lines[2:])
  165. start, end = map(cls.srt_to_seconds, times.split(' --> '))
  166. start = editor.cvsecs(start)
  167. end = editor.cvsecs(end)
  168. text = cls.split_text(text, 10)
  169. # /System/Library/Fonts/Hiragino Sans GB.ttc 本地字体
  170. sub = editor.TextClip(text, font="/usr/share/fonts/truetype/liberation/LiberationSans-Bold.ttf",
  171. fontsize=30, color="black").set_duration(end - start).set_start(
  172. start).set_position(
  173. ("center", final_height - 80)).set_opacity(0.8)
  174. subtitle_clips.append(sub)
  175. # 将字幕添加到视频上
  176. video_with_subtitles = editor.CompositeVideoClip([final_clip] + subtitle_clips)
  177. else:
  178. text_clip = (
  179. editor.TextClip("分享、转发给群友", font="/System/Library/Fonts/Hiragino Sans GB.ttc",
  180. fontsize=30, color="black").
  181. set_position(("center", final_height - 80)).
  182. set_duration(duration_limit).
  183. set_opacity(0.8)
  184. )
  185. # 把 `文本剪贴板` 贴在视频上
  186. video_with_subtitles = editor.CompositeVideoClip([final_clip, text_clip])
  187. # 生成视频
  188. video_with_subtitles.write_videofile(output_path, codec='libx264', fps=30, threads=4)
  189. if os.path.isfile(output_path):
  190. Common.logger().info("视频生成成功!生成路径为:", output_path)
  191. return included_videos
  192. else:
  193. Common.logger().info("视频生成失败,请检查代码和文件路径。")
  194. return ""
  195. @classmethod
  196. def get_audio_url(cls, i):
  197. url = f"https://admin.piaoquantv.com/manager/video/detail/{i}"
  198. payload = {}
  199. headers = {
  200. 'authority': 'admin.piaoquantv.com',
  201. 'accept': 'application/json, text/plain, */*',
  202. 'accept-language': 'zh-CN,zh;q=0.9',
  203. 'cache-control': 'no-cache',
  204. 'cookie': 'SESSION=MDJiNGM4YzgtY2RiMC00ZjRkLThlNzEtOThhZDJkMjE0Yzgx',
  205. 'pragma': 'no-cache',
  206. 'referer': f'https://admin.piaoquantv.com/cms/post-detail/{i}/detail',
  207. 'sec-ch-ua': '"Not_A Brand";v="8", "Chromium";v="120", "Google Chrome";v="120"',
  208. 'sec-ch-ua-mobile': '?0',
  209. 'sec-ch-ua-platform': '"macOS"',
  210. 'sec-fetch-dest': 'empty',
  211. 'sec-fetch-mode': 'cors',
  212. 'sec-fetch-site': 'same-origin',
  213. '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'
  214. }
  215. response = requests.request("GET", url, headers=headers, data=payload)
  216. data = response.json()
  217. audio_url = data["content"]["transedVideoPath"]
  218. print(audio_url)
  219. return audio_url
  220. @classmethod
  221. def video_stitching(cls):
  222. count = 0
  223. # 获取音频
  224. audio = Material.get_audio()
  225. for i in audio:
  226. # 获取已入库的用户id
  227. account_id = cls.get_account_id()
  228. for j in account_id:
  229. account = j[0].replace('(', '').replace(')', '').replace(',', '')
  230. Common.logger().info(f"获取用户ID:{account}")
  231. # 获取 未使用的视频链接
  232. url_list = cls.get_url_list(i, account)
  233. # 获取音频url
  234. audio = cls.get_audio_url(i)
  235. Common.logger().info(f"获取音频地址:{audio}")
  236. videos = [list(item) for item in url_list]
  237. videos = Oss.get_oss_url(videos)
  238. # 视频截取
  239. try:
  240. audio_url = cls.concatenate_videos(videos, str(audio), i)
  241. if len(audio_url) == 0:
  242. Common.logger().info(f"视频生成失败")
  243. continue
  244. # 随机生成视频id
  245. id = cls.random_id()
  246. Common.logger().info(f"生成视频id为:{id}")
  247. # 上传 oss
  248. oss_object_key = Oss.stitching_sync_upload_oss(output_path, id)
  249. status = oss_object_key.get("status")
  250. # 获取 oss 视频地址
  251. oss_object_key = oss_object_key.get("oss_object_key")
  252. Common.logger().info(f"新拼接视频,oss发送成功,oss地址:{oss_object_key}")
  253. if status == 200:
  254. time.sleep(10)
  255. # 发送成功 已使用视频存入数据库
  256. cls.insert_videoAudio(audio_url, i)
  257. Common.logger().info(f"发送成功 已使用视频存入数据库完成")
  258. if os.path.isfile(output_path):
  259. os.remove(output_path)
  260. Common.logger().info(f"文件删除成功{output_path}")
  261. else:
  262. Common.logger().info(f"文件不存在{output_path}")
  263. piaoquantv = cls.insert_piaoquantv(oss_object_key)
  264. if piaoquantv:
  265. count += 1
  266. Common.logger().info(f"视频添加到对应用户成功")
  267. if count >= 20:
  268. break
  269. except Exception as e:
  270. Common.logger().warning(f"新拼接视频发送oss失败:{e}\n")
  271. continue
  272. if count >= 1:
  273. break
  274. if __name__ == '__main__':
  275. VideoStitching.video_stitching()