News:

Please request registration email again and then check your "Spam" folder

Former www.henthighschool.com

Girl Pack discussion thread

Started by Leortha, May 09, 2022, 09:45 PM

Previous topic - Next topic

Jman

#315
Quote from: InfiniteTomorrow on Oct 20, 2025, 04:58 PMDid anyone ever figure out auto tagging?
Sorta.
And with strange aeons even death may die...

InfiniteTomorrow

#316
messed around with auto tagging got it working. wrote some programs too uhh can i share files on here?




My tools, install python slap these in a .py and then install pip. open cmd (win key then type cmd), pip install any of the imports at the top of the program eg pip install PIL:

Merge Tool (multiple packs of the same name, so you dont have to rename all the (0000) to (0001) by hand:

import sys
import os
import re
import shutil
from PyQt6.QtWidgets import (
    QApplication, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit,
    QPushButton, QTextEdit, QFileDialog
)
from PyQt6.QtCore import Qt, QSettings


def find_next_available_name(dest_folder, base_name, ext):
    match = re.match(r"^(.*?)(?:\s*\((\d+)\))?$", base_name)
    root_name = match.group(1).strip() if match else base_name

    existing_nums = []
    for f in os.listdir(dest_folder):
        n, e = os.path.splitext(f)
        if e.lower() != ext.lower():
            continue
        m = re.match(rf"^{re.escape(root_name)}\s*\((\d+)\)$", n)
        if m:
            existing_nums.append(int(m.group(1)))

    next_num = max(existing_nums, default=-1) + 1
    return f"{root_name} ({next_num}){ext}"


def move_and_rename(src_folder, dest_folder, output_callback):
    os.makedirs(dest_folder, exist_ok=True)

    for filename in os.listdir(src_folder):
        src_path = os.path.join(src_folder, filename)
        if not os.path.isfile(src_path):
            continue

        name, ext = os.path.splitext(filename)
        dest_path = os.path.join(dest_folder, filename)

        if not os.path.exists(dest_path):
            shutil.move(src_path, dest_path)
            output_callback(f"Moved: {filename}")
        else:
            new_filename = find_next_available_name(dest_folder, name, ext)
            new_path = os.path.join(dest_folder, new_filename)
            shutil.move(src_path, new_path)
            output_callback(f"Renamed and moved: {filename} → {new_filename}")

    output_callback("\n✅ Merge complete! (files renamed if conflicts found)")


class MergeApp(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Image Folder Merger (Crash Guard)")
        self.resize(600, 400)

        self.settings = QSettings("NoodleSoft", "ImageFolderMerger")

        layout = QVBoxLayout()

        # Source folder input
        src_layout = QHBoxLayout()
        self.src_input = QLineEdit()
        self.src_browse = QPushButton("Browse")
        self.src_browse.clicked.connect(self.browse_source)
        src_layout.addWidget(QLabel("Source Folder:"))
        src_layout.addWidget(self.src_input)
        src_layout.addWidget(self.src_browse)
        layout.addLayout(src_layout)

        # Destination folder input
        dest_layout = QHBoxLayout()
        self.dest_input = QLineEdit()
        self.dest_browse = QPushButton("Browse")
        self.dest_browse.clicked.connect(self.browse_destination)
        dest_layout.addWidget(QLabel("Destination Folder:"))
        dest_layout.addWidget(self.dest_input)
        dest_layout.addWidget(self.dest_browse)
        layout.addLayout(dest_layout)

        # Output box
        self.output_box = QTextEdit()
        self.output_box.setReadOnly(True)
        layout.addWidget(QLabel("Output:"))
        layout.addWidget(self.output_box)

        # Start button
        self.start_btn = QPushButton("Start Merge")
        self.start_btn.clicked.connect(self.start_merge)
        layout.addWidget(self.start_btn)

        self.setLayout(layout)

        # Load last paths
        self.load_settings()

    # -------- persistence ----------
    def load_settings(self):
        src = self.settings.value("last_source", "")
        dest = self.settings.value("last_dest", "")
        if src:
            self.src_input.setText(src)
        if dest:
            self.dest_input.setText(dest)

    def save_settings(self):
        self.settings.setValue("last_source", self.src_input.text().strip())
        self.settings.setValue("last_dest", self.dest_input.text().strip())

    # -------- browsing -------------
    def browse_source(self):
        folder = QFileDialog.getExistingDirectory(self, "Select Source Folder")
        if folder:
            self.src_input.setText(folder)
            self.save_settings()

    def browse_destination(self):
        folder = QFileDialog.getExistingDirectory(self, "Select Destination Folder")
        if folder:
            self.dest_input.setText(folder)
            self.save_settings()

    # -------- logging & merge -------
    def log(self, message):
        self.output_box.append(message)
        self.output_box.verticalScrollBar().setValue(
            self.output_box.verticalScrollBar().maximum()
        )

    def start_merge(self):
        src = self.src_input.text().strip()
        dest = self.dest_input.text().strip()

        if not os.path.isdir(src):
            self.log("❌ Invalid source folder.")
            return
        if not os.path.isdir(dest):
            os.makedirs(dest, exist_ok=True)

        self.save_settings()
        self.log(f"Starting merge from:\n{src}\n→ {dest}\n")
        move_and_rename(src, dest, self.log)

    def closeEvent(self, event):
        self.save_settings()
        event.accept()


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MergeApp()
    window.show()
    sys.exit(app.exec())


Duplicate remover (removes pixel perfect duplicates in a directory):

import sys, os, hashlib
from PIL import Image
from PyQt6.QtWidgets import (
    QApplication, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton,
    QFileDialog, QTextEdit, QMessageBox, QProgressBar
)
from PyQt6.QtGui import QPixmap
from PyQt6.QtCore import Qt, QThread, pyqtSignal

def file_hash(image_path):
    """Return a unique hash of the image's pixel data."""
    try:
        with Image.open(image_path) as img:
            img = img.convert("RGB")
            data = img.tobytes()
            return hashlib.md5(data).hexdigest()
    except Exception:
        return None


# ---------------- Thread for scanning ----------------
class ScanThread(QThread):
    progress = pyqtSignal(int, int)  # current, total
    finished = pyqtSignal(list)

    def __init__(self, folder):
        super().__init__()
        self.folder = folder

    def run(self):
        seen, duplicates = {}, []
        files = []
        for root, _, fs in os.walk(self.folder):
            for f in fs:
                if f.lower().endswith(('.png', '.jpg', '.jpeg', '.bmp', '.gif', '.webp')):
                    files.append(os.path.join(root, f))
        total = len(files)
        for i, path in enumerate(files, start=1):
            h = file_hash(path)
            if h:
                if h in seen:
                    duplicates.append((seen[h], path))
                else:
                    seen[h] = path
            self.progress.emit(i, total)
        self.finished.emit(duplicates)


# ---------------- Main window ----------------
class ConfirmOnceDuplicateRemover(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Duplicate Image Remover (with Progress)")
        self.resize(900, 600)

        layout = QVBoxLayout(self)

        # Folder selection
        top_layout = QHBoxLayout()
        self.btn_select = QPushButton("Select Folder")
        self.btn_select.clicked.connect(self.select_folder)
        top_layout.addWidget(self.btn_select)
        layout.addLayout(top_layout)

        # Progress bar
        self.progress_label = QLabel("Progress: 0 / 0")
        self.progress_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        self.progress_bar = QProgressBar()
        self.progress_bar.setValue(0)
        layout.addWidget(self.progress_label)
        layout.addWidget(self.progress_bar)

        # Image preview
        self.img_layout = QHBoxLayout()
        self.label_left = QLabel("Original")
        self.label_left.setAlignment(Qt.AlignmentFlag.AlignCenter)
        self.label_right = QLabel("Duplicate")
        self.label_right.setAlignment(Qt.AlignmentFlag.AlignCenter)
        self.img_layout.addWidget(self.label_left)
        self.img_layout.addWidget(self.label_right)
        layout.addLayout(self.img_layout)

        # Control buttons
        control_layout = QHBoxLayout()
        self.btn_confirm = QPushButton("✅ Confirm & Remove All Duplicates")
        self.btn_confirm.clicked.connect(self.confirm_and_remove)
        self.btn_skip = QPushButton("❌ Cancel")
        self.btn_skip.clicked.connect(self.close)
        control_layout.addWidget(self.btn_confirm)
        control_layout.addWidget(self.btn_skip)
        layout.addLayout(control_layout)

        # Log box
        self.output = QTextEdit()
        self.output.setReadOnly(True)
        layout.addWidget(self.output)

        self.folder = None
        self.duplicates = []
        self.scan_thread = None

    def log(self, msg):
        self.output.append(msg)
        self.output.verticalScrollBar().setValue(self.output.verticalScrollBar().maximum())

    def select_folder(self):
        folder = QFileDialog.getExistingDirectory(self, "Select Folder")
        if not folder:
            return
        self.folder = folder
        self.log(f"Scanning {folder} for duplicates...")
        self.progress_label.setText("Scanning...")
        self.progress_bar.setValue(0)

        self.scan_thread = ScanThread(folder)
        self.scan_thread.progress.connect(self.update_progress)
        self.scan_thread.finished.connect(self.scan_complete)
        self.scan_thread.start()

    def update_progress(self, current, total):
        percent = int((current / total) * 100)
        self.progress_bar.setValue(percent)
        self.progress_label.setText(f"Progress: {current} / {total}")

    def scan_complete(self, duplicates):
        self.duplicates = duplicates
        if not duplicates:
            QMessageBox.information(self, "No Duplicates", "No pixel-identical duplicates found.")
            self.progress_label.setText("Done: No duplicates found.")
            return

        self.log(f"Found {len(self.duplicates)} duplicate pairs.")
        self.progress_label.setText("Scan complete.")
        self.progress_bar.setValue(100)
        self.show_first_pair()

    def show_first_pair(self):
        if not self.duplicates:
            return
        orig, dup = self.duplicates[0]
        self.log(f"Previewing first duplicate pair:\n{os.path.basename(orig)} ↔ {os.path.basename(dup)}")

        pix1 = QPixmap(orig).scaled(400, 400, Qt.AspectRatioMode.KeepAspectRatio)
        pix2 = QPixmap(dup).scaled(400, 400, Qt.AspectRatioMode.KeepAspectRatio)
        self.label_left.setPixmap(pix1)
        self.label_right.setPixmap(pix2)

    def confirm_and_remove(self):
        if not self.duplicates:
            QMessageBox.information(self, "No Duplicates", "Nothing to remove.")
            return

        confirm = QMessageBox.question(
            self, "Confirm",
            f"Delete {len(self.duplicates)} duplicate images?\nThis cannot be undone.",
            QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
        )

        if confirm != QMessageBox.StandardButton.Yes:
            return

        deleted = 0
        total = len(self.duplicates)
        for i, (_, dup) in enumerate(self.duplicates, start=1):
            try:
                os.remove(dup)
                deleted += 1
                self.log(f"🗑� Deleted {os.path.basename(dup)}")
            except Exception as e:
                self.log(f"⚠️ Failed to delete {dup}: {e}")
            self.progress_bar.setValue(int((i / total) * 100))

        self.log(f"\n✅ Done! {deleted} duplicates removed.")
        QMessageBox.information(self, "Complete", f"{deleted} duplicates removed successfully.")
        self.progress_label.setText("Removal complete.")
        self.progress_bar.setValue(100)


if __name__ == "__main__":
    app = QApplication(sys.argv)
    w = ConfirmOnceDuplicateRemover()
    w.show()
    sys.exit(app.exec())




Jman

Files can't be attached to the forum due to bandwidth limitations, but you can upload them somewhere else.

That having been said:
  • I kinda doubt you 'wrote' that code,
  • I can't really see much point in renaming, since most any file manager should allow you to auto-rename and keep files of the same name.
There are also other tools to remove duplicate images available since forever, although the two functionalities bundled together might be of some use.
And with strange aeons even death may die...

InfiniteTomorrow

mm..yes....that being quite apparent, perhaps someone will find it useful like i did!