diff --git a/CHANGELOG.md b/CHANGELOG.md index 8be7c3e..3b06397 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,22 @@ All notable changes to MGT-python will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Removed +- `motion_mp()` is retired. It rendered a motion video across several processes, coordinating them + with a socket server and an argparse child process, and it raised on its first call: two fields + were read as one between the parent and the workers, and behind that failure it called + `save_txt` and `save_analysis` with arguments in the wrong positions. It therefore produced + nothing for anyone who tried it, and had no test. `motion()` does the same work in one process + and is tested. The method remains as a stub that says all this and points there; the two modules + behind it are gone. Closes #370. + +### Changed +- Internal typing, issue #350: `mypy musicalgestures/` is at 17 errors in 9 files, from 90 when + this work started. Retiring `motion_mp()` accounts for nine of the reduction, since typing code + that cannot run would have been effort spent hiding a breakage. + ## [1.13.0] — 2026-08-23 ### Fixed diff --git a/docs/MODULES.md b/docs/MODULES.md index 817ff8e..617c883 100644 --- a/docs/MODULES.md +++ b/docs/MODULES.md @@ -41,8 +41,6 @@ Each page is rendered from the source docstrings by - [Motiontempo](musicalgestures/_motiontempo.md) - [Motionvectors](musicalgestures/_motionvectors.md) - [Motionvideo](musicalgestures/_motionvideo.md) -- [Motionvideo Mp Render](musicalgestures/_motionvideo_mp_render.md) -- [Motionvideo Mp Run](musicalgestures/_motionvideo_mp_run.md) - [Movementbeats](musicalgestures/_movementbeats.md) - [Peaks](musicalgestures/_peaks.md) - [Physio](musicalgestures/_physio.md) diff --git a/docs/musicalgestures/_motionvideo_mp_render.md b/docs/musicalgestures/_motionvideo_mp_render.md deleted file mode 100644 index f02f572..0000000 --- a/docs/musicalgestures/_motionvideo_mp_render.md +++ /dev/null @@ -1,3 +0,0 @@ -# Motionvideo Mp Render - -::: musicalgestures._motionvideo_mp_render diff --git a/docs/musicalgestures/_motionvideo_mp_run.md b/docs/musicalgestures/_motionvideo_mp_run.md deleted file mode 100644 index 275200d..0000000 --- a/docs/musicalgestures/_motionvideo_mp_run.md +++ /dev/null @@ -1,3 +0,0 @@ -# Motionvideo Mp Run - -::: musicalgestures._motionvideo_mp_run diff --git a/docs/musicalgestures/index.md b/docs/musicalgestures/index.md index 3b39a43..c13203a 100644 --- a/docs/musicalgestures/index.md +++ b/docs/musicalgestures/index.md @@ -49,8 +49,6 @@ module. - [Motiontempo](_motiontempo.md) - [Motionvectors](_motionvectors.md) - [Motionvideo](_motionvideo.md) -- [Motionvideo Mp Render](_motionvideo_mp_render.md) -- [Motionvideo Mp Run](_motionvideo_mp_run.md) - [Movementbeats](_movementbeats.md) - [Peaks](_peaks.md) - [Physio](_physio.md) diff --git a/docs/user-guide/results.md b/docs/user-guide/results.md index 9badcee..9a97841 100644 --- a/docs/user-guide/results.md +++ b/docs/user-guide/results.md @@ -131,7 +131,7 @@ motion plot only if `motionplots()` has been called. | method | stores | |---|---| -| `motion()`, `motiongrams()`, `motion_mp()` | `motion_video`, `motion_plot_image`, `motiongram_vertical_image`, `motiongram_horizontal_image`, `ssm_figure` | +| `motion()`, `motiongrams()` | `motion_video`, `motion_plot_image`, `motiongram_vertical_image`, `motiongram_horizontal_image`, `ssm_figure` | | `motionvideo()` | `motion_video` | | `videograms()` | `videogram_vertical_image`, `videogram_horizontal_image` | | `history()` | `history_video` | diff --git a/docs/user-guide/video-analysis.md b/docs/user-guide/video-analysis.md index 6600fee..2220b2e 100644 --- a/docs/user-guide/video-analysis.md +++ b/docs/user-guide/video-analysis.md @@ -35,7 +35,7 @@ commonly confused pairs, one line each: Most analysis methods stream frames straight through FFmpeg and run on any container, including MP4, e.g. `motion()`, `motiongrams()`, `average()`/`blend()`, `videograms()`, `heatmap()`, `eulerian()`, `motiontempo()`, `sonomotiongram()`, `grid()`, `subtract()`, and `history()`. -A few methods that decode frame-by-frame with OpenCV first convert the input to an all-intra **MJPEG `.avi`** (cached once as `self.as_avi`) for frame-accurate decoding: `flow.dense()`/`flow.sparse()`, `directograms()`, `impacts()`, `motion_mp()`, and `history_cv2()`. The motion **video output** keeps the source container: an mp4 in gives `_motion.mp4` out. +A few methods that decode frame-by-frame with OpenCV first convert the input to an all-intra **MJPEG `.avi`** (cached once as `self.as_avi`) for frame-accurate decoding: `flow.dense()`/`flow.sparse()`, `directograms()`, `impacts()`, and `history_cv2()`. The motion **video output** keeps the source container: an mp4 in gives `_motion.mp4` out. If your MP4 decodes reliably and you want to skip that conversion (faster, no extra file), pass `convert=False`: diff --git a/musicalgestures/_motionvideo_mp_render.py b/musicalgestures/_motionvideo_mp_render.py deleted file mode 100644 index 4c83069..0000000 --- a/musicalgestures/_motionvideo_mp_render.py +++ /dev/null @@ -1,257 +0,0 @@ -import multiprocessing -import numpy as np -import os -import argparse -import cv2 -# import musicalgestures -from musicalgestures._utils import frame2ms -from musicalgestures._motionanalysis import centroid -from musicalgestures._filter import filter_frame -import socket - - -def mg_motion_mp(args): - - target_folder, of, fex, fps, width, height, length, color, filtertype, thresh, blur, kernel_size, inverted_motionvideo, inverted_motiongram, equalize_motiongram, save_data, save_motiongrams, save_video, start_frame, num_frames, process_id, client = args - - # init - target_name_video = None - gramx = None - gramy = None - time = None - qom = None - com = None - out = None - fourcc = None - process_id_str = '{:02d}'.format(process_id) - - # ignore runtime warnings when dividing by 0 - np.seterr(divide='ignore', invalid='ignore') - - # load video - vidcap = cv2.VideoCapture(of+fex) - - # set start frame - vidcap.set(cv2.CAP_PROP_POS_FRAMES, start_frame) - ret, frame = vidcap.read() - - if save_video: - target_name_video = target_folder + os.path.basename(of) + '_motion_' + process_id_str + fex - # print(target_name_video) - client.sendall(bytes(target_name_video, 'utf-8')) - fourcc = cv2.VideoWriter_fourcc(*'MJPG') - out = cv2.VideoWriter(target_name_video, fourcc, fps, (width, height)) - - if save_motiongrams: - gramx = np.zeros([1, width, 3]) - gramy = np.zeros([height, 1, 3]) - if save_data: - time = np.array([]) # time in ms - qom = np.array([]) # quantity of motion - com = np.array([]) # centroid of motion - - ii = 0 - - if color == False: - frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) - if save_motiongrams: - gramx = np.zeros([1, width]) - gramy = np.zeros([height, 1]) - - try: - while(ii < num_frames): - # report progress - client.sendall(bytes("progress:" + str(process_id) + " " + str(ii), 'utf-8')) - - if blur.lower() == 'average': - prev_frame = cv2.blur(frame, (10, 10)) - elif blur.lower() == 'none': - prev_frame = frame - - ret, frame = vidcap.read() - - if not ret: - break - - if blur.lower() == 'average': - # The higher these numbers the more blur you get - frame = cv2.blur(frame, (10, 10)) - - if color == False: - frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) - - frame = np.array(frame) - frame = frame.astype(np.int32) - - if color == True: - motion_frame_rgb = np.zeros( - [height, width, 3]) - - for i in range(frame.shape[2]): - motion_frame = ( - np.abs(frame[:, :, i]-prev_frame[:, :, i])).astype(np.uint8) - motion_frame = filter_frame( - motion_frame, filtertype, thresh, kernel_size) - motion_frame_rgb[:, :, i] = motion_frame - - if save_motiongrams: - movement_y = np.mean(motion_frame_rgb, axis=1).reshape(height, 1, 3) - movement_x = np.mean(motion_frame_rgb, axis=0).reshape(1, width, 3) - gramy = np.append(gramy, movement_y, axis=1) - gramx = np.append(gramx, movement_x, axis=0) - - else: - motion_frame = ( - np.abs(frame-prev_frame)).astype(np.uint8) - motion_frame = filter_frame( - motion_frame, filtertype, thresh, kernel_size) - - if save_motiongrams: - movement_y = np.mean( - motion_frame, axis=1).reshape(height, 1) - movement_x = np.mean( - motion_frame, axis=0).reshape(1, width) - gramy = np.append(gramy, movement_y, axis=1) - gramx = np.append(gramx, movement_x, axis=0) - - if color == False: - motion_frame = cv2.cvtColor( - motion_frame, cv2.COLOR_GRAY2BGR) - motion_frame_rgb = motion_frame - - if save_video: - # if this is not the first process (rendering the start of the video) then drop the first frame of the output (it'll always be black). - if process_id != 0 and ii > 0: - if inverted_motionvideo: - out.write(cv2.bitwise_not( - motion_frame_rgb.astype(np.uint8))) - else: - out.write(motion_frame_rgb.astype(np.uint8)) - elif process_id == 0: - if inverted_motionvideo: - out.write(cv2.bitwise_not( - motion_frame_rgb.astype(np.uint8))) - else: - out.write(motion_frame_rgb.astype(np.uint8)) - - - if save_data: - combite, qombite = centroid(motion_frame_rgb.astype( - np.uint8), width, height) - if ii == 0: - time = frame2ms(ii, fps) - com = combite.reshape(1, 2) - qom = qombite - else: - time = np.append(time, frame2ms(ii, fps)) - com = np.append(com, combite.reshape(1, 2), axis=0) - qom = np.append(qom, qombite) - - ii += 1 - - except: - vidcap.release() - if save_video: - out.release() - - if save_motiongrams: - # save grams to npy files - with open(target_folder + f'gramx_{process_id_str}.npy', 'wb') as f: - np.save(f, gramx) - with open(target_folder + f'gramy_{process_id_str}.npy', 'wb') as f: - np.save(f, gramy) - - if save_data: - # save data to npy files - with open(target_folder + f'time_{process_id_str}.npy', 'wb') as f: - np.save(f, time) - with open(target_folder + f'com_{process_id_str}.npy', 'wb') as f: - np.save(f, com) - with open(target_folder + f'qom_{process_id_str}.npy', 'wb') as f: - np.save(f, qom) - - # resetting numpy warnings for dividing by 0 - np.seterr(divide='warn', invalid='warn') - - vidcap.release() - if save_video: - out.release() - - -def run_pool(func, args, numprocesses): - pool = multiprocessing.Pool(numprocesses) - pool.map(func, args) - - -def calc_frame_groups(framecount, num_cores): - import math - groups = [] # [startframe, numframes] - frames_per_core = math.floor(framecount / num_cores) - remaining = framecount - (num_cores * frames_per_core) - - for i in range(num_cores): - startframe = i * frames_per_core - numframes = frames_per_core + 1 if i < num_cores-1 else frames_per_core + remaining - groups.append([startframe, numframes]) - - return groups - - -def bool_from_str(boolstring): - return True if boolstring == "True" else False - - -if __name__ == "__main__": - parser = argparse.ArgumentParser(description='Render motion video and data in a multicore process') - parser.add_argument('target_folder', metavar='target_folder', type=str, help='path to temporary folder') - parser.add_argument('of', metavar='of', type=str, help='filename without path or ext') - parser.add_argument('fex', metavar='fex', type=str, help='file extension including dot') - parser.add_argument('fps', metavar='fps', type=str, help='fps') - parser.add_argument('width', metavar='width', type=str, help='width') - parser.add_argument('height', metavar='height', type=str, help='height') - parser.add_argument('length', metavar='length', type=str, help='length') - parser.add_argument('color', metavar='color', type=str, help='color') - parser.add_argument('filtertype', metavar='filtertype', type=str, help='filtertype') - parser.add_argument('thresh', metavar='thresh', type=str, help='thresh') - parser.add_argument('blur', metavar='blur', type=str, help='blur') - parser.add_argument('kernel_size', metavar='kernel_size', type=str, help='kernel_size') - parser.add_argument('inverted_motionvideo', metavar='inverted_motionvideo', type=str, help='inverted_motionvideo') - parser.add_argument('inverted_motiongram', metavar='inverted_motiongram', type=str, help='inverted_motiongram') - parser.add_argument('equalize_motiongram', metavar='equalize_motiongram', type=str, help='equalize_motiongram') - parser.add_argument('save_data', metavar='save_data', type=str, help='save_data') - parser.add_argument('save_motiongrams', metavar='save_motiongrams', type=str, help='save_motiongrams') - parser.add_argument('save_video', metavar='save_video', type=str, help='save_video') - parser.add_argument('num_processes', metavar='num_processes', type=str, help='num_processes') - - args = parser.parse_args() - - HOST = '127.0.0.1' # The server's hostname or IP address - PORT = 65432 # The port used by the server - - client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - client.connect((HOST, PORT)) - - target_folder = os.path.abspath(args.target_folder).replace('\\', '/') - if target_folder[-1] != "/": - target_folder += '/' - - of, fex = args.of, args.fex - fps, width, height, length = int(float(args.fps)), int(float(args.width)), int(float(args.height)), int(float(args.length)) - color, filtertype, thresh, blur, kernel_size = bool_from_str(args.color), args.filtertype, float(args.thresh), args.blur, int(float(args.kernel_size)) - inverted_motionvideo, inverted_motiongram, equalize_motiongram = bool_from_str(args.inverted_motionvideo), bool_from_str(args.inverted_motiongram), bool_from_str(args.equalize_motiongram) - save_data, save_motiongrams, save_video = bool_from_str(args.save_data), bool_from_str(args.save_motiongrams), bool_from_str(args.save_video) - num_processes = multiprocessing.cpu_count() if int(float(args.num_processes)) < 1 else min(int(float(args.num_processes)), multiprocessing.cpu_count()) - - frame_groups = calc_frame_groups(length, num_processes) - - feed_args = [] - - for i in range(num_processes): - start_frame, num_frames = frame_groups[i] - initargs = [target_folder, of, fex, fps, width, height, length, color, filtertype, thresh, blur, kernel_size, inverted_motionvideo, inverted_motiongram, equalize_motiongram, save_data, save_motiongrams, save_video, start_frame, num_frames, i, client] - # client.sendall(bytes(str(initargs), 'utf-8')) - feed_args.append(initargs) - - run_pool(mg_motion_mp, feed_args, num_processes) - - client.close() \ No newline at end of file diff --git a/musicalgestures/_motionvideo_mp_run.py b/musicalgestures/_motionvideo_mp_run.py deleted file mode 100644 index eee55b6..0000000 --- a/musicalgestures/_motionvideo_mp_run.py +++ /dev/null @@ -1,366 +0,0 @@ -from musicalgestures._utils import ffmpeg_cmd, generate_outfilename, get_length, wrap_str, convert_to_avi, MgProgressbar, extract_wav, embed_audio_in_video, MgImage -from musicalgestures._motionvideo import save_txt, save_analysis -import subprocess -import tempfile -import shutil -import platform -import musicalgestures -import os -import numpy as np -import socket -import threading -import multiprocessing -import cv2 - - -def mg_motion_mp( - self: "musicalgestures.MgVideo", - filtertype='Regular', - threshold=0.05, - blur='None', - kernel_size=5, - inverted_motionvideo=False, - inverted_motiongram=False, - unit='seconds', - equalize_motiongram=True, - save_plot=True, - title=None, - save_data=True, - data_format="csv", - save_motiongrams=True, - save_video=True, - target_name_video=None, - target_name_plot=None, - target_name_data=None, - target_name_mgx=None, - target_name_mgy=None, - convert=True, - overwrite=True, - num_processes=-1) -> "musicalgestures.MgVideo": - - of, fex = self.of, self.fex - - # Convert to avi if the input is not avi - necesarry for cv2 compatibility on all platforms - if convert and fex != '.avi': - # first check if there already is a converted version, if not create one and register it to the parent self - if "as_avi" not in self.__dict__.keys(): - file_as_avi = convert_to_avi(of + fex, overwrite=overwrite) - # register it as the avi version for the file - self.as_avi = musicalgestures.MgVideo(file_as_avi) - # point of and fex to the avi version - of, fex = self.as_avi.of, self.as_avi.fex - - module_path = os.path.abspath(os.path.dirname( - musicalgestures.__file__)).replace('\\', '/') - the_system = platform.system() - pythonkw = "python" - if the_system != "Windows": - pythonkw += "3" - pyfile = wrap_str(module_path + '/_motionvideo_mp_render.py') - - temp_folder = tempfile.mkdtemp().replace('\\', '/') - if temp_folder[-1] != "/": - temp_folder += '/' - # print("Temp folder:", temp_folder) - - of_feed = of.replace('\\', '/') - of_feed = os.path.basename(of_feed) - - save_data_feed = save_data or save_plot - - command = [pythonkw, pyfile, temp_folder, of_feed, fex, self.fps, self.width, self.height, self.length, self.color, filtertype, threshold, blur, - kernel_size, inverted_motionvideo, inverted_motiongram, equalize_motiongram, save_data_feed, save_motiongrams, save_video, num_processes] - command = [str(item) for item in command] - # print() - # print(command) - # print() - - pgbar_text = 'Rendering motion' + ", ".join(np.array(["-video", "-grams", "-plots", "-data"])[ - np.array([save_video, save_motiongrams, save_plot, save_data])]) + ":" - pb = MgProgressbar(total=self.length, prefix=pgbar_text) - progress = 0 - - # set up socket server thread - HOST = '127.0.0.1' # Standard loopback interface address (localhost) - PORT = 65432 # Port to listen on (non-privileged ports are > 1023) - - process = subprocess.Popen(command) - - num_cores = multiprocessing.cpu_count() - # print("starting thread") - t = threading.Thread(target=run_socket_server(HOST, PORT, pb, num_cores)) - t.start() - - try: - # print("will wait") - process.wait() - - except KeyboardInterrupt: - try: - process.terminate() - except OSError: - pass - process.wait() - # print("will join") - t.join() - # print("joined") - raise KeyboardInterrupt - - # print("will join") - t.join() - # print("joined") - - # print("organizing results...") - results = os.listdir(temp_folder) - time_files = [temp_folder + - file for file in results if file.startswith("time")] - time_files.sort() - com_files = [temp_folder + - file for file in results if file.startswith("com")] - com_files.sort() - qom_files = [temp_folder + - file for file in results if file.startswith("qom")] - qom_files.sort() - gramx_files = [temp_folder + - file for file in results if file.startswith("gramx")] - gramx_files.sort() - gramy_files = [temp_folder + - file for file in results if file.startswith("gramy")] - gramy_files.sort() - video_files = [temp_folder + - file for file in results if file.endswith("avi")] - video_files.sort() - - gramx, gramy, time, com, qom = None, None, None, None, None - - if save_motiongrams: - # load gramx - # if we only used a single chunk, load everything - if len(gramx_files) == 1: - gramx = np.load(gramx_files[0]) - # or in case there were multiple chunks... - else: - for idx, item in enumerate(gramx_files): - if idx == 0: - # do not drop first row in first chunk - gramx = np.load(item)[:-1] - elif idx == len(gramy_files) - 1: - # do not drop the last row in last chunk - gramx = np.append(gramx, np.load(item)[1:], axis=0) - else: - # else drop first and last rows from chunk - gramx = np.append(gramx, np.load(item)[1:-1], axis=0) - - # load gramy - # if we only used a single chunk, load everything - if len(gramy_files) == 1: - gramy = np.load(gramy_files[0]) - # or in case there were multiple chunks... - else: - for idx, item in enumerate(gramy_files): - if idx == 0: - # do not drop first column in first chunk - gramy = np.load(item)[:, :-1] - elif idx == len(gramy_files) - 1: - # do not drop the last column in last chunk - gramy = np.append(gramy, np.load(item)[:, 1:], axis=1) - else: - # else drop first and last columns from chunk - gramy = np.append(gramy, np.load(item)[:, 1:-1], axis=1) - - if self.color == False: - # Normalize before converting to uint8 to keep precision - gramx = gramx/gramx.max()*255 - gramy = gramy/gramy.max()*255 - gramx = cv2.cvtColor(gramx.astype( - np.uint8), cv2.COLOR_GRAY2BGR) - gramy = cv2.cvtColor(gramy.astype( - np.uint8), cv2.COLOR_GRAY2BGR) - - gramx = (gramx-gramx.min())/(gramx.max()-gramx.min())*255.0 - gramy = (gramy-gramy.min())/(gramy.max()-gramy.min())*255.0 - - if equalize_motiongram: - gramx = gramx.astype(np.uint8) - gramx_hsv = cv2.cvtColor(gramx, cv2.COLOR_BGR2HSV) - gramx_hsv[:, :, 2] = cv2.equalizeHist(gramx_hsv[:, :, 2]) - gramx = cv2.cvtColor(gramx_hsv, cv2.COLOR_HSV2BGR) - - gramy = gramy.astype(np.uint8) - gramy_hsv = cv2.cvtColor(gramy, cv2.COLOR_BGR2HSV) - gramy_hsv[:, :, 2] = cv2.equalizeHist(gramy_hsv[:, :, 2]) - gramy = cv2.cvtColor(gramy_hsv, cv2.COLOR_HSV2BGR) - - if target_name_mgx is None: - target_name_mgx = of+'_mgv.png' - if target_name_mgy is None: - target_name_mgy = of+'_mgh.png' - if not overwrite: - target_name_mgx = generate_outfilename(target_name_mgx) - target_name_mgy = generate_outfilename(target_name_mgy) - - if inverted_motiongram: - cv2.imwrite(target_name_mgx, cv2.bitwise_not( - gramx.astype(np.uint8))) - cv2.imwrite(target_name_mgy, cv2.bitwise_not( - gramy.astype(np.uint8))) - else: - cv2.imwrite(target_name_mgx, gramx.astype(np.uint8)) - cv2.imwrite(target_name_mgy, gramy.astype(np.uint8)) - - # save rendered motiongrams as MgImages into parent MgVideo - self.motiongram_vertical_image = MgImage(target_name_mgx) - self.motiongram_horizontal_image = MgImage(target_name_mgy) - - if save_data: - # load time - for idx, item in enumerate(time_files): - if idx == 0: - time = np.load(item) - else: - last_time = time[-1] - time = np.append(time, np.load(item)[1:] + last_time) - - # load qom - for idx, item in enumerate(qom_files): - if idx == 0: - qom = np.load(item) - else: - qom = np.append(qom, np.load(item)[1:]) - - # load com - for idx, item in enumerate(com_files): - if idx == 0: - com = np.load(item) - else: - com = np.append(com, np.load(item)[1:], axis=0) - - save_txt(of, time, com, qom, self.width, self.height, data_format, - target_name_data=target_name_data, overwrite=overwrite) - - if save_plot: - if not save_data: - # load qom - for idx, item in enumerate(qom_files): - if idx == 0: - qom = np.load(item) - else: - qom = np.append(qom, np.load(item)[1:]) - - # load com - for idx, item in enumerate(com_files): - if idx == 0: - com = np.load(item) - else: - com = np.append(com, np.load(item)[1:], axis=0) - - if title is None: - title = os.path.basename(of + fex) - # save plot as an MgImage at motion_plot_image for parent MgVideo - self.motion_plot_image = MgImage(save_analysis(of, self.fps, com, qom, self.width, - self.height, unit, title, target_name_plot=target_name_plot, overwrite=overwrite)) - - if save_video: - if target_name_video is None: - target_name_video = of + '_motion' + fex - # enforce avi - else: - target_name_video = os.path.splitext(target_name_video)[0] + fex - if not overwrite: - target_name_video = generate_outfilename(target_name_video) - - # print("stitching video...") - concatenated = concat_videos(video_files, pb_prefix='Cleanup') - # print("moving...") - os.replace(concatenated, target_name_video) - # print("checking audio...") - destination_video = target_name_video - if self.has_audio: - source_audio = extract_wav(of + fex) - embed_audio_in_video(source_audio, destination_video) - os.remove(source_audio) - # save rendered motion video as the motion_video of the parent MgVideo - self.motion_video = musicalgestures.MgVideo( - destination_video, color=self.color, returned_by_process=True) - - # print("Cleanup...") - shutil.rmtree(temp_folder) - # print("Removed temp folder.") - - if save_video: - return self.motion_video - else: - return self - - -def run_socket_server(host, port, pb, numprocesses): - # print("will start server") - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - s.bind((host, port)) - s.listen() - conn, addr = s.accept() - with conn: - # print('Connected by', addr) - tracker = TrackMultiProgress(numprocesses) - while True: - data = conn.recv(1024) - # print(data.decode()) - data_str = data.decode() - if data_str.startswith("progress:"): - progress_message = data_str[len("progress:"):] - progress_message_list = progress_message.split('progress:') - progress = None - for progress_bit in progress_message_list: - node, iteration = progress_bit.split(' ') - node, iteration = int(node), int(iteration) - progress = tracker.progress(node, iteration) - pb.progress(progress) - else: - print() - print("got something else...") - print(data_str) - # pass - - if not data: - print("shutting down server") - break - - -class TrackMultiProgress(): - def __init__(self, numprocesses): - self.numprocesses = numprocesses - self.processmap = np.zeros(self.numprocesses) - - def progress(self, node, iteration): - self.processmap[node] = iteration - return np.sum(self.processmap) - - def reset(self): - self.processmap = np.zeros(self.numprocesses) - - -def concat_videos(list_of_videos, target_name=None, overwrite=True, pb_prefix='Concatenating videos:', stream=True): - import os - of, fex = os.path.splitext(list_of_videos[0]) - if not target_name: - target_name = of + '_concat' + fex - if not overwrite: - target_name = generate_outfilename(target_name) - cmds = ['ffmpeg', '-y'] - - for video in list_of_videos: - cmds.append('-i') - cmds.append(video) - - cmd_filter = ["-filter_complex", f'concat=n={len(list_of_videos)}:v=1:a=0'] - for cmdlet in cmd_filter: - cmds.append(cmdlet) - - cmd_end = ["-c:v", "mjpeg", "-q:v", "3", target_name] - for cmdlet in cmd_end: - cmds.append(cmdlet) - - ffmpeg_cmd(cmds, get_length( - list_of_videos[0]) * len(list_of_videos), pb_prefix=pb_prefix, stream=stream) - - return target_name diff --git a/musicalgestures/_video.py b/musicalgestures/_video.py index abba599..566472d 100644 --- a/musicalgestures/_video.py +++ b/musicalgestures/_video.py @@ -209,6 +209,26 @@ def __init__( videogram_vertical_image: MgImage warp_video: "musicalgestures.MgVideo" + def motion_mp(self, *args, **kwargs): + """Retired. Use :meth:`motion` instead. + + This rendered a motion video across several processes, coordinating them + with a socket server and an argparse child process. It raised on its first + call --- two fields were read as one between the parent and the workers, + and behind that failure it called `save_txt` and `save_analysis` with + arguments in the wrong positions --- so it produced nothing for anyone who + tried it. See issue #370. + + `motion()` does the same work in one process and is tested. + + Raises: + NotImplementedError: Always. + """ + raise NotImplementedError( + "motion_mp() has been retired: it raised on its first call and produced " + "nothing (see issue #370). Use motion(), which does the same work in one " + "process.") + # Retired names, kept working for one release. See issue #346. motion_plot = deprecated_alias("motion_plot", "motion_plot_image") motiongram_x = deprecated_alias("motiongram_x", "motiongram_vertical_image") @@ -230,7 +250,6 @@ def __init__( from musicalgestures._motionvideo import mg_motionplots as motionplots # type: ignore[misc] from musicalgestures._motionvideo import mg_motionvideo as motionvideo # type: ignore[misc] from musicalgestures._motionvideo import mg_motionscore as motionscore # type: ignore[misc] - from musicalgestures._motionvideo_mp_run import mg_motion_mp as motion_mp # type: ignore[misc] from musicalgestures._subtract import mg_subtract as subtract # type: ignore[misc] from musicalgestures._ssm import mg_ssm as ssm # type: ignore[misc] from musicalgestures._videograms import videograms_ffmpeg as videograms # type: ignore[misc]