video_stitching.py 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614
  1. # -*- coding: utf-8 -*-
  2. # @Time: 2023/12/26
  3. import datetime
  4. import random
  5. import os
  6. import re
  7. import sys
  8. import time
  9. import resource
  10. import requests
  11. import urllib.parse
  12. sys.path.append(os.getcwd())
  13. from datetime import datetime
  14. from common import Feishu
  15. from common.aliyun_oss_uploading import Oss
  16. from common.common import Common
  17. from common.db import MysqlHelper
  18. from common.material import Material
  19. from moviepy.editor import concatenate_videoclips
  20. from moviepy.video.io.VideoFileClip import VideoFileClip
  21. from moviepy import editor
  22. from moviepy.video.compositing.concatenate import concatenate
  23. output_path = "./video_stitching/video/new_video.mp4"
  24. class VideoStitching():
  25. @classmethod
  26. def split_text(cls, text, max_length):
  27. words = text.split(' ')
  28. lines = []
  29. current_line = ''
  30. for word in words:
  31. if len(current_line) + len(word) <= max_length:
  32. current_line += word + ' '
  33. else:
  34. lines.append(current_line.strip())
  35. current_line = word + ' '
  36. lines.append(current_line.strip())
  37. result = ''.join(lines)
  38. result = result[:11] + '\n' + result[11:] # 在第10个字后面增加换行
  39. return result
  40. @classmethod
  41. def srt_to_seconds(cls,srt_time):
  42. hours, minutes, seconds = map(float, srt_time.replace(',', '.').split(':'))
  43. return hours * 3600 + minutes * 60 + seconds
  44. @classmethod
  45. def insert_videoAudio(cls, audio_url, i, channel_type):
  46. current_time = datetime.now()
  47. formatted_time = current_time.strftime("%Y-%m-%d")
  48. if channel_type == "douyin":
  49. video_type = 0
  50. elif channel_type == "kschunjie":
  51. video_type = 5
  52. elif channel_type == "dypinjie":
  53. video_type = 6
  54. else:
  55. video_type = 2
  56. for j in audio_url:
  57. insert_sql = f"""INSERT INTO video_audio (audio, video_id, account_id, oss_object_key, time, video_type) values ('{i}', '{j[0]}', '{j[1]}', '{j[2]}', '{formatted_time}', {video_type})"""
  58. MysqlHelper.update_values(
  59. sql=insert_sql,
  60. env="prod",
  61. machine="",
  62. )
  63. @classmethod
  64. def insert_video_typeAudio(cls, video, audio_id, channel_type):
  65. current_time = datetime.now()
  66. formatted_time = current_time.strftime("%Y-%m-%d")
  67. if channel_type == "jieri":
  68. video_type = 3
  69. else:
  70. video_type = 1
  71. insert_sql = f"""INSERT INTO video_audio (audio, account_id, oss_object_key, time, video_type) values ('{audio_id}', '{video}', '{video}', '{formatted_time}', {video_type})"""
  72. MysqlHelper.update_values(
  73. sql=insert_sql,
  74. env="prod",
  75. machine="",
  76. )
  77. # 随机生成id
  78. @classmethod
  79. def random_id(cls):
  80. now = datetime.now()
  81. rand_num = random.randint(10000, 99999)
  82. id = "{}{}".format(now.strftime("%Y%m%d%H%M%S"), rand_num)
  83. return id
  84. @classmethod
  85. def get_account_id(cls, channel_type):
  86. account_id = f"""select account_id from video_url where oss_object_key LIKE '%{channel_type}%' group by account_id ;"""
  87. account_id = MysqlHelper.get_values(account_id, "prod")
  88. return account_id
  89. @classmethod
  90. def get_audio_list(cls):
  91. audio_list = f"""select oss_object_key from video_koubo_url ;"""
  92. audio_list = MysqlHelper.get_values(audio_list, "prod")
  93. return audio_list
  94. @classmethod
  95. def get_url_list(cls, audio_id, account):
  96. current_time = datetime.now()
  97. formatted_time = current_time.strftime("%Y-%m-%d")
  98. url_list = f"""SELECT a.video_id,a.account_id,a.oss_object_key FROM video_url a WHERE NOT EXISTS (
  99. SELECT video_id
  100. FROM video_audio b
  101. WHERE a.oss_object_key = b.oss_object_key AND b.time = '{formatted_time}'
  102. ) AND a.account_id = {account} and a.`status` = 1 limit 35;"""
  103. url_list = MysqlHelper.get_values(url_list, "prod")
  104. return url_list
  105. @classmethod
  106. def get_zizhi_url_list(cls, account):
  107. url_list = f"""SELECT video_id,account_id,oss_object_key FROM video_url where account_id = {account} limit 35;"""
  108. url_list = MysqlHelper.get_values(url_list, "prod")
  109. return url_list
  110. @classmethod
  111. def get_audio_url_list(cls, videos, audio_id):
  112. url_list = f"""SELECT audio FROM video_audio where audio = {audio_id} and oss_object_key = '{videos}';"""
  113. url_list = MysqlHelper.get_values(url_list, "prod")
  114. return url_list
  115. # 新生成视频上传到对应账号下
  116. @classmethod
  117. def insert_piaoquantv(cls, oss_object_key, title_list, video_type, channel_type):
  118. #list = ["66481136", "66481137", "66481140", "66481141", "66481142"] 老用户id
  119. if video_type == "自制--春节":
  120. title = title_list
  121. list = ['15924999', '50322241', '50322258', '57463797', '50322235', '57463790', '50322234', '6605563', '18981907', '50322198', '50322239', '57463838', '14500202']
  122. code = 1
  123. for i in range(len(list)):
  124. # title_list = [item for item in title_list if item is not None]
  125. # title = random.choice(title_list)
  126. # title = title[0].strip("[]'")
  127. url = "https://vlogapi.piaoquantv.com/longvideoapi/crawler/video/send"
  128. payload = dict(pageSource='vlog-pages/post/post-video-post', videoPath=oss_object_key, width='720',
  129. height='1280', fileExtensions='mp4', viewStatus='1', title=title, careModelStatus='1',
  130. token='f04f58d6e664cbc9902660a1e8d20ce6cd7fdb0f', loginUid=list[i], versionCode='719',
  131. machineCode='weixin_openid_o0w175aZ4FJtqVsA1tcozJDJHdDU', appId='wx89e7eb06478361d7',
  132. clientTimestamp='1703337579331',
  133. 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"}',
  134. sessionId='1703337560040-27bfe208-a389-f476-db1d-840681e04b32',
  135. subSessionId='1703337569952-8f56d53c-b36d-760e-8abe-0b4a027cd5bd', senceType='1089',
  136. hotSenceType='1089', id='1050', channel='pq')
  137. payload['videoPath'] = oss_object_key
  138. payload['title'] = title
  139. data = urllib.parse.urlencode(payload)
  140. headers = {
  141. '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',
  142. 'Accept-Encoding': 'gzip,compress,br,deflate',
  143. 'Referer': 'https://servicewechat.com/wx89e7eb06478361d7/726/page-frame.html',
  144. 'Content-Type': 'application/x-www-form-urlencoded',
  145. 'Cookie': 'JSESSIONID=A60D96E7A300A25EA05425B069C8B459'
  146. }
  147. response = requests.post(url, data=data, headers=headers)
  148. data = response.json()
  149. code = data["code"]
  150. if code == 0:
  151. return True
  152. else:
  153. return False
  154. else:
  155. if video_type == "口播--美文类":
  156. list = ["67231152", "67231153", "67231154", "67231155", "67231157"]
  157. elif channel_type == "douyin":
  158. list = ["67231113", "67231112", "67231111", "67231110", "67231109"]
  159. elif channel_type == "dypinjie":
  160. list = ["68276195", "68276196", "68276197", "68276198", "68276199"]
  161. elif channel_type == "kspinjie":
  162. list = ["68276262", "68276263", "68276264", "68276265", "68276267"]
  163. else:
  164. list = ["67413406", "67413407", "67413408", "67413409", "67413410"]
  165. code = 1
  166. for i in range(2):
  167. item = random.choice(list)
  168. title_list = [item for item in title_list if item is not None]
  169. title = random.choice(title_list)
  170. # title = title[0].strip("[]'")
  171. url = "https://vlogapi.piaoquantv.com/longvideoapi/crawler/video/send"
  172. payload = dict(pageSource='vlog-pages/post/post-video-post', videoPath=oss_object_key, width='720',
  173. height='1280', fileExtensions='mp4', viewStatus='1', title=title, careModelStatus='1',
  174. token='f04f58d6e664cbc9902660a1e8d20ce6cd7fdb0f', loginUid=item, versionCode='719',
  175. machineCode='weixin_openid_o0w175aZ4FJtqVsA1tcozJDJHdDU', appId='wx89e7eb06478361d7',
  176. clientTimestamp='1703337579331',
  177. 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"}',
  178. sessionId='1703337560040-27bfe208-a389-f476-db1d-840681e04b32',
  179. subSessionId='1703337569952-8f56d53c-b36d-760e-8abe-0b4a027cd5bd', senceType='1089',
  180. hotSenceType='1089', id='1050', channel='pq')
  181. payload['videoPath'] = oss_object_key
  182. payload['title'] = title
  183. data = urllib.parse.urlencode(payload)
  184. headers = {
  185. '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',
  186. 'Accept-Encoding': 'gzip,compress,br,deflate',
  187. 'Referer': 'https://servicewechat.com/wx89e7eb06478361d7/726/page-frame.html',
  188. 'Content-Type': 'application/x-www-form-urlencoded',
  189. 'Cookie': 'JSESSIONID=A60D96E7A300A25EA05425B069C8B459'
  190. }
  191. response = requests.post(url, data=data, headers=headers)
  192. data = response.json()
  193. code = data["code"]
  194. if code == 0:
  195. return True
  196. else:
  197. return False
  198. # 视频拼接
  199. @classmethod
  200. def concatenate_videos(cls, videos, audio, srt, type_audio):
  201. clips = []
  202. total_duration = 0
  203. included_videos = []
  204. # 设置最大可使用的内存限制(单位:字节)
  205. memory_limit = 6 * 1024 * 1024 * 1024 # 6GB
  206. resource.setrlimit(resource.RLIMIT_AS, (memory_limit, memory_limit))
  207. # 提取视频的音频
  208. Common.logger("video").info(f"开始提取视频的音频{audio}")
  209. video1 = VideoFileClip(audio)
  210. mp3 = video1.audio
  211. Common.logger("video").info(f"提取视频的音频成功")
  212. # 获取音频时长(以秒为单位)
  213. duration_limit = mp3.duration
  214. if type_audio == "口播--美文类":
  215. # 读取视频文件,获取视频时长
  216. clip = f"./video_stitching/video_material/koubo.mp4"
  217. clip = VideoFileClip(clip)
  218. video_duration = clip.duration
  219. # 计算需要循环播放的视频个数
  220. n_videos = int(duration_limit / video_duration) + 1
  221. # 循环生成视频剪辑
  222. video_clips = [clip] * n_videos
  223. # 连接视频剪辑
  224. final_clip = concatenate(video_clips, method="compose")
  225. # 设置最终剪辑的时长与音频时长一致
  226. final_clip = final_clip.set_duration(duration_limit)
  227. else:
  228. # 遍历每个视频并计算总时长
  229. for i, video in enumerate(videos):
  230. filename = video[2].split("/")[-1]
  231. clip = VideoFileClip(f'./video_stitching/video_material/{filename}.mp4')
  232. clips.append(clip)
  233. total_duration += clip.duration
  234. if total_duration >= duration_limit:
  235. break
  236. # 如果总时长小于等于目标时长,则不做视频拼接
  237. if total_duration <= duration_limit:
  238. Common.logger("video").info(f"时长小于等于目标时长,不做视频拼接")
  239. # 关闭视频文件
  240. for clip in clips:
  241. clip.close()
  242. for video in videos:
  243. filename = video[2].split("/")[-1]
  244. os.remove(f'./video_stitching/video_material/{filename}.mp4')
  245. return ""
  246. else:
  247. Common.logger("video").info(f"总时长大于目标时长")
  248. remaining_time = duration_limit
  249. final_clips = []
  250. for clip, video in zip(clips, videos):
  251. if remaining_time - clip.duration >= 0:
  252. final_clips.append(clip)
  253. included_videos.append(video)
  254. remaining_time -= clip.duration
  255. else:
  256. # 如果剩余时间不足以加入下一个视频,则截断当前视频并返回已包含的URL
  257. final_clips.append(clip.subclip(0, remaining_time))
  258. included_videos.append(video)
  259. break
  260. final_clip = concatenate_videoclips(final_clips)
  261. final_clip = final_clip.set_audio(mp3)
  262. # 统一设置视频分辨率
  263. final_width = 320
  264. final_height = 480
  265. final_clip = final_clip.resize((final_width, final_height))
  266. color_clip = editor.ColorClip(size=(final_width, 90),
  267. color=(255, 255, 0)).set_duration(duration_limit)
  268. final_clip = editor.CompositeVideoClip([final_clip, color_clip.set_position(("center", final_height - 80))])
  269. Common.logger("video").info(f"字幕内容为:{srt}")
  270. if srt != None:
  271. Common.logger("video").info(f"处理字幕文件")
  272. # 使用正则表达式提取时间码和字幕内容
  273. pattern = r"(\d{2}:\d{2}:\d{2},\d{3}) --> (\d{2}:\d{2}:\d{2},\d{3})\n([\s\S]+?(?=\n\d|$))"
  274. matches = re.findall(pattern, srt)
  275. Common.logger("video").info(f"字幕{matches}")
  276. # 获取字幕
  277. subtitle_clips = []
  278. for match in matches:
  279. start = editor.cvsecs(cls.srt_to_seconds(match[0]))
  280. end = editor.cvsecs(cls.srt_to_seconds(match[1]))
  281. text = match[2].strip()
  282. text = cls.split_text(text, 10)
  283. # 设置背景色
  284. # color_clip = editor.ColorClip(size=(final_width, 90),
  285. # color=(255, 255, 0)).set_duration(end - start).set_start(start)
  286. # final_clip = editor.CompositeVideoClip(
  287. # [final_clip, color_clip.set_position(("center", final_height - 80))])
  288. # /System/Library/Fonts/Hiragino Sans GB.ttc 本地字体
  289. # /usr/share/fonts/truetype/wqy/wqy-zenhei.ttc 服务器地址
  290. Common.logger("video").info(f"字幕:{text}")
  291. sub = editor.TextClip(text, font="/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc",
  292. fontsize=18, color="black").set_duration(end - start).set_start(
  293. start).set_position(
  294. ("center", final_height - 60)).set_opacity(0.8)
  295. subtitle_clips.append(sub)
  296. Common.logger("video").info(f"将字幕添加到视频上")
  297. # 将字幕添加到视频上
  298. video_with_subtitles = editor.CompositeVideoClip([final_clip] + subtitle_clips)
  299. else:
  300. Common.logger("video").info(f"添加固定字幕")
  301. text_clip = (
  302. editor.TextClip("分享、转发给群友", font="/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc",
  303. fontsize=30, color="black").
  304. set_position(("center", final_height - 70)).
  305. set_duration(duration_limit).
  306. set_opacity(0.8)
  307. )
  308. # 把 `文本剪贴板` 贴在视频上
  309. video_with_subtitles = editor.CompositeVideoClip([final_clip, text_clip])
  310. # 生成视频
  311. video_with_subtitles.write_videofile(output_path, fps=35)
  312. if os.path.isfile(output_path):
  313. Common.logger("video").info("视频生成成功!生成路径为:", output_path)
  314. return included_videos, video_with_subtitles, clips
  315. else:
  316. Common.logger("video").info("视频生成失败,请检查代码和文件路径。")
  317. return "", video_with_subtitles, clips
  318. @classmethod
  319. def get_audio_url(cls, i):
  320. cookie = Material.get_houtai_cookie()
  321. url = f"https://admin.piaoquantv.com/manager/video/detail/{i}"
  322. payload = {}
  323. headers = {
  324. 'authority': 'admin.piaoquantv.com',
  325. 'accept': 'application/json, text/plain, */*',
  326. 'accept-language': 'zh-CN,zh;q=0.9',
  327. 'cache-control': 'no-cache',
  328. 'cookie': cookie,
  329. 'pragma': 'no-cache',
  330. 'referer': f'https://admin.piaoquantv.com/cms/post-detail/{i}/detail',
  331. 'sec-ch-ua': '"Not_A Brand";v="8", "Chromium";v="120", "Google Chrome";v="120"',
  332. 'sec-ch-ua-mobile': '?0',
  333. 'sec-ch-ua-platform': '"macOS"',
  334. 'sec-fetch-dest': 'empty',
  335. 'sec-fetch-mode': 'cors',
  336. 'sec-fetch-site': 'same-origin',
  337. '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'
  338. }
  339. response = requests.request("GET", url, headers=headers, data=payload)
  340. data = response.json()
  341. try:
  342. code = data["code"]
  343. if code != 0:
  344. Common.logger("video").info(
  345. f"未登录,请更换cookie,{data}")
  346. Feishu.bot('recommend', '管理后台', '管理后台cookie失效,请及时更换~')
  347. return ""
  348. audio_url = data["content"]["transedVideoPath"]
  349. print(audio_url)
  350. return audio_url
  351. except Exception as e:
  352. Common.logger("video").warning(f"获取音频视频链接失败:{e}\n")
  353. return ""
  354. @classmethod
  355. def video_stitching(cls, video_type, count, channel_type):
  356. Common.logger("video").info(f"获取音频类型+字幕")
  357. # 获取音频类型+字幕
  358. audio_id, srt, title_list = Material.get_audio_type(video_type, count, channel_type)
  359. # 获取音频url
  360. if audio_id == None:
  361. Common.logger("video").info(f"获取音频url为空")
  362. return
  363. audio = cls.get_audio_url(audio_id)
  364. if audio == "":
  365. Common.logger("video").info(f"获取音频地址为空")
  366. return
  367. Common.logger("video").info(f"获取音频地址:{audio},获取用户id:{audio_id}")
  368. if video_type == "口播--美文类":
  369. # 获取已入库的口播视频
  370. audio_list = cls.get_audio_list()
  371. videos = [list(item) for item in audio_list]
  372. videos = random.choice(videos) #随机选择视频
  373. # 判断该视频+音频是否生成过视频
  374. # video = VideoStitching.get_audio_url_list(videos, audio_id)
  375. # if video:
  376. # Common.logger("video").info(f"该视频+音频已经生成过视频,不做视频拼接")
  377. # return
  378. elif video_type == "自制--春节":
  379. # 获取已入库的用户id
  380. account_id = cls.get_account_id(channel_type)
  381. account = random.choice(account_id)
  382. account = str(account).replace('(', '').replace(')', '').replace(',', '')
  383. Common.logger("video").info(f"获取用户ID:{account}")
  384. # 获取视频链接
  385. url_list = cls.get_zizhi_url_list(account)
  386. if url_list == None:
  387. Common.logger("video").info(f"未使用视频链接为空:{url_list}")
  388. return
  389. videos = [list(item) for item in url_list]
  390. else:
  391. # 获取已入库的用户id
  392. account_id = cls.get_account_id(channel_type)
  393. account = random.choice(account_id)
  394. account = str(account).replace('(', '').replace(')', '').replace(',', '')
  395. Common.logger("video").info(f"获取用户ID:{account}")
  396. # 获取 未使用的视频链接
  397. url_list = cls.get_url_list(audio_id, account)
  398. if url_list == None:
  399. Common.logger("video").info(f"未使用视频链接为空:{url_list}")
  400. return
  401. videos = [list(item) for item in url_list]
  402. videos = Oss.get_oss_url(video_type, videos)
  403. # 视频截取
  404. try:
  405. audio_url, video_with_subtitles, clips = cls.concatenate_videos(videos, str(audio), srt, video_type)
  406. if len(audio_url) == 0:
  407. Common.logger("video").info(f"视频生成失败")
  408. # 随机生成视频id
  409. id = cls.random_id()
  410. Common.logger("video").info(f"生成视频id为:{id}")
  411. # 上传 oss
  412. oss_object_key = Oss.stitching_sync_upload_oss(output_path, id)
  413. status = oss_object_key.get("status")
  414. # 获取 oss 视频地址
  415. oss_object_key = oss_object_key.get("oss_object_key")
  416. Common.logger("video").info(f"新拼接视频,oss发送成功,oss地址:{oss_object_key}")
  417. if status == 200:
  418. time.sleep(10)
  419. # 发送成功 已使用视频存入数据库
  420. if video_type == "口播--美文类":
  421. cls.insert_video_typeAudio(videos, audio_id, channel_type)
  422. else:
  423. cls.insert_videoAudio(audio_url, audio_id, channel_type)
  424. Common.logger("video").info(f"发送成功 已使用视频存入数据库完成")
  425. if os.path.isfile(output_path):
  426. os.remove(output_path)
  427. Common.logger("video").info(f"文件删除成功{output_path}")
  428. else:
  429. Common.logger("video").info(f"文件不存在{output_path}")
  430. if video_type == "口播--美文类":
  431. os.remove("./video_stitching/video_material/koubo.mp4")
  432. else:
  433. for video in videos:
  434. filename = video[2].split("/")[-1]
  435. os.remove(f'./video_stitching/video_material/{filename}.mp4')
  436. piaoquantv = cls.insert_piaoquantv(oss_object_key, title_list, video_type, channel_type)
  437. if piaoquantv:
  438. Common.logger("video").info(f"视频添加到对应用户成功")
  439. # 关闭视频文件
  440. for clip in clips:
  441. clip.close()
  442. # 释放视频对象
  443. video_with_subtitles.close()
  444. except Exception as e:
  445. Common.logger("video").warning(f"新拼接视频发送oss失败:{e}\n")
  446. return
  447. # 视频拼接
  448. @classmethod
  449. def concatenate_pinjie_videos(cls, videos):
  450. clips = []
  451. total_duration = 0
  452. included_videos = []
  453. # 设置最大可使用的内存限制(单位:字节)
  454. memory_limit = 6 * 1024 * 1024 * 1024 # 6GB
  455. resource.setrlimit(resource.RLIMIT_AS, (memory_limit, memory_limit))
  456. # 设置固定
  457. duration_limit = 120
  458. # 遍历每个视频并计算总时长
  459. for i, video in enumerate(videos):
  460. filename = video[2].split("/")[-1]
  461. clip = VideoFileClip(f'./video_stitching/video_material/{filename}.mp4')
  462. clips.append(clip)
  463. total_duration += clip.duration
  464. if total_duration >= duration_limit:
  465. break
  466. # 如果总时长小于等于目标时长,则不做视频拼接
  467. if total_duration <= duration_limit:
  468. Common.logger("video").info(f"时长小于等于目标时长,不做视频拼接")
  469. # 关闭视频文件
  470. for clip in clips:
  471. clip.close()
  472. for video in videos:
  473. filename = video[2].split("/")[-1]
  474. os.remove(f'./video_stitching/video_material/{filename}.mp4')
  475. return ""
  476. else:
  477. Common.logger("video").info(f"总时长大于目标时长")
  478. remaining_time = duration_limit
  479. final_clips = []
  480. for clip, video in zip(clips, videos):
  481. if remaining_time - clip.duration >= 0:
  482. final_clips.append(clip)
  483. included_videos.append(video)
  484. remaining_time -= clip.duration
  485. else:
  486. # 如果剩余时间不足以加入下一个视频,则截断当前视频并返回已包含的URL
  487. final_clips.append(clip)
  488. included_videos.append(video)
  489. break
  490. final_clip = concatenate_videoclips(final_clips)
  491. # 统一设置视频分辨率
  492. final_width = 320
  493. final_height = 480
  494. video_with_subtitles = final_clip.resize((final_width, final_height))
  495. # 生成视频
  496. video_with_subtitles.write_videofile(output_path, fps=35)
  497. if os.path.isfile(output_path):
  498. Common.logger("video").info("视频生成成功!生成路径为:", output_path)
  499. return included_videos, video_with_subtitles, clips
  500. else:
  501. Common.logger("video").info("视频生成失败,请检查代码和文件路径。")
  502. return "", video_with_subtitles, clips
  503. @classmethod
  504. def video_stitching_pinjie(cls, video_type, count, channel_type):
  505. title_list = Material.get_pinjie_title()
  506. # 获取已入库的用户id
  507. account_id = cls.get_account_id(channel_type)
  508. account = random.choice(account_id)
  509. account = str(account).replace('(', '').replace(')', '').replace(',', '')
  510. Common.logger("video").info(f"获取用户ID:{account}")
  511. # 获取 未使用的视频链接
  512. url_list = cls.get_url_list('', account)
  513. if url_list == None:
  514. Common.logger("video").info(f"未使用视频链接为空:{url_list}")
  515. return
  516. videos = [list(item) for item in url_list]
  517. videos = Oss.get_oss_url(video_type, videos)
  518. # 视频截取
  519. try:
  520. audio_url, video_with_subtitles, clips = cls.concatenate_pinjie_videos(videos)
  521. if len(audio_url) == 0:
  522. Common.logger("video").info(f"视频生成失败")
  523. # 随机生成视频id
  524. id = cls.random_id()
  525. Common.logger("video").info(f"生成视频id为:{id}")
  526. # 上传 oss
  527. oss_object_key = Oss.stitching_sync_upload_oss(output_path, id)
  528. status = oss_object_key.get("status")
  529. # 获取 oss 视频地址
  530. oss_object_key = oss_object_key.get("oss_object_key")
  531. Common.logger("video").info(f"新拼接视频,oss发送成功,oss地址:{oss_object_key}")
  532. if status == 200:
  533. time.sleep(10)
  534. # 发送成功 已使用视频存入数据库
  535. cls.insert_videoAudio(audio_url, '', channel_type)
  536. Common.logger("video").info(f"发送成功 已使用视频存入数据库完成")
  537. if os.path.isfile(output_path):
  538. os.remove(output_path)
  539. Common.logger("video").info(f"文件删除成功{output_path}")
  540. else:
  541. Common.logger("video").info(f"文件不存在{output_path}")
  542. for video in videos:
  543. filename = video[2].split("/")[-1]
  544. os.remove(f'./video_stitching/video_material/{filename}.mp4')
  545. piaoquantv = cls.insert_piaoquantv(oss_object_key, title_list, video_type, channel_type)
  546. if piaoquantv:
  547. Common.logger("video").info(f"视频添加到对应用户成功")
  548. # 关闭视频文件
  549. for clip in clips:
  550. clip.close()
  551. # 释放视频对象
  552. video_with_subtitles.close()
  553. except Exception as e:
  554. Common.logger("video").warning(f"新拼接视频发送oss失败:{e}\n")
  555. return
  556. if __name__ == '__main__':
  557. koubo_count = 1
  558. video_type = "口播"
  559. channel_type = "douyin"
  560. VideoStitching.video_stitching(video_type, koubo_count, channel_type)