jmoona-cli 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- jmoona_cli-1.0.0/PKG-INFO +227 -0
- jmoona_cli-1.0.0/README.md +208 -0
- jmoona_cli-1.0.0/jmoona/__init__.py +1 -0
- jmoona_cli-1.0.0/jmoona/app.py +225 -0
- jmoona_cli-1.0.0/jmoona/art.py +117 -0
- jmoona_cli-1.0.0/jmoona/cli.py +59 -0
- jmoona_cli-1.0.0/jmoona/config.py +55 -0
- jmoona_cli-1.0.0/jmoona/downloader.py +64 -0
- jmoona_cli-1.0.0/jmoona/extractor.py +336 -0
- jmoona_cli-1.0.0/jmoona/language.py +94 -0
- jmoona_cli-1.0.0/jmoona/player.py +89 -0
- jmoona_cli-1.0.0/jmoona/providers.py +50 -0
- jmoona_cli-1.0.0/jmoona/storage.py +76 -0
- jmoona_cli-1.0.0/jmoona/subtitles.py +103 -0
- jmoona_cli-1.0.0/jmoona/tmdb.py +99 -0
- jmoona_cli-1.0.0/jmoona/ui.py +88 -0
- jmoona_cli-1.0.0/jmoona_cli.egg-info/PKG-INFO +227 -0
- jmoona_cli-1.0.0/jmoona_cli.egg-info/SOURCES.txt +22 -0
- jmoona_cli-1.0.0/jmoona_cli.egg-info/dependency_links.txt +1 -0
- jmoona_cli-1.0.0/jmoona_cli.egg-info/entry_points.txt +2 -0
- jmoona_cli-1.0.0/jmoona_cli.egg-info/requires.txt +12 -0
- jmoona_cli-1.0.0/jmoona_cli.egg-info/top_level.txt +1 -0
- jmoona_cli-1.0.0/pyproject.toml +39 -0
- jmoona_cli-1.0.0/setup.cfg +4 -0
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: jmoona-cli
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Films & séries du monde en streaming depuis le terminal
|
|
5
|
+
License: MIT
|
|
6
|
+
Project-URL: Homepage, https://github.com/yourusername/jmoona-cli
|
|
7
|
+
Keywords: streaming,films,séries,mpv,cli,terminal
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
Requires-Dist: requests>=2.28.0
|
|
11
|
+
Requires-Dist: selenium>=4.9.0
|
|
12
|
+
Requires-Dist: undetected-chromedriver>=3.5.0
|
|
13
|
+
Requires-Dist: pyvirtualdisplay>=3.0; sys_platform == "linux"
|
|
14
|
+
Requires-Dist: curl-cffi>=0.6.0
|
|
15
|
+
Provides-Extra: dev
|
|
16
|
+
Requires-Dist: pytest; extra == "dev"
|
|
17
|
+
Requires-Dist: black; extra == "dev"
|
|
18
|
+
Requires-Dist: ruff; extra == "dev"
|
|
19
|
+
|
|
20
|
+
# 🎬 jmoona-cli
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
_ _ _
|
|
24
|
+
(_) | (_)
|
|
25
|
+
_ _ __ ___ ___ ___ _ __ __ _ ___| |_
|
|
26
|
+
| | '_ ` _ \ / _ \ / _ \| '_ \ / _` |/ __| | |
|
|
27
|
+
| | | | | | | (_) | (_) | | | | (_| | (__| | |
|
|
28
|
+
| |_| |_| |_|\___/ \___/|_| |_|\__,_|\___|_|_|
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
> **L'émulateur ultime de films et séries** — streaming depuis votre terminal
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## ✨ Fonctionnalités
|
|
36
|
+
|
|
37
|
+
- 🔍 Recherche de films & séries via TMDB
|
|
38
|
+
- 🎬 Streaming en un clic via `mpv`
|
|
39
|
+
- 🔤 Sous-titres français automatiques (OpenSubtitles)
|
|
40
|
+
- 📥 Téléchargement avec `yt-dlp`
|
|
41
|
+
- 🕹️ Reprise de lecture automatique
|
|
42
|
+
- 💾 Historique de visionnage
|
|
43
|
+
- 🌐 Multi-sources (Cloudnestra, vidsrc, smashystream, ...)
|
|
44
|
+
- 🖥️ Compatible Linux · macOS · Windows · Docker
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## 📦 Installation rapide
|
|
49
|
+
|
|
50
|
+
### 🐧 Linux / 🍎 macOS
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
bash <(curl -fsSL https://raw.githubusercontent.com/riter675/jmoona-cli/main/install.sh)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### 🪟 Windows (PowerShell en administrateur)
|
|
57
|
+
|
|
58
|
+
```powershell
|
|
59
|
+
irm https://raw.githubusercontent.com/riter675/jmoona-cli/main/install.ps1 | iex
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### 🐳 Docker (n'importe quel OS)
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
docker build -t jmoona .
|
|
66
|
+
# Avec affichage vidéo sur l'écran hôte (Linux) :
|
|
67
|
+
docker run -it --rm \
|
|
68
|
+
-e DISPLAY=$DISPLAY \
|
|
69
|
+
-v /tmp/.X11-unix:/tmp/.X11-unix \
|
|
70
|
+
-v "$HOME/Downloads/jmoona:/root/Downloads/jmoona" \
|
|
71
|
+
jmoona
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Installation manuelle (pip)
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
git clone https://github.com/riter675/jmoona-cli.git
|
|
78
|
+
cd jmoona-cli
|
|
79
|
+
pip install -e .
|
|
80
|
+
jmoona
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## ⚠️ Prérequis
|
|
86
|
+
|
|
87
|
+
| Outil | Rôle | Requis |
|
|
88
|
+
|-------|------|--------|
|
|
89
|
+
| Python 3.10+ | Moteur | ✅ Obligatoire |
|
|
90
|
+
| `mpv` | Lecteur vidéo | ✅ Obligatoire |
|
|
91
|
+
| `yt-dlp` | Extracteur de streams | ✅ Obligatoire |
|
|
92
|
+
| `fzf` | Interface menus améliorée | ⚡ Recommandé |
|
|
93
|
+
| `ffmpeg` / `ffprobe` | Analyse des pistes | ⚡ Recommandé |
|
|
94
|
+
| Chromium + chromedriver | Extraction Selenium | 🔧 Optionnel |
|
|
95
|
+
|
|
96
|
+
### Installation des prérequis
|
|
97
|
+
|
|
98
|
+
**Ubuntu/Debian:**
|
|
99
|
+
```bash
|
|
100
|
+
sudo apt install mpv yt-dlp fzf ffmpeg python3 python3-pip
|
|
101
|
+
pip install jmoona-cli
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**Fedora:**
|
|
105
|
+
```bash
|
|
106
|
+
sudo dnf install mpv yt-dlp fzf ffmpeg python3 python3-pip
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**Arch Linux:**
|
|
110
|
+
```bash
|
|
111
|
+
sudo pacman -S mpv yt-dlp fzf ffmpeg python python-pip
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**macOS (Homebrew):**
|
|
115
|
+
```bash
|
|
116
|
+
brew install mpv yt-dlp fzf ffmpeg python
|
|
117
|
+
pip3 install jmoona-cli
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
**Windows (winget + pip):**
|
|
121
|
+
```powershell
|
|
122
|
+
winget install mpv.mpv yt-dlp.yt-dlp Git.Git Python.Python.3.12
|
|
123
|
+
pip install jmoona-cli
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## 🚀 Utilisation
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
jmoona # Lancer l'interface interactive
|
|
132
|
+
jmoona "Inception" # Recherche directe
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Navigation
|
|
136
|
+
- `↑↓` ou numéro — choisir dans les menus
|
|
137
|
+
- `Enter` — valider
|
|
138
|
+
- `q` ou `Ctrl+C` — quitter
|
|
139
|
+
- Dans mpv : `f` = plein écran, `←→` = avance/recul, `q` = quitter
|
|
140
|
+
|
|
141
|
+
### Modes de lecture
|
|
142
|
+
|
|
143
|
+
| Mode | Description |
|
|
144
|
+
|------|-------------|
|
|
145
|
+
| 🎵 **Auto** | FR si disponible, anglais sinon (recommandé) |
|
|
146
|
+
| 🔤 **VOSTFR** | Audio original + sous-titres français (idéal animés) |
|
|
147
|
+
| 🇬🇧 **VA** | Version originale sans sous-titres |
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## ⚙️ Configuration
|
|
152
|
+
|
|
153
|
+
Fichier de config par OS :
|
|
154
|
+
- **Linux** : `~/.config/jmoona/config.json`
|
|
155
|
+
- **macOS** : `~/Library/Application Support/jmoona/config.json`
|
|
156
|
+
- **Windows** : `%APPDATA%\jmoona\config.json`
|
|
157
|
+
|
|
158
|
+
Options disponibles :
|
|
159
|
+
|
|
160
|
+
```json
|
|
161
|
+
{
|
|
162
|
+
"player": "mpv",
|
|
163
|
+
"player_args": "--fs",
|
|
164
|
+
"quality": "best",
|
|
165
|
+
"provider": "auto",
|
|
166
|
+
"use_fzf": true,
|
|
167
|
+
"resume": true,
|
|
168
|
+
"download_dir": "~/Downloads/jmoona"
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## 🔧 Dépendances Python
|
|
175
|
+
|
|
176
|
+
```
|
|
177
|
+
requests>=2.28.0
|
|
178
|
+
selenium>=4.9.0
|
|
179
|
+
undetected-chromedriver>=3.5.0
|
|
180
|
+
pyvirtualdisplay>=3.0 # Linux uniquement
|
|
181
|
+
curl-cffi>=0.6.0
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## 📁 Structure du projet
|
|
187
|
+
|
|
188
|
+
```
|
|
189
|
+
jmoona-cli/
|
|
190
|
+
├── jmoona/
|
|
191
|
+
│ ├── app.py # Logique principale
|
|
192
|
+
│ ├── cli.py # Point d'entrée CLI
|
|
193
|
+
│ ├── extractor.py # Extraction des streams
|
|
194
|
+
│ ├── player.py # Lecture via mpv
|
|
195
|
+
│ ├── downloader.py # Téléchargement via yt-dlp
|
|
196
|
+
│ ├── subtitles.py # Sous-titres via OpenSubtitles
|
|
197
|
+
│ ├── tmdb.py # API TMDB
|
|
198
|
+
│ ├── ui.py # Interface terminal
|
|
199
|
+
│ ├── art.py # ASCII art
|
|
200
|
+
│ ├── language.py # Détection pistes audio
|
|
201
|
+
│ ├── storage.py # Historique & reprise
|
|
202
|
+
│ ├── config.py # Configuration cross-platform
|
|
203
|
+
│ └── providers.py # Liste des providers
|
|
204
|
+
├── pyproject.toml
|
|
205
|
+
├── install.sh # Installateur Linux/macOS
|
|
206
|
+
├── install.ps1 # Installateur Windows
|
|
207
|
+
├── Dockerfile
|
|
208
|
+
└── README.md
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## 🐛 Problèmes courants
|
|
214
|
+
|
|
215
|
+
**`mpv: command not found`** → Installez mpv (voir prérequis ci-dessus)
|
|
216
|
+
|
|
217
|
+
**Stream introuvable** → Essayez un autre provider : `jmoona` → paramètres → providers
|
|
218
|
+
|
|
219
|
+
**Chromium manquant** → Normal, Selenium est optionnel. Les streams HTTP fonctionnent sans.
|
|
220
|
+
|
|
221
|
+
**Windows : couleurs absentes** → Activez les couleurs ANSI : `Set-ItemProperty HKCU:\Console VirtualTerminalLevel 1`
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## 📄 Licence
|
|
226
|
+
|
|
227
|
+
MIT — fait avec ❤️ par jmoona
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# 🎬 jmoona-cli
|
|
2
|
+
|
|
3
|
+
```
|
|
4
|
+
_ _ _
|
|
5
|
+
(_) | (_)
|
|
6
|
+
_ _ __ ___ ___ ___ _ __ __ _ ___| |_
|
|
7
|
+
| | '_ ` _ \ / _ \ / _ \| '_ \ / _` |/ __| | |
|
|
8
|
+
| | | | | | | (_) | (_) | | | | (_| | (__| | |
|
|
9
|
+
| |_| |_| |_|\___/ \___/|_| |_|\__,_|\___|_|_|
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
> **L'émulateur ultime de films et séries** — streaming depuis votre terminal
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## ✨ Fonctionnalités
|
|
17
|
+
|
|
18
|
+
- 🔍 Recherche de films & séries via TMDB
|
|
19
|
+
- 🎬 Streaming en un clic via `mpv`
|
|
20
|
+
- 🔤 Sous-titres français automatiques (OpenSubtitles)
|
|
21
|
+
- 📥 Téléchargement avec `yt-dlp`
|
|
22
|
+
- 🕹️ Reprise de lecture automatique
|
|
23
|
+
- 💾 Historique de visionnage
|
|
24
|
+
- 🌐 Multi-sources (Cloudnestra, vidsrc, smashystream, ...)
|
|
25
|
+
- 🖥️ Compatible Linux · macOS · Windows · Docker
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## 📦 Installation rapide
|
|
30
|
+
|
|
31
|
+
### 🐧 Linux / 🍎 macOS
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
bash <(curl -fsSL https://raw.githubusercontent.com/riter675/jmoona-cli/main/install.sh)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### 🪟 Windows (PowerShell en administrateur)
|
|
38
|
+
|
|
39
|
+
```powershell
|
|
40
|
+
irm https://raw.githubusercontent.com/riter675/jmoona-cli/main/install.ps1 | iex
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### 🐳 Docker (n'importe quel OS)
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
docker build -t jmoona .
|
|
47
|
+
# Avec affichage vidéo sur l'écran hôte (Linux) :
|
|
48
|
+
docker run -it --rm \
|
|
49
|
+
-e DISPLAY=$DISPLAY \
|
|
50
|
+
-v /tmp/.X11-unix:/tmp/.X11-unix \
|
|
51
|
+
-v "$HOME/Downloads/jmoona:/root/Downloads/jmoona" \
|
|
52
|
+
jmoona
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Installation manuelle (pip)
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
git clone https://github.com/riter675/jmoona-cli.git
|
|
59
|
+
cd jmoona-cli
|
|
60
|
+
pip install -e .
|
|
61
|
+
jmoona
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## ⚠️ Prérequis
|
|
67
|
+
|
|
68
|
+
| Outil | Rôle | Requis |
|
|
69
|
+
|-------|------|--------|
|
|
70
|
+
| Python 3.10+ | Moteur | ✅ Obligatoire |
|
|
71
|
+
| `mpv` | Lecteur vidéo | ✅ Obligatoire |
|
|
72
|
+
| `yt-dlp` | Extracteur de streams | ✅ Obligatoire |
|
|
73
|
+
| `fzf` | Interface menus améliorée | ⚡ Recommandé |
|
|
74
|
+
| `ffmpeg` / `ffprobe` | Analyse des pistes | ⚡ Recommandé |
|
|
75
|
+
| Chromium + chromedriver | Extraction Selenium | 🔧 Optionnel |
|
|
76
|
+
|
|
77
|
+
### Installation des prérequis
|
|
78
|
+
|
|
79
|
+
**Ubuntu/Debian:**
|
|
80
|
+
```bash
|
|
81
|
+
sudo apt install mpv yt-dlp fzf ffmpeg python3 python3-pip
|
|
82
|
+
pip install jmoona-cli
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**Fedora:**
|
|
86
|
+
```bash
|
|
87
|
+
sudo dnf install mpv yt-dlp fzf ffmpeg python3 python3-pip
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**Arch Linux:**
|
|
91
|
+
```bash
|
|
92
|
+
sudo pacman -S mpv yt-dlp fzf ffmpeg python python-pip
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**macOS (Homebrew):**
|
|
96
|
+
```bash
|
|
97
|
+
brew install mpv yt-dlp fzf ffmpeg python
|
|
98
|
+
pip3 install jmoona-cli
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
**Windows (winget + pip):**
|
|
102
|
+
```powershell
|
|
103
|
+
winget install mpv.mpv yt-dlp.yt-dlp Git.Git Python.Python.3.12
|
|
104
|
+
pip install jmoona-cli
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## 🚀 Utilisation
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
jmoona # Lancer l'interface interactive
|
|
113
|
+
jmoona "Inception" # Recherche directe
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Navigation
|
|
117
|
+
- `↑↓` ou numéro — choisir dans les menus
|
|
118
|
+
- `Enter` — valider
|
|
119
|
+
- `q` ou `Ctrl+C` — quitter
|
|
120
|
+
- Dans mpv : `f` = plein écran, `←→` = avance/recul, `q` = quitter
|
|
121
|
+
|
|
122
|
+
### Modes de lecture
|
|
123
|
+
|
|
124
|
+
| Mode | Description |
|
|
125
|
+
|------|-------------|
|
|
126
|
+
| 🎵 **Auto** | FR si disponible, anglais sinon (recommandé) |
|
|
127
|
+
| 🔤 **VOSTFR** | Audio original + sous-titres français (idéal animés) |
|
|
128
|
+
| 🇬🇧 **VA** | Version originale sans sous-titres |
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## ⚙️ Configuration
|
|
133
|
+
|
|
134
|
+
Fichier de config par OS :
|
|
135
|
+
- **Linux** : `~/.config/jmoona/config.json`
|
|
136
|
+
- **macOS** : `~/Library/Application Support/jmoona/config.json`
|
|
137
|
+
- **Windows** : `%APPDATA%\jmoona\config.json`
|
|
138
|
+
|
|
139
|
+
Options disponibles :
|
|
140
|
+
|
|
141
|
+
```json
|
|
142
|
+
{
|
|
143
|
+
"player": "mpv",
|
|
144
|
+
"player_args": "--fs",
|
|
145
|
+
"quality": "best",
|
|
146
|
+
"provider": "auto",
|
|
147
|
+
"use_fzf": true,
|
|
148
|
+
"resume": true,
|
|
149
|
+
"download_dir": "~/Downloads/jmoona"
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## 🔧 Dépendances Python
|
|
156
|
+
|
|
157
|
+
```
|
|
158
|
+
requests>=2.28.0
|
|
159
|
+
selenium>=4.9.0
|
|
160
|
+
undetected-chromedriver>=3.5.0
|
|
161
|
+
pyvirtualdisplay>=3.0 # Linux uniquement
|
|
162
|
+
curl-cffi>=0.6.0
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## 📁 Structure du projet
|
|
168
|
+
|
|
169
|
+
```
|
|
170
|
+
jmoona-cli/
|
|
171
|
+
├── jmoona/
|
|
172
|
+
│ ├── app.py # Logique principale
|
|
173
|
+
│ ├── cli.py # Point d'entrée CLI
|
|
174
|
+
│ ├── extractor.py # Extraction des streams
|
|
175
|
+
│ ├── player.py # Lecture via mpv
|
|
176
|
+
│ ├── downloader.py # Téléchargement via yt-dlp
|
|
177
|
+
│ ├── subtitles.py # Sous-titres via OpenSubtitles
|
|
178
|
+
│ ├── tmdb.py # API TMDB
|
|
179
|
+
│ ├── ui.py # Interface terminal
|
|
180
|
+
│ ├── art.py # ASCII art
|
|
181
|
+
│ ├── language.py # Détection pistes audio
|
|
182
|
+
│ ├── storage.py # Historique & reprise
|
|
183
|
+
│ ├── config.py # Configuration cross-platform
|
|
184
|
+
│ └── providers.py # Liste des providers
|
|
185
|
+
├── pyproject.toml
|
|
186
|
+
├── install.sh # Installateur Linux/macOS
|
|
187
|
+
├── install.ps1 # Installateur Windows
|
|
188
|
+
├── Dockerfile
|
|
189
|
+
└── README.md
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## 🐛 Problèmes courants
|
|
195
|
+
|
|
196
|
+
**`mpv: command not found`** → Installez mpv (voir prérequis ci-dessus)
|
|
197
|
+
|
|
198
|
+
**Stream introuvable** → Essayez un autre provider : `jmoona` → paramètres → providers
|
|
199
|
+
|
|
200
|
+
**Chromium manquant** → Normal, Selenium est optionnel. Les streams HTTP fonctionnent sans.
|
|
201
|
+
|
|
202
|
+
**Windows : couleurs absentes** → Activez les couleurs ANSI : `Set-ItemProperty HKCU:\Console VirtualTerminalLevel 1`
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## 📄 Licence
|
|
207
|
+
|
|
208
|
+
MIT — fait avec ❤️ par jmoona
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.0.0"
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import os
|
|
3
|
+
from .ui import C, fzf_or_numbered, _strip_ansi, success, warn, error, clear_line, spinner
|
|
4
|
+
from .tmdb import tmdb_client
|
|
5
|
+
from .storage import load_config, add_history, get_history, get_bookmarks, add_bookmark, get_resume
|
|
6
|
+
from .extractor import extract
|
|
7
|
+
from .player import play
|
|
8
|
+
from .language import detect_tracks, get_lang_label
|
|
9
|
+
from .downloader import download
|
|
10
|
+
from .art import get_random_art
|
|
11
|
+
from .subtitles import fetch_subtitle
|
|
12
|
+
|
|
13
|
+
def format_item(item):
|
|
14
|
+
title = item.get("title") or item.get("name") or "Inconnu"
|
|
15
|
+
date = (item.get("release_date") or item.get("first_air_date") or "")[:4]
|
|
16
|
+
date_str = f"({date})" if date else ""
|
|
17
|
+
vt = item.get("vote_count", 0)
|
|
18
|
+
if vt > 1000: vt_str = f"{vt/1000:.1f}k"
|
|
19
|
+
else: vt_str = str(vt)
|
|
20
|
+
icon = "🎬" if item.get("media_type", "movie") == "movie" else "📺"
|
|
21
|
+
return f"{icon} {C.BOLD}{title}{C.RESET} {C.DIM}{date_str}{C.RESET} {C.YELLOW}★{item.get('vote_average', 0):.1f}{C.RESET} {C.DIM}{vt_str} votes{C.RESET}"
|
|
22
|
+
|
|
23
|
+
def print_details(item):
|
|
24
|
+
title = item.get("title") or item.get("name") or "Inconnu"
|
|
25
|
+
date = (item.get("release_date") or item.get("first_air_date") or "")[:4]
|
|
26
|
+
mtype = "🎬 Film" if item.get("media_type", "movie") == "movie" else "📺 Série"
|
|
27
|
+
overview = item.get("overview", "Aucune description.")
|
|
28
|
+
if len(overview) > 150:
|
|
29
|
+
overview = overview[:147] + "..."
|
|
30
|
+
|
|
31
|
+
print(f"\n{C.CYAN}════════════════════════════════════════════════════════════{C.RESET}")
|
|
32
|
+
print(f" {C.BOLD}{title}{C.RESET} ({date}) {mtype}")
|
|
33
|
+
print(f" {C.YELLOW}★{item.get('vote_average', 0):.1f}/10{C.RESET} | {item.get('vote_count', 0)} votes")
|
|
34
|
+
if "original_language" in item:
|
|
35
|
+
print(f" Langue originale: {get_lang_label(item['original_language'])}")
|
|
36
|
+
print(f"\n {C.DIM}{overview}{C.RESET}")
|
|
37
|
+
print(f"{C.CYAN}════════════════════════════════════════════════════════════{C.RESET}")
|
|
38
|
+
|
|
39
|
+
def select_media(results, config, prompt="Résultats"):
|
|
40
|
+
if not results:
|
|
41
|
+
warn("Aucun résultat.")
|
|
42
|
+
return None
|
|
43
|
+
return fzf_or_numbered(results, prompt, format_item, use_fzf=config.get("use_fzf", True))
|
|
44
|
+
|
|
45
|
+
def main_flow(query=None, args=None):
|
|
46
|
+
config = load_config()
|
|
47
|
+
if args and getattr(args, 'no_fzf', False):
|
|
48
|
+
config["use_fzf"] = False
|
|
49
|
+
|
|
50
|
+
if query:
|
|
51
|
+
spinner(f"Recherche de '{query}'...", art=get_random_art())
|
|
52
|
+
results = tmdb_client.search(query, media_type=getattr(args, 'type', 'multi') or 'multi')
|
|
53
|
+
clear_line()
|
|
54
|
+
item = select_media(results, config, "Choisir un titre")
|
|
55
|
+
if item: handle_item(item, args, config)
|
|
56
|
+
return
|
|
57
|
+
|
|
58
|
+
menu = [
|
|
59
|
+
("🎬 Chercher un film", lambda: handle_search("movie", args, config)),
|
|
60
|
+
("📺 Chercher une série", lambda: handle_search("tv", args, config)),
|
|
61
|
+
("🔥 Tendances", lambda: handle_list(tmdb_client.trending(), "Tendances", args, config)),
|
|
62
|
+
("⭐ Films populaires", lambda: handle_list(tmdb_client.popular("movie"), "Films Populaires", args, config)),
|
|
63
|
+
("📡 Séries populaires", lambda: handle_list(tmdb_client.popular("tv"), "Séries Populaires", args, config)),
|
|
64
|
+
("🏆 Mieux notés", lambda: handle_list(tmdb_client.top_rated("movie"), "Mieux Notés", args, config)),
|
|
65
|
+
("🕒 Historique", lambda: handle_history(args, config)),
|
|
66
|
+
("🔖 Favoris", lambda: handle_bookmarks(args, config)),
|
|
67
|
+
("👋 Quitter", lambda: sys.exit(0))
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
ASCII_LOGO = rf"""{C.MAGENTA}
|
|
71
|
+
_ _ _
|
|
72
|
+
(_) | (_)
|
|
73
|
+
_ _ __ ___ ___ ___ _ __ __ _ ___| |_
|
|
74
|
+
| | '_ ` _ \ / _ \ / _ \| '_ \ / _` |/ __| | |
|
|
75
|
+
| | | | | | | (_) | (_) | | | | (_| | (__| | |
|
|
76
|
+
| |_| |_| |_|\___/ \___/|_| |_|\__,_|\___|_|_|
|
|
77
|
+
_/ |
|
|
78
|
+
|__/ {C.RESET}{C.DIM}v1.0.0 - L'émulateur ultime de films et séries{C.RESET}
|
|
79
|
+
"""
|
|
80
|
+
if not query:
|
|
81
|
+
print("\033c", end="") # Clear screen
|
|
82
|
+
print(ASCII_LOGO)
|
|
83
|
+
print(f" {C.BOLD}{C.CYAN}Bienvenue sur votre émulateur de films et séries by jmoona.{C.RESET}\n")
|
|
84
|
+
print(get_random_art())
|
|
85
|
+
try:
|
|
86
|
+
input(f"\n {C.DIM}Appuyez sur Entrée pour continuer...{C.RESET}")
|
|
87
|
+
except (KeyboardInterrupt, EOFError):
|
|
88
|
+
sys.exit(0)
|
|
89
|
+
print("\033c", end="") # Clear screen again before menu
|
|
90
|
+
print(ASCII_LOGO)
|
|
91
|
+
print(f" {C.BOLD}{C.CYAN}Menu Principal{C.RESET}\n")
|
|
92
|
+
|
|
93
|
+
choice = fzf_or_numbered(menu, "Menu principal", lambda x: x[0], use_fzf=config.get("use_fzf", True))
|
|
94
|
+
if choice:
|
|
95
|
+
choice[1]()
|
|
96
|
+
|
|
97
|
+
def handle_search(mtype, args, config):
|
|
98
|
+
q = input(f"\nRecherche ({'Film' if mtype == 'movie' else 'Série'}) : ").strip()
|
|
99
|
+
if q:
|
|
100
|
+
spinner("Recherche...", art=get_random_art())
|
|
101
|
+
results = tmdb_client.search(q, media_type=mtype)
|
|
102
|
+
clear_line()
|
|
103
|
+
item = select_media(results, config, "Résultats")
|
|
104
|
+
if item:
|
|
105
|
+
item["media_type"] = mtype
|
|
106
|
+
handle_item(item, args, config)
|
|
107
|
+
|
|
108
|
+
def handle_list(items, title, args, config):
|
|
109
|
+
item = select_media(items, config, title)
|
|
110
|
+
if item:
|
|
111
|
+
if "media_type" not in item:
|
|
112
|
+
item["media_type"] = "tv" if "first_air_date" in item else "movie"
|
|
113
|
+
handle_item(item, args, config)
|
|
114
|
+
|
|
115
|
+
def handle_history(args, config):
|
|
116
|
+
history = get_history()
|
|
117
|
+
if not history:
|
|
118
|
+
warn("Historique vide.")
|
|
119
|
+
return
|
|
120
|
+
item = select_media(history, config, "Historique")
|
|
121
|
+
if item: handle_item(item, args, config)
|
|
122
|
+
|
|
123
|
+
def handle_bookmarks(args, config):
|
|
124
|
+
bookmarks = get_bookmarks()
|
|
125
|
+
if not bookmarks:
|
|
126
|
+
warn("Favoris vides.")
|
|
127
|
+
return
|
|
128
|
+
item = select_media(bookmarks, config, "Favoris")
|
|
129
|
+
if item: handle_item(item, args, config)
|
|
130
|
+
|
|
131
|
+
def handle_item(item, args, config):
|
|
132
|
+
if "media_type" not in item:
|
|
133
|
+
item["media_type"] = "tv" if "first_air_date" in item else "movie"
|
|
134
|
+
|
|
135
|
+
print_details(item)
|
|
136
|
+
opts = ["▶ Regarder", "⬇ Télécharger", "🔖 Ajouter aux favoris", "🔙 Retour"]
|
|
137
|
+
choice = fzf_or_numbered(opts, "Action", lambda x: x, use_fzf=config.get("use_fzf", True))
|
|
138
|
+
|
|
139
|
+
if not choice or "Retour" in choice:
|
|
140
|
+
return
|
|
141
|
+
|
|
142
|
+
if "Favoris" in choice:
|
|
143
|
+
add_bookmark(item)
|
|
144
|
+
success("Ajouté aux favoris.")
|
|
145
|
+
return
|
|
146
|
+
|
|
147
|
+
season, episode = 1, 1
|
|
148
|
+
if item["media_type"] == "tv":
|
|
149
|
+
if args and getattr(args, 'season', None) and getattr(args, 'episode', None):
|
|
150
|
+
season, episode = args.season, args.episode
|
|
151
|
+
else:
|
|
152
|
+
try:
|
|
153
|
+
s_str = input("Saison (défaut=1) : ").strip()
|
|
154
|
+
if s_str: season = int(s_str)
|
|
155
|
+
e_str = input("Épisode (défaut=1) : ").strip()
|
|
156
|
+
if e_str: episode = int(e_str)
|
|
157
|
+
except ValueError:
|
|
158
|
+
pass
|
|
159
|
+
|
|
160
|
+
provider = args.provider if args and getattr(args, 'provider', None) else config.get("provider", "auto")
|
|
161
|
+
quality = args.quality if args and getattr(args, 'quality', None) else config.get("quality", "best")
|
|
162
|
+
|
|
163
|
+
# Mode de lecture (simple)
|
|
164
|
+
is_anime = item.get("original_language") in ("ja", "ko", "zh")
|
|
165
|
+
lang_menu = [
|
|
166
|
+
("🎵 Auto — FR si disponible, sinon VO (recommandé)", "auto"),
|
|
167
|
+
("🔤 VOSTFR — VO + sous-titres français", "vostfr"),
|
|
168
|
+
("🇬🇧 VA — Version originale uniquement", "va"),
|
|
169
|
+
]
|
|
170
|
+
lang_choice = fzf_or_numbered(lang_menu, "Mode de lecture", lambda x: x[0], use_fzf=config.get("use_fzf", True))
|
|
171
|
+
lang_mode = lang_choice[1] if lang_choice else ("vostfr" if is_anime else "auto")
|
|
172
|
+
|
|
173
|
+
stream_url, _ = extract(item["id"], item["media_type"], season, episode, quality=quality, lang="en", provider=provider)
|
|
174
|
+
|
|
175
|
+
if not stream_url:
|
|
176
|
+
error("Impossible de trouver un flux vidéo.")
|
|
177
|
+
return
|
|
178
|
+
|
|
179
|
+
# Audio language preference for mpv (no slow track scan needed)
|
|
180
|
+
audio_lang_mpv = {
|
|
181
|
+
"auto": "fr,en", # mpv picks FR if available, falls back to EN
|
|
182
|
+
"va": "en",
|
|
183
|
+
"vostfr": "en", # keep original audio, subs handle French
|
|
184
|
+
}.get(lang_mode, "en")
|
|
185
|
+
|
|
186
|
+
# Subtitles — VOSTFR only
|
|
187
|
+
sub_file = None
|
|
188
|
+
if lang_mode == "vostfr":
|
|
189
|
+
spinner("Recherche de sous-titres français ...", art=get_random_art())
|
|
190
|
+
sub_file = fetch_subtitle(item["id"], item["media_type"], lang="fr", season=season, episode=episode)
|
|
191
|
+
clear_line()
|
|
192
|
+
if sub_file:
|
|
193
|
+
success(f"✓ VOSTFR — {os.path.basename(sub_file)}")
|
|
194
|
+
else:
|
|
195
|
+
warn("Aucun sous-titre FR disponible sur OpenSubtitles — lecture en VO.")
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
item["episode"] = episode
|
|
199
|
+
add_history(item)
|
|
200
|
+
|
|
201
|
+
if "Télécharger" in choice:
|
|
202
|
+
out_dir = os.path.expanduser(config.get("download_dir", "~/Downloads/jmoona"))
|
|
203
|
+
title = item.get("title") or item.get("name")
|
|
204
|
+
if item["media_type"] == "tv":
|
|
205
|
+
title += f" S{season:02d}E{episode:02d}"
|
|
206
|
+
|
|
207
|
+
download(stream_url, title, out_dir, quality=quality,
|
|
208
|
+
audio_lang=audio_lang_mpv or "en",
|
|
209
|
+
sub_path=sub_file)
|
|
210
|
+
else:
|
|
211
|
+
rkey = f"{item['media_type']}_{item['id']}"
|
|
212
|
+
if item["media_type"] == "tv":
|
|
213
|
+
rkey += f"_s{season}e{episode}"
|
|
214
|
+
|
|
215
|
+
resume_pos = get_resume(rkey)
|
|
216
|
+
|
|
217
|
+
play(
|
|
218
|
+
stream_url,
|
|
219
|
+
title=(item.get("title") or item.get("name")),
|
|
220
|
+
player=config.get("player", "mpv"),
|
|
221
|
+
player_args=config.get("player_args", "--fs"),
|
|
222
|
+
audio_lang=audio_lang_mpv,
|
|
223
|
+
sub_path=sub_file,
|
|
224
|
+
resume_pos=resume_pos
|
|
225
|
+
)
|