Compare commits
No commits in common. "main" and "1.1" have entirely different histories.
48
README.md
48
README.md
@ -1,33 +1,31 @@
|
|||||||
# File Renamer CLI
|
# Suffix Renamer
|
||||||
|
|
||||||

|
Ein einfaches, rekursives CLI-Tool zum automatisierten Umbenennen von Dateien mit einem Suffix.
|
||||||

|
|
||||||

|
|
||||||
|
|
||||||
Ein rekursives CLI-Tool zum Umbenennen von Dateien mit Suffix und automatischer TMDb-Titelerkennung. Ideal für die Organisation von Film- und Serien-Dateien.
|
> Autor: M_Viper
|
||||||
|
|
||||||
## Features
|
---
|
||||||
|
|
||||||
- **Rekursives Umbenennen**: Verarbeitet Dateien in Ordnern und Unterordnern.
|
## 🔍 Überblick
|
||||||
- **TMDb-Integration**: Erkennt Film- und Serientitel automatisch über die TMDb-API.
|
|
||||||
- **Konfigurierbare Suffixe**: Fügt benutzerdefinierte Suffixe zu Dateinamen hinzu.
|
|
||||||
- **Flexible Modi**:
|
|
||||||
- Vorschau-Modus: Zeigt Änderungen ohne sie anzuwenden.
|
|
||||||
- Vorschau mit Bestätigung: Änderungen werden nach Bestätigung durchgeführt.
|
|
||||||
- Direkter Modus: Sofortiges Umbenennen ohne Vorschau.
|
|
||||||
- **Caching**: Speichert TMDb-Abfragen für schnellere Verarbeitung.
|
|
||||||
- **Konfigurationsmanagement**: Speichert Einstellungen in einer JSON-Datei.
|
|
||||||
- **Versionsprüfung**: Prüft automatisch auf Updates über Gitea.
|
|
||||||
|
|
||||||
## Voraussetzungen
|
Dieses Tool durchsucht einen Ordner (rekursiv), fragt bei der ersten Ausführung den gewünschten Ordnerpfad und ein Suffix ab, speichert diese Konfiguration, und hängt das Suffix an alle Dateinamen an (z. B. `film.mp4` → `film @Name.mp4`).
|
||||||
|
|
||||||
- **Node.js**: Version >= 14.0.0
|
---
|
||||||
- **TMDb API-Token**: Erforderlich für die Titelabfrage. [Hier anmelden](https://www.themoviedb.org/documentation/api).
|
|
||||||
- Optional: `dotenv` für die Verwaltung von Umgebungsvariablen.
|
|
||||||
|
|
||||||
## Installation
|
## 🖥️ Funktionen
|
||||||
|
|
||||||
1. **Repository klonen**:
|
- ✅ Rekursives Durchsuchen von Ordnern
|
||||||
```bash
|
- ✅ Automatische oder benutzerdefinierte Pfadauswahl
|
||||||
git clone https://git.viper.ipv64.net/M_Viper/file-renamer-cli.git
|
- ✅ Benutzerdefiniertes Suffix
|
||||||
cd file-renamer-cli
|
- ✅ Konfigurationsspeicherung in `Documents/config.json`
|
||||||
|
- ✅ Überspringt bereits umbenannte oder geschützte Dateien
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 Verwendung
|
||||||
|
|
||||||
|
### 1. Repository klonen
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/M-Viper/suffix-renamer.git
|
||||||
|
cd suffix-renamer
|
||||||
|
455
index.js
455
index.js
@ -1,42 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
* Projekt: File Renamer CLI
|
* Projekt: File Renamer CLI
|
||||||
* Version: 1.2
|
* Beschreibung: Ein rekursives CLI-Tool zum Umbenennen von Dateien mit Suffix.
|
||||||
* Beschreibung: Ein rekursives CLI-Tool zum Umbenennen von Dateien mit Suffix und TMDb-Titelerkennung.
|
* Autor: M_Viper
|
||||||
* Autor: M_Viper
|
* Lizenz: MIT
|
||||||
* Lizenz: MIT
|
* GitHub: https://git.viper.ipv64.net/M_Viper/file-renamer-cli
|
||||||
* Gitea: https://git.viper.ipv64.net/M_Viper/file-renamer-cli
|
* Webseite: https://m-viper.de
|
||||||
* Webseite: https://m-viper.de
|
*/
|
||||||
*/
|
|
||||||
|
|
||||||
const fs = require('fs').promises;
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const os = require('os');
|
const os = require('os');
|
||||||
const readline = require('readline');
|
const readline = require('readline');
|
||||||
const https = require('https');
|
|
||||||
|
|
||||||
// Optional: dotenv für Umgebungsvariablen
|
const configPath = path.join(os.homedir(), 'Documents', 'config.json');
|
||||||
let dotenv;
|
|
||||||
try {
|
|
||||||
dotenv = require('dotenv');
|
|
||||||
dotenv.config();
|
|
||||||
} catch {
|
|
||||||
console.warn('dotenv nicht gefunden, verwende Standardwerte oder direkte Eingabe.');
|
|
||||||
}
|
|
||||||
|
|
||||||
const localVersion = '1.2';
|
// ================== Banner ==================
|
||||||
|
|
||||||
// TMDb API Bearer Token (aus .env oder direkt hier einfügen)
|
|
||||||
const TMDB_API_BEARER_TOKEN = process.env.TMDB_API_BEARER_TOKEN || 'YOUR_TOKEN_HERE';
|
|
||||||
|
|
||||||
// Konfigurationspfad relativ zur EXE oder im Benutzerverzeichnis
|
|
||||||
const configPath = process.env.NODE_ENV === 'production'
|
|
||||||
? path.join(path.dirname(process.execPath), 'config.json')
|
|
||||||
: path.join(os.homedir(), 'Documents', 'config.json');
|
|
||||||
|
|
||||||
const tmdbCache = new Map(); // Cache für TMDb-Abfragen
|
|
||||||
let processedFiles = 0;
|
|
||||||
|
|
||||||
// ================== Hilfsfunktionen ==================
|
|
||||||
function greenMessage(text) {
|
function greenMessage(text) {
|
||||||
console.log('\x1b[32m%s\x1b[0m', text);
|
console.log('\x1b[32m%s\x1b[0m', text);
|
||||||
}
|
}
|
||||||
@ -49,7 +27,8 @@ function centerText(text, width) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function showAsciiLogo() {
|
function showAsciiLogo() {
|
||||||
const width = process.stdout.columns || 80;
|
const width = process.stdout.columns || 80; // Terminalbreite, fallback 80
|
||||||
|
|
||||||
const logoLines = [
|
const logoLines = [
|
||||||
'███████╗██╗██╗ ███████╗███╗ ██╗ █████╗ ███╗ ███╗███████╗ ██████╗██╗ ██╗',
|
'███████╗██╗██╗ ███████╗███╗ ██╗ █████╗ ███╗ ███╗███████╗ ██████╗██╗ ██╗',
|
||||||
'██╔════╝██║██║ ██╔════╝████╗ ██║██╔══██╗████╗ ████║██╔════╝ ██╔════╝██║ ██║',
|
'██╔════╝██║██║ ██╔════╝████╗ ██║██╔══██╗████╗ ████║██╔════╝ ██╔════╝██║ ██║',
|
||||||
@ -59,93 +38,57 @@ function showAsciiLogo() {
|
|||||||
'╚═╝ ╚═╝╚══════╝╚══════╝╚═╝ ╚═══╝╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝ ╚═════╝╚══════╝╚═╝',
|
'╚═╝ ╚═╝╚══════╝╚══════╝╚═╝ ╚═══╝╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝ ╚═════╝╚══════╝╚═╝',
|
||||||
];
|
];
|
||||||
|
|
||||||
console.log('\x1b[32m');
|
console.log('\x1b[32m'); // grün starten
|
||||||
for (const line of logoLines) {
|
for (const line of logoLines) {
|
||||||
console.log(centerText(line, width));
|
console.log(centerText(line, width));
|
||||||
}
|
}
|
||||||
console.log('\x1b[0m');
|
console.log('\x1b[0m'); // Farbe zurücksetzen
|
||||||
console.log('');
|
console.log('');
|
||||||
}
|
}
|
||||||
|
|
||||||
function showBanner() {
|
function showBanner() {
|
||||||
console.clear();
|
console.clear();
|
||||||
showAsciiLogo();
|
showAsciiLogo();
|
||||||
const width = process.stdout.columns || 60;
|
|
||||||
|
const width = process.stdout.columns || 60; // Breite des Terminals, fallback 60
|
||||||
|
|
||||||
const bannerLines = [
|
const bannerLines = [
|
||||||
'Version 1.2',
|
'Version 1.0',
|
||||||
'Script by',
|
'Script by',
|
||||||
'@M_Viper',
|
'@M_Viper',
|
||||||
'__________________________',
|
'__________________________',
|
||||||
'',
|
'',
|
||||||
'Gitea: https://git.viper.ipv64.net/M_Viper/file-renamer-cli',
|
'Git: https://git.viper.ipv64.net/M_Viper/file-renamer-cli',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Obere Rahmenlinie
|
||||||
greenMessage('╔' + '═'.repeat(width - 2) + '╗');
|
greenMessage('╔' + '═'.repeat(width - 2) + '╗');
|
||||||
|
|
||||||
|
// Bannerzeilen mit Rahmen und Zentrierung
|
||||||
for (const line of bannerLines) {
|
for (const line of bannerLines) {
|
||||||
const centered = centerText(line, width - 4);
|
const centered = centerText(line, width - 4); // 4 wegen Rahmen links und rechts + Leerzeichen
|
||||||
greenMessage('║ ' + centered + ' ║');
|
greenMessage('║ ' + centered + ' ║');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Untere Rahmenlinie
|
||||||
greenMessage('╚' + '═'.repeat(width - 2) + '╝');
|
greenMessage('╚' + '═'.repeat(width - 2) + '╝');
|
||||||
greenMessage('');
|
greenMessage('');
|
||||||
}
|
}
|
||||||
|
// ============================================
|
||||||
|
|
||||||
// ================== Versionsprüfung (Gitea) ==================
|
function saveConfig(config) {
|
||||||
async function checkForUpdates() {
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8');
|
||||||
return new Promise((resolve) => {
|
|
||||||
const options = {
|
|
||||||
hostname: 'git.viper.ipv64.net',
|
|
||||||
path: '/api/v1/repos/M_Viper/file-renamer-cli/releases',
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
'User-Agent': 'File-Renamer-CLI',
|
|
||||||
'Accept': 'application/json',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const req = https.request(options, (res) => {
|
|
||||||
let data = '';
|
|
||||||
res.on('data', (chunk) => (data += chunk));
|
|
||||||
res.on('end', () => {
|
|
||||||
try {
|
|
||||||
const releases = JSON.parse(data);
|
|
||||||
if (!Array.isArray(releases) || releases.length === 0) {
|
|
||||||
return resolve(null);
|
|
||||||
}
|
|
||||||
const latestVersion = releases[0].tag_name.replace(/^v/i, '');
|
|
||||||
const cleanLocal = localVersion.replace(/^v/i, '');
|
|
||||||
if (latestVersion > cleanLocal) {
|
|
||||||
resolve(latestVersion);
|
|
||||||
} else {
|
|
||||||
resolve(null);
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
resolve(null);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
req.on('error', () => resolve(null));
|
|
||||||
req.end();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ================== Konfiguration ==================
|
function loadConfig() {
|
||||||
async function saveConfig(config) {
|
if (fs.existsSync(configPath)) {
|
||||||
try {
|
try {
|
||||||
await fs.writeFile(configPath, JSON.stringify(config, null, 2), 'utf-8');
|
return JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
||||||
console.log(`Konfiguration gespeichert unter: ${configPath}`);
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Fehler beim Speichern der Konfiguration:', err.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadConfig() {
|
|
||||||
try {
|
|
||||||
const data = await fs.readFile(configPath, 'utf-8');
|
|
||||||
return JSON.parse(data);
|
|
||||||
} catch {
|
} catch {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function askQuestion(query) {
|
function askQuestion(query) {
|
||||||
@ -153,201 +96,60 @@ function askQuestion(query) {
|
|||||||
input: process.stdin,
|
input: process.stdin,
|
||||||
output: process.stdout,
|
output: process.stdout,
|
||||||
});
|
});
|
||||||
return new Promise((resolve) => rl.question(query, (ans) => {
|
return new Promise(resolve => rl.question(query, ans => {
|
||||||
rl.close();
|
rl.close();
|
||||||
resolve(ans.trim());
|
resolve(ans.trim());
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getConfig() {
|
async function getConfig() {
|
||||||
// Prüfe auf --setup Kommandozeilenoption
|
const existingConfig = loadConfig();
|
||||||
const forceSetup = process.argv.includes('--setup');
|
if (existingConfig?.folderPath && existingConfig?.suffix) {
|
||||||
if (forceSetup) {
|
console.log(`Verwende gespeicherte Konfiguration:`);
|
||||||
console.log('Erzwinge Ersteinrichtung (--setup angegeben)...');
|
console.log(`Ordner: ${existingConfig.folderPath}`);
|
||||||
return await setupConfig();
|
console.log(`Suffix: ${existingConfig.suffix}`);
|
||||||
}
|
|
||||||
|
|
||||||
const existingConfig = await loadConfig();
|
|
||||||
|
|
||||||
// Prüfe, ob die Konfiguration vollständig und gültig ist
|
|
||||||
if (
|
|
||||||
existingConfig?.folderPath &&
|
|
||||||
existingConfig?.suffix &&
|
|
||||||
['preview', 'preview-confirm', 'direct'].includes(existingConfig?.renameMode)
|
|
||||||
) {
|
|
||||||
try {
|
|
||||||
await fs.access(existingConfig.folderPath); // Prüfe, ob der Ordner existiert
|
|
||||||
if (/[<>"|?*]/.test(existingConfig.suffix)) {
|
|
||||||
console.log('Ungültiger Suffix in Konfiguration gefunden. Starte Ersteinrichtung...');
|
|
||||||
return await setupConfig();
|
|
||||||
}
|
|
||||||
return existingConfig;
|
return existingConfig;
|
||||||
} catch {
|
|
||||||
console.log('Konfigurationsordner existiert nicht mehr. Starte Ersteinrichtung...');
|
|
||||||
return await setupConfig();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ersteinrichtung, wenn keine oder ungültige Konfiguration
|
|
||||||
return await setupConfig();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function setupConfig() {
|
|
||||||
console.log('\n--- Ersteinrichtung ---');
|
|
||||||
|
|
||||||
// Ordnerauswahl
|
|
||||||
console.log('Möchtest du den automatischen Ordner nutzen?');
|
console.log('Möchtest du den automatischen Ordner nutzen?');
|
||||||
console.log('1 = Ja (Desktop\\Filme)');
|
console.log('1 = Ja (Desktop\\Filme)');
|
||||||
console.log('2 = Benutzerdefinierter Ordner');
|
console.log('2 = Benutzerdefinierter Ordner');
|
||||||
|
|
||||||
const antwort = await askQuestion('Deine Wahl (1 oder 2): ');
|
const antwort = await askQuestion('Deine Wahl (1 oder 2): ');
|
||||||
|
|
||||||
let folderPath;
|
let folderPath;
|
||||||
|
|
||||||
if (antwort === '1') {
|
if (antwort === '1') {
|
||||||
folderPath = path.join(os.homedir(), 'Desktop', 'Filme');
|
folderPath = path.join(os.homedir(), 'Desktop', 'Filme');
|
||||||
console.log(`Automatischer Ordner gewählt: ${folderPath}`);
|
console.log(`Automatischer Ordner gewählt: ${folderPath}`);
|
||||||
} else if (antwort === '2') {
|
} else if (antwort === '2') {
|
||||||
folderPath = await askQuestion('Bitte gib den vollständigen Pfad zum Ordner ein:\n');
|
folderPath = await askQuestion('Bitte gib den vollständigen Pfad zum Ordner ein:\n');
|
||||||
try {
|
|
||||||
await fs.access(folderPath);
|
|
||||||
} catch {
|
|
||||||
console.log('Ordner existiert nicht, bitte überprüfe den Pfad.');
|
|
||||||
return setupConfig();
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
console.log('Ungültige Eingabe, bitte versuche es nochmal.');
|
console.log('Ungültige Eingabe, bitte versuche es nochmal.');
|
||||||
return setupConfig();
|
return getConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Suffix
|
const suffix = await askQuestion('Welcher Text soll am Ende der Dateinamen hinzugefügt werden? (z. B. @Name)\n');
|
||||||
const suffix = await askQuestion(
|
|
||||||
'Welcher Text soll am Ende der Dateinamen hinzugefügt werden? (z. B. @Name)\n'
|
|
||||||
);
|
|
||||||
if (!suffix || /[<>"|?*]/.test(suffix)) {
|
|
||||||
console.log('Ungültiger Suffix. Vermeide Sonderzeichen wie <, >, ?, * usw.');
|
|
||||||
return setupConfig();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Umbenennungsmodus
|
const config = { folderPath, suffix };
|
||||||
console.log('Wie soll die Umbenennung durchgeführt werden?');
|
saveConfig(config);
|
||||||
console.log('1 = Nur Vorschau der Änderungen');
|
|
||||||
console.log('2 = Vorschau mit Bestätigung');
|
|
||||||
console.log('3 = Direkte Umbenennung ohne Vorschau');
|
|
||||||
const modeChoice = await askQuestion('Deine Wahl (1, 2 oder 3): ');
|
|
||||||
let renameMode;
|
|
||||||
if (modeChoice === '1') {
|
|
||||||
renameMode = 'preview';
|
|
||||||
console.log('[DEBUG] Modus: Nur Vorschau');
|
|
||||||
} else if (modeChoice === '2') {
|
|
||||||
renameMode = 'preview-confirm';
|
|
||||||
console.log('[DEBUG] Modus: Vorschau mit Bestätigung');
|
|
||||||
} else if (modeChoice === '3') {
|
|
||||||
renameMode = 'direct';
|
|
||||||
console.log('[DEBUG] Modus: Direkte Umbenennung');
|
|
||||||
} else {
|
|
||||||
console.log('Ungültige Eingabe. Bitte gib "1", "2" oder "3" ein.');
|
|
||||||
return setupConfig();
|
|
||||||
}
|
|
||||||
|
|
||||||
const config = { folderPath, suffix, renameMode };
|
|
||||||
await saveConfig(config);
|
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ================== TMDb API ==================
|
function umbenennenSync(pfad, suffix) {
|
||||||
async function fetchMovieDataFromTMDb(title, year) {
|
console.log('Prüfe Ordner:', pfad);
|
||||||
if (!title) return null;
|
|
||||||
const cacheKey = `${title}|${year || ''}`;
|
|
||||||
if (tmdbCache.has(cacheKey)) {
|
|
||||||
console.log(`Verwende gecachte TMDb-Daten für: ${title} (${year || 'ohne Jahr'})`);
|
|
||||||
return tmdbCache.get(cacheKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
if (!fs.existsSync(pfad)) {
|
||||||
const query = encodeURIComponent(title);
|
console.log('Ordner existiert nicht, erstelle:', pfad);
|
||||||
let url = `https://api.themoviedb.org/3/search/movie?query=${query}&include_adult=false`;
|
fs.mkdirSync(pfad, { recursive: true });
|
||||||
if (year) url += `&year=${year}`;
|
|
||||||
|
|
||||||
const options = {
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${TMDB_API_BEARER_TOKEN}`,
|
|
||||||
'Content-Type': 'application/json;charset=utf-8',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
https
|
|
||||||
.get(url, options, (res) => {
|
|
||||||
if (res.statusCode === 429) {
|
|
||||||
console.warn('TMDb Rate-Limit erreicht, warte kurz...');
|
|
||||||
setTimeout(() => fetchMovieDataFromTMDb(title, year).then(resolve).catch(reject), 1000);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let data = '';
|
const eintraege = fs.readdirSync(pfad, { withFileTypes: true });
|
||||||
res.on('data', (chunk) => (data += chunk));
|
|
||||||
res.on('end', () => {
|
|
||||||
try {
|
|
||||||
const json = JSON.parse(data);
|
|
||||||
const result = json.results && json.results.length > 0 ? json.results[0] : null;
|
|
||||||
tmdbCache.set(cacheKey, result);
|
|
||||||
resolve(result);
|
|
||||||
} catch (err) {
|
|
||||||
console.error(`Fehler beim Parsen der TMDb-Antwort für ${title}:`, err.message);
|
|
||||||
resolve(null);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.on('error', (err) => {
|
|
||||||
console.error(`Fehler bei TMDb-Abfrage für ${title}:`, err.message);
|
|
||||||
resolve(null); // Fallback: Keine Daten
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
console.error(`Unerwarteter Fehler bei TMDb-Abfrage für ${title}:`, err.message);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ================== Umbenennung ==================
|
|
||||||
async function umbenennenSync(pfad, suffix, mode = 'preview') {
|
|
||||||
console.log('Prüfe Ordner:', pfad);
|
|
||||||
console.log(`[DEBUG] Umbenennungsmodus: ${mode}`);
|
|
||||||
|
|
||||||
try {
|
|
||||||
await fs.access(pfad);
|
|
||||||
} catch (err) {
|
|
||||||
console.log('Ordner existiert nicht, erstelle:', pfad);
|
|
||||||
try {
|
|
||||||
await fs.mkdir(pfad, { recursive: true });
|
|
||||||
} catch (mkdirErr) {
|
|
||||||
console.error('Fehler beim Erstellen des Ordners:', mkdirErr.message);
|
|
||||||
}
|
|
||||||
return { shouldChangeConfig: false };
|
|
||||||
}
|
|
||||||
|
|
||||||
let eintraege;
|
|
||||||
try {
|
|
||||||
eintraege = await fs.readdir(pfad, { withFileTypes: true });
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Fehler beim Lesen des Ordners:', err.message);
|
|
||||||
return { shouldChangeConfig: false };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (eintraege.length === 0) {
|
if (eintraege.length === 0) {
|
||||||
console.log('Ordner ist leer:', pfad);
|
console.log('Ordner ist leer:', pfad);
|
||||||
if (mode === 'preview') {
|
|
||||||
const changeConfig = await askQuestion('Möchten Sie die Konfiguration ändern? (ja/nein): ');
|
|
||||||
if (changeConfig.toLowerCase() === 'ja') {
|
|
||||||
return { shouldChangeConfig: true };
|
|
||||||
}
|
}
|
||||||
await askQuestion('Drücken Sie Enter zum Beenden...');
|
|
||||||
}
|
|
||||||
return { shouldChangeConfig: false };
|
|
||||||
}
|
|
||||||
|
|
||||||
const jahrRegex = /(\d{4})/;
|
|
||||||
const serieRegex = /(S\d{1,2}E\d{1,2})/i;
|
|
||||||
const changes = [];
|
|
||||||
|
|
||||||
for (const eintrag of eintraege) {
|
for (const eintrag of eintraege) {
|
||||||
const vollerPfad = path.join(pfad, eintrag.name);
|
const vollerPfad = path.join(pfad, eintrag.name);
|
||||||
@ -362,10 +164,7 @@ async function umbenennenSync(pfad, suffix, mode = 'preview') {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
console.log('Betrete Unterordner:', vollerPfad);
|
console.log('Betrete Unterordner:', vollerPfad);
|
||||||
const result = await umbenennenSync(vollerPfad, suffix, mode);
|
umbenennenSync(vollerPfad, suffix);
|
||||||
if (result.shouldChangeConfig) {
|
|
||||||
return { shouldChangeConfig: true };
|
|
||||||
}
|
|
||||||
} else if (eintrag.isFile()) {
|
} else if (eintrag.isFile()) {
|
||||||
const ext = path.extname(eintrag.name);
|
const ext = path.extname(eintrag.name);
|
||||||
const name = path.basename(eintrag.name, ext);
|
const name = path.basename(eintrag.name, ext);
|
||||||
@ -375,148 +174,30 @@ async function umbenennenSync(pfad, suffix, mode = 'preview') {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const serieMatch = name.match(serieRegex);
|
const neuerName = `${name} ${suffix}${ext}`;
|
||||||
let neuerNameTeil;
|
const neuerPfad = path.join(pfad, neuerName);
|
||||||
|
|
||||||
if (serieMatch) {
|
|
||||||
const serienTeil = serieMatch[0];
|
|
||||||
const titelVorSerie = name.substring(0, serieMatch.index).trim().replace(/[\.\-_]+/g, ' ');
|
|
||||||
neuerNameTeil = `${titelVorSerie} ${serienTeil} ${suffix}`.trim();
|
|
||||||
} else {
|
|
||||||
const jahrMatch = name.match(jahrRegex);
|
|
||||||
const jahr = jahrMatch ? jahrMatch[1] : null;
|
|
||||||
const titelRaw = name.replace(jahrRegex, '').replace(/[\.\-_]+/g, ' ').trim();
|
|
||||||
|
|
||||||
const filmDaten = await fetchMovieDataFromTMDb(titelRaw, jahr);
|
|
||||||
let filmTitel = titelRaw;
|
|
||||||
let filmJahr = jahr;
|
|
||||||
|
|
||||||
if (filmDaten) {
|
|
||||||
filmTitel = filmDaten.title || filmTitel;
|
|
||||||
filmJahr = filmDaten.release_date ? filmDaten.release_date.substring(0, 4) : filmJahr;
|
|
||||||
}
|
|
||||||
|
|
||||||
neuerNameTeil = filmJahr ? `${filmTitel} (${filmJahr}) ${suffix}` : `${filmTitel} ${suffix}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const neuerDateiname = neuerNameTeil + ext;
|
|
||||||
const neuerPfad = path.join(pfad, neuerDateiname);
|
|
||||||
|
|
||||||
if (neuerDateiname === eintrag.name) {
|
|
||||||
console.log('Dateiname ist bereits korrekt:', neuerDateiname);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await fs.access(neuerPfad);
|
fs.renameSync(vollerPfad, neuerPfad);
|
||||||
console.log(`Datei existiert bereits: ${neuerDateiname}, überspringe`);
|
console.log(`Umbenannt: ${eintrag.name} → ${neuerName}`);
|
||||||
continue;
|
} catch (e) {
|
||||||
} catch {
|
console.error(`Fehler bei ${eintrag.name}:`, e.message);
|
||||||
// Datei existiert nicht, kann umbenannt werden
|
if (e.code === 'EPERM' || e.code === 'EACCES') {
|
||||||
}
|
console.error('Keine Zugriffsrechte oder Datei geschützt. Übersprungen.');
|
||||||
|
|
||||||
changes.push({ alt: eintrag.name, neu: neuerDateiname, vollerPfad, neuerPfad });
|
|
||||||
processedFiles++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (changes.length > 0) {
|
|
||||||
if (mode === 'preview' || mode === 'preview-confirm') {
|
|
||||||
console.log(`\nVorschau der Änderungen (${changes.length}):`);
|
|
||||||
changes.forEach(({ alt, neu }) => {
|
|
||||||
console.log(`${alt} → ${neu}`);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mode === 'preview') {
|
|
||||||
console.log('\nNur Vorschau: Keine Änderungen wurden vorgenommen.');
|
|
||||||
const changeConfig = await askQuestion('Möchten Sie die Konfiguration ändern? (ja/nein): ');
|
|
||||||
if (changeConfig.toLowerCase() === 'ja') {
|
|
||||||
return { shouldChangeConfig: true };
|
|
||||||
}
|
|
||||||
await askQuestion('Drücken Sie Enter zum Beenden...');
|
|
||||||
return { shouldChangeConfig: false };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mode === 'preview-confirm') {
|
|
||||||
console.log('\nAusführungs-Modus: Änderungen werden nach Bestätigung durchgeführt.');
|
|
||||||
const confirm = await askQuestion('Sollen diese Änderungen durchgeführt werden? (ja/nein): ');
|
|
||||||
if (confirm.toLowerCase() !== 'ja') {
|
|
||||||
console.log('Umbenennung abgebrochen.');
|
|
||||||
return { shouldChangeConfig: false };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Für 'preview-confirm' (nach Bestätigung) oder 'direct'
|
|
||||||
if (mode === 'preview-confirm' || mode === 'direct') {
|
|
||||||
if (mode === 'direct') {
|
|
||||||
console.log('\nDirekter Umbenennungs-Modus: Änderungen werden sofort durchgeführt.');
|
|
||||||
}
|
|
||||||
for (const { vollerPfad, neuerPfad } of changes) {
|
|
||||||
try {
|
|
||||||
await fs.rename(vollerPfad, neuerPfad);
|
|
||||||
console.log(`Umbenannt: ${path.basename(vollerPfad)} → ${path.basename(neuerPfad)}`);
|
|
||||||
} catch (err) {
|
|
||||||
console.error(`Fehler beim Umbenennen von ${path.basename(vollerPfad)}:`, err.message);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log(`\n${mode === 'preview' ? 'Vorschau-Modus' : mode === 'preview-confirm' ? 'Ausführungs-Modus' : 'Direkter Modus'}: Keine Änderungen erforderlich.`);
|
console.log('Kein Datei- oder Ordner-Eintrag, überspringe:', eintrag.name);
|
||||||
if (mode === 'preview') {
|
|
||||||
const changeConfig = await askQuestion('Möchten Sie die Konfiguration ändern? (ja/nein): ');
|
|
||||||
if (changeConfig.toLowerCase() === 'ja') {
|
|
||||||
return { shouldChangeConfig: true };
|
|
||||||
}
|
|
||||||
await askQuestion('Drücken Sie Enter zum Beenden...');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { shouldChangeConfig: false };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ================== Hauptprogramm ==================
|
async function main() {
|
||||||
(async () => {
|
showBanner(); // Banner anzeigen
|
||||||
showBanner();
|
|
||||||
|
|
||||||
if (!TMDB_API_BEARER_TOKEN || TMDB_API_BEARER_TOKEN === 'API-TOKEN') {
|
const config = await getConfig();
|
||||||
console.error('Fehler: TMDb API-Token fehlt. Bitte in .env-Datei konfigurieren.');
|
umbenennenSync(config.folderPath, config.suffix);
|
||||||
console.error('Hinweis: Verwende niemals einen echten Token direkt im Code!');
|
console.log('Fertig!');
|
||||||
console.error('Erstelle eine .env-Datei mit: TMDB_API_BEARER_TOKEN=dein_tmdb_token');
|
}
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
main();
|
||||||
const updateVerfuegbar = await checkForUpdates();
|
|
||||||
if (updateVerfuegbar) {
|
|
||||||
console.log(`Neue Version verfügbar: ${updateVerfuegbar} (Du hast: ${localVersion})`);
|
|
||||||
console.log('Bitte aktualisiere das Tool über Gitea.\n');
|
|
||||||
} else {
|
|
||||||
console.log('Du hast die aktuellste Version.\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
let config = await getConfig();
|
|
||||||
let shouldContinue = true;
|
|
||||||
|
|
||||||
while (shouldContinue) {
|
|
||||||
console.log(`Starte Umbenennung im Ordner: ${config.folderPath}`);
|
|
||||||
console.log(`Suffix: ${config.suffix}`);
|
|
||||||
console.log(`Umbenennungsmodus: ${config.renameMode}\n`);
|
|
||||||
|
|
||||||
processedFiles = 0; // Zurücksetzen für neue Umbenennung
|
|
||||||
const result = await umbenennenSync(config.folderPath, config.suffix, config.renameMode);
|
|
||||||
|
|
||||||
if (result.shouldChangeConfig) {
|
|
||||||
console.log('\nStarte Konfigurationsänderung...');
|
|
||||||
config = await setupConfig();
|
|
||||||
} else {
|
|
||||||
shouldContinue = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`\nFertig! Verarbeitete Dateien: ${processedFiles}`);
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Unerwarteter Fehler im Hauptprogramm:', err.message);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user