diff --git a/requirements.txt b/requirements.txt index b559d24..16bb708 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,3 +2,4 @@ imageio_ffmpeg av moviepy numexpr +mutagen diff --git a/scripts/modelscope/process_modelscope.py b/scripts/modelscope/process_modelscope.py index b51fc9a..ef9f299 100644 --- a/scripts/modelscope/process_modelscope.py +++ b/scripts/modelscope/process_modelscope.py @@ -233,7 +233,7 @@ def process_modelscope(args_dict, extra_args=None): f"{i:06}.png", samples[i]) # save settings to a file - if opts.data.get("modelscope_save_info_to_file") if opts.data is not None and opts.data.get("modelscope_save_info_to_file") is not None else False: + if opts.data is not None and opts.data.get("modelscope_save_info_to_file"): args_file = os.path.join(outdir_current,'args.txt') with open(args_file, 'w', encoding='utf-8') as f: @@ -242,10 +242,16 @@ def process_modelscope(args_dict, extra_args=None): # TODO: add params to the GUI if not video_args.skip_video_creation: + metadata = None + if opts.data is not None and opts.data.get("modelscope_save_metadata") is not None: + if opts.data.get("modelscope_save_metadata"): + metadata = infotext + else: + metadata = infotext ffmpeg_stitch_video(ffmpeg_location=video_args.ffmpeg_location, fps=video_args.fps, outmp4_path=outdir_current + os.path.sep + f"vid.mp4", imgs_path=os.path.join(outdir_current, "%06d.png"), stitch_from_frame=0, stitch_to_frame=-1, add_soundtrack=video_args.add_soundtrack, - audio_path=vid2vid_frames_path if video_args.add_soundtrack == 'Init Video' else video_args.soundtrack_path, crf=video_args.ffmpeg_crf, preset=video_args.ffmpeg_preset) + audio_path=vid2vid_frames_path if video_args.add_soundtrack == 'Init Video' else video_args.soundtrack_path, crf=video_args.ffmpeg_crf, preset=video_args.ffmpeg_preset, metadata=metadata) print(f't2v complete, result saved at {outdir_current}') mp4 = open(outdir_current + os.path.sep + f"vid.mp4", 'rb').read() diff --git a/scripts/t2v_helpers/args.py b/scripts/t2v_helpers/args.py index eca3052..fe8a688 100644 --- a/scripts/t2v_helpers/args.py +++ b/scripts/t2v_helpers/args.py @@ -9,6 +9,7 @@ import os import modules.paths as ph from t2v_helpers.general_utils import get_model_location from modules.shared import opts +from mutagen.mp4 import MP4 welcome_text_videocrafter = '''- Download pretrained T2V models via this link, and put the model.ckpt in models/VideoCrafter/model.ckpt. Then use the same GUI pipeline as ModelScope does. ''' @@ -150,6 +151,20 @@ Example: `0:(0), "max_i_f/4":(1), "3*max_i_f/4":(1), "max_i_f-1":(0)` ''') ffmpeg_preset = gr.Dropdown(label="Preset", choices=['veryslow', 'slower', 'slow', 'medium', 'fast', 'faster', 'veryfast', 'superfast', 'ultrafast'], interactive=True, value=dv.ffmpeg_preset, type="value") with gr.Row(equal_height=True, variant='compact', visible=True) as ffmpeg_location_row: ffmpeg_location = gr.Textbox(label="Location", lines=1, interactive=True, value=dv.ffmpeg_location) + with gr.Accordion(label='Metadata viewer', open=False, visible=True): + with gr.Row(variant='compact'): + metadata_file = gr.File(label="Video", interactive=True, file_count="single", file_types=["video"], elem_id="metadata_chosen_file") + with gr.Row(variant='compact'): + metadata_btn = gr.Button(value='Get metadata') + with gr.Row(variant='compact'): + metadata_box = gr.HTML() + + def get_metadata(file): + print('Reading metadata') + video = MP4(file.name) + return video["\xa9cmt"] + + metadata_btn.click(get_metadata, inputs=[metadata_file], outputs=[metadata_box]) with gr.Tab('How to install? Where to get help, how to help?'): gr.Markdown(welcome_text) diff --git a/scripts/t2v_helpers/video_audio_utils.py b/scripts/t2v_helpers/video_audio_utils.py index c2aba1a..712b2a1 100644 --- a/scripts/t2v_helpers/video_audio_utils.py +++ b/scripts/t2v_helpers/video_audio_utils.py @@ -7,6 +7,8 @@ import os, shutil import cv2 from modules.shared import state from pkg_resources import resource_filename +import requests +from mutagen.mp4 import MP4 def get_frame_name(path): name = os.path.basename(path) @@ -121,7 +123,7 @@ def find_ffmpeg_binary(): return 'ffmpeg' # Stitch images to a h264 mp4 video using ffmpeg -def ffmpeg_stitch_video(ffmpeg_location=None, fps=None, outmp4_path=None, stitch_from_frame=0, stitch_to_frame=None, imgs_path=None, add_soundtrack=None, audio_path=None, crf=17, preset='veryslow'): +def ffmpeg_stitch_video(ffmpeg_location=None, fps=None, outmp4_path=None, stitch_from_frame=0, stitch_to_frame=None, imgs_path=None, add_soundtrack=None, audio_path=None, crf=17, preset='veryslow', metadata=None): start_time = time.time() print(f"Got a request to stitch frames to video using FFmpeg.\nFrames:\n{imgs_path}\nTo Video:\n{outmp4_path}") @@ -145,8 +147,10 @@ def ffmpeg_stitch_video(ffmpeg_location=None, fps=None, outmp4_path=None, stitch '-crf', str(crf), '-preset', preset, '-pattern_type', 'sequence', - outmp4_path ] + + cmd.append(outmp4_path) + process = subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = process.communicate() @@ -174,8 +178,10 @@ def ffmpeg_stitch_video(ffmpeg_location=None, fps=None, outmp4_path=None, stitch '-map', '1:a', '-c:v', 'copy', '-shortest', - outmp4_path+'.temp.mp4' ] + + cmd.append(outmp4_path+'.temp.mp4') + process = subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = process.communicate() @@ -195,6 +201,14 @@ def ffmpeg_stitch_video(ffmpeg_location=None, fps=None, outmp4_path=None, stitch else: print("\r" + " " * len(msg_to_print), end="", flush=True) print(f"\r{msg_to_print}", flush=True) + + # adding metadata + if metadata is not None: + print('Writing metadata') + video = MP4(outmp4_path) + video["\xa9cmt"] = metadata + video.save() + print(f"\rVideo stitching \033[0;32mdone\033[0m in {time.time() - start_time:.2f} seconds!", flush=True) # quick-retreive frame count, FPS and H/W dimensions of a video (local or URL-based) @@ -237,7 +251,7 @@ def duplicate_pngs_from_folder(from_folder, to_folder, img_batch_id, orig_vid_na cv2.imwrite(new_path, image, [cv2.IMWRITE_PNG_COMPRESSION, 0]) return frames_handled -def add_soundtrack(ffmpeg_location=None, fps=None, outmp4_path=None, stitch_from_frame=0, stitch_to_frame=None, imgs_path=None, add_soundtrack=None, audio_path=None, crf=17, preset='veryslow'): +def add_soundtrack(ffmpeg_location=None, fps=None, outmp4_path=None, stitch_from_frame=0, stitch_to_frame=None, imgs_path=None, add_soundtrack=None, audio_path=None, crf=17, preset='veryslow', metadata=None): if add_soundtrack is None: return msg_to_print = f"Adding soundtrack to *video*..." diff --git a/scripts/text2vid.py b/scripts/text2vid.py index 20597b4..3ee416e 100644 --- a/scripts/text2vid.py +++ b/scripts/text2vid.py @@ -112,6 +112,8 @@ def on_ui_settings(): -1, "How many videos to show on the right panel on completion (-1 = show all)", gr.Number, {"interactive": True, "visible": True}, section=section)) shared.opts.add_option("modelscope_save_info_to_file", shared.OptionInfo( False, "Save generation params to a text file near the video", gr.Checkbox, {'interactive':True, 'visible':True}, section=section)) + shared.opts.add_option("modelscope_save_metadata", shared.OptionInfo( + True, "Save generation params as video metadata", gr.Checkbox, {'interactive':True, 'visible':True}, section=section)) script_callbacks.on_ui_tabs(on_ui_tabs) script_callbacks.on_ui_settings(on_ui_settings) diff --git a/style.css b/style.css index 1dde770..f85bd20 100644 --- a/style.css +++ b/style.css @@ -3,7 +3,7 @@ Read LICENSE for usage terms. */ -#vid_to_vid_chosen_file .w-full, #inpainting_chosen_file .w-full { +#vid_to_vid_chosen_file .w-full, #inpainting_chosen_file .w-full, #metadata_chosen_file .w-full { display: flex !important; align-items: flex-start !important; justify-content: center !important;