refactor editors to move core logic into the editors themselves

This commit is contained in:
Tanishq Dubey 2023-03-07 19:35:23 -05:00
parent 8787caed83
commit 174a828988
3 changed files with 233 additions and 114 deletions

145
main.py
View File

@ -1,26 +1,22 @@
import argparse import argparse
import concurrent.futures
import hashlib import hashlib
import multiprocessing import multiprocessing
import random
import sys import sys
import time import time
from functools import partial from functools import partial
from pathlib import Path from pathlib import Path
import numpy as np
import structlog import structlog
from src.utils.prereq import check_ffmpeg, install_ffmpeg from src.utils.prereq import check_ffmpeg
check_ffmpeg() check_ffmpeg()
from src.editors.amplitude.editor import AmplitudeEditor from src.editors.amplitude.editor import AmplitudeEditor
from src.editors.sentiment.editor import SentimentEditor from src.editors.sentiment.editor import SentimentEditor
from src.math.cost import quadratic_loss from src.math.cost import quadratic_loss
from src.math.distribution import create_distribution
from src.mediautils.audio import extract_audio_from_video from src.mediautils.audio import extract_audio_from_video
from src.mediautils.video import filter_moments, render_moments from src.mediautils.video import render_moments
log = structlog.get_logger() log = structlog.get_logger()
@ -90,116 +86,37 @@ def main(args):
costfunc = ERROR_FUNCS[args.cost] costfunc = ERROR_FUNCS[args.cost]
desired = args.duration desired = args.duration
# Generate center of large window and small window size result = []
large_window_center = random.uniform(30, 50) try:
small_window_center = random.uniform(5, 15) result = editor.full_edit(costfunc, desired, vars(args))
except Exception as e:
log.fatal("there was an error during editing the video", error=e)
sys.exit(-1)
# The spread multiplier, or epsilon, slowly decays as we approach the center of the gradient if len(result) == 0:
spread_multiplier = random.uniform(0.15, 0.18) log.fatal("no viable edit was found for the provided parameters, please try again with different values")
sys.exit(-2)
# The decay rate, or how quickly our spread multiplier decreases as we approach the center of the gradient log.info(
spread_decay = random.uniform(0.000001, 0.0001) "found edit within target duration",
target=desired,
parallelism = args.parallelism )
out_path = Path(args.destination)
# The main loop of the program starts here log.info("rendering...")
# we first create distributions start = time.time()
# use workers to simultanously create many possible edits render_moments(
# find the best edit of the lot -> this is determined by lowest "cost" result,
# if the best fits within our desitred time range, output, otherwise str(in_vid_path.resolve()),
# reset the distributions using the best as the new center, then repeat str(out_path.resolve()),
# Create distribution of large and small intro_path=intro_file,
parallelism=args.parallelism,
complete = False )
iterations = 0 log.info(
while not complete: "render complete",
large_distribution = create_distribution( duration=time.time() - start,
large_window_center, spread_multiplier, parallelism output=str(out_path.resolve()),
) )
np.random.shuffle(large_distribution) sys.exit(0)
small_distribution = create_distribution(
small_window_center, spread_multiplier, parallelism
)
np.random.shuffle(small_distribution)
# Fire off workers to generate edits
moment_results = []
with concurrent.futures.ThreadPoolExecutor() as executor:
futures = []
pairs = list(zip(large_distribution, small_distribution))
for pair in pairs:
futures.append(
executor.submit(
editor.edit,
pair[0] if pair[0] > pair[1] else pair[1],
pair[1] if pair[0] > pair[1] else pair[0],
vars(args),
)
)
for future in concurrent.futures.as_completed(futures):
try:
moment_results.append(list(future.result()))
except Exception:
log.exception("error during editing")
sys.exit(-2)
moment_results
costs = []
durations = []
for result in moment_results:
total_duration = 0
result[0] = filter_moments(result[0], args.mindur, args.maxdur)
for moment in result[0]:
total_duration = total_duration + moment.get_duration()
costs.append(costfunc(desired, total_duration))
durations.append(total_duration)
index_min = min(range(len(costs)), key=costs.__getitem__)
large_window_center = moment_results[index_min][1]
small_window_center = moment_results[index_min][2]
log.info(
"batch complete",
best_large=large_window_center,
best_small=small_window_center,
duration=durations[index_min],
)
if (
durations[index_min] > desired * 0.95
and desired * 1.05 > durations[index_min]
):
log.info(
"found edit within target duration",
target=desired,
duration=durations[index_min],
)
out_path = Path(args.destination)
log.info("rendering...")
start = time.time()
render_moments(
moment_results[index_min][0],
str(in_vid_path.resolve()),
str(out_path.resolve()),
intro_path=intro_file,
parallelism=args.parallelism,
)
log.info(
"render complete",
duration=time.time() - start,
output=str(out_path.resolve()),
)
sys.exit(0)
iterations = iterations + parallelism
if iterations > 50000:
log.error(
"could not find a viable edit in the target duration, try other params",
target=desired,
)
sys.exit(-4)
spread_multiplier = spread_multiplier - spread_decay
if spread_multiplier < 0:
log.warn("spread reached 0, resetting")
large_window_center = random.uniform(30, 50)
small_window_center = random.uniform(5, 15)
spread_multiplier = random.uniform(0.15, 0.18)
spread_decay = random.uniform(0.0001, 0.001)
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -1,9 +1,13 @@
import numpy as np import numpy as np
import structlog import structlog
import random
import concurrent.futures
from ...math.average import np_moving_average from ...math.average import np_moving_average
from ...math.distribution import create_distribution
from ...mediautils.audio import process_audio, resample from ...mediautils.audio import process_audio, resample
from ..common import find_moving_average_highlights from ..common import find_moving_average_highlights
from ...mediautils.video import filter_moments
class AmplitudeEditor: class AmplitudeEditor:
@ -33,3 +37,100 @@ class AmplitudeEditor:
short_ma, long_ma, self.factor / self.bitrate short_ma, long_ma, self.factor / self.bitrate
) )
return highlights, large_window, small_window return highlights, large_window, small_window
def full_edit(self, costfunc, desired_time, params):
desired = desired_time
# Generate center of large window and small window size
large_window_center = random.uniform(30, 50)
small_window_center = random.uniform(5, 15)
# The spread multiplier, or epsilon, slowly decays as we approach the center of the gradient
spread_multiplier = random.uniform(0.15, 0.18)
# The decay rate, or how quickly our spread multiplier decreases as we approach the center of the gradient
spread_decay = random.uniform(0.000001, 0.0001)
parallelism = params['parallelism']
# The main loop of the program starts here
# we first create distributions
# use workers to simultanously create many possible edits
# find the best edit of the lot -> this is determined by lowest "cost"
# if the best fits within our desitred time range, output, otherwise
# reset the distributions using the best as the new center, then repeat
# Create distribution of large and small
complete = False
iterations = 0
while not complete:
large_distribution = create_distribution(
large_window_center, spread_multiplier, parallelism
)
np.random.shuffle(large_distribution)
small_distribution = create_distribution(
small_window_center, spread_multiplier, parallelism
)
np.random.shuffle(small_distribution)
# Fire off workers to generate edits
moment_results = []
with concurrent.futures.ThreadPoolExecutor() as executor:
futures = []
pairs = list(zip(large_distribution, small_distribution))
for pair in pairs:
futures.append(
executor.submit(
self.edit,
pair[0] if pair[0] > pair[1] else pair[1],
pair[1] if pair[0] > pair[1] else pair[0],
vars(params),
)
)
failed = None
for future in concurrent.futures.as_completed(futures):
try:
moment_results.append(list(future.result()))
except Exception as e:
self.logger.exception("error during editing", error=e)
failed = e
if failed is not None:
raise failed
costs = []
durations = []
for result in moment_results:
total_duration = 0
result[0] = filter_moments(result[0], params['mindur'], params['maxdur'])
for moment in result[0]:
total_duration = total_duration + moment.get_duration()
costs.append(costfunc(desired, total_duration))
durations.append(total_duration)
index_min = min(range(len(costs)), key=costs.__getitem__)
large_window_center = moment_results[index_min][1]
small_window_center = moment_results[index_min][2]
self.logger.info(
"batch complete",
best_large=large_window_center,
best_small=small_window_center,
duration=durations[index_min],
)
if (
durations[index_min] > desired * 0.95
and desired * 1.05 > durations[index_min]
):
return moment_results[index_min][0]
iterations = iterations + parallelism
if iterations > 50000:
self.logger.warn(
"could not find a viable edit in the target duration, try other params",
target=desired,
)
return []
spread_multiplier = spread_multiplier - spread_decay
if spread_multiplier < 0:
self.logger.warn("spread reached 0, resetting")
large_window_center = random.uniform(30, 50)
small_window_center = random.uniform(5, 15)
spread_multiplier = random.uniform(0.15, 0.18)
spread_decay = random.uniform(0.0001, 0.001)

View File

@ -2,6 +2,9 @@ import json
import tempfile import tempfile
from dataclasses import dataclass from dataclasses import dataclass
from pathlib import Path from pathlib import Path
import random
import concurrent.futures
from ...math.distribution import create_distribution
import numpy as np import numpy as np
import structlog import structlog
@ -11,6 +14,7 @@ from flair.models import TextClassifier
from ...math.average import np_moving_average from ...math.average import np_moving_average
from ..common import find_moving_average_highlights from ..common import find_moving_average_highlights
from ...mediautils.video import filter_moments
@dataclass @dataclass
@ -69,3 +73,100 @@ class SentimentEditor:
short_ma, long_ma, 1.0 / window_factor short_ma, long_ma, 1.0 / window_factor
) )
return highlights, large_window, small_window return highlights, large_window, small_window
def full_edit(self, costfunc, desired_time, params):
desired = desired_time
# Generate center of large window and small window size
large_window_center = random.uniform(30, 50)
small_window_center = random.uniform(5, 15)
# The spread multiplier, or epsilon, slowly decays as we approach the center of the gradient
spread_multiplier = random.uniform(0.15, 0.18)
# The decay rate, or how quickly our spread multiplier decreases as we approach the center of the gradient
spread_decay = random.uniform(0.000001, 0.0001)
parallelism = params['parallelism']
# The main loop of the program starts here
# we first create distributions
# use workers to simultanously create many possible edits
# find the best edit of the lot -> this is determined by lowest "cost"
# if the best fits within our desitred time range, output, otherwise
# reset the distributions using the best as the new center, then repeat
# Create distribution of large and small
complete = False
iterations = 0
while not complete:
large_distribution = create_distribution(
large_window_center, spread_multiplier, parallelism
)
np.random.shuffle(large_distribution)
small_distribution = create_distribution(
small_window_center, spread_multiplier, parallelism
)
np.random.shuffle(small_distribution)
# Fire off workers to generate edits
moment_results = []
with concurrent.futures.ThreadPoolExecutor() as executor:
futures = []
pairs = list(zip(large_distribution, small_distribution))
for pair in pairs:
futures.append(
executor.submit(
self.edit,
pair[0] if pair[0] > pair[1] else pair[1],
pair[1] if pair[0] > pair[1] else pair[0],
params,
)
)
failed = None
for future in concurrent.futures.as_completed(futures):
try:
moment_results.append(list(future.result()))
except Exception as e:
self.logger.exception("error during editing", error=e)
failed = e
if failed is not None:
raise failed
costs = []
durations = []
for result in moment_results:
total_duration = 0
result[0] = filter_moments(result[0], params['mindur'], params['maxdur'])
for moment in result[0]:
total_duration = total_duration + moment.get_duration()
costs.append(costfunc(desired, total_duration))
durations.append(total_duration)
index_min = min(range(len(costs)), key=costs.__getitem__)
large_window_center = moment_results[index_min][1]
small_window_center = moment_results[index_min][2]
self.logger.info(
"batch complete",
best_large=large_window_center,
best_small=small_window_center,
duration=durations[index_min],
)
if (
durations[index_min] > desired * 0.95
and desired * 1.05 > durations[index_min]
):
return moment_results[index_min][0]
iterations = iterations + parallelism
if iterations > 50000:
self.logger.warn(
"could not find a viable edit in the target duration, try other params",
target=desired,
)
return []
spread_multiplier = spread_multiplier - spread_decay
if spread_multiplier < 0:
self.logger.warn("spread reached 0, resetting")
large_window_center = random.uniform(30, 50)
small_window_center = random.uniform(5, 15)
spread_multiplier = random.uniform(0.15, 0.18)
spread_decay = random.uniform(0.0001, 0.001)