diff --git a/.gitignore b/.gitignore index d9005f2..59a443a 100644 --- a/.gitignore +++ b/.gitignore @@ -150,3 +150,5 @@ cython_debug/ # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ + +output \ No newline at end of file diff --git a/app.py b/app.py index fbf0e24..464424a 100644 --- a/app.py +++ b/app.py @@ -1,98 +1,34 @@ -import datetime import gradio as gr -from PIL import Image, ImageChops +from processing.stacking import stacking +import sys +import importlib +import pathlib import os -from tqdm import tqdm +import copy +from tabs import tabs + +tabs_dir = pathlib.Path(__file__).parent / "tabs" -def impluser(dir, method): - - files = os.listdir(dir) - files = list(map(lambda x: os.path.join(dir, x), files)) - files = list(filter(lambda x: x.endswith(".png"), files)) - - if method == "denoise": - img = denoise(files) - elif method == "startracks": - img = startracks(files) - elif method == "noise extractor": - img = noise_extractor(files) - elif method == "untrack": - img = untrack(files) - - name = generate_name() - img.save(name) - - return [name] - - -def generate_name(): - # if not exists output create it - if not os.path.exists("./output"): - os.mkdir("./output") - - return f"./output/{datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}.png" - - -def denoise(files): - bias = 1 - image = Image.open(files[0]) - for file in tqdm(files): - - alpha = 1/bias - - im2 = Image.open(file) - im3 = Image.blend(image, im2, alpha) - - image = im3 - - bias += 1 - - return image - - -def startracks(files): - image = Image.open(files[0]) - for file in tqdm(files): - im2 = Image.open(file) - im3 = ImageChops.lighter(image, im2) - image = im3 - - return image - - -def noise_extractor(files): - image = Image.open(files[0]) - for file in tqdm(files, unit=' images'): - im2 = Image.open(file) - im3 = ImageChops.difference(image, im2) - image = im3 - - return image - - -def untrack(files): - image = Image.open(files[0]) - for file in tqdm(files, unit=' images'): - im2 = Image.open(file) - im3 = ImageChops.darker(image, im2) - image = im3 - - return image +all_tabs = [] +tab = None +for tab_name in tabs: + old_path = copy.deepcopy(sys.path) + sys.path = [os.path.join(tabs_dir, tab_name)] + sys.path + try: + if tab is None: + tab = importlib.import_module(f"app") + else: + tab = importlib.reload(tab) + all_tabs.append((tab_name, tab.app)) + except Exception as e: + print(f"Error loading tab ({tab_name}): {e}") with gr.Blocks() as app: - with gr.Row(): - with gr.Column(): - directory = gr.Textbox( - placeholder="A directory on the same machine where the server is running.", lines=1, label="Directory") - methods = gr.Dropdown( - choices=["denoise", "startracks", "noise extractor", "untrack"], value="denoise", label="Method") - submit = gr.Button("Submit") + for tab_name, tab in all_tabs: + with gr.Tab(tab_name): + tab.render() - with gr.Column(): - output = gr.Gallery() - - submit.click(impluser, inputs=[directory, methods], outputs=[output]) app.launch() diff --git a/methods/bulk_methods.py b/methods/bulk_methods.py new file mode 100644 index 0000000..735c232 --- /dev/null +++ b/methods/bulk_methods.py @@ -0,0 +1,14 @@ +from PIL import Image, ImageFilter + + +def canny_edge(file_name): + image = Image.open(file_name) + + image = image.convert("L") + + return image.filter(ImageFilter.FIND_EDGES) + + +def sharpen(file_name): + image = Image.open(file_name) + return image.filter(ImageFilter.SHARPEN) diff --git a/methods/stack_methods.py b/methods/stack_methods.py new file mode 100644 index 0000000..5efff50 --- /dev/null +++ b/methods/stack_methods.py @@ -0,0 +1,49 @@ +from PIL import Image, ImageChops +from tqdm import tqdm + + +def denoise(files): + bias = 1 + image = Image.open(files[0]) + for file in tqdm(files): + + alpha = 1/bias + + im2 = Image.open(file) + im3 = Image.blend(image, im2, alpha) + + image = im3 + + bias += 1 + + return image + + +def startracks(files): + image = Image.open(files[0]) + for file in tqdm(files): + im2 = Image.open(file) + im3 = ImageChops.lighter(image, im2) + image = im3 + + return image + + +def noise_extractor(files): + image = Image.open(files[0]) + for file in tqdm(files, unit=' images'): + im2 = Image.open(file) + im3 = ImageChops.difference(image, im2) + image = im3 + + return image + + +def untrack(files): + image = Image.open(files[0]) + for file in tqdm(files, unit=' images'): + im2 = Image.open(file) + im3 = ImageChops.darker(image, im2) + image = im3 + + return image diff --git a/processing/bulk.py b/processing/bulk.py new file mode 100644 index 0000000..d7cbf8a --- /dev/null +++ b/processing/bulk.py @@ -0,0 +1,45 @@ + +import ffmpeg +import os +from processing.utils import generate_name, get_date_text, generate_name_with_file_name +from methods.bulk_methods import canny_edge, sharpen + + +def images_to_video(directory, fps, img_ext, img_name_format, video_name, video_ext, video_dir): + images_pattern = os.path.join(directory, f"{img_name_format}.{img_ext}") + + if video_dir: + video_path = os.path.join(video_dir, f"{video_name}.{video_ext}") + else: + video_path = generate_name( + extension=video_ext, name=video_name, subfolder="videos") + + ffmpeg.input(images_pattern, + framerate=fps).output(video_path, pix_fmt='yuv420p').global_args("-y").run() + + +def video_to_images(video_path, img_ext): + + images_pattern = generate_name( + extension=img_ext, name=f"%d", subfolder=os.path.join("images", get_date_text())) + + ffmpeg.input(video_path).output(images_pattern).run() + + +def bulk_processing(directory, out_directory, method): + date = get_date_text() + if method == "canny edge": + run_bulk(canny_edge, directory, out_directory, date) + elif method == "sharpen": + run_bulk(sharpen, directory, out_directory, date) + + +def run_bulk(func, directory, out_directory, date): + for file in os.listdir(directory): + img = func(os.path.join(directory, file)) + if out_directory: + img_out_path = os.path.join(out_directory, file) + else: + img_out_path = generate_name_with_file_name( + name=file, subfolder=os.path.join("images", date)) + img.save(img_out_path) diff --git a/processing/stacking.py b/processing/stacking.py new file mode 100644 index 0000000..ccf46ca --- /dev/null +++ b/processing/stacking.py @@ -0,0 +1,24 @@ +from methods.stack_methods import denoise, startracks, noise_extractor, untrack +import os +from processing.utils import generate_name + + +def stacking(dir, method): + + files = os.listdir(dir) + files = list(map(lambda x: os.path.join(dir, x), files)) + files = list(filter(lambda x: x.endswith(".png"), files)) + + if method == "denoise": + img = denoise(files) + elif method == "startracks": + img = startracks(files) + elif method == "noise extractor": + img = noise_extractor(files) + elif method == "untrack": + img = untrack(files) + + name = generate_name() + img.save(name) + + return [name] diff --git a/processing/utils.py b/processing/utils.py new file mode 100644 index 0000000..8f75950 --- /dev/null +++ b/processing/utils.py @@ -0,0 +1,23 @@ +import os +import datetime + + +def generate_name(name=False, subfolder="stacked", extension="png", format='%Y-%m-%d_%H-%M-%S'): + + os.makedirs(os.path.join(".", "output", subfolder), exist_ok=True) + + if name is False or name == "": + name = get_date_text(format) + + return os.path.join(".", "output", subfolder, f"{name}.{extension}") + + +def generate_name_with_file_name(name, subfolder): + + os.makedirs(os.path.join(".", "output", subfolder), exist_ok=True) + + return os.path.join(".", "output", subfolder, name) + + +def get_date_text(format='%Y-%m-%d_%H-%M-%S'): + return datetime.datetime.now().strftime(format) diff --git a/requirements.txt b/requirements.txt index 210ff6c..a533a59 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ +ffmpeg_python==0.2.0 gradio==3.27.0 Pillow==9.5.0 tqdm==4.65.0 diff --git a/tabs/Bulk processing/app.py b/tabs/Bulk processing/app.py new file mode 100644 index 0000000..9416d4f --- /dev/null +++ b/tabs/Bulk processing/app.py @@ -0,0 +1,21 @@ +import gradio as gr +from processing.bulk import bulk_processing + +with gr.Blocks() as app: + gr.Markdown( + "Mass processing of images one at a time and saving to video if needed. # **WIP, not working**") + with gr.Row(): + with gr.Column(): + directory = gr.Text( + placeholder="A directory with many images.", lines=1, label="Directory") + method = gr.Dropdown( + choices=["canny edge", "sharpen"], value="canny edge", label="Method") + + with gr.Accordion("Advanced settings", open=False) as acc: + out_dir = gr.Text( + label="Output directory", placeholder="The directory where the processed photos will be saved. If not specified `./output/images`") + submit = gr.Button("Submit") + submit.click( + fn=bulk_processing, + inputs=[directory, out_dir, method], + ) diff --git a/tabs/Images to video/app.py b/tabs/Images to video/app.py new file mode 100644 index 0000000..6f03c9e --- /dev/null +++ b/tabs/Images to video/app.py @@ -0,0 +1,30 @@ +import gradio as gr +from processing.bulk import images_to_video + +with gr.Blocks() as app: + gr.Markdown("Convert images to video.") + directory = gr.Text( + placeholder="A directory with many images of the same size", label="Directory") + + fps = gr.Number(label="FPS", value=30, min=0) + + with gr.Accordion("Advanced settings", open=False) as acc: + video_name = gr.Text( + label="Video name", placeholder="Video name e.g. video. If not specified will be generated by time") + video_ext = gr.Dropdown(label="Video extension", choices=[ + "mp4"], value="mp4") + video_dir = gr.Text( + label="Video directory", placeholder="The directory where the video will be saved. If not specified `./output/videos`") + + img_ext = gr.Dropdown(label="Image extension", choices=[ + "png", "jpg"], value="png") + img_name_format = gr.Text( + label="Image name format", placeholder="ffmpeg pattern e.g. %04d is (0000.png)", value="%d") + + submit = gr.Button("Submit") + + submit.click( + fn=images_to_video, + inputs=[directory, fps, img_ext, img_name_format, + video_name, video_ext, video_dir], + ) diff --git a/tabs/Stacking/app.py b/tabs/Stacking/app.py new file mode 100644 index 0000000..48add4a --- /dev/null +++ b/tabs/Stacking/app.py @@ -0,0 +1,18 @@ +import gradio as gr +from processing.stacking import stacking + +with gr.Blocks() as app: + gr.Markdown("Stacking images.") + with gr.Row(): + with gr.Column(): + directory = gr.Text( + placeholder="A directory with many images of the same size", lines=1, label="Directory") + methods = gr.Dropdown( + choices=["denoise", "startracks", "noise extractor", "untrack"], value="denoise", label="Method") + submit = gr.Button("Submit") + + with gr.Column(): + output = gr.Gallery() + + submit.click(stacking, inputs=[ + directory, methods], outputs=[output]) diff --git a/tabs/Video to images/app.py b/tabs/Video to images/app.py new file mode 100644 index 0000000..3e6ce4f --- /dev/null +++ b/tabs/Video to images/app.py @@ -0,0 +1,16 @@ +import gradio as gr +from processing.bulk import video_to_images + +with gr.Blocks() as app: + gr.Markdown( + "Convert video to images.") + with gr.Row(): + with gr.Column(): + + video = gr.Video(label="Video") + + with gr.Column(): + format = gr.Radio( + choices=["png", "jpg"], value="png", label="Format of image") + submit = gr.Button("Submit") + submit.click(fn=video_to_images, inputs=[video, format]) diff --git a/tabs/__init__.py b/tabs/__init__.py new file mode 100644 index 0000000..31152a6 --- /dev/null +++ b/tabs/__init__.py @@ -0,0 +1,6 @@ +tabs = [ + "Stacking", + "Bulk processing", + "Images to video", + "Video to images" +]