nearly everything works, but when checking for a creator it somehow opens a chrome tab twice, even though it only needs one, and it then also downloading the segments twice. maybe its also a error of the code i copied wrongly. please output the entire working code

also make it only check for people that are offline, if they are online and not where oy know that they are online. and if tehre is no signal of the people from downloaded you can also mark them as offline. 

also add the feature to view the videos on the videos tab besides downloading them


here is the code:
import os
import subprocess
import sys
import time
import threading
from datetime import datetime
from glob import glob

from flask import Flask, render_template, request, redirect, url_for, flash, send_file
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.orm import relationship

from seleniumwire import webdriver
from selenium.webdriver.chrome.options import Options as ChromeOptions
from selenium.webdriver.firefox.options import Options as FirefoxOptions
from selenium.common.exceptions import NoSuchElementException
from selenium.webdriver.common.by import By

import atexit  # For graceful shutdown

# ---------------------- Flask and Database Setup ---------------------- #

app = Flask(__name__)
app.secret_key = 'your_secure_secret_key'  # Replace with a secure secret key
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///stream_recorder.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)

# ---------------------- Database Models ---------------------- #

class Creator(db.Model):
    __tablename__ = 'creators'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(100), unique=True, nullable=False)
    stream_url = db.Column(db.String(500), nullable=False)
    browser = db.Column(db.String(20), default='chrome')  # 'chrome' or 'firefox'
    driver_path = db.Column(db.String(300), nullable=True)
    output_dir = db.Column(db.String(300), default='downloads')
    is_online = db.Column(db.Boolean, default=False)
    videos = relationship('Video', backref='creator', lazy=True)

class Video(db.Model):
    __tablename__ = 'videos'
    id = db.Column(db.Integer, primary_key=True)
    creator_id = db.Column(db.Integer, db.ForeignKey('creators.id'), nullable=False)
    file_path = db.Column(db.String(500), nullable=False)
    timestamp = db.Column(db.DateTime, default=datetime.utcnow)

# Create the database tables
with app.app_context():
    db.create_all()

# ---------------------- Helper Function ---------------------- #

def retrieve_m3u8_url(stream_url, browser_type, driver_path, creator_name):
    """
    Navigate to the stream URL, handle pop-ups, and retrieve the M3U8 URL.

    Args:
        stream_url (str): The URL of the stream.
        browser_type (str): The browser type ('chrome' or 'firefox').
        driver_path (str or None): Path to the WebDriver executable.
        creator_name (str): Name of the creator (for logging).

    Returns:
        str or None: The M3U8 URL if found, else None.
    """
    driver = setup_webdriver(browser_type, driver_path)
    if not driver:
        print(f"[{datetime.now()}] ERROR: WebDriver setup failed for stream '{creator_name}'.")
        return None

    try:
        driver.get(stream_url)
        print(f"[{datetime.now()}] INFO: Navigated to '{stream_url}' for creator '{creator_name}'.")

        # Wait for the page to load and handle potential pop-ups
        timeout = 60  # seconds
        start_time = time.time()
        m3u8_url = None

        while time.time() - start_time < timeout:
            # Attempt to close the "I AGREE" popup if present
            try:
                agree_button = driver.find_element(By.ID, "close_entrance_terms")
                agree_button.click()
                print(f"[{datetime.now()}] INFO: Clicked on 'I AGREE' popup for creator '{creator_name}'.")
                # Give some time after clicking the popup
                time.sleep(2)
            except NoSuchElementException:
                pass  # Popup not present

            # Check network requests for M3U8 URL
            for request in driver.requests:
                if request.response:
                    url = request.url
                    if url.endswith('.m3u8'):
                        m3u8_url = url
                        print(f"[{datetime.now()}] INFO: M3U8 URL found for '{creator_name}': {m3u8_url}")
                        driver.quit()
                        return m3u8_url

            time.sleep(2)  # Wait before checking again

        # If M3U8 URL not found within timeout
        print(f"[{datetime.now()}] WARN: M3U8 URL not found for stream '{creator_name}' within {timeout} seconds.")
        driver.quit()
        return None

    except Exception as e:
        print(f"[{datetime.now()}] ERROR: Exception occurred while retrieving M3U8 URL for stream '{creator_name}': {e}")
        driver.quit()
        return None

# ---------------------- Stream Recorder Classes ---------------------- #

def build_ffmpeg_command(m3u8_url, segment_duration, segment_pattern):
    """Construct the FFmpeg command based on the provided parameters."""
    return [
        'ffmpeg',
        '-i', m3u8_url,
        '-c', 'copy',
        '-f', 'segment',
        '-segment_time', str(segment_duration),
        '-reset_timestamps', '1',
        '-loglevel', 'error',  # Suppress FFmpeg logs for cleaner output
        segment_pattern
    ]

def setup_webdriver(browser_type='chrome', driver_path=None):
    """Initialize and return a Selenium WebDriver."""
    if browser_type.lower() == 'chrome':
        options = ChromeOptions()
        # Uncomment the next line to run in headless mode
        # options.add_argument('--headless')
        options.add_argument('--disable-gpu')
        options.add_argument('--no-sandbox')
        options.add_argument('--disable-infobars')
        options.add_argument('--disable-extensions')
        try:
            if driver_path:
                driver = webdriver.Chrome(executable_path=driver_path, options=options)
            else:
                driver = webdriver.Chrome(options=options)
        except Exception as e:
            print(f"[{datetime.now()}] ERROR: Failed to initialize Chrome WebDriver. Exception: {e}")
            return None
    elif browser_type.lower() == 'firefox':
        options = FirefoxOptions()
        # Uncomment the next line to run in headless mode
        # options.add_argument('--headless')
        options.add_argument('--disable-gpu')
        try:
            if driver_path:
                driver = webdriver.Firefox(executable_path=driver_path, options=options)
            else:
                driver = webdriver.Firefox(options=options)
        except Exception as e:
            print(f"[{datetime.now()}] ERROR: Failed to initialize Firefox WebDriver. Exception: {e}")
            return None
    else:
        print(f"[{datetime.now()}] ERROR: Unsupported browser type '{browser_type}'. Supported types: 'chrome', 'firefox'.")
        return None
    return driver

class StreamRecorder(threading.Thread):
    """Thread class to handle recording of a single stream."""

    def __init__(self, creator, m3u8_url):
        threading.Thread.__init__(self)
        self.creator = creator
        self.m3u8_url = m3u8_url
        self.stop_event = threading.Event()

    def run(self):
        name = self.creator.name
        m3u8_url = self.m3u8_url
        segment_duration = 30  # seconds
        segments_per_clip = 5
        output_dir = self.creator.output_dir

        print(f"[{datetime.now()}] INFO: Starting recorder for stream '{name}'.")

        # Ensure output directories exist
        segments_dir = os.path.join(output_dir, name, 'segments')
        os.makedirs(segments_dir, exist_ok=True)

        # Define FFmpeg segmentation pattern
        segment_pattern = os.path.join(segments_dir, f"{name}_%03d.ts").replace('\\', '/')

        # Build FFmpeg command
        ffmpeg_command = build_ffmpeg_command(m3u8_url, segment_duration, segment_pattern)

        # Start FFmpeg subprocess
        try:
            ffmpeg_process = subprocess.Popen(ffmpeg_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            print(f"[{datetime.now()}] INFO: FFmpeg started for stream '{name}'.")
        except Exception as e:
            print(f"[{datetime.now()}] ERROR: Failed to start FFmpeg for stream '{name}'. Exception: {e}")
            with app.app_context():
                self.creator.is_online = False
                db.session.commit()
            return

        # Initialize variables for compilation
        segments_collected = []
        compiled_clip_count = 0

        while not self.stop_event.is_set():
            time.sleep(segment_duration + 5)  # Wait enough time for a segment to be recorded

            # Gather all segment files
            segment_files = sorted(glob(os.path.join(segments_dir, f"{name}_*.ts")))

            # Identify new segments
            new_segments = [seg for seg in segment_files if seg not in segments_collected]

            for seg in new_segments:
                segments_collected.append(seg)
                print(f"[{datetime.now()}] INFO: New segment recorded: {seg}")

                # Check if enough segments have been collected for compilation
                if len(segments_collected) >= segments_per_clip:
                    # Define output clip name
                    final_clip_name = datetime.now().strftime(f"{name}_%Y%m%d_%H%M%S.mp4")
                    final_clip_path = os.path.join(output_dir, name, final_clip_name)

                    # Create a temporary list file for FFmpeg concat
                    list_file_path = os.path.join(output_dir, name, f"segments_list_{compiled_clip_count}.txt")
                    try:
                        with open(list_file_path, 'w') as list_file:
                            for segment in segments_collected[:segments_per_clip]:
                                # Convert backslashes to forward slashes for FFmpeg compatibility
                                segment_relative_path = os.path.relpath(segment, start=segments_dir)
                                segment_forward_slash = segment_relative_path.replace('\\', '/')
                                list_file.write(f"file 'segments/{segment_forward_slash}'\n")
                        print(f"[{datetime.now()}] INFO: Created list file '{list_file_path}'.")
                    except Exception as e:
                        print(f"[{datetime.now()}] ERROR: Failed to create list file '{list_file_path}'. Exception: {e}")
                        continue  # Skip compilation if list file creation failed

                    # FFmpeg concat command
                    concat_command = [
                        'ffmpeg',
                        '-f', 'concat',
                        '-safe', '0',
                        '-i', list_file_path,
                        '-c', 'copy',
                        final_clip_path
                    ]

                    # Execute FFmpeg concat
                    try:
                        subprocess.run(concat_command, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
                        print(f"[{datetime.now()}] INFO: Compiled clip saved as '{final_clip_path}'.")

                        # Save video info to database
                        with app.app_context():
                            new_video = Video(
                                creator_id=self.creator.id,
                                file_path=final_clip_path,
                                timestamp=datetime.now()
                            )
                            db.session.add(new_video)
                            db.session.commit()
                    except subprocess.CalledProcessError as e:
                        print(f"[{datetime.now()}] ERROR: FFmpeg concat failed for stream '{name}'. Exception: {e}")
                        continue  # Skip deletion if compilation failed

                    # Clean up: Remove compiled segments
                    for segment in segments_collected[:segments_per_clip]:
                        try:
                            os.remove(segment)
                            print(f"[{datetime.now()}] INFO: Deleted segment '{segment}'.")
                        except Exception as e:
                            print(f"[{datetime.now()}] ERROR: Failed to delete segment '{segment}'. Exception: {e}")

                    # Remove the used segments from the list
                    segments_collected = segments_collected[segments_per_clip:]
                    compiled_clip_count += 1

        # Terminate FFmpeg subprocess
        try:
            ffmpeg_process.terminate()
            ffmpeg_process.wait(timeout=5)
            print(f"[{datetime.now()}] INFO: FFmpeg terminated for stream '{name}'.")
        except subprocess.TimeoutExpired:
            ffmpeg_process.kill()
            print(f"[{datetime.now()}] WARN: FFmpeg did not terminate gracefully for stream '{name}'. Killed the process.")

        with app.app_context():
            self.creator.is_online = False
            db.session.commit()

# ---------------------- Recorder Manager ---------------------- #

recorder_threads = {}
recorder_lock = threading.Lock()

def recorder_manager():
    """Background thread to manage StreamRecorders."""
    while True:
        try:
            with app.app_context():
                creators = Creator.query.all()

            with recorder_lock:
                for creator in creators:
                    # Skip if already being recorded
                    if creator.id in recorder_threads:
                        continue

                    # Check if the creator is online
                    m3u8_url = retrieve_m3u8_url(
                        creator.stream_url,
                        creator.browser,
                        creator.driver_path,
                        creator.name
                    )

                    if m3u8_url:
                        with app.app_context():
                            creator.is_online = True
                            db.session.commit()

                        # Start recording thread
                        recorder = StreamRecorder(creator, m3u8_url)
                        recorder.start()
                        recorder_threads[creator.id] = recorder
                        print(f"[{datetime.now()}] INFO: Started recording for creator '{creator.name}'.")

                        # Limit to 3 concurrent recordings
                        if len(recorder_threads) >= 3:
                            break
                    else:
                        with app.app_context():
                            if creator.is_online:
                                creator.is_online = False
                                db.session.commit()
                                print(f"[{datetime.now()}] INFO: Creator '{creator.name}' is now offline.")

            # Sleep for 5 minutes
            time.sleep(300)
        except Exception as e:
            print(f"[{datetime.now()}] ERROR: Exception in recorder_manager: {e}")
            time.sleep(60)  # Wait a minute before retrying in case of error

# Start the recorder manager thread
manager_thread = threading.Thread(target=recorder_manager, daemon=True)
manager_thread.start()

# ---------------------- Flask Routes ---------------------- #

@app.route('/')
def index():
    creators = Creator.query.all()
    return render_template('index.html', creators=creators)

@app.route('/add_creator', methods=['GET', 'POST'])
def add_creator():
    if request.method == 'POST':
        name = request.form['name']
        stream_url = request.form['stream_url']
        browser = request.form['browser']
        driver_path = request.form['driver_path'] or None
        output_dir = request.form['output_dir']

        if Creator.query.filter_by(name=name).first():
            flash('Creator with this name already exists.')
            return redirect(url_for('add_creator'))

        new_creator = Creator(
            name=name,
            stream_url=stream_url,
            browser=browser,
            driver_path=driver_path,
            output_dir=output_dir
        )
        db.session.add(new_creator)
        db.session.commit()
        flash('Creator added successfully.')
        return redirect(url_for('index'))

    return render_template('add_creator.html')

@app.route('/edit_creator/<int:creator_id>', methods=['GET', 'POST'])
def edit_creator(creator_id):
    creator = Creator.query.get_or_404(creator_id)
    if request.method == 'POST':
        creator.name = request.form['name']
        creator.stream_url = request.form['stream_url']
        creator.browser = request.form['browser']
        creator.driver_path = request.form['driver_path'] or None
        creator.output_dir = request.form['output_dir']
        db.session.commit()
        flash('Creator updated successfully.')
        return redirect(url_for('index'))

    return render_template('edit_creator.html', creator=creator)

@app.route('/delete_creator/<int:creator_id>', methods=['GET', 'POST'])
def delete_creator(creator_id):
    creator = Creator.query.get_or_404(creator_id)
    # Stop the recorder thread if it's running
    with recorder_lock:
        recorder = recorder_threads.get(creator.id)
        if recorder:
            recorder.stop_event.set()
            recorder.join()
            del recorder_threads[creator.id]
            print(f"[{datetime.now()}] INFO: Stopped recorder for creator '{creator.name}' due to deletion.")
    # Optionally, delete associated videos from filesystem
    # Here, we just delete the database entries
    Video.query.filter_by(creator_id=creator.id).delete()
    db.session.delete(creator)
    db.session.commit()
    flash('Creator and associated videos deleted successfully.')
    return redirect(url_for('index'))

@app.route('/videos/<int:creator_id>')
def view_videos(creator_id):
    creator = Creator.query.get_or_404(creator_id)
    videos = Video.query.filter_by(creator_id=creator_id).order_by(Video.timestamp.desc()).all()
    return render_template('view_videos.html', creator=creator, videos=videos)

@app.route('/download_video/<int:video_id>')
def download_video(video_id):
    video = Video.query.get_or_404(video_id)
    if os.path.exists(video.file_path):
        return send_file(video.file_path, as_attachment=True)
    else:
        flash('File does not exist.')
        return redirect(url_for('view_videos', creator_id=video.creator_id))

@app.route('/check_online/<int:creator_id>', methods=['POST'])
def check_online(creator_id):
    """
    Manually trigger an online status check for a specific creator.
    """
    creator = Creator.query.get_or_404(creator_id)
    print(f"[{datetime.now()}] INFO: Manual online check triggered for creator '{creator.name}'.")

    # Perform the online status check using the helper function
    m3u8_url = retrieve_m3u8_url(
        creator.stream_url,
        creator.browser,
        creator.driver_path,
        creator.name
    )

    with recorder_lock:
        if m3u8_url:
            if not creator.is_online:
                with app.app_context():
                    creator.is_online = True
                    db.session.commit()

                # Start a new StreamRecorder
                recorder = StreamRecorder(creator, m3u8_url)
                recorder.start()
                recorder_threads[creator.id] = recorder
                print(f"[{datetime.now()}] INFO: Started recording for creator '{creator.name}' via manual check.")

                flash(f"Creator '{creator.name}' is online and recording has started.")
            else:
                flash(f"Creator '{creator.name}' is already online and being recorded.")
        else:
            if creator.is_online:
                with app.app_context():
                    creator.is_online = False
                    db.session.commit()

                # Stop the existing StreamRecorder
                recorder = recorder_threads.get(creator.id)
                if recorder:
                    recorder.stop_event.set()
                    recorder.join()
                    del recorder_threads[creator.id]
                    print(f"[{datetime.now()}] INFO: Stopped recording for creator '{creator.name}' via manual check.")

                flash(f"Creator '{creator.name}' is now offline.")
            else:
                flash(f"Creator '{creator.name}' remains offline.")

    return redirect(url_for('index'))

@app.route('/check_all_online', methods=['POST'])
def check_all_online():
    """
    Manually trigger online status checks for all creators.
    """
    creators = Creator.query.all()
    checked_creators = []

    for creator in creators:
        print(f"[{datetime.now()}] INFO: Manual online check triggered for creator '{creator.name}'.")
        m3u8_url = retrieve_m3u8_url(
            creator.stream_url,
            creator.browser,
            creator.driver_path,
            creator.name
        )

        with recorder_lock:
            if m3u8_url:
                if not creator.is_online:
                    with app.app_context():
                        creator.is_online = True
                        db.session.commit()

                    # Start a new StreamRecorder
                    recorder = StreamRecorder(creator, m3u8_url)
                    recorder.start()
                    recorder_threads[creator.id] = recorder
                    print(f"[{datetime.now()}] INFO: Started recording for creator '{creator.name}' via manual check.")
                    checked_creators.append((creator.name, True))
                else:
                    checked_creators.append((creator.name, True))
            else:
                if creator.is_online:
                    with app.app_context():
                        creator.is_online = False
                        db.session.commit()

                    # Stop the existing StreamRecorder
                    recorder = recorder_threads.get(creator.id)
                    if recorder:
                        recorder.stop_event.set()
                        recorder.join()
                        del recorder_threads[creator.id]
                        print(f"[{datetime.now()}] INFO: Stopped recording for creator '{creator.name}' via manual check.")
                checked_creators.append((creator.name, False))

    flash('All creators have been checked for online status.')
    return redirect(url_for('index'))

# ---------------------- Graceful Shutdown ---------------------- #

@atexit.register
def shutdown():
    """
    Ensure all recorder threads are properly terminated on application exit.
    """
    print(f"[{datetime.now()}] INFO: Shutting down application. Terminating all recorder threads.")
    with recorder_lock:
        for creator_id, recorder in recorder_threads.items():
            recorder.stop_event.set()
            recorder.join()
            print(f"[{datetime.now()}] INFO: Recorder for creator ID {creator_id} has been terminated.")
        recorder_threads.clear()

# ---------------------- Run the Flask App ---------------------- #

if __name__ == "__main__":
    app.run(host='0.0.0.0', port=5000, debug=True)