Используя процессы, я сократил время всего на 15-20 секунд, потому что ffmpeg
даже в одном процессе использовала почти полную мощность процессора, а у моего компьютера не было мощности для более быстрого запуска других процессов.
Сначала я сократил код, чтобы сделать его короче.
В коде try
и except
были похожие элементы, поэтому я переместил их в сторону try/except
.
Далее я использовал
if end > video_duration:
end = video_duration
а мне вообще не нужен был try/except
.
При использовании os.makedirs(..., exist_ok=True)
мне не нужно запускать его в try/except
Тем временем я сократил время на 20 секунд, используя
clip = VideoFileClip(filename).subclip(start, end)
вместо того
temp_filename = f"{base_folder}/cut_{number}.mp4"
fmpeg_extract_subclip(filename, start, end, targetname=temp_filename)
clip = VideoFileClip(temp_filename)
Таким образом, я не записываю подклип на диск, и мне не нужно читать его снова с диска.
from moviepy.video.io.ffmpeg_tools import ffmpeg_extract_subclip
from moviepy.editor import *
import time
def main():
text = input("Enter Your text please: ") [::-1]
#text = 'Hello World'
base_folder = time.strftime("result_%Y_%m_%d_%H_%M_%S")
os.makedirs(f"{base_folder}/result_edit", exist_ok=True)
filename = "video.mp4"
#filename = "BigBuckBunny.mp4"
video_duration = VideoFileClip(filename).duration
number = 0 # instead of `cut_name_num` and `txt_part` because both had the same value
time_start = time.time()
for start in range(0, int(video_duration), 60):
end = start + 60
if end > video_duration:
end = video_duration
number += 1
clip_duration = end - start
print(f'[DEBUG] number: {number:2} | start: {start:6.2f} | end: {end:6.2f} | duration: {clip_duration:.2f}')
final_text = f"{number} {text}"
temp_filename = f"{base_folder}/cut_{number}.mp4"
final_filename = f"{base_folder}/result_edit/cut_{number}.mp4"
#ffmpeg_extract_subclip(filename, start, end, targetname=temp_filename)
#clip = VideoFileClip(temp_filename)
clip = VideoFileClip(filename).subclip(start, end)
clip = clip.volumex(2)
txt_clip = TextClip(final_text, font="font/VarelaRound-Regular.ttf", fontsize=50, color='white')
txt_clip = txt_clip.set_pos(("center","top")).set_duration(60)
video = CompositeVideoClip([clip, txt_clip])
video.write_videofile(final_filename)
# - after loop -
# because I use `number += 1` before loop so now `number` has number of subclips
print('number of subclips:', number)
time_end = time.time()
diff = time_end - time_start
print(f'time: {diff:.2f}s ({diff//60:02.0f}:{diff%60:02.2f})')
if __name__ == "__main__":
main()
Затем я переместил код в функцию с аргументами my_process(filename, text, start, end, number, base_folder)
from moviepy.video.io.ffmpeg_tools import ffmpeg_extract_subclip
from moviepy.editor import *
import time
def my_process(filename, text, start, end, number, base_folder):
clip_duration = end - start
print(f'[DEBUG] number: {number:2} | start: {start:6.2f} | end: {end:6.2f} | duration: {clip_duration:.2f}')
final_text = f"{number} {text}"
temp_filename = f"{base_folder}/cut_{number}.mp4"
final_filename = f"{base_folder}/result_edit/cut_{number}.mp4"
#print('[DEBUG] ffmpeg_extract_subclip')
#ffmpeg_extract_subclip(filename, start, end, targetname=temp_filename)
#print('[DEBUG] VideoClip')
#clip = VideoFileClip(temp_filename)
clip = VideoFileClip(filename).subclip(start, end)
clip = clip.volumex(2)
#print('[DEBUG] TextClip')
txt_clip = TextClip(final_text, font="font/VarelaRound-Regular.ttf", fontsize=50, color='white')
txt_clip = txt_clip.set_pos(("center","top")).set_duration(60)
#print('[DEBUG] CompositeVideoClip')
video = CompositeVideoClip([clip, txt_clip])
#print('[DEBUG] CompositeVideoClip write')
video.write_videofile(final_filename)
#print('[DEBUG] CompositeVideoClip end')
def main():
text = input("Enter Your text please: ") [::-1]
#text = 'Hello World'
base_folder = time.strftime("result_%Y_%m_%d_%H_%M_%S")
os.makedirs(f"{base_folder}/result_edit", exist_ok=True)
filename = "video.mp4"
#filename = "BigBuckBunny.mp4"
video_duration = VideoFileClip(filename).duration
number = 0 # instead of `cut_name_num` and `txt_part` because both had the same value
time_start = time.time()
for start in range(0, int(video_duration), 60):
end = start + 60
if end > video_duration:
end = video_duration
number += 1
my_process(filename, text, start, end, number, base_folder)
# - after loop -
# because I use `number += 1` before loop so now `number` has number of subclips
print('number of subclips:', number)
time_end = time.time()
diff = time_end - time_start
print(f'time: {diff:.2f}s ({diff//60:02.0f}:{diff%60:02.2f})')
if __name__ == "__main__":
main()
И теперь я могу запускать функцию в отдельных процессах, используя стандартный модуль multiprocessing.
(или стандартные модули threading, concurrent.futures или внешние модули Joblib, Ray и т. д. .).
Начинает единый процесс
# it has to use named arguments`target=`, `args=`
p = multiprocessing.Process(target=my_process, args=(filename, text, start, end, number, base_folder))
p.start() # start it
но если я использую его в цикле, я запускаю много процессов одновременно.
from moviepy.video.io.ffmpeg_tools import ffmpeg_extract_subclip
from moviepy.editor import *
import time
import multiprocessing
def my_process(filename, text, start, end, number, base_folder):
clip_duration = end - start
print(f'[DEBUG] number: {number:2} | start: {start:6.2f} | end: {end:6.2f} | duration: {clip_duration:.2f}')
final_text = f"{number} {text}"
temp_filename = f"{base_folder}/cut_{number}.mp4"
final_filename = f"{base_folder}/result_edit/cut_{number}.mp4"
#print('[DEBUG] ffmpeg_extract_subclip')
#ffmpeg_extract_subclip(filename, start, end, targetname=temp_filename)
#print('[DEBUG] VideoClip')
#clip = VideoFileClip(temp_filename)
clip = VideoFileClip(filename).subclip(start, end)
clip = clip.volumex(2)
#print('[DEBUG] TextClip')
txt_clip = TextClip(final_text, font="font/VarelaRound-Regular.ttf", fontsize=50, color='white')
txt_clip = txt_clip.set_pos(("center","top")).set_duration(60)
#print('[DEBUG] CompositeVideoClip')
video = CompositeVideoClip([clip, txt_clip])
#print('[DEBUG] CompositeVideoClip write')
video.write_videofile(final_filename)
#print('[DEBUG] CompositeVideoClip end')
def main():
text = input("Enter Your text please: ") [::-1]
#text = 'Hello World'
base_folder = time.strftime("result_%Y_%m_%d_%H_%M_%S")
os.makedirs(f"{base_folder}/result_edit", exist_ok=True)
filename = "video.mp4"
#filename = "BigBuckBunny.mp4"
video_duration = VideoFileClip(filename).duration
number = 0 # instead of `cut_name_num` and `txt_part` because both had the same value
time_start = time.time()
all_processes = []
for start in range(0, int(video_duration), 60):
end = start + 60
if end > video_duration:
end = video_duration
number += 1
print("add process:", number)
p = multiprocessing.Process(target=my_process, args=(filename, text, start, end, number, base_folder)) # it has to use `target=`, `args=`
p.start() # start it
all_processes.append(p) # keep it to use `join()`
# - after loop -
for p in all_processes:
p.join() # wait for the end of process
# because I use `number += 1` before loop so now `number` has number of subclips
print('number of subclips:', number)
time_end = time.time()
diff = time_end - time_start
print(f'time: {diff:.2f}s ({diff//60:02.0f}:{diff%60:02.2f})')
if __name__ == "__main__":
main()
Предыдущая версия для 11 подклипов запускает 11 процессов. Используя Pool(4)
, вы можете объединить все процессы в пул, и он будет запускать 4 процесса одновременно. Когда один процесс завершит задачу, он запустит следующий процесс с новыми аргументами.
На этот раз я использую цикл для создания списка с аргументами для всех процессов.
args_for_all_processes = []
for start in range(0, int(video_duration), 60):
end = start + 60
if end > video_duration:
end = video_duration
number += 1
print("add process:", number)
args_for_all_processes.append( (filename, text, start, end, number, base_folder) )
и я использую этот список с Pool
, и он сделает все остальное.
# I have 4 CPU so I use Pool(4) - but without value it should automatically use `os.cpu_count()`
with multiprocessing.Pool(4) as pool:
results = pool.starmap(my_process, args_for_all_processes)
#print(results)
Pool
может запускать процессы в другом порядке, но если они используют return
для отправки какого-либо результата, то Pool
выдаст результаты в правильном порядке.
from moviepy.video.io.ffmpeg_tools import ffmpeg_extract_subclip
from moviepy.editor import *
import time
import multiprocessing
def my_process(filename, text, start, end, number, base_folder):
clip_duration = end - start
print(f'[DEBUG] number: {number:2} | start: {start:6.2f} | end: {end:6.2f} | duration: {clip_duration:.2f}')
final_text = f"{number} {text}"
temp_filename = f"{base_folder}/cut_{number}.mp4"
final_filename = f"{base_folder}/result_edit/cut_{number}.mp4"
#print('[DEBUG] ffmpeg_extract_subclip')
#ffmpeg_extract_subclip(filename, start, end, targetname=temp_filename)
#print('[DEBUG] VideoClip')
#clip = VideoFileClip(temp_filename)
clip = VideoFileClip(filename).subclip(start, end)
clip = clip.volumex(2)
#print('[DEBUG] TextClip')
txt_clip = TextClip(final_text, font="font/VarelaRound-Regular.ttf", fontsize=50, color='white')
txt_clip = txt_clip.set_pos(("center","top")).set_duration(60)
#print('[DEBUG] CompositeVideoClip')
video = CompositeVideoClip([clip, txt_clip])
#print('[DEBUG] CompositeVideoClip write')
video.write_videofile(final_filename)
#print('[DEBUG] CompositeVideoClip end')
# return "OK" # you can use `return` to send result/information to main process.
def main():
text = input("Enter Your text please: ") [::-1]
#text = 'Hello World'
base_folder = time.strftime("result_%Y_%m_%d_%H_%M_%S")
os.makedirs(f"{base_folder}/result_edit", exist_ok=True)
filename = "video.mp4"
#filename = "BigBuckBunny.mp4"
video_duration = VideoFileClip(filename).duration
number = 0 # instead of `cut_name_num` and `txt_part` because both had the same value
time_start = time.time()
# first create list with arguments for all processes
args_for_all_processes = []
for start in range(0, int(video_duration), 60):
end = start + 60
if end > video_duration:
end = video_duration
number += 1
print("add process:", number)
args_for_all_processes.append( (filename, text, start, end, number, base_folder) )
# - after loop -
# next put all processes to pool
with multiprocessing.Pool(4) as pool: # I have 4 CPU so I use Pool(4) - but it should use `os.cpu_count()` in `Pool()
results = pool.starmap(my_process, args_for_all_processes)
#print(results)
# - after loop -
# because I use `number += 1` before loop so now `number` has number of subclips
print('number of subclips:', number)
time_end = time.time()
diff = time_end - time_start
print(f'time: {diff:.2f}s ({diff//60:02.0f}:{diff%60:02.2f})')
if __name__ == "__main__":
main()
person
furas
schedule
16.05.2021
while
-цикле - код послеfinal_text = ...
и доstart += 60
. И сначала попробуйте запустить его как функцию - вы увидите, нужно ли использовать какие-то параметры для запуска этой функции. А позже вы можете попробовать запустить эту функцию сmultiprocessing
. - person furas   schedule 16.05.2021try
иexcept
. Если бы вы использовалиif/else
для проверки значенийstart
, video_duration и т. Д., То код был бы короче и читабельнее. Кстати: в более новом Python вы можете использоватьos.makedirs(..., exist_ok=True)
, и тогда вам не нужно использоватьtry/except
- person furas   schedule 16.05.2021f-string
, вам не нужноstr()
- person furas   schedule 16.05.2021(filename, txt)
- вам нужно(filename, txt, start, end, cut_name_num, txt_part)
, потому чтоprocess
не имеет доступа к переменным в основном процессе. Кстати: переменныеcut_name_num
,txt_part
всегда имеют одинаковые значения, поэтому вы можете использовать одну переменную, т.е.number
- person furas   schedule 16.05.2021start, end_seconds
, и вы также должны отправить их в функцию. - person furas   schedule 16.05.2021Process()
для запуска функции с разными аргументами, тогда он будет запускать все процессы одновременно, и вам не нужно ничего переключать. Если вы используетеPool(8)
и поместите список с аргументами для всех процессов, тогда он будет запускать 8 процессов одновременно - когда один процесс завершит задание, он автоматически запустит следующий процесс из списка - и вам не нужно ничего переключать. Я тестирую обе версии, но на моем компьютере она сокращается всего на 20 секунд, потому чтоffmpeg
уже использует полную мощность ЦП для одного процесса, и нет мощности для более быстрой работы других, - person furas   schedule 16.05.2021VideoFileClip
со многимиTextClip
, которые будут иметь разные.set_start(start)
иCompositeVideoClip([clip, txt_clip1, txt_clip2, txt_clip3, ...])
- person furas   schedule 16.05.2021