my input file is zip file from Google drive and my output file is also zip file and a text file which contains all links. I have 3 script for media processing but I want make one script with more flexibility and choosing options and easy interface like app, can you give me some ideas how I can achieve these. here is my all 3 script. # @title ( zip marking each of the video) Video Cropping Tool for Google Drive


# Install necessary packages
!pip install opencv-python-headless ffmpeg-python -q

import os
import cv2
import ffmpeg
import zipfile
import shutil
from google.colab import drive
import ipywidgets as widgets
from IPython.display import display, clear_output
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle

# Mount Google Drive
drive.mount('/content/drive')

# Function to browse directories and list ZIP files in Google Drive
def browse_zip_files(base_folder='/content/drive/My Drive'):
    """Returns a sorted list of ZIP file paths within the base folder and its subfolders."""
    zip_files = []
    for dirname, _, filenames in os.walk(base_folder):
        for filename in filenames:
            if filename.lower().endswith('.zip'):
                zip_files.append(os.path.join(dirname, filename))
    zip_files_sorted = sorted(zip_files)
    return zip_files_sorted

# Initialize global variables
zip_file_path = None
extracted_videos_dir = '/content/extracted_videos'
cropped_videos_dir = '/content/cropped_videos'
files_list = []
current_file_index = 0
crop_settings = {}
width_cache = {}
height_cache = {}
original_zip_dir = ''

# Create directories if they don't exist
os.makedirs(extracted_videos_dir, exist_ok=True)
os.makedirs(cropped_videos_dir, exist_ok=True)

# Dropdown for ZIP file selection
zip_dropdown = widgets.Dropdown(
    options=browse_zip_files(),
    description='Select ZIP File:',
    layout=widgets.Layout(width='100%'),
    style={'description_width': 'initial'}
)

# Button to extract ZIP
extract_button = widgets.Button(
    description="Extract ZIP",
    button_style='primary',
    layout=widgets.Layout(width='100%')
)

# Sliders for crop settings (percentages)
x_start_slider = widgets.IntSlider(
    value=0,
    min=0,
    max=100,
    step=1,
    description="Start X (%)",
    continuous_update=False,
    layout=widgets.Layout(width='100%')
)
y_start_slider = widgets.IntSlider(
    value=0,
    min=0,
    max=100,
    step=1,
    description="Start Y (%)",
    continuous_update=False,
    layout=widgets.Layout(width='100%')
)
x_end_slider = widgets.IntSlider(
    value=100,
    min=0,
    max=100,
    step=1,
    description="End X (%)",
    continuous_update=False,
    layout=widgets.Layout(width='100%')
)
y_end_slider = widgets.IntSlider(
    value=100,
    min=0,
    max=100,
    step=1,
    description="End Y (%)",
    continuous_update=False,
    layout=widgets.Layout(width='100%')
)

# Buttons for interactions
set_button = widgets.Button(
    description="Set Crop for Current Video",
    button_style='success',
    layout=widgets.Layout(width='100%')
)
next_button = widgets.Button(
    description="Next Video",
    button_style='info',
    layout=widgets.Layout(width='100%')
)
crop_all_button = widgets.Button(
    description="Start Cropping All Videos",
    button_style='danger',
    layout=widgets.Layout(width='100%')
)

# Output widget for displaying video preview and messages
output = widgets.Output(
    layout=widgets.Layout(
        border='1px solid black',
        height='300px',
        overflow='auto'
    )
)

# Organize widgets into containers
controls_box = widgets.VBox([
    zip_dropdown,
    extract_button
])

crop_controls_box = widgets.VBox([
    x_start_slider,
    y_start_slider,
    x_end_slider,
    y_end_slider,
    set_button,
    next_button,
    crop_all_button
])

# Display initial widgets
display(controls_box, crop_controls_box, output)

def extract_zip(b):
    """Extracts the selected ZIP file and initializes video cropping."""
    global zip_file_path, files_list, current_file_index, crop_settings, original_zip_dir
    zip_file_path = zip_dropdown.value
    if not zip_file_path:
        with output:
            clear_output()
            print("Please select a ZIP file to extract.")
        return
    
    original_zip_dir = os.path.dirname(zip_file_path)
    video_formats = ('.mp4', '.avi', '.mov', '.mkv')
    
    # Clear previous extracted and cropped videos
    shutil.rmtree(extracted_videos_dir, ignore_errors=True)
    shutil.rmtree(cropped_videos_dir, ignore_errors=True)
    os.makedirs(extracted_videos_dir, exist_ok=True)
    os.makedirs(cropped_videos_dir, exist_ok=True)
    
    # Extract ZIP
    try:
        with zipfile.ZipFile(zip_file_path, 'r') as zip_ref:
            zip_ref.extractall(extracted_videos_dir)
        with output:
            clear_output()
            print(f"✅ Successfully extracted '{os.path.basename(zip_file_path)}'.")
    except zipfile.BadZipFile:
        with output:
            clear_output()
            print("❌ Error: The selected file is not a valid ZIP file.")
        return
    except Exception as e:
        with output:
            clear_output()
            print(f"❌ Error extracting ZIP file: {e}")
        return
    
    # List video files
    files_list = []
    for dirname, _, filenames in os.walk(extracted_videos_dir):
        for filename in filenames:
            if filename.lower().endswith(video_formats):
                files_list.append(os.path.join(dirname, filename))
    
    if not files_list:
        with output:
            clear_output()
            print("❌ No video files found in the extracted ZIP.")
        return
    else:
        with output:
            clear_output()
            print(f"✅ Found {len(files_list)} video file(s) in the ZIP.")
    
    # Initialize crop settings for each file
    crop_settings = {file: (0, 0, 100, 100) for file in files_list}
    width_cache.clear()
    height_cache.clear()
    current_file_index = 0
    
    # Update crop controls and show preview
    update_sliders_for_current_file()
    show_preview()

def set_slider_max_values(video_path):
    """
    Retrieves the width and height of the first frame of the video.
    Caches the values to avoid redundant computations.
    """
    if video_path in width_cache and video_path in height_cache:
        return width_cache[video_path], height_cache[video_path]
    
    cap = cv2.VideoCapture(video_path)
    ret, frame = cap.read()
    cap.release()

    if not ret:
        print(f"❌ Error: Unable to read video {os.path.basename(video_path)}.")
        return None, None  # Indicate failure

    height, width = frame.shape[:2]
    width_cache[video_path] = width
    height_cache[video_path] = height
    return width, height

def update_sliders_for_current_file():
    """Sets the slider values based on the current video's crop settings."""
    if files_list:
        current_file = files_list[current_file_index]
        start_x, start_y, end_x, end_y = crop_settings[current_file]
        x_start_slider.value = start_x
        y_start_slider.value = start_y
        x_end_slider.value = end_x
        y_end_slider.value = end_y
    else:
        with output:
            clear_output()
            print("❌ No video files available to set sliders.")

def show_preview(*args):
    """Displays the first frame of the current video with the cropping rectangle."""
    if not files_list:
        with output:
            clear_output()
            print("❌ No video files available to preview.")
        return

    current_file = files_list[current_file_index]
    width, height = set_slider_max_values(current_file)
    
    if width is None or height is None:
        with output:
            clear_output()
            print(f"❌ Cannot preview '{os.path.basename(current_file)}' due to read error.")
        return

    cap = cv2.VideoCapture(current_file)
    ret, frame = cap.read()
    cap.release()

    if not ret:
        with output:
            clear_output()
            print(f"❌ Error reading video: {os.path.basename(current_file)}")
        return

    frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
    fig, ax = plt.subplots(figsize=(4, 3))  # Reduced figure size for mobile
    ax.imshow(frame_rgb)
    ax.set_title(f"Crop area for: {os.path.basename(current_file)}", fontsize=8)  # Smaller title font

    # Calculate pixel values based on slider percentages
    start_x = int(x_start_slider.value / 100 * width)
    start_y = int(y_start_slider.value / 100 * height)
    end_x = int(x_end_slider.value / 100 * width)
    end_y = int(y_end_slider.value / 100 * height)

    # Ensure coordinates are within frame bounds
    start_x = max(0, min(start_x, width - 1))
    end_x = max(start_x + 1, min(end_x, width))
    start_y = max(0, min(start_y, height - 1))
    end_y = max(start_y + 1, min(end_y, height))

    crop_width = end_x - start_x
    crop_height = end_y - start_y

    # Draw the cropping rectangle
    rect = Rectangle((start_x, start_y), crop_width, crop_height,
                     linewidth=2, edgecolor='r', facecolor='none')
    ax.add_patch(rect)
    plt.axis('off')
    plt.tight_layout()  # Adjust layout to fit the smaller figure

    with output:
        clear_output(wait=True)
        # Display the image in the output widget
        display(fig)

def set_crop_for_current_file(b):
    """Saves the current slider values as crop settings for the active video."""
    if not files_list:
        with output:
            clear_output()
            print("❌ No video files available to set crop.")
        return

    current_file = files_list[current_file_index]
    crop_settings[current_file] = (
        x_start_slider.value,
        y_start_slider.value,
        x_end_slider.value,
        y_end_slider.value
    )
    
    with output:
        print(f"✅ Set crop for '{os.path.basename(current_file)}': "
              f"Start X: {x_start_slider.value}%, Start Y: {y_start_slider.value}%, "
              f"End X: {x_end_slider.value}%, End Y: {y_end_slider.value}%")
    show_preview()

def next_video(b):
    """Navigates to the next video in the list."""
    global current_file_index
    if current_file_index < len(files_list) - 1:
        current_file_index += 1
        update_sliders_for_current_file()
        show_preview()
    else:
        with output:
            print("🔔 You have reached the last video.")

def crop_videos(b):
    """Applies the crop settings to all videos, compresses them into a ZIP, and saves the ZIP back to Drive."""
    if not files_list:
        with output:
            clear_output()
            print("❌ No videos to crop.")
        return

    with output:
        clear_output()
        print("⏳ Starting the cropping process...")

    for idx, input_file in enumerate(files_list, 1):
        file_name = os.path.basename(input_file)
        file_base, file_ext = os.path.splitext(file_name)
        cropped_file_path = os.path.join(cropped_videos_dir, f"{file_base}_cropped{file_ext}")

        crop_info = crop_settings.get(input_file, (0, 0, 100, 100))
        start_x_percent, start_y_percent, end_x_percent, end_y_percent = crop_info

        width, height = set_slider_max_values(input_file)
        if width is None or height is None:
            with output:
                print(f"❌ Skipping '{file_name}' due to read error.")
            continue

        start_x = int(start_x_percent / 100 * width)
        start_y = int(start_y_percent / 100 * height)
        end_x = int(end_x_percent / 100 * width)
        end_y = int(end_y_percent / 100 * height)

        # Validate crop dimensions
        if end_x <= start_x or end_y <= start_y:
            with output:
                print(f"❌ Invalid crop dimensions for '{file_name}'. Skipping.")
            continue

        crop_width = end_x - start_x
        crop_height = end_y - start_y

        try:
            # Apply cropping using FFmpeg with explicit audio stream mapping
            (
                ffmpeg
                .input(input_file)
                .crop(x=start_x, y=start_y, width=crop_width, height=crop_height)
                .output(cropped_file_path, vcodec='libx264', acodec='copy', map='0')
                .overwrite_output()
                .run(quiet=False)  # Disable quiet mode for debugging
            )
            with output:
                print(f"✅ [{idx}/{len(files_list)}] Cropped and saved as: {cropped_file_path}")
        except ffmpeg.Error as e:
            with output:
                print(f"❌ Error cropping '{file_name}': {e}. Skipping.")
        except Exception as e:
            with output:
                print(f"❌ Unexpected error for '{file_name}': {e}. Skipping.")
    
    # Compress cropped videos into a new ZIP file
    original_zip_name = os.path.basename(zip_file_path)
    original_zip_basename, _ = os.path.splitext(original_zip_name)
    new_zip_name = f"{original_zip_basename}_cropped.zip"
    new_zip_path = os.path.join(original_zip_dir, new_zip_name)
    
    try:
        with zipfile.ZipFile(new_zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
            for root, _, files in os.walk(cropped_videos_dir):
                for file in files:
                    file_path = os.path.join(root, file)
                    arcname = os.path.relpath(file_path, cropped_videos_dir)
                    zipf.write(file_path, arcname)
        with output:
            print(f"✅ Successfully created cropped ZIP file: {new_zip_path}")
    except Exception as e:
        with output:
            print(f"❌ Error creating cropped ZIP file: {e}")
        return
    
    with output:
        print("🎉 Cropping process completed.")

# Attach event handlers to buttons
extract_button.on_click(extract_zip)
set_button.on_click(set_crop_for_current_file)
next_button.on_click(next_video)
crop_all_button.on_click(crop_videos)

# Automatically update the preview when sliders are adjusted
x_start_slider.observe(show_preview, names='value')
y_start_slider.observe(show_preview, names='value')
x_end_slider.observe(show_preview, names='value')
y_end_slider.observe(show_preview, names='value')