Recommended Posts

Updated the Python PDF name search app. 

  1. Give it a TXT file with a list of first names.
  2. Give it a dictionary full of english words.
  3. Give it a PDF to search

It looks through the PDF for a first name match. Then, it takes the next word following the first name and checks the English dictionary to see if it is an English word.

As an example, if it finds "Scarlett drove the car to the property," it would find "drove" in the dictionary and skip it. If it finds "Scarlett Wilberding drove the car to the property," it takes the word "Wilberding" and sees that it's not in the dictionary and then records "Scarlett Wilberding" along with the page number it was found on behind it.

Each name is recorded once, and the page numbers on which it appears show up in the back of its name.

I updated the app for the Epstein list, which is supposed to drop today..

In the example below, I used the one of the public Epstein. Court document

You can either copy pasted code or grab the .py file and names.txt and dictionary.txt

From this dropbox link
https://www.dropbox.com/scl/fo/06zqmpi5xstjh5w7vawdj/ANmedXX0X4BkNEoHtTQ1c50?rlkey=53892676fmz51ua83elhnzk0a&st=5qrqjsfj&dl=0

image.thumb.png.506317de17d38d8c1b8aa8dfa96a2fad.png

 

import sys
from PyQt5.QtWidgets import (
    QApplication,
    QMainWindow,
    QWidget,
    QVBoxLayout,
    QHBoxLayout,
    QFileDialog,
    QPushButton,
    QTextEdit,
    QLineEdit,
    QLabel,
    QProgressBar,
    QMessageBox,
)
from PyQt5.QtCore import Qt, QThread, pyqtSignal
import fitz  # PyMuPDF
import re


class SearchThread(QThread):
    update_progress = pyqtSignal(int)  # Signal to update progress bar
    update_results = pyqtSignal(str, list)  # Signal to update results text (name, pages)

    def __init__(self, pdf_path, names, dictionary, stop_flag):
        super().__init__()
        self.pdf_path = pdf_path
        self.names = names
        self.dictionary = dictionary
        self.stop_flag = stop_flag
        self.results = {}  # Stores results as {name: [pages]}

    def run(self):
        self.update_results.emit("Startingch...\n", [])
        pdf_document = fitz.open(self.pdf_path)
        total_pages = pdf_document.page_count

        for page_number in range(total_pages):
            if self.stop_flag():
                self.update_results.emit("Searchped.\n", [])
                break

            page = pdf_document[page_number]
            text = page.get_text("text")

            for name in self.names:
                if re.search(rf'\b{name.lower()}\b', text.lower()):
                    if name.strip():
                        parts = text.lower().split(name.lower())
                        if len(parts) > 1:
                            next_word_parts = parts[1].split()
                            if next_word_parts:
                                next_word = next_word_parts[0].rstrip(',\'')
                                if len(re.sub(r'\W', next_word)) in {1, 2}:
                                    continue
                                if re.match(r'\w+\.\wnext_word):
                                    continue
                                if name.lower() in self.dictionary:
                                    continue
                                content_in_parentheses = re.search(r'\((.*?)\)', name)
                                if content_in_parentheses:
                                    content_word = content_in_parentheses.group(1).strip()
                                    if content_word.lower() in self.dictionary:
                                        continue
                                if next_word.isdigit():
                                    continue
                                if self.is_valid_second_word(next_word) and next_word.lower() not in self.dictionary:
                                    clean_next_word = self.clean_word(next_word)
                                    full_name = f"{name} {clean_next_word}"
                                    if full_name not in self.results:
                                        self.results[full_name] = []
                                    self.results[full_name].append(page_number + 1)
                                    # Emit the updated result
                                    self.update_results.emit(full_name, self.results[full_name])

            # Update progress
            progress = int((page_number + 1) / total_pages * 100)
            self.update_progress.emit(progress)

        pdf_document.close()
        self.update_results.emit("Searchleted.\n", [])

    def is_valid_second_word(self, word):
        invalid_words = {f"{i}," for i in range(1, 32)}
        invalid_special_chars = {'#', '&', '-', '?', '.'}
        cleaned_word = ''.join(char for char in word if char.isalnum() or char in invalid_special_chars)
        return (
            cleaned_word not in invalid_words
            and cleaned_word not in invalid_special_chars
            and len(cleaned_word) > 1
        )

    def clean_word(self, word):
        cleaned_word = ''.join(char for char in word if char.isalnum() or char in {'-', '_'})
        return cleaned_word.strip()


class PDFSearchApp(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("PDF Search App")
        self.setGeometry(100, 100, 600, 700)

        self.stop_search_flag = False
        self.found_results = {}  # Stores results as {name: [pages]}
        self.dictionary = set()

        self.init_ui()

    def init_ui(self):
        self.central_widget = QWidget()
        self.setCentralWidget(self.central_widget)

        self.layout = QVBoxLayout()

        # File pickers
        self.names_file_picker = self.create_file_picker("Names File:")
        self.pdf_file_picker = self.create_file_picker("PDF File:")
        self.dictionary_file_picker = self.create_file_picker("Dictionary File:")

        # Search and Stop buttons
        self.search_button = QPushButton("Search")
        self.search_button.clicked.connect(self.on_search)
        self.stop_button = QPushButton("Stop Search")
        self.stop_button.clicked.connect(self.on_stop_search)

        # Results text area
        self.results_text = QTextEdit()
        self.results_text.setReadOnly(True)

        # Search exact text
        self.search_exact_label = QLabel("Search Exact Text:")
        self.search_exact_input = QLineEdit()
        self.search_exact_button = QPushButton("Search Exact")
        self.search_exact_button.clicked.connect(self.on_search_exact)

        # Save results button
        self.save_button = QPushButton("Save Results")
        self.save_button.clicked.connect(self.on_save_results)

        # Sort buttons
        self.sort_first_last_button = QPushButton("Sort First Name Last Name")
        self.sort_first_last_button.clicked.connect(self.on_sort_first_last)
        self.sort_last_first_button = QPushButton("Sort Last Name, First Name")
        self.sort_last_first_button.clicked.connect(self.on_sort_last_first)

        # Progress bar
        self.progress_bar = QProgressBar()
        self.progress_bar.setValue(0)

        # Add widgets to layout
        self.layout.addWidget(self.names_file_picker)        
        self.layout.addWidget(self.dictionary_file_picker)
        self.layout.addWidget(self.pdf_file_picker)
        self.layout.addWidget(self.search_button)
        self.layout.addWidget(self.stop_button)
        self.layout.addWidget(self.results_text)
        self.layout.addWidget(self.search_exact_label)
        self.layout.addWidget(self.search_exact_input)
        self.layout.addWidget(self.search_exact_button)
        self.layout.addWidget(self.save_button)
        self.layout.addWidget(self.sort_first_last_button)
        self.layout.addWidget(self.sort_last_first_button)
        self.layout.addWidget(self.progress_bar)

        self.central_widget.setLayout(self.layout)

    def create_file_picker(self, label_text):
        layout = QHBoxLayout()
        label = QLabel(label_text)
        file_picker = QPushButton("Browse...")
        file_picker.clicked.connect(lambda: self.open_file_dialog(label_text))
        layout.addWidget(label)
        layout.addWidget(file_picker)
        widget = QWidget()
        widget.setLayout(layout)
        return widget

    def open_file_dialog(self, label_text):
        file_path, _ = QFileDialog.getOpenFileName(self, f"Select {label_text}", "", "Text Files (*.txt);;PDF Files (*.pdf)")
        if file_path:
            if "Names" in label_text:
                self.names_file_path = file_path
            elif "PDF" in label_text:
                self.pdf_file_path = file_path
            elif "Dictionary" in label_text:
                self.dictionary_file_path = file_path

    def on_search(self):
        self.stop_search_flag = False
        self.found_results.clear()
        self.results_text.clear()

        if not hasattr(self, 'pdf_file_path'):
            QMessageBox.critical(self, "Error", "Please select a PDF file.")
            return

        names = self.load_names(self.names_file_path)
        self.load_dictionary(self.dictionary_file_path)

        self.search_thread = SearchThread(self.pdf_file_path, names, self.dictionary, lambda: self.stop_search_flag)
        self.search_thread.update_progress.connect(self.progress_bar.setValue)
        self.search_thread.update_results.connect(self.update_results_text)
        self.search_thread.start()

    def on_stop_search(self):
        self.stop_search_flag = True

    def on_search_exact(self):
        self.stop_search_flag = False
        self.found_results.clear()
        self.results_text.clear()

        if not hasattr(self, 'pdf_file_path'):
            QMessageBox.critical(self, "Error", "Please select a PDF file.")
            return

        exact_text = self.search_exact_input.text().strip()
        if not exact_text:
            QMessageBox.critical(self, "Error", "Please enter exact text to search.")
            return

        self.search_thread = SearchThread(self.pdf_file_path, [exact_text], self.dictionary, lambda: self.stop_search_flag)
        self.search_thread.update_progress.connect(self.progress_bar.setValue)
        self.search_thread.update_results.connect(self.update_results_text)
        self.search_thread.start()

    def on_save_results(self):
        file_path, _ = QFileDialog.getSaveFileName(self, "Save Results", "", "Text Files (*.txt)")
        if file_path:
            self.save_results_to_file(file_path)
            QMessageBox.information(self, "Info", f"Results saved successfully to:\n{file_path}")

    def on_sort_first_last(self):
        self.sort_results("first_last")

    def on_sort_last_first(self):
        self.sort_results("last_first")

    def sort_results(self, sort_order):
        sorted_results = sorted(self.found_results.items(), key=lambda x: self.get_sort_key(x[0], sort_order))

        # Clear the results dictionary and update with the sorted names
        new_results = {}
        self.results_text.clear()
        
        for result_name, result_pages in sorted_results:
            if sort_order == "last_first":
                parts = result_name.split()
                if len(parts) > 1:
                    new_name = f"{parts[-1]} {parts[0]}"  # Rewriting the name as "Last First"
                else:
                    new_name = parts[0]  # If there's only one part, keep it unchanged
            else:
                new_name = result_name  # Keep original order

            new_results[new_name] = result_pages
            self.results_text.append(f"{new_name}: {', '.join(map(str, result_pages))}")

        # Replace found_results with updated names
        self.found_results = new_results


    def get_sort_key(self, name, sort_order):
        parts = name.split()
        if sort_order == "first_last":
            return ' '.join(parts)
        elif sort_order == "last_first":
            if len(parts) > 1:
                return f"{parts[-1]}, {' '.join(parts[:-1])}"
            else:
                return parts[0]
        return name

    def load_names(self, file_path):
        with open(file_path, 'r', encoding='utf-8') as file:
            return [name.strip() for name in file]

    def load_dictionary(self, file_path):
        with open(file_path, 'r', encoding='utf-8') as file:
            self.dictionary = {line.strip().split()[0].lower() for line in file if line.strip()}

    def save_results_to_file(self, file_path):
        sorted_results = sorted(self.found_results.items(), key=lambda x: x[0].lower())
        with open(file_path, "w", encoding="utf-8") as file:
            for name, pages in sorted_results:
                file.write(f"{name, '.join(map(str, pages))}\n")

    def update_results_text(self, name, pages):
        if name in ["Starting search...\n", "Search stopped.\n", "Search completed.\n"]:
            self.results_text.append(name)
            return

        # Ensure last name is capitalized
        parts = name.split()
        if len(parts) > 1:
            formatted_name = f"{parts[0]} {parts[1].capitalize()}"  # Capitalize last name
        else:
            formatted_name = name.capitalize()  # If single name, just capitalize it

        # Update found_results dictionary
        if formatted_name not in self.found_results:
            self.found_results[formatted_name] = []
        self.found_results[formatted_name] = pages

        # Clear and rebuild results text with updated capitalization
        self.results_text.clear()
        sorted_results = sorted(self.found_results.items(), key=lambda x: x[0].lower())
        for result_name, result_pages in sorted_results:
            self.results_text.append(f"{result_name}: {', '.join(map(str, result_pages))}")



if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = PDFSearchApp()
    window.show()
    sys.exit(app.exec_())import sys
from PyQt5.QtWidgets import (
    QApplication,
    QMainWindow,
    QWidget,
    QVBoxLayout,
    QHBoxLayout,
    QFileDialog,
    QPushButton,
    QTextEdit,
    QLineEdit,
    QLabel,
    QProgressBar,
    QMessageBox,
)
from PyQt5.QtCore import Qt, QThread, pyqtSignal
import fitz  # PyMuPDF
import re


class SearchThread(QThread):
    update_progress = pyqtSignal(int)  # Signal to update progress bar
    update_results = pyqtSignal(str, list)  # Signal to update results text (name, pages)

    def __init__(self, pdf_path, names, dictionary, stop_flag):
        super().__init__()
        self.pdf_path = pdf_path
        self.names = names
        self.dictionary = dictionary
        self.stop_flag = stop_flag
        self.results = {}  # Stores results as {name: [pages]}

    def run(self):
        self.update_results.emit("Startingch...\n", [])
        pdf_document = fitz.open(self.pdf_path)
        total_pages = pdf_document.page_count

        for page_number in range(total_pages):
            if self.stop_flag():
                self.update_results.emit("Searchped.\n", [])
                break

            page = pdf_document[page_number]
            text = page.get_text("text")

            for name in self.names:
                if re.search(rf'\b{name.lower()}\b', text.lower()):
                    if name.strip():
                        parts = text.lower().split(name.lower())
                        if len(parts) > 1:
                            next_word_parts = parts[1].split()
                            if next_word_parts:
                                next_word = next_word_parts[0].rstrip(',\'')
                                if len(re.sub(r'\W', next_word)) in {1, 2}:
                                    continue
                                if re.match(r'\w+\.\wnext_word):
                                    continue
                                if name.lower() in self.dictionary:
                                    continue
                                content_in_parentheses = re.search(r'\((.*?)\)', name)
                                if content_in_parentheses:
                                    content_word = content_in_parentheses.group(1).strip()
                                    if content_word.lower() in self.dictionary:
                                        continue
                                if next_word.isdigit():
                                    continue
                                if self.is_valid_second_word(next_word) and next_word.lower() not in self.dictionary:
                                    clean_next_word = self.clean_word(next_word)
                                    full_name = f"{name} {clean_next_word}"
                                    if full_name not in self.results:
                                        self.results[full_name] = []
                                    self.results[full_name].append(page_number + 1)
                                    # Emit the updated result
                                    self.update_results.emit(full_name, self.results[full_name])

            # Update progress
            progress = int((page_number + 1) / total_pages * 100)
            self.update_progress.emit(progress)

        pdf_document.close()
        self.update_results.emit("Searchleted.\n", [])

    def is_valid_second_word(self, word):
        invalid_words = {f"{i}," for i in range(1, 32)}
        invalid_special_chars = {'#', '&', '-', '?', '.'}
        cleaned_word = ''.join(char for char in word if char.isalnum() or char in invalid_special_chars)
        return (
            cleaned_word not in invalid_words
            and cleaned_word not in invalid_special_chars
            and len(cleaned_word) > 1
        )

    def clean_word(self, word):
        cleaned_word = ''.join(char for char in word if char.isalnum() or char in {'-', '_'})
        return cleaned_word.strip()


class PDFSearchApp(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("PDF Search App")
        self.setGeometry(100, 100, 600, 700)

        self.stop_search_flag = False
        self.found_results = {}  # Stores results as {name: [pages]}
        self.dictionary = set()

        self.init_ui()

    def init_ui(self):
        self.central_widget = QWidget()
        self.setCentralWidget(self.central_widget)

        self.layout = QVBoxLayout()

        # File pickers
        self.names_file_picker = self.create_file_picker("Names File:")
        self.pdf_file_picker = self.create_file_picker("PDF File:")
        self.dictionary_file_picker = self.create_file_picker("Dictionary File:")

        # Search and Stop buttons
        self.search_button = QPushButton("Search")
        self.search_button.clicked.connect(self.on_search)
        self.stop_button = QPushButton("Stop Search")
        self.stop_button.clicked.connect(self.on_stop_search)

        # Results text area
        self.results_text = QTextEdit()
        self.results_text.setReadOnly(True)

        # Search exact text
        self.search_exact_label = QLabel("Search Exact Text:")
        self.search_exact_input = QLineEdit()
        self.search_exact_button = QPushButton("Search Exact")
        self.search_exact_button.clicked.connect(self.on_search_exact)

        # Save results button
        self.save_button = QPushButton("Save Results")
        self.save_button.clicked.connect(self.on_save_results)

        # Sort buttons
        self.sort_first_last_button = QPushButton("Sort First Name Last Name")
        self.sort_first_last_button.clicked.connect(self.on_sort_first_last)
        self.sort_last_first_button = QPushButton("Sort Last Name, First Name")
        self.sort_last_first_button.clicked.connect(self.on_sort_last_first)

        # Progress bar
        self.progress_bar = QProgressBar()
        self.progress_bar.setValue(0)

        # Add widgets to layout
        self.layout.addWidget(self.names_file_picker)
        self.layout.addWidget(self.pdf_file_picker)
        self.layout.addWidget(self.dictionary_file_picker)
        self.layout.addWidget(self.search_button)
        self.layout.addWidget(self.stop_button)
        self.layout.addWidget(self.results_text)
        self.layout.addWidget(self.search_exact_label)
        self.layout.addWidget(self.search_exact_input)
        self.layout.addWidget(self.search_exact_button)
        self.layout.addWidget(self.save_button)
        self.layout.addWidget(self.sort_first_last_button)
        self.layout.addWidget(self.sort_last_first_button)
        self.layout.addWidget(self.progress_bar)

        self.central_widget.setLayout(self.layout)

    def create_file_picker(self, label_text):
        layout = QHBoxLayout()
        label = QLabel(label_text)
        file_picker = QPushButton("Browse...")
        file_picker.clicked.connect(lambda: self.open_file_dialog(label_text))
        layout.addWidget(label)
        layout.addWidget(file_picker)
        widget = QWidget()
        widget.setLayout(layout)
        return widget

    def open_file_dialog(self, label_text):
        file_path, _ = QFileDialog.getOpenFileName(self, f"Select {label_text}", "", "Text Files (*.txt);;PDF Files (*.pdf)")
        if file_path:
            if "Names" in label_text:
                self.names_file_path = file_path
            elif "PDF" in label_text:
                self.pdf_file_path = file_path
            elif "Dictionary" in label_text:
                self.dictionary_file_path = file_path

    def on_search(self):
        self.stop_search_flag = False
        self.found_results.clear()
        self.results_text.clear()

        if not hasattr(self, 'pdf_file_path'):
            QMessageBox.critical(self, "Error", "Please select a PDF file.")
            return

        names = self.load_names(self.names_file_path)
        self.load_dictionary(self.dictionary_file_path)

        self.search_thread = SearchThread(self.pdf_file_path, names, self.dictionary, lambda: self.stop_search_flag)
        self.search_thread.update_progress.connect(self.progress_bar.setValue)
        self.search_thread.update_results.connect(self.update_results_text)
        self.search_thread.start()

    def on_stop_search(self):
        self.stop_search_flag = True

    def on_search_exact(self):
        self.stop_search_flag = False
        self.found_results.clear()
        self.results_text.clear()

        if not hasattr(self, 'pdf_file_path'):
            QMessageBox.critical(self, "Error", "Please select a PDF file.")
            return

        exact_text = self.search_exact_input.text().strip()
        if not exact_text:
            QMessageBox.critical(self, "Error", "Please enter exact text to search.")
            return

        self.search_thread = SearchThread(self.pdf_file_path, [exact_text], self.dictionary, lambda: self.stop_search_flag)
        self.search_thread.update_progress.connect(self.progress_bar.setValue)
        self.search_thread.update_results.connect(self.update_results_text)
        self.search_thread.start()

    def on_save_results(self):
        file_path, _ = QFileDialog.getSaveFileName(self, "Save Results", "", "Text Files (*.txt)")
        if file_path:
            self.save_results_to_file(file_path)
            QMessageBox.information(self, "Info", f"Results saved successfully to:\n{file_path}")

    def on_sort_first_last(self):
        self.sort_results("first_last")

    def on_sort_last_first(self):
        self.sort_results("last_first")

    def sort_results(self, sort_order):
        sorted_results = sorted(self.found_results.items(), key=lambda x: self.get_sort_key(x[0], sort_order))

        # Clear the results dictionary and update with the sorted names
        new_results = {}
        self.results_text.clear()
        
        for result_name, result_pages in sorted_results:
            if sort_order == "last_first":
                parts = result_name.split()
                if len(parts) > 1:
                    new_name = f"{parts[-1]} {parts[0]}"  # Rewriting the name as "Last First"
                else:
                    new_name = parts[0]  # If there's only one part, keep it unchanged
            else:
                new_name = result_name  # Keep original order

            new_results[new_name] = result_pages
            self.results_text.append(f"{new_name}: {', '.join(map(str, result_pages))}")

        # Replace found_results with updated names
        self.found_results = new_results


    def get_sort_key(self, name, sort_order):
        parts = name.split()
        if sort_order == "first_last":
            return ' '.join(parts)
        elif sort_order == "last_first":
            if len(parts) > 1:
                return f"{parts[-1]}, {' '.join(parts[:-1])}"
            else:
                return parts[0]
        return name

    def load_names(self, file_path):
        with open(file_path, 'r', encoding='utf-8') as file:
            return [name.strip() for name in file]

    def load_dictionary(self, file_path):
        with open(file_path, 'r', encoding='utf-8') as file:
            self.dictionary = {line.strip().split()[0].lower() for line in file if line.strip()}

    def save_results_to_file(self, file_path):
        sorted_results = sorted(self.found_results.items(), key=lambda x: x[0].lower())
        with open(file_path, "w", encoding="utf-8") as file:
            for name, pages in sorted_results:
                file.write(f"{name, '.join(map(str, pages))}\n")

    def update_results_text(self, name, pages):
        if name in ["Starting search...\n", "Search stopped.\n", "Search completed.\n"]:
            self.results_text.append(name)
            return

        # Ensure last name is capitalized
        parts = name.split()
        if len(parts) > 1:
            formatted_name = f"{parts[0]} {parts[1].capitalize()}"  # Capitalize last name
        else:
            formatted_name = name.capitalize()  # If single name, just capitalize it

        # Update found_results dictionary
        if formatted_name not in self.found_results:
            self.found_results[formatted_name] = []
        self.found_results[formatted_name] = pages

        # Clear and rebuild results text with updated capitalization
        self.results_text.clear()
        sorted_results = sorted(self.found_results.items(), key=lambda x: x[0].lower())
        for result_name, result_pages in sorted_results:
            self.results_text.append(f"{result_name}: {', '.join(map(str, result_pages))}")



if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = PDFSearchApp()
    window.show()
    sys.exit(app.exec_())

 

 

Link to comment
https://www.neowin.net/forum/topic/1437118-pdf-name-search-python-app/
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
  • Posts

    • TP-Link Tri-Band AXE5400 Wi-Fi 6E Gaming Router Archer GXE75 is just $135 by Sayan Sen If you’re juggling gaming consoles, 4K streams and a growing roster of smart devices, the TP-Link Archer GXE75 Tri-Band AXE5400 Wi-Fi 6E Gaming Router is a good option to look at right now, considering the device is currently on sale at just $135 (purchase link under the specs table below). The Archer GXE75 promises up to 5.4 Gbps of throughput across three bands: 574 Mbps on 2.4 GHz, 2402 Mbps on 5 GHz and 2402 Mbps on 6 GHz (HE160 channels enabled). A 2.5 Gbps WAN/LAN port pairs with three 1 Gbps LAN ports and a USB 3.0 port for fast file sharing or printer access (purchase link down below). The quad-core CPU and 512 MB of RAM should help to handle multiple streams without bogging down, while MU-MIMO, OFDMA and 1024-QAM improve efficiency when several devices connect simultaneously. The router’s built-in Game Accelerator engine is said to automatically prioritize gaming packets (both wired and wireless), and TP-Link’s GPN (Gamers Private Network) acceleration can reduce packet loss for supported titles. HomeShield security brings firewall protection, device quarantining and parental controls. The technical specifications of the router are given below: Specification Details Wireless Standards IEEE 802.11ax 6 GHz; IEEE 802.11ax/ac/n/a 5 GHz; IEEE 802.11ax/n/g/b 2.4 GHz Wi-Fi Speeds 6 GHz: 2402 Mbps (802.11ax); 5 GHz: 2402 Mbps (802.11ax); 2.4 GHz: 574 Mbps (802.11ax) Spatial Streams 6 streams (tri-band OFDMA/MU-MIMO) Processor 1.7 GHz 64-bit Quad-Core CPU Memory 512 MB high-speed RAM Ethernet Ports 1× 2.5 Gbps WAN/LAN; 1× 1 Gbps WAN/LAN; 3× 1 Gbps LAN USB 1× USB 3.0 SuperSpeed port (up to 10× faster than USB 2.0) Antennas 4× high-performance external antennas with Beamforming Buttons Power On/Off; Reset; WPS/Wi-Fi; LED On/Off Working Modes Router Mode; Access Point Mode Security WPA, WPA2, WPA3, WPA/WPA2-Enterprise; SPI firewall; Access Control; IP/MAC binding; Application-layer gateway; HomeShield security suite VPN OpenVPN, PPTP, L2TP (server & client); WireGuard (server & client) Software & Services IPv4/IPv6; TP-Link HomeShield; EasyMesh; Parental controls; QoS by device; WAN types (Dynamic IP, Static IP, PPPoE, PPTP, L2TP); DDNS (TP-Link, NO-IP, DynDNS); auto firmware updates Gaming Features Game QoS boost; gaming-port priority; Gamers Private Network acceleration; game-port forwarding; real-time game panel with stats & RGB control Operating Environment 0 °C – 40 °C; 10 %– 90 % non-condensing humidity Get the TP-Link Tri-Band AXE5400 Wi-Fi 6E Gaming Router Archer GXE75 at the link below: TP-Link Tri-Band AXE5400 Wi-Fi 6E Gaming Router Archer GXE75 | EasyMesh, HomeShield: $159.99 + $25 off with coupon => $134.99 (Sold and Shipped by Amazon US This Amazon deal is US-specific and not available in other regions unless specified. If you don't like it or want to look at more options, check out the Amazon US deals page here. Get Prime (SNAP), Prime Video, Audible Plus or Kindle / Music Unlimited. Free for 30 days. As an Amazon Associate, we earn from qualifying purchases.
    • Oh no... here we go again. Tha same sh*t that happaned to Mail & Calendar...
    • I too have worked with pro gear for decades and so I found the interface to be unintuitive from both the pro and casual side (a rare "accomplishment" by what are obviously otherwise skilled coders, hehe). I eventually got it to work, thanks for offering, but I found other compatibility issues with my own use case, so I just dropped it entirely. Right now, I just use an analog line out/line in approach which works as expected across all usage scenarios. With both machines on the same power block/outlet, I'm not getting any analog hum or hiss. My next step will be to try the updated Multiplicity 4 when a bug, addressing this very issue unfortunately, is resolved. Knowing Stardock, that could be tomorrow or five years from now, so I check back every few months to see it it's fixed and I want to upgrade. Again, thanks for offering to help.
    • We recognize that performance can use some improvements, and we continue to work on improving it. However, it's worth noting that massive performance improvements don't happen overnight, they take a lot of work and effort, and in most cases, the improvements are more noticeable when you compare across several updates. That said, Files is open-source and everyone is invited to help with these efforts 🙂
  • Recent Achievements

    • Rookie
      Snake Doc went up a rank
      Rookie
    • First Post
      nobody9 earned a badge
      First Post
    • One Month Later
      Ricky Chan earned a badge
      One Month Later
    • First Post
      leoniDAM earned a badge
      First Post
    • Reacting Well
      Ian_ earned a badge
      Reacting Well
  • Popular Contributors

    1. 1
      +primortal
      494
    2. 2
      Michael Scrip
      203
    3. 3
      ATLien_0
      197
    4. 4
      Xenon
      137
    5. 5
      +FloatingFatMan
      116
  • Tell a friend

    Love Neowin? Tell a friend!