bu kodu amacına göre full oop yap ve dosyalara böl. main.py de çalışsın. ve yorum satırları da ekle açıklayıcı şekilde

import tkinter as tk
from tkinter import ttk, messagebox
from PIL import Image, ImageTk
import requests
import threading
import io
import os
import vlc  # Ensure you have python-vlc installed
from pynput import mouse  # For mouse listening
import time  # Added for time handling


class VideoPlayer:
    def __init__(self, canvas, video_url, vlc_instance, play_button, seek_var=None, seek_scale=None, main_app=None, index=0):
        """
        Initializes the VideoPlayer.

        :param canvas: Tkinter Canvas where the video will be displayed.
        :param video_url: URL of the video to be played.
        :param vlc_instance: Shared VLC Instance object.
        :param play_button: Button widget to control play/pause.
        :param seek_var: DoubleVar linked to the seek Scale.
        :param seek_scale: Scale widget for seeking.
        :param main_app: Reference to the main ImageApp.
        :param index: Index of this video in the main_app's video_players list.
        """
        self.canvas = canvas
        self.video_url = video_url
        self.vlc_instance = vlc_instance
        self.play_button = play_button
        self.seek_var = seek_var
        self.seek_scale = seek_scale
        self.main_app = main_app
        self.index = index

        self.player = self.vlc_instance.media_player_new()
        self.media = self.vlc_instance.media_new(self.video_url) if self.video_url else None
        if self.media:
            self.player.set_media(self.media)
        self.playing = False
        self.fullscreen_player = None

        # Bind destroy event to cleanup
        self.canvas.bind("<Destroy>", self.on_destroy)

        # Configure seek scale
        if self.seek_scale and self.seek_var:
            self.seek_scale.configure(from_=0, to=100)
            self.seek_scale.bind("<ButtonPress-1>", self.on_seek_start)
            self.seek_scale.bind("<ButtonRelease-1>", self.on_seek_end)

    def on_seek_start(self, event):
        """Called when the user starts interacting with the seek bar."""
        self.seeking = True

    def on_seek_end(self, event):
        """Called when the user finishes interacting with the seek bar."""
        if self.get_length() > 0:
            new_time = (self.seek_var.get() / 100) * self.get_length()
            self.player.set_time(int(new_time))
        self.seeking = False

    def on_destroy(self, event):
        """Called when the widget is destroyed. Performs cleanup."""
        self.stop_video()
        if self.media:
            self.player.release()
            self.media.release()

    def play_video(self):
        """Starts or resumes video playback."""
        try:
            if not self.playing and self.player:
                # Assign the window handle if not already assigned
                if os.name == "nt":
                    self.player.set_hwnd(self.canvas.winfo_id())
                elif os.name == "posix":
                    self.player.set_xwindow(self.canvas.winfo_id())
                else:
                    pass  # MacOS handling if needed

                self.player.play()
                self.playing = True
                self.play_button.config(text="■")  # Pause icon
                self.update_seek_bar()
        except Exception as e:
            print(f"Error in play_video: {e}")
            messagebox.showerror("Playback Error", f"An error occurred while playing the video: {e}")

    def pause_video(self):
        """Pauses video playback."""
        try:
            if self.playing and self.player:
                self.player.pause()
                self.playing = False
                self.play_button.config(text="▶")  # Play icon
        except Exception as e:
            print(f"Error in pause_video: {e}")
            messagebox.showerror("Playback Error", f"An error occurred while pausing the video: {e}")

    def toggle_play_pause(self):
        """Toggles between play and pause."""
        if self.playing:
            self.pause_video()
        else:
            self.play_video()

    def stop_video(self):
        """Stops video playback."""
        try:
            if self.playing and self.player:
                self.player.stop()
                self.playing = False
                self.play_button.config(text="▶")  # Play icon
            # If fullscreen is active, close it
            if self.fullscreen_player and self.fullscreen_player.fullscreen_window:
                self.fullscreen_player.exit_fullscreen()
                self.fullscreen_player = None
        except Exception as e:
            print(f"Error in stop_video: {e}")

    def get_length(self):
        """Returns the length of the video in milliseconds."""
        if self.player:
            return self.player.get_length()
        return 0

    def get_time(self):
        """Returns the current playback time in milliseconds."""
        if self.player:
            return self.player.get_time()
        return 0

    def update_seek_bar(self):
        """Updates the seek bar based on the current playback time."""
        if self.playing and not getattr(self, 'seeking', False) and self.seek_var:
            current_time = self.get_time()
            length = self.get_length()
            if length > 0:
                progress = (current_time / length) * 100
                self.seek_var.set(progress)
        # Schedule next update
        if self.seek_scale:
            self.after_id = self.seek_scale.after(500, self.update_seek_bar)

    def enter_fullscreen(self):
        """Enters fullscreen mode with additional controls."""
        try:
            # If already in fullscreen, do nothing
            if self.fullscreen_player:
                return

            # Create a new fullscreen window
            self.fullscreen_player = VideoPlayerFullscreen(self)

            # Pause the video in the original window to prevent duplicate playback
            self.pause_video()
        except Exception as e:
            print(f"Error in enter_fullscreen: {e}")
            messagebox.showerror("Fullscreen Error", f"An error occurred while entering fullscreen: {e}")

    def seek_video(self, milliseconds):
        """Seeks the video by the given milliseconds."""
        if self.player:
            current_time = self.get_time()
            new_time = current_time + milliseconds
            new_time = max(0, min(new_time, self.get_length()))
            self.player.set_time(int(new_time))


class VideoPlayerFullscreen:
    def __init__(self, video_player):
        """
        Initializes the fullscreen video player.

        :param video_player: Instance of VideoPlayer.
        """
        self.video_player = video_player
        self.vlc_instance = video_player.vlc_instance
        self.main_app = video_player.main_app

        # Create a new Toplevel window
        self.fullscreen_window = tk.Toplevel()
        self.fullscreen_window.attributes("-fullscreen", True)
        self.fullscreen_window.configure(bg='black')

        # Bind escape key to exit fullscreen
        self.fullscreen_window.bind("<Escape>", self.exit_fullscreen)

        # Bind window close event to ensure proper exit
        self.fullscreen_window.protocol("WM_DELETE_WINDOW", self.exit_fullscreen)

        # Create a canvas in the fullscreen window
        self.fullscreen_canvas = tk.Canvas(self.fullscreen_window, bg='black', highlightthickness=0)
        self.fullscreen_canvas.pack(fill="both", expand=True)

        # Reassign the VLC player to the fullscreen canvas
        if os.name == "nt":
            handle = self.fullscreen_canvas.winfo_id()
            self.video_player.player.set_hwnd(handle)
        elif os.name == "posix":
            handle = self.fullscreen_canvas.winfo_id()
            self.video_player.player.set_xwindow(handle)
        else:
            pass  # MacOS handling if needed

        # Remove the video image from the original canvas to prevent overlay
        self.video_player.canvas.delete("all")

        # Ensure the GUI updates before playing
        self.fullscreen_window.update_idletasks()

        # Start playing
        self.video_player.player.play()
        self.video_player.playing = True
        self.video_player.play_button.config(text="■")  # Pause icon

        # Create control frame
        control_frame = ttk.Frame(self.fullscreen_window)
        control_frame.place(relx=0.5, rely=0.95, anchor='center')  # Positioned at bottom center

        # Previous Button
        prev_button = ttk.Button(
            control_frame,
            text="⏮️ Previous",
            command=self.main_app.previous_video_fullscreen,
            style='Ctrl.TButton'
        )
        prev_button.grid(row=0, column=0, padx=5)

        # Play/Pause Button
        self.play_pause_fullscreen = ttk.Button(
            control_frame,
            text="❚❚",
            command=self.video_player.toggle_play_pause,
            style='Ctrl.TButton'
        )
        self.play_pause_fullscreen.grid(row=0, column=1, padx=5)

        # Next Button
        next_button = ttk.Button(
            control_frame,
            text="Next ⏭️",
            command=self.main_app.next_video_fullscreen,
            style='Ctrl.TButton'
        )
        next_button.grid(row=0, column=2, padx=5)

        # Seek Backward Button
        seek_back_button = ttk.Button(
            control_frame,
            text="⏪ 10s",
            command=lambda: self.video_player.seek_video(-10000),
            style='Ctrl.TButton'
        )
        seek_back_button.grid(row=0, column=3, padx=5)

        # Seek Forward Button
        seek_forward_button = ttk.Button(
            control_frame,
            text="10s ⏩",
            command=lambda: self.video_player.seek_video(10000),
            style='Ctrl.TButton'
        )
        seek_forward_button.grid(row=0, column=4, padx=5)

        # Exit Fullscreen Button
        exit_fullscreen_button = ttk.Button(
            control_frame,
            text="Exit Fullscreen ⛌",
            command=self.exit_fullscreen,
            style='Ctrl.TButton'
        )
        exit_fullscreen_button.grid(row=0, column=5, padx=5)

        # Start updating the seek bar
        if self.video_player.seek_scale and self.video_player.seek_var:
            self.video_player.update_seek_bar()

    def exit_fullscreen(self, event=None):
        """Exits fullscreen mode and returns the video to the original canvas."""
        try:
            # Stop the VLC player before reassigning
            self.video_player.player.stop()

            # Reassign the VLC player back to the original canvas
            if os.name == "nt":
                handle = self.video_player.canvas.winfo_id()
                self.video_player.player.set_hwnd(handle)
            elif os.name == "posix":
                handle = self.video_player.canvas.winfo_id()
                self.video_player.player.set_xwindow(handle)
            else:
                pass  # MacOS handling if needed

            # Restart the video playback on the original canvas
            self.video_player.play_video()

            # Destroy the fullscreen window
            self.fullscreen_window.destroy()
            self.fullscreen_window = None
            self.video_player.fullscreen_player = None

            # Inform the main app that fullscreen has been exited
            if self.video_player.main_app.current_fullscreen_vp == self.video_player:
                self.video_player.main_app.current_fullscreen_vp = None

            # Ensure the GUI updates before playing
            self.video_player.canvas.update_idletasks()

            # Start updating the seek bar
            if self.video_player.seek_scale and self.video_player.seek_var:
                self.video_player.update_seek_bar()
        except Exception as e:
            print(f"Error in exit_fullscreen: {e}")
            messagebox.showerror("Fullscreen Error", f"An error occurred while exiting fullscreen: {e}")


def download_video(video_url, code, button):
    """Downloads a video asynchronously and updates the UI upon completion."""
    if not video_url:
        print(f"Video URL not found (Code: {code}).")
        return

    def download():
        try:
            response = requests.get(video_url, stream=True)
            response.raise_for_status()

            # Create 'videos' directory if it doesn't exist
            if not os.path.exists('videos'):
                os.makedirs('videos')

            # Create filename using code
            file_name = f"{code}.mp4"
            save_path = os.path.join('videos', file_name)

            with open(save_path, 'wb') as f:
                for chunk in response.iter_content(chunk_size=8192):
                    if chunk:
                        f.write(chunk)
            print(f"Video successfully downloaded: {save_path}")

            # Update download button to green and disable it
            button.after(0, lambda: button.config(style='Green.TButton', text="Downloaded", state="disabled"))
        except Exception as e:
            print(f"Error downloading video (Code: {code}): {e}")
            button.after(0, lambda: messagebox.showerror("Download Error", f"Could not download video: {e}"))

    # Start download in a separate daemon thread
    threading.Thread(target=download, daemon=True).start()


def fetch_data(callback):
    """Fetches video data from Instagram and invokes the callback with the data."""
    headers = {
        'user-agent': 'Instagram 146.0.0.27.125 (iPhone12,1; iOS 13_3; en_US; en-US; scale=2.00; 1656x3584; 190542906)'
    }
    data = {
        'target_user_id': '55408673825',
        'page_size': '12'
    }
    try:
        response = requests.post('https://i.instagram.com/api/v1/clips/user/', headers=headers, data=data)
        response.raise_for_status()  # Catch HTTP errors
        response_data = response.json()

        max_id = response_data['paging_info'].get('max_id')
        print("Max ID:", max_id)

        items = []
        for i, item in enumerate(response_data.get("items", [])):
            try:
                media = item["media"]
                image_versions = media["image_versions2"]
                candidates = image_versions.get("candidates", [])

                # Take the first image candidate
                if isinstance(candidates, list) and candidates:
                    image_url = candidates[0].get("url")
                else:
                    image_url = None

                # Get the video URL
                video_versions = media.get("video_versions", [])
                video_url = video_versions[0].get("url") if video_versions else None

                # Get the 'code' value
                code = media.get("code", f"video_{i}")

                if image_url:
                    items.append({
                        "image_url": image_url,
                        "video_url": video_url,
                        "code": code
                    })
            except (KeyError, IndexError, TypeError) as e:
                print(f"Error fetching data for item {i}: {e}")

        callback(items)

    except Exception as e:
        print("Error fetching data:", e)
        callback([])  # Return empty list on error


def load_images(items, frame, vlc_instance, main_app, columns=4, image_size=(600, 900)):
    """Downloads images and adds them to the GUI."""
    def download_and_display(index, item):
        image_url = item["image_url"]
        video_url = item["video_url"]
        code = item["code"]

        try:
            img_response = requests.get(image_url)
            img_response.raise_for_status()
            img_data = img_response.content
            image = Image.open(io.BytesIO(img_data))
            image = image.resize(image_size, Image.Resampling.LANCZOS)
            photo = ImageTk.PhotoImage(image)

            # Tkinter updates must occur in the main thread using 'after'
            def add_image():
                row = index // columns
                col = index % columns

                # Create Image Frame
                image_frame = ttk.Frame(frame, relief="raised", borderwidth=2)
                image_frame.grid(row=row, column=col, padx=5, pady=5, sticky="nsew")
                frame.grid_rowconfigure(row, weight=1)
                frame.grid_columnconfigure(col, weight=1)

                # Create Canvas for Video and Buttons
                video_canvas = tk.Canvas(image_frame, width=image_size[0], height=image_size[1],
                                         highlightthickness=0, bg='black')
                video_canvas.pack(fill="both", expand=True)

                # Add Image to Canvas as a placeholder
                video_canvas.create_image(0, 0, anchor='nw', image=photo)
                video_canvas.image = photo  # Keep a reference

                # Create Frame for Buttons and place it at the top-right corner
                buttons_frame = ttk.Frame(image_frame)
                buttons_frame.place(relx=0.85, rely=0.01)

                # Create Seek Bar
                seek_frame = ttk.Frame(image_frame)
                seek_frame.pack(fill="x", padx=5, pady=5)

                seek_var = tk.DoubleVar()
                seek_scale = ttk.Scale(seek_frame, orient='horizontal', length=image_size[0],
                                       from_=0, to=100, variable=seek_var)
                seek_scale.pack(fill='x')

                # Create Play/Stop Button
                play_stop_button = ttk.Button(buttons_frame, text="▶", width=3)
                play_stop_button.pack(side="top", pady=2)

                # Create Download Button
                if video_url:
                    download_button = ttk.Button(buttons_frame, text="⬇️")
                    download_button.config(command=lambda b=download_button: download_video(video_url, code, b))
                else:
                    download_button = ttk.Button(buttons_frame, text="⬇️", state="disabled")
                download_button.pack(side="top", pady=2)

                # Create Fullscreen Button
                if video_url:
                    fullscreen_button = ttk.Button(buttons_frame, text="⛶", width=3)
                    # Initially disable the fullscreen button until VideoPlayer is created
                else:
                    fullscreen_button = ttk.Button(buttons_frame, text="⛶", state="disabled")
                fullscreen_button.pack(side="top", pady=2)

                # Instantiate VideoPlayer now with all widgets
                video_player = VideoPlayer(
                    video_canvas,
                    video_url,
                    vlc_instance,
                    play_stop_button,
                    seek_var,
                    seek_scale,
                    main_app,
                    index
                )

                if video_url:
                    # Configure Play/Stop Button
                    play_stop_button.config(command=lambda vp=video_player: vp.toggle_play_pause())

                    # Configure Fullscreen Button
                    fullscreen_button.config(command=lambda vp=video_player: vp.main_app.play_video_in_fullscreen(vp.index),
                                             state="normal")
                else:
                    # Disable buttons if no video
                    play_stop_button.config(state="disabled")

                # Assign the VideoPlayer to the correct index in video_players
                if index < len(main_app.video_players):
                    main_app.video_players[index] = video_player
                else:
                    main_app.video_players.append(video_player)

            frame.after(0, add_image)
        except Exception as e:
            print(f"Error downloading image ({image_url}): {e}")

    # Initialize video_players list with None to ensure correct indexing
    if not hasattr(main_app, 'video_players') or not isinstance(main_app.video_players, list):
        main_app.video_players = [None] * len(items)
    else:
        # Ensure the list is the correct size
        while len(main_app.video_players) < len(items):
            main_app.video_players.append(None)

    # Download and add images concurrently
    for idx, item in enumerate(items):
        threading.Thread(target=download_and_display, args=(idx, item), daemon=True).start()


class ImageApp(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("Instagram Videos")
        self.state('zoomed')  # Maximize window (OS-specific)
        self.bind("<F11>", self.toggle_fullscreen)  # Toggle fullscreen with F11
        self.bind("<Escape>", self.exit_fullscreen)  # Exit fullscreen with Escape

        # Style Definitions
        self.style = ttk.Style()
        self.style.theme_use('clam')  # Use 'clam' theme for better styling
        self.style.configure('Green.TButton', foreground='white', background='#4CAF50',
                             font=('Helvetica', 10, 'bold'))
        self.style.map('Green.TButton',
                       foreground=[('active', 'white')],
                       background=[('active', '#45a049')])

        self.style.configure('Ctrl.TButton', foreground='white', background='#2196F3',
                             font=('Helvetica', 10, 'bold'))
        self.style.map('Ctrl.TButton',
                       foreground=[('active', 'white')],
                       background=[('active', '#1E88E5')])

        # Shared VLC instance
        self.vlc_instance = vlc.Instance()

        # List to keep track of VideoPlayer instances
        self.video_players = []

        # Track the currently active fullscreen VideoPlayer
        self.current_fullscreen_vp = None

        # Scrollable Frame Setup
        container = ttk.Frame(self)
        container.pack(fill="both", expand=True)

        canvas = tk.Canvas(container, bg='gray20')
        scrollbar = ttk.Scrollbar(container, orient="vertical", command=canvas.yview)
        self.scrollable_frame = ttk.Frame(canvas)

        canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw")
        canvas.configure(yscrollcommand=scrollbar.set, bg='gray20')

        self.scrollable_canvas = canvas  # Make canvas accessible

        self.scrollable_frame.bind(
            "<Configure>",
            lambda e: canvas.configure(
                scrollregion=canvas.bbox("all")
            )
        )

        # Initialize debounce variables for mouse scrolling
        self.last_scroll_time = 0
        self.scroll_delay = 100  # milliseconds

        # Start mouse wheel listener using Pynput
        self.start_mouse_listener(canvas)

        canvas.pack(side="left", fill="both", expand=True)
        scrollbar.pack(side="right", fill="y")

        # Initial loading message
        self.loading_label = ttk.Label(
            self.scrollable_frame,
            text="Loading images...",
            font=("Arial", 16),
            foreground='white',
            background='gray20'
        )
        self.loading_label.grid(row=0, column=0, columnspan=4, padx=10, pady=10)

        # Start data fetching in a separate thread
        threading.Thread(target=self.start_fetching, daemon=True).start()

        # Handle GUI closure
        self.protocol("WM_DELETE_WINDOW", self.on_closing)

    def start_mouse_listener(self, canvas):
        """Starts a global mouse wheel listener using Pynput."""
        def on_scroll(x, y, dx, dy):
            current_time = time.time() * 1000  # Current time in ms
            if current_time - self.last_scroll_time < self.scroll_delay:
                return  # Debounce: ignore if within delay
            self.last_scroll_time = current_time

            # dy: Vertical scroll (positive: down, negative: up)
            if dy > 0:
                direction = -1  # Scroll down
            elif dy < 0:
                direction = 1  # Scroll up
            else:
                direction = 0

            if direction != 0:
                # Must update Tkinter in the main thread
                canvas.after(0, lambda: canvas.yview_scroll(direction, "units"))

        def listen():
            with mouse.Listener(on_scroll=on_scroll) as listener:
                listener.join()

        # Start the mouse listener in a separate daemon thread
        listener_thread = threading.Thread(target=listen, daemon=True)
        listener_thread.start()

    def toggle_fullscreen(self, event=None):
        """Toggles fullscreen mode for the entire application."""
        try:
            is_fullscreen = self.attributes("-fullscreen")
            self.attributes("-fullscreen", not is_fullscreen)
        except Exception as e:
            print(f"Error in toggle_fullscreen: {e}")
            messagebox.showerror("Error", f"An error occurred while toggling fullscreen: {e}")

    def exit_fullscreen(self, event=None):
        """Exits fullscreen mode for the entire application."""
        try:
            self.attributes("-fullscreen", False)
            # Additionally, if any fullscreen video is open, exit it
            if self.current_fullscreen_vp:
                self.current_fullscreen_vp.fullscreen_player.exit_fullscreen()
                self.current_fullscreen_vp = None
        except Exception as e:
            print(f"Error in exit_fullscreen: {e}")
            messagebox.showerror("Error", f"An error occurred while exiting fullscreen: {e}")

    def start_fetching(self):
        """Starts fetching data."""
        fetch_data(self.on_data_fetched)

    def on_data_fetched(self, items):
        """Handles data after fetching."""
        # Remove loading message
        self.scrollable_frame.after(0, self.loading_label.destroy)

        if not items:
            # Inform the user if no videos were found or data couldn't be fetched
            info_label = ttk.Label(
                self.scrollable_frame,
                text="No videos found or data could not be fetched.",
                font=("Arial", 14),
                foreground='white',
                background='gray20'
            )
            info_label.grid(row=1, column=0, padx=10, pady=10)
            return

        # Initialize video_players list with None to ensure correct indexing
        self.video_players = [None] * len(items)

        # Load and display images
        load_images(
            items,
            self.scrollable_frame,
            self.vlc_instance,
            self,
            columns=4,
            image_size=(600, 900)  # Preserved image_size
        )

    def on_closing(self):
        """Stops all VideoPlayers and then destroys the main window."""
        try:
            # Stop all VideoPlayer instances
            for vp in self.video_players:
                if vp:  # Check if VideoPlayer exists at this index
                    vp.stop_video()

            # Exit fullscreen if any VideoPlayer is in fullscreen
            if self.current_fullscreen_vp:
                self.current_fullscreen_vp.fullscreen_player.exit_fullscreen()
        except Exception as e:
            print(f"Error during closing: {e}")
        finally:
            # Destroy the main window
            self.destroy()

    def next_video_fullscreen(self):
        """Plays the next video in fullscreen."""
        current_vp = self.get_current_fullscreen_vp()
        if current_vp is not None:
            next_index = (current_vp.index + 1) % len(self.video_players)
            self.play_video_in_fullscreen(next_index)

    def previous_video_fullscreen(self):
        """Plays the previous video in fullscreen."""
        current_vp = self.get_current_fullscreen_vp()
        if current_vp is not None:
            prev_index = (current_vp.index - 1) % len(self.video_players)
            self.play_video_in_fullscreen(prev_index)

    def get_current_fullscreen_vp(self):
        """Returns the VideoPlayer instance that is currently in fullscreen."""
        return self.current_fullscreen_vp

    def play_video_in_fullscreen(self, index):
        """Plays the video at the specified index in fullscreen."""
        if 0 <= index < len(self.video_players):
            vp = self.video_players[index]
            if vp and vp.video_url:
                # If there's an active fullscreen video, exit its fullscreen
                if self.current_fullscreen_vp and self.current_fullscreen_vp.fullscreen_player:
                    self.current_fullscreen_vp.fullscreen_player.exit_fullscreen()

                # Set the new fullscreen video
                self.current_fullscreen_vp = vp
                vp.enter_fullscreen()


# Run the Application
if __name__ == "__main__":
    app = ImageApp()
    app.mainloop()