Initial commit: Tagueur Radarr-TMDB pour spectacles vivants

- Detection automatique des spectacles vivants (stand-up, theatre, one-man shows)
- Exclusion explicite des concerts de musique
- Gestion securisee des secrets dans ../.env.global
- Mode dry-run par defaut avec option --apply
- Export CSV des resultats
- Documentation complete en francais
- Checklist pre-commit incluse
This commit is contained in:
laurent
2026-02-22 12:31:54 +01:00
commit 5f3b68cedf
6 changed files with 881 additions and 0 deletions

40
.gitignore vendored Normal file
View File

@@ -0,0 +1,40 @@
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
env/
venv/
ENV/
env.bak/
venv.bak/
# Secrets - IMPORTANT: Ne jamais commiter le fichier .env.global
.env.global
.env
.env.local
.env.*.local
# Config (on garde l'exemple mais pas le fichier actif)
config.yaml
!config.yaml.example
# Output files
*.csv
results_*.csv
# Logs
*.log
logs/
# IDE
.vscode/
.idea/
*.swp
*.swo
*~
# OS
.DS_Store
Thumbs.db

209
README.md Normal file
View File

@@ -0,0 +1,209 @@
# 🎭 Radarr ↔ TMDB Tagueur Automatique
Un script Python qui détecte automatiquement les spectacles vivants (stand-up, théâtre, one-man shows...) dans votre bibliothèque Radarr et leur applique un tag "spectacle".
**Important** : Les concerts de musique sont explicitement exclus.
## 📋 Sommaire
- [Installation](#installation)
- [Configuration](#configuration)
- [Utilisation](#utilisation)
- [Sécurité](#sécurité)
- [Rollback](#rollback)
- [Checklist](#checklist)
## 🚀 Installation
### 1. Cloner le repo
### 2. Créer un environnement virtuel (recommandé)
### 3. Installer les dépendances
Defaulting to user installation because normal site-packages is not writeable
Collecting requests>=2.28.0 (from -r requirements.txt (line 1))
Downloading requests-2.32.5-py3-none-any.whl.metadata (4.9 kB)
Collecting pyyaml>=6.0 (from -r requirements.txt (line 2))
Downloading pyyaml-6.0.3-cp313-cp313-win_amd64.whl.metadata (2.4 kB)
Collecting python-dotenv>=1.0.0 (from -r requirements.txt (line 3))
Downloading python_dotenv-1.2.1-py3-none-any.whl.metadata (25 kB)
Collecting charset_normalizer<4,>=2 (from requests>=2.28.0->-r requirements.txt (line 1))
Downloading charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl.metadata (38 kB)
Collecting idna<4,>=2.5 (from requests>=2.28.0->-r requirements.txt (line 1))
Downloading idna-3.11-py3-none-any.whl.metadata (8.4 kB)
Collecting urllib3<3,>=1.21.1 (from requests>=2.28.0->-r requirements.txt (line 1))
Downloading urllib3-2.6.3-py3-none-any.whl.metadata (6.9 kB)
Collecting certifi>=2017.4.17 (from requests>=2.28.0->-r requirements.txt (line 1))
Downloading certifi-2026.1.4-py3-none-any.whl.metadata (2.5 kB)
Downloading requests-2.32.5-py3-none-any.whl (64 kB)
Downloading charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl (107 kB)
Downloading idna-3.11-py3-none-any.whl (71 kB)
Downloading urllib3-2.6.3-py3-none-any.whl (131 kB)
Downloading pyyaml-6.0.3-cp313-cp313-win_amd64.whl (154 kB)
Downloading python_dotenv-1.2.1-py3-none-any.whl (21 kB)
Downloading certifi-2026.1.4-py3-none-any.whl (152 kB)
Installing collected packages: urllib3, pyyaml, python-dotenv, idna, charset_normalizer, certifi, requests
Successfully installed certifi-2026.1.4 charset_normalizer-3.4.4 idna-3.11 python-dotenv-1.2.1 pyyaml-6.0.3 requests-2.32.5 urllib3-2.6.3
## ⚙️ Configuration
### Fichier des secrets ()
**⚠️ Important** : Ce fichier est situé **UN NIVEAU AU-DESSUS** du repo Git pour ne jamais être commité par accident.
#### Option 1 : Création automatique (recommandée)
Lancez simplement le script. S'il ne trouve pas le fichier, il vous demandera les informations :
Le script créera automatiquement avec vos secrets.
#### Option 2 : Création manuelle
Créez le fichier (un dossier au-dessus du repo) :
Contenu :
**Obtenir vos clés API :**
- **Radarr** : Paramètres → Général → Clé API
- **TMDB** : https://www.themoviedb.org/settings/api → Créer une clé
### Fichier de configuration ()
Copiez l'exemple et adaptez :
Modifiez selon vos besoins :
## 🎯 Utilisation
### Dry-run (par défaut - recommandé pour tester)
Analyse 5 films et montre ce qui serait tagué sans rien modifier :
**Sortie :**
- Liste des films détectés dans le terminal
- Export CSV avec les détails ()
### Appliquer les tags
Une fois satisfait du dry-run, appliquez réellement les tags :
### Options disponibles
Options :
- : Limite à N films
- : Applique réellement les tags (sinon dry-run)
- : Utilise un autre fichier de config
- ou : Mode verbeux (plus de logs)
### Exemples
## 🔒 Sécurité
### Protection des secrets
**Bien** :
- Les secrets sont dans (hors du repo)
- Les clés API sont masquées lors de la saisie interactive
- Les secrets ne sont jamais logués ni affichés
- Le fichier CSV d'export ne contient aucun secret
**Ne faites jamais** :
- Ne copiez pas dans le repo
- Ne committez jamais vos clés API
- Ne partagez pas votre fichier CSV s'il contient des chemins sensibles
### Sauvegarde avant modifications
**Avant d'utiliser **, sauvegardez votre base Radarr :
Ou exportez vos tags actuels via l'API.
## ↩️ Rollback
En cas d'erreur, vous pouvez retirer les tags facilement.
### Méthode 1 : Via le CSV généré
Le fichier CSV contient tous les films tagués. Vous pouvez utiliser cette liste pour identifier les films à corriger.
### Méthode 2 : Via l'API Radarr (manuel)
### Méthode 3 : Via l'interface Radarr
1. Allez dans Films
2. Filtrez par tag "spectacle"
3. Sélectionnez les films concernés
4. Actions en masse → Supprimer le tag
## ✅ Checklist avant commit
Voir [checklist.txt](checklist.txt)
## 🐛 Dépannage
### Erreur : "Fichier ../.env.global non trouvé"
Le script va vous demander de créer le fichier interactivement. Suivez les instructions.
### Erreur : "Variable manquante dans ../.env.global"
Vérifiez que les 3 variables sont bien présentes :
- RADARR_URL
- RADARR_APIKEY
- TMDB_APIKEY
Supprimez le fichier et relancez le script pour le recréer.
### Erreur de connexion à Radarr
Vérifiez :
- L'URL Radarr est accessible depuis la machine
- La clé API est correcte
- Radarr est en ligne
### Rate limit TMDB
Si vous avez beaucoup de films, vous pouvez atteindre la limite de requêtes TMDB. Le script attend automatiquement, mais soyez patient.
## 📝 Notes
- Le script analyse uniquement les films ayant un ID TMDB
- Le mode "loose" (défaut) détecte plus de spectacles mais peut avoir des faux positifs
- Le mode "strict" est plus conservateur
- Les concerts de musique sont TOUJOURS exclus, même avec des mots-clés ambigus
## 🍷 Une petite touche d'humour
> *"À cinquante ans, on ne tague plus les films avec ses doigts fatigués.
> On laisse Python faire le boulot pendant qu'on savoure un bon café."*
---
**Bon tagging !** 🎭

78
checklist.txt Normal file
View File

@@ -0,0 +1,78 @@
CHECKLIST PRE-COMMIT
====================
□ 1. SECURITE - Fichier ../.env.global
- Verifier que ../.env.global existe (hors du repo)
- Verifier qu'il contient bien les 3 variables :
* RADARR_URL
* RADARR_APIKEY
* TMDB_APIKEY
- NE JAMAIS committer ce fichier !
- Le fichier doit etre dans .gitignore
□ 2. CONFIGURATION
- Copier config.yaml.example en config.yaml
- Verifier les parametres :
* tag_name: "spectacle"
* dry_run: true (par defaut)
* limit: 5 (pour les tests)
* extra_keywords et exclude_keywords OK
□ 3. DEPENDANCES
- pip install -r requirements.txt
- Verifier que requests, pyyaml et python-dotenv sont installes
□ 4. TEST DRY-RUN
- Executer : python script.py --limit 5
- Verifier qu'aucune erreur ne survient
- Verifier le fichier CSV genere : results_spectacle_dryrun.csv
- Verifier que les films detectes sont bien des spectacles
- Verifier qu'aucun concert de musique n'est present
□ 5. VERIFICATION DU CSV
- Ouvrir results_spectacle_dryrun.csv
- Verifier les colonnes : Radarr ID, TMDB ID, Titre, Annee, Duree, Score, Raisons
- Verifier qu'aucune information sensible n'a fuite
- Verifier que les scores et raisons sont coherents
□ 6. SAUVEGARDE RADARR (avant --apply)
- Dans Radarr : Systeme → Sauvegarde → Sauvegarder
- Conserver la sauvegarde en cas de rollback necessaire
□ 7. TEST AVEC --apply (si dry-run OK)
- Executer : python script.py --limit 5 --apply
- Verifier dans Radarr que les tags sont bien appliques
- Verifier que les films concernes ont le tag "spectacle"
□ 8. FICHIERS A COMMITTER
- script.py (code source)
- README.md (documentation)
- config.yaml.example (configuration d'exemple)
- requirements.txt (dependances)
- .gitignore (fichiers a ignorer)
- checklist.txt (ce fichier)
□ 9. FICHIERS A NE PAS COMMITTER
- ../.env.global (secrets)
- config.yaml (config active)
- results_spectacle_dryrun.csv (resultats)
- *.log (fichiers de log)
- venv/ (environnement virtuel)
□ 10. COMMANDES GIT
git add script.py README.md config.yaml.example requirements.txt .gitignore checklist.txt
git commit -m "Initial commit: Tagueur Radarr-TMDB pour spectacles vivants"
git push origin main
REMARQUES
=========
- Toujours tester en dry-run avant --apply
- Le mode dry-run ne modifie rien dans Radarr
- Le CSV de sortie peut etre ouvert dans Excel/LibreOffice
- Conserver le CSV pour un eventuel rollback
POST-COMMIT
===========
- Verifier sur Gitea que tous les fichiers sont bien pouses
- Verifier qu'aucun secret n'a ete commite par erreur
- Tester le script sur un autre poste pour valider la portabilite

58
config.yaml.example Normal file
View File

@@ -0,0 +1,58 @@
# Configuration du tagueur automatique Radarr ↔ TMDB
# Ce fichier est un exemple - copiez-le en config.yaml et adaptez-le
# Nom du tag à créer/appliquer dans Radarr
tag_name: "spectacle"
# Mode dry-run par défaut (true) - mettre à false ou utiliser --apply pour appliquer les tags
dry_run: true
# Limitation du nombre de films à traiter (None = tous)
limit: 5
# Runtime en minutes (plage acceptable pour un spectacle)
min_runtime: 15
max_runtime: 240
# Mots-clés pour détecter un spectacle (insensible à la casse)
extra_keywords:
- "stand"
- "stand-up"
- "standup"
- "one man"
- "one-man"
- "one woman"
- "one-woman"
- "theatre"
- "théâtre"
- "play"
- "pièce"
- "monologue"
- "cabaret"
- "sketch"
- "performance"
- "spoken word"
- "variety"
- "revue"
- "comedy special"
# Mots-clés d'exclusion prioritaires (concert de musique, etc.)
exclude_keywords:
- "concert"
- "music"
- "live concert"
- "music video"
- "festival"
- "musician"
- "band"
# Mode de détection : "strict" ou "loose"
# strict : tous les critères doivent être remplis
# loose : au moins un critère suffit
detection_mode: "loose"
# Fichier de sortie pour le dry-run
output_csv: "results_spectacle_dryrun.csv"
# Niveau de log : DEBUG, INFO, WARNING, ERROR
log_level: "INFO"

3
requirements.txt Normal file
View File

@@ -0,0 +1,3 @@
requests>=2.28.0
pyyaml>=6.0
python-dotenv>=1.0.0

493
script.py Normal file
View File

@@ -0,0 +1,493 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import sys
import csv
import argparse
import logging
import getpass
from pathlib import Path
from typing import Dict, List, Optional, Tuple
from dataclasses import dataclass
import requests
import yaml
from dotenv import load_dotenv
DEFAULT_CONFIG = {
'tag_name': 'spectacle',
'dry_run': True,
'limit': 5,
'min_runtime': 15,
'max_runtime': 240,
'extra_keywords': [
'stand', 'stand-up', 'standup', 'one man', 'one-man',
'one woman', 'one-woman', 'theatre', 'théâtre', 'play',
'piece', 'monologue', 'cabaret', 'sketch', 'performance',
'spoken word', 'variety', 'revue', 'comedy special'
],
'exclude_keywords': [
'concert', 'music', 'live concert', 'music video',
'festival', 'musician', 'band'
],
'detection_mode': 'loose',
'output_csv': 'results_spectacle_dryrun.csv',
'log_level': 'INFO'
}
def get_env_file_path() -> Path:
script_dir = Path(__file__).parent.resolve()
parent_dir = script_dir.parent
return parent_dir / '.env.global'
def prompt_for_secrets():
print()
print('='*60)
print('Configuration initiale - Creation de ../.env.global')
print('='*60)
print()
print('Ce fichier stockera vos secrets en dehors du repo Git.')
print('Il ne sera JAMAIS commite.')
print()
secrets = {}
secrets['RADARR_URL'] = input('Radarr URL (ex: http://localhost:7878): ').strip()
secrets['RADARR_APIKEY'] = getpass.getpass('Radarr API Key: ').strip()
secrets['TMDB_APIKEY'] = getpass.getpass('TMDB API Key: ').strip()
return secrets
def create_env_file(env_path: Path, secrets) -> bool:
try:
env_path.parent.mkdir(parents=True, exist_ok=True)
with open(env_path, 'w', encoding='utf-8') as f:
f.write('# Fichier de secrets - Ne JAMAIS commiter ce fichier !
')
f.write('RADARR_URL="' + secrets['RADARR_URL'] + '"
')
f.write('RADARR_APIKEY="' + secrets['RADARR_APIKEY'] + '"
')
f.write('TMDB_APIKEY="' + secrets['TMDB_APIKEY'] + '"
')
try:
os.chmod(env_path, 0o600)
except:
pass
print()
print('Fichier cree: ' + str(env_path))
print('Secrets sauvegardes (fichier protege)')
print()
return True
except Exception as e:
print()
print('Erreur lors de la creation du fichier: ' + str(e))
print()
return False
def load_secrets():
env_path = get_env_file_path()
if not env_path.exists():
print()
print('Fichier non trouve: ' + str(env_path))
secrets = prompt_for_secrets()
if not all(secrets.values()):
print()
print('Erreur: Tous les champs sont obligatoires.')
return None
if create_env_file(env_path, secrets):
load_dotenv(env_path)
else:
return None
else:
load_dotenv(env_path)
required_vars = ['RADARR_URL', 'RADARR_APIKEY', 'TMDB_APIKEY']
secrets = {}
for var in required_vars:
value = os.getenv(var)
if not value:
print()
print('Erreur: Variable manquante dans ' + str(env_path) + ': ' + var)
print('Verifiez votre fichier ou supprimez-le pour le recreer.')
print()
return None
secrets[var] = value
return secrets
def load_config(config_path='config.yaml'):
config = DEFAULT_CONFIG.copy()
if os.path.exists(config_path):
try:
with open(config_path, 'r', encoding='utf-8') as f:
user_config = yaml.safe_load(f)
if user_config:
config.update(user_config)
logging.info('Configuration chargee depuis ' + config_path)
except Exception as e:
logging.warning('Impossible de charger ' + config_path + ': ' + str(e))
logging.warning('Utilisation de la configuration par defaut')
else:
logging.info(config_path + ' non trouve, utilisation de la config par defaut')
return config
@dataclass
class MovieMatch:
radarr_id: int
tmdb_id: int
title: str
year: int
runtime: int
reasons: List[str]
score: float
class RadarrAPI:
def __init__(self, base_url, api_key):
self.base_url = base_url.rstrip('/')
self.api_key = api_key
self.session = requests.Session()
self.session.headers.update({
'X-Api-Key': api_key,
'Content-Type': 'application/json'
})
def _request(self, method, endpoint, **kwargs):
url = self.base_url + '/api/v3' + endpoint
for attempt in range(3):
try:
response = self.session.request(method, url, timeout=30, **kwargs)
response.raise_for_status()
return response
except requests.exceptions.Timeout:
if attempt == 2:
raise
logging.warning('Timeout, tentative ' + str(attempt + 2) + '/3...')
except requests.exceptions.RequestException:
if attempt == 2:
raise
logging.warning('Erreur reseau, tentative ' + str(attempt + 2) + '/3...')
def ensure_tag_exists(self, tag_name):
response = self._request('GET', '/tag')
tags = response.json()
for tag in tags:
if tag['label'] == tag_name:
logging.debug('Tag "' + tag_name + '" trouve avec ID ' + str(tag['id']))
return tag['id']
logging.info('Creation du tag "' + tag_name + '"...')
response = self._request('POST', '/tag', json={'label': tag_name})
new_tag = response.json()
logging.info('Tag cree avec ID ' + str(new_tag['id']))
return new_tag['id']
def get_movies(self, limit=None):
logging.info('Recuperation des films depuis Radarr...')
response = self._request('GET', '/movie')
movies = response.json()
if limit:
movies = movies[:limit]
logging.info('Limite a ' + str(limit) + ' films')
logging.info(str(len(movies)) + ' films recuperes')
return movies
def add_tag_to_movie(self, movie_id, tag_id):
response = self._request('GET', '/movie/' + str(movie_id))
movie = response.json()
current_tags = movie.get('tags', [])
if tag_id in current_tags:
logging.debug('Tag deja present sur le film ' + str(movie_id))
return
current_tags.append(tag_id)
movie['tags'] = current_tags
self._request('PUT', '/movie', json=movie)
logging.info('Tag ajoute au film ' + str(movie_id))
class TMDBAPI:
def __init__(self, api_key):
self.api_key = api_key
self.base_url = 'https://api.themoviedb.org/3'
self.session = requests.Session()
def _request(self, endpoint, **kwargs):
url = self.base_url + endpoint
params = kwargs.pop('params', {})
params['api_key'] = self.api_key
for attempt in range(3):
try:
response = self.session.get(url, params=params, timeout=30, **kwargs)
if response.status_code == 429:
logging.warning('Rate limit TMDB atteint, attente...')
import time
time.sleep(1)
continue
response.raise_for_status()
return response
except requests.exceptions.Timeout:
if attempt == 2:
raise
logging.warning('Timeout TMDB, tentative ' + str(attempt + 2) + '/3...')
except requests.exceptions.RequestException:
if attempt == 2:
raise
logging.warning('Erreur TMDB, tentative ' + str(attempt + 2) + '/3...')
def get_movie_details(self, tmdb_id):
response = self._request('/movie/' + str(tmdb_id))
return response.json()
def get_movie_keywords(self, tmdb_id):
try:
response = self._request('/movie/' + str(tmdb_id) + '/keywords')
data = response.json()
return [kw['name'].lower() for kw in data.get('keywords', [])]
except:
return []
def detect_spectacle(movie_data, keywords, config):
reasons = []
score = 0.0
title = (movie_data.get('title', '') or '').lower()
overview = (movie_data.get('overview', '') or '').lower()
runtime = movie_data.get('runtime', 0) or 0
# Exclusions prioritaires
text_to_check = title + ' ' + overview + ' ' + ' '.join(keywords)
for exclude_kw in config['exclude_keywords']:
if exclude_kw.lower() in text_to_check:
return False, ['Exclusion: "' + exclude_kw + '" detecte'], 0.0
# Verification du runtime
min_runtime = config['min_runtime']
max_runtime = config['max_runtime']
if min_runtime <= runtime <= max_runtime:
score += 0.3
reasons.append('Runtime OK (' + str(runtime) + ' min)')
elif runtime > 0:
score -= 0.2
reasons.append('Runtime hors plage (' + str(runtime) + ' min)')
# Verification des mots-cles
extra_keywords = [kw.lower() for kw in config['extra_keywords']]
for kw in extra_keywords:
if kw in title:
score += 0.4
reasons.append('Keyword dans titre: "' + kw + '"')
break
for kw in extra_keywords:
if kw in overview:
score += 0.3
reasons.append('Keyword dans synopsis: "' + kw + '"')
break
for kw in extra_keywords:
if any(kw in tmdb_kw for tmdb_kw in keywords):
score += 0.3
reasons.append('Keyword TMDB: "' + kw + '"')
break
# Decision finale
detection_mode = config.get('detection_mode', 'loose')
if detection_mode == 'strict':
is_spectacle = (score >= 0.6) and (min_runtime <= runtime <= max_runtime)
else:
is_spectacle = score >= 0.4
return is_spectacle, reasons, score
def export_to_csv(matches, output_path):
with open(output_path, 'w', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
writer.writerow(['Radarr ID', 'TMDB ID', 'Titre', 'Annee', 'Duree (min)', 'Score', 'Raisons'])
for match in matches:
writer.writerow([
match.radarr_id,
match.tmdb_id,
match.title,
match.year,
match.runtime,
'{:.2f}'.format(match.score),
' | '.join(match.reasons)
])
logging.info('Resultats exportes dans ' + output_path)
def apply_tags(matches, radarr, tag_id, dry_run):
if dry_run:
logging.info('')
logging.info('[DRY-RUN] ' + str(len(matches)) + ' films seraient tagues:')
for match in matches:
logging.info(' - ' + match.title + ' (' + str(match.year) + ')')
return
logging.info('')
logging.info('Application du tag a ' + str(len(matches)) + ' films...')
for match in matches:
try:
radarr.add_tag_to_movie(match.radarr_id, tag_id)
logging.info(' ' + match.title + ' (' + str(match.year) + ')')
except Exception as e:
logging.error(' Erreur sur ' + match.title + ': ' + str(e))
def main():
parser = argparse.ArgumentParser(
description='Tagueur automatique Radarr TMDB pour spectacles vivants'
)
parser.add_argument('--limit', type=int, help='Limite le nombre de films')
parser.add_argument('--apply', action='store_true', help='Applique les tags (sinon dry-run)')
parser.add_argument('--config', default='config.yaml', help='Chemin vers config.yaml')
parser.add_argument('--verbose', '-v', action='store_true', help='Mode verbeux')
args = parser.parse_args()
config = load_config(args.config)
if args.limit is not None:
config['limit'] = args.limit
if args.apply:
config['dry_run'] = False
if args.verbose:
config['log_level'] = 'DEBUG'
logging.basicConfig(
level=getattr(logging, config['log_level']),
format='%(asctime)s - %(levelname)s - %(message)s',
datefmt='%H:%M:%S'
)
logging.info('Chargement des secrets depuis ../.env.global...')
secrets = load_secrets()
if not secrets:
print()
print('='*60)
print('ERREUR: Impossible de charger les secrets')
print('='*60)
print()
print('Veuillez creer le fichier: ' + str(get_env_file_path()))
print()
print('Format attendu:')
print('RADARR_URL="http://localhost:7878"')
print('RADARR_APIKEY="votre-cle-radarr"')
print('TMDB_APIKEY="votre-cle-tmdb"')
print()
print('Ou relancez le script pour creer ce fichier interactivement.')
print()
sys.exit(1)
logging.info('Secrets charges (valeurs masquees)')
try:
radarr = RadarrAPI(secrets['RADARR_URL'], secrets['RADARR_APIKEY'])
tmdb = TMDBAPI(secrets['TMDB_APIKEY'])
except Exception as e:
logging.error('Erreur d'initialisation: ' + str(e))
sys.exit(1)
try:
tag_id = radarr.ensure_tag_exists(config['tag_name'])
except Exception as e:
logging.error('Erreur lors de la creation du tag: ' + str(e))
sys.exit(1)
try:
movies = radarr.get_movies(config['limit'])
except Exception as e:
logging.error('Erreur lors de la recuperation des films: ' + str(e))
sys.exit(1)
matches = []
logging.info('')
logging.info('Analyse des ' + str(len(movies)) + ' films...')
logging.info('-' * 60)
for i, movie in enumerate(movies, 1):
tmdb_id = movie.get('tmdbId')
if not tmdb_id:
logging.debug('[' + str(i) + '/' + str(len(movies)) + '] ' + movie.get('title', '') + ' - Pas de TMDB ID, ignore')
continue
try:
movie_data = tmdb.get_movie_details(tmdb_id)
keywords = tmdb.get_movie_keywords(tmdb_id)
is_spectacle, reasons, score = detect_spectacle(movie_data, keywords, config)
if is_spectacle:
match = MovieMatch(
radarr_id=movie['id'],
tmdb_id=tmdb_id,
title=movie['title'],
year=movie.get('year', 0),
runtime=movie_data.get('runtime', 0),
reasons=reasons,
score=score
)
matches.append(match)
logging.info('[' + str(i) + '/' + str(len(movies)) + '] ' + movie['title'] + ' (' + str(movie.get('year', 0)) + ') - SCORE: {:.2f}'.format(score))
for reason in reasons:
logging.info(' -> ' + reason)
else:
logging.debug('[' + str(i) + '/' + str(len(movies)) + '] ' + movie['title'] + ' - Pas un spectacle ({:.2f})'.format(score))
except Exception as e:
logging.warning('[' + str(i) + '/' + str(len(movies)) + '] Erreur sur ' + movie.get('title', '') + ': ' + str(e))
continue
logging.info('-' * 60)
logging.info('')
logging.info(str(len(matches)) + ' spectacles detectes sur ' + str(len(movies)) + ' films analyses')
if matches:
export_to_csv(matches, config['output_csv'])
apply_tags(matches, radarr, tag_id, config['dry_run'])
else:
logging.info('Aucun spectacle detecte.')
if not config['dry_run'] and matches:
print()
print('='*60)
print('ROLLBACK - Pour retirer le tag en cas d'erreur:')
print('='*60)
print('Liste des films tagues sauvegardee dans: ' + config['output_csv'])
print('Pour retirer le tag "' + config['tag_name'] + '" d'un film:')
print('curl -X PUT "' + secrets['RADARR_URL'] + '/api/v3/movie/<movie_id>" ')
print(' -H "X-Api-Key: <API_KEY>" ')
print(' -H "Content-Type: application/json" ')
print(' -d "{\\"tags\\": []}"')
print('='*60)
print()
if __name__ == '__main__':
main()