Merge branch 'cleanAndCustom'

This commit is contained in:
Grizouille
2025-11-10 14:05:36 +01:00
9 changed files with 303 additions and 109 deletions

199
README.md
View File

@@ -1,33 +1,185 @@
# 🎵 Deezer Downloader for Nextcloud
# 🎵 Deezer Downloader for Nextcloud WebDav
## 🧩 Description
Ce projet a pour objectif dévoluer en une **application Nextcloud**.
Pour linstant, il sagit dune **application externe**.
Pour linstant, il sagit dune **application externe utilasant WEBDAV **.
Le projet est basé sur le dépôt GitHub de [deezer-downloader](https://github.com/kmille/deezer-downloader).
Un grand merci aux auteurs pour leur excellent travail !
## ❓ Pourquoi
Je souhaitais intégrer à **Nextcloud** un **raccourci** permettant aux utilisateurs de **télécharger leur musique, playlists, albums**, etc., directement depuis **Deezer**.
Je souhaitais intégrer à **Nextcloud** un **raccourci** permettant aux utilisateurs de **télécharger leur musique, playlists, albums**, etc., directement depuis **Nextcloud**.
Les téléchargement sont directement accessible des **Fichiers** de Nextcloud. Vous pouvez aussi choisir un répertoire situé dans le dossier de syncronisation de l'application interne **Musique** pour profiter directement du téléchargement sur une appli de streaming style subsonic...
## 🔧 Modifications apportées par rapport au dépôt original
* 🔐 Utilisation du **JWT de Nextcloud** pour obtenir les informations didentification de lutilisateur connecté.
* 📂 Téléchargement des fichiers dans le **dossier personnel** de lutilisateur connecté, dans un répertoire créé à la racine : `DeezerDl`.
* 🍪 Utilisation dun **cookie** pour sauvegarder le répertoire de destination choisi par lutilisateur.
* 📂 Téléchargement des fichiers dans le **dossier personnel** de lutilisateur connecté, dans un répertoire créé à la racine : `Music/DeezerDl`.
* 🚫 Vérification de la **connexion à Nextcloud** : si non connecté → retour **403 Forbidden**.
## ⚠️ Limites actuelles
* 💡 Fonctionne uniquement avec le plugin **AppExterne**.
* 👥 Pas de gestion de **sessions utilisateurs séparées** :
il est possible que le répertoire de destination ne soit pas correct si deux utilisateurs téléchargent simultanément.
Cependant, chaque utilisateur ayant son propre cookie, cela **devrait fonctionner correctement** dans la plupart des cas.
# Installation sur NextCloud (Debian 12)
## Dans le container DeezerDownloader
### Mettre à jour le container
```
apt update
apt upgrade
```
### Créer le point de montage où son stoké les données Nextcloud.
J'ai créé un point de montage directement vers les fichiers de nextcloud:
`mp0: /Data/NAS/nextcloud/data_directory/data/,mp=/nextcloud_user_data`
### Créer un utilisateur `www-data` pour faire tourner le service.
Verifier si deja existant: `id www-data`
Si vous obtenez des informations sur lutilisateur et le groupe, il existe déjà.
Sinon, vous aurez un message du type no such user.
Créer lutilisateur et le groupe `www-data`:
```
groupadd www-data
useradd -r -s /usr/sbin/nologin -g www-data www-data
```
### Donner à l'utilisateur `www-data` le meme `uid` user et group que celui de `www-data` du contenair Nextcloud.
```
ls -ln /nextcloud_user_data/
> drwxr-xr-x 5 100033 100033 5 Oct 19 14:57 Parent
```
`100033 100033` est l uid et le gid
```
usermod -u 100033 www-data
groupmod -g 100033 www-data
```
Résultat:
`id www-data
uid=100033(www-data) gid=100033(www-data) groups=100033(www-data)`
## Dev sous windows (vsCode):
### Installer `python` et le `venv`
```
apt install -y python3 python3-venv python3-pip sudo ffmpeg
```
Créer le `venv` pour `www-data`
```
mkdir -p /var/www/venv
chown www-data:www-data /var/www/venv
chmod 750 /var/www/venv
sudo -u www-data python3 -m venv /var/www/venv
sudo -u www-data bash
source /var/www/venv/bin/activate
python --version
pip --version
deactivate
```
**Pour désactiver le venv** `deactivate`
### Installer le site DeezerDownloader
```
sudo mkdir -p /var/www/app
sudo chown www-data:www-data /var/www/app
apt install git
sudo -u www-data git clone -b cleanAndCustom --single-branch https://git.bonisco.fr/Grizouille/Deezer-Downloader-Nextcloud.git /var/www/app
```
#### Installer les dépandences:
```
sudo -u www-data /var/www/venv/bin/pip install /var/www/app
sudo -u www-data /var/www/venv/bin/pip install yt-dlp
sudo -u www-data /var/www/venv/bin/pip install paramiko
sudo -u www-data /var/www/venv/bin/pip install PyJWT
```
#### Ajouter le fichier de config
```
sudo -u www-data bash
source /var/www/venv/bin/activate
deezer-downloader --show-config-template > /var/www/app/config.ini
deactivate
```
Rester sous `www-data` et aller éditer le fichier `config.ini`, les explication son à l'interieur...
### Créer le service pour qu'il soit autonome
```
nano /etc/systemd/system/deezer_downloader.service
[Unit]
Description=Deezer Downloader Service
After=network.target
[Service]
# Répertoire de travail (là où est ton config.ini)
WorkingDirectory=/var/www/app
# Commande de lancement (binaire du venv)
ExecStart=/var/www/venv/bin/deezer-downloader --config /var/www/app/config.ini
# Redémarrage automatique en cas derreur
Restart=on-failure
# Exécution sous root (puisque ton venv est dans /root)
User=www-data
Group=www-data
# Variables denvironnement
Environment="PATH=/var/www/venv/bin:/usr/bin"
Environment=PYTHONUNBUFFERED=1
[Install]
WantedBy=multi-user.target
```
Puis l'activer et le lancer:
```
sudo systemctl daemon-reload
sudo systemctl enable deezer_downloader.service
sudo systemctl start deezer_downloader.service
sudo journalctl -u deezer_downloader.service -f
```
### Configurer les settings
Dans le container nextcloud:
```
adduser nextclouddeezer
apt install sudo
nano visudo
```
ajouter en fin de fichier la ligne:
```
nextclouddeezer ALL=(www-data) NOPASSWD: /usr/bin/php
```
cela permet d'executer des command php avec l'utilisateur `nextclouddeezer`
Dans le confg.ini dans la container deezer-dowloader, ajouter le mdp de l'utilisateur ue vous venez de dréer.
# Dev sous windows (vsCode):
Pour que powershell accepte d'exécuter des scripts:
```
@@ -52,28 +204,3 @@ run du srv en ligne de cmd:
```
poetry run deezer-downloader --config .\deezer_downloader\settings.ini
```
# Installation sur NextCloud (Debian 12)
Dans le container nextcloud:
```
adduser nextclouddeezer
```
installer `sudo`, puis taper `visudo`,
ajouter en fin de fichier la ligne:
```
nextclouddeezer ALL=(www-data) NOPASSWD: /usr/bin/php
```
cela permet d'executer des command php avec l'utilisateur `nextclouddeezer`
Dans le container DeezerDownloader
* Créer le point de montage où son stoké les données Nextcloud.
* Créer un utilisateur `www-data` pour faire tourner le service.
* Donner à l'utilisateur `www-data` le meme `uid` user et group que celui de `www-data` du contenair Nextcloud.
* `ls -ln /nextcloud_user_data/`
* Installer `python` et le `venv`
* Installer le site DeezerDownloader
* Créer le service pour qu'il soit autonome
* Configurer les settings

View File

@@ -10,13 +10,13 @@ port = 6600
music_dir_root = /tmp/deezer-downloader
[download_dirs]
base = /tmp/deezer-downloader
base = /nextcloud_user_data
songs = %(base)s/songs
albums = %(base)s/albums
zips = %(base)s/zips
playlists = %(base)s/playlists
youtubedl = %(base)s/youtube-dl
songs = %(base)s/{uid}/files/Music/DeezerDl/songs
albums = %(base)s/{uid}/files/Music/DeezerDl/albums
zips = %(base)s/{uid}/files/Music/DeezerDl/zips
playlists = %(base)s/{uid}/files/Music/DeezerDl/playlists
youtubedl = %(base)s/{uid}/files/Music/DeezerDl/youtube-dl
[debug]
; debug output used for /debug
@@ -54,7 +54,7 @@ quality = mp3
[youtubedl]
; you are responsible for keeping yt-dlp up-to-date (https://github.com/yt-dlp/yt-dlp)
; command = /home/kmille/projects/deezer-downloader/app/venv/bin/yt-dlp
command = /usr/bin/yt-dlp
command = /var/www/venv/bin/yt-dlp
[nextcloud]
@@ -67,13 +67,13 @@ jwt_name_parameter = jwt
[nextcloud_ssh_scan_files]
; ip from nextcloud server
host = 192.168.10.9
host = 192.168.1..
; port ssh from nextcloud server
port = 22
; username ssh nextcloud server
username = nextclouddeezer
; password ssh nextcloud server
password = Q8QbqQru3hRc16vK
password = dqflfgkfgmvgv615vs
; occ path nextcloud server
occ_path = /var/www/nextcloud/occ

Binary file not shown.

View File

@@ -2,13 +2,9 @@
import os
from subprocess import Popen, PIPE
from functools import wraps
import requests
import atexit
from flask import Flask, render_template, request, jsonify, session
from markupsafe import escape
from flask_autoindex import AutoIndex
import warnings
import giphypop
from deezer_downloader.configuration import config
from deezer_downloader.web.music_backend import sched
@@ -17,11 +13,6 @@ from deezer_downloader.nextcloud import addJwtInUserSession
app = Flask(__name__)
app.secret_key = "vilvhmqerjgùqrgojpùqjgvnùzevoijrpùvqpzejgijzepgùg"
auto_index = AutoIndex(app, config["download_dirs"]["base"], add_url_rules=False)
auto_index.add_icon_rule('music.png', ext='m3u8')
warnings.filterwarnings("ignore", message="You are using the giphy public api key")
giphy = giphypop.Giphy()
def init():
@@ -110,21 +101,6 @@ def show_debug():
return jsonify({'debug_msg': stdout.decode()})
@app.route("/downloads/")
@app.route("/downloads/<path:path>")
def autoindex(path="."):
# directory index - flask version (let the user download mp3/zip in the browser)
try:
gif = giphy.random_gif(tag="cat")
media_url = gif.media_url
except requests.exceptions.HTTPError:
# the api is rate-limited. Fallback:
media_url = "https://cataas.com/cat"
template_context = {'gif_url': media_url}
return auto_index.render_autoindex(path=session['user_base_dir'], template_context=template_context)
@app.route('/queue', methods=['GET'])
def show_queue():
"""
@@ -190,7 +166,7 @@ def deezer_download_song_or_album():
@app.route('/youtubedl', methods=['POST'])
@validate_schema("url", "add_to_playlist")
@validate_schema("url", "add_to_playlist", "getVideo")
def youtubedl_download():
"""
takes an url and tries to download it via youtuble-dl
@@ -203,7 +179,8 @@ def youtubedl_download():
task = sched.enqueue_task(desc, "download_youtubedl_and_queue",
video_url=user_input['url'],
add_to_playlist=user_input['add_to_playlist'],
uid_user=session['user_uid'])
uid_user=session['user_uid'],
video=user_input['getVideo'])
return jsonify({"task_id": id(task), })

View File

@@ -232,11 +232,12 @@ def download_spotify_playlist_and_queue_and_zip(playlist_name, playlist_id, add_
@sched.register_command()
def download_youtubedl_and_queue(video_url, add_to_playlist, uid_user):
def download_youtubedl_and_queue(video_url, add_to_playlist, uid_user, video=False):
check_and_set_download_dirs_exist(uid_user)
filename_absolute = youtubedl_download(video_url,
config["download_dirs"]["youtubedl"],
config["proxy"]["server"])
config["proxy"]["server"],
video)
call_scan(filename_absolute)
update_mpd_db(filename_absolute, add_to_playlist)
return make_song_paths_relative_to_mpd_root([filename_absolute])

View File

@@ -2,6 +2,7 @@
display: flex;
height: 38px;
border: 1px solid #ced4da;
background-color: white;
border-radius: 7px;
padding: 0 8px;
}
@@ -13,12 +14,12 @@ input.search {
border: none;
outline: none;
background-color: transparent;
color: white !important;
color: black !important;
}
button.search {
height: 100%;
color: white;
color: #6c757d;
font-size: 14px;
border: none;
background-color: transparent;
@@ -84,3 +85,15 @@ h3, th, td {
.btn {
color: white !important;
}
form {
background-color: #292929;
border-radius: 8px;
padding: 30px;
max-width: 500px;
margin: 60px auto;
box-shadow: 0 0 15px rgba(0,0,0,0.5);
}
form label {
color: white;
}

View File

@@ -41,7 +41,16 @@ $(document).ready(function() {
function youtubedl_download(add_to_playlist) {
$.post(deezer_downloader_api_root + '/youtubedl',
JSON.stringify({ url: $('#youtubedl-query').val(), add_to_playlist: add_to_playlist }),
JSON.stringify({ url: $('#youtubedl-query').val(), add_to_playlist: add_to_playlist, getVideo: false }),
function(data) {
console.log(data);
$.jGrowl("As you wish", { life: 4000 });
});
}
function youtubedl_download_video(add_to_playlist) {
$.post(deezer_downloader_api_root + '/youtubedl',
JSON.stringify({ url: $('#youtubedl-query-video').val(), add_to_playlist: add_to_playlist, getVideo: true}),
function(data) {
console.log(data);
$.jGrowl("As you wish", { life: 4000 });
@@ -238,6 +247,10 @@ $(document).ready(function() {
youtubedl_download(false);
});
$("#yt_download_video").click(function() {
youtubedl_download_video(false);
});
$("#yt_download_play").click(function() {
youtubedl_download(true);
});
@@ -306,6 +319,14 @@ $(document).ready(function() {
$("#" + id_content).addClass("active show")
}
$('#youtubedl-query-video').on('keypress', function(e) {
if (e.which === 13) {
e.preventDefault();
$('#yt_download_video').click();
}
});
var bbody = document.getElementById('body');
bbody.onkeydown = function (event) {

View File

@@ -46,7 +46,7 @@
<a class="nav-link active" id="nav-songs-albums" data-toggle="tab" href="#songs_albums">Songs/Albums (1)</a>
</li>
<li class="nav-item">
<a class="nav-link" id="nav-youtubedl" data-toggle="tab" href="#youtubedl">Youtube-dl (2)</a>
<a class="nav-link" id="nav-youtubedl" data-toggle="tab" href="#youtubedl">Audio/Video downloader (2)</a>
</li>
<li class="nav-item">
<a class="nav-link" id="nav-spotify-playlists" data-toggle="tab" href="#spotify-playlists">Spotify Playlists (3)</a>
@@ -103,18 +103,40 @@
<div id="youtubedl" class="container tab-pane fade">
<br>
<h3>Download stuff via youtube-dl</h3>
<h3>Download audio files via youtube-dl</h3>
<div class="input-group">
<input type="text" class="form-control" id="youtubedl-query" placeholder="Download audio from YouTube, Invidious, Vimeo, Soundcloud, YouPorn, ... " />
&nbsp;
</div>
<input type="text" class="form-control" id="youtubedl-query" placeholder="Download audio from YouTube, Invidious, Vimeo, Soundcloud, ... " />
</div>
<br>
<span class="input-group-btn">
<button type="button" class="btn btn-info" id="yt_download_play">Download & Play</button>
<button type="button" class="btn btn-info" id="yt_download">Download</button>
<button type="button" class="btn btn-info" onclick="$('#youtubedl-query').val('')" >Clear</button>
</span>
</div> <!-- end div tab youtube-dl -->
<span class="input-group-btn">
<button type="button" class="btn btn-info" id="yt_download_play" title="Download & Play Audio">
<i class="fa fa-play"></i> Download & Play
</button>
<button type="button" class="btn btn-info" id="yt_download" title="Download Audio">
<i class="fa fa-download"></i> Download Audio
</button>
<button type="button" class="btn btn-info" onclick="$('#youtubedl-query').val('')" title="Clear">
<i class="fa fa-times"></i> Clear
</button>
</span>
<br/>
<br/>
<h3>Download video files via youtube-dl</h3>
<div class="input-group">
<input type="text" class="form-control" id="youtubedl-query-video" placeholder="Download video from YouTube, Invidious, Vimeo, Soundcloud, ... " />
</div>
<br>
<span class="input-group-btn">
<button type="button" class="btn btn-info" id="yt_download_video" title="Download Video">
<i class="fa fa-download"></i> Download Video
</button>
<button type="button" class="btn btn-info" onclick="$('#youtubedl-query-video').val('')" title="Clear">
<i class="fa fa-times"></i> Clear
</button>
</span>
</div>
<!-- end div tab youtube-dl -->
<div id="spotify-playlists" class="container tab-pane fade">
<br>
@@ -128,10 +150,18 @@
<br>
<div class="input-group">
<span class="input-group-btn">
<button type="button" class="btn btn-info" id="spotify_download_play">Download & Play</button>
<button type="button" class="btn btn-info" id="spotify_download">Download</button>
<button type="button" class="btn btn-info" id="spotify_zip">Give me a zip</button>
<button type="button" class="btn btn-info" onclick="$('input[id^=\'spotify\']').val('')" >Clear</button>
<button type="button" class="btn btn-info" id="spotify_download_play" title="Download & Play">
<i class="fa fa-play"></i> Download & Play
</button>
<button type="button" class="btn btn-info" id="spotify_download" title="Download">
<i class="fa fa-download"></i> Download
</button>
<button type="button" class="btn btn-info" id="spotify_zip" title="Give me a zip">
<i class="fa fa-file-archive-o"></i> Give me a zip
</button>
<button type="button" class="btn btn-info" onclick="$('input[id^=\'spotify\']').val('')" title="Clear">
<i class="fa fa-times"></i> Clear
</button>
</span>
</div>
</div> <!-- end div tab spotify playlists -->
@@ -146,10 +176,18 @@
<br>
<div class="input-group">
<span class="input-group-btn">
<button type="button" class="btn btn-info" id="deezer_playlist_download_play">Download & Play</button>
<button type="button" class="btn btn-info" id="deezer_playlist_download">Download</button>
<button type="button" class="btn btn-info" id="deezer_playlist_zip">Give me a zip</button>
<button type="button" class="btn btn-info" onclick="$('#deezer-playlist-url').val('')" >Clear</button>
<button type="button" class="btn btn-info" id="deezer_playlist_download_play" title="Download & Play">
<i class="fa fa-play"></i> Download & Play
</button>
<button type="button" class="btn btn-info" id="deezer_playlist_download" title="Download">
<i class="fa fa-download"></i> Download
</button>
<button type="button" class="btn btn-info" id="deezer_playlist_zip" title="Give me a zip">
<i class="fa fa-file-archive-o"></i> Give me a zip
</button>
<button type="button" class="btn btn-info" onclick="$('#deezer-playlist-url').val('')" title="Clear">
<i class="fa fa-times"></i> Clear
</button>
</span>
</div>
@@ -162,10 +200,18 @@
<br>
<div class="input-group">
<span class="input-group-btn">
<button type="button" class="btn btn-info" id="deezer_favorites_download_play">Download & Play</button>
<button type="button" class="btn btn-info" id="deezer_favorites_download">Download</button>
<button type="button" class="btn btn-info" id="deezer_favorites_zip">Give me a zip</button>
<button type="button" class="btn btn-info" onclick="$('#deezer-favorites-userid').val('')" >Clear</button>
<button type="button" class="btn btn-info" id="deezer_favorites_download_play" title="Download & Play">
<i class="fa fa-play"></i> Download & Play
</button>
<button type="button" class="btn btn-info" id="deezer_favorites_download" title="Download">
<i class="fa fa-download"></i> Download
</button>
<button type="button" class="btn btn-info" id="deezer_favorites_zip" title="Give me a zip">
<i class="fa fa-file-archive-o"></i> Give me a zip
</button>
<button type="button" class="btn btn-info" onclick="$('#deezer-favorites-userid').val('')" title="Clear">
<i class="fa fa-times"></i> Clear
</button>
</span>
</div>
@@ -188,6 +234,7 @@
</table>
</div> <!-- end div tab queue -->
<footer class="footer">
<div class="container">
<span class="text-muted">

View File

@@ -32,7 +32,7 @@ def get_absolute_filename(stdout, stderr):
return regex_foo.group(1)
def youtubedl_download(url, destination_dir, proxy=None):
def youtubedl_download(url, destination_dir, proxy=None, video=False):
# url, e.g. https://www.youtube.com/watch?v=ZbZSe6N_BXs
# destination_dir: /tmp/
# proxy: https/socks5 proxy (e. g. socks5://user:pass@127.0.0.1:1080/)
@@ -42,14 +42,22 @@ def youtubedl_download(url, destination_dir, proxy=None):
# DownloadedFileNotFoundException if we cannot get the converted output file from youtube-dl with a regex
proxy_command = f" --proxy {proxy}" if proxy else ""
youtube_dl_cmd = config["youtubedl"]["command"] + \
proxy_command + \
" -x --audio-format mp3 " + \
"--audio-quality 0 " + \
f"-o '{destination_dir}/%(title)s.%(ext)s' " + \
"--embed-metadata " + \
"--no-embed-chapters " + \
quote(url)
if video:
youtube_dl_cmd = config["youtubedl"]["command"] + \
proxy_command + \
" -f \"best\" " + \
"--merge-output-format mp4 " + \
f"-o \"{destination_dir}/%(title)s.%(ext)s\" " + \
quote(url)
else:
youtube_dl_cmd = config["youtubedl"]["command"] + \
proxy_command + \
" -x --audio-format mp3 " + \
"--audio-quality 0 " + \
f"-o \"{destination_dir}/%(title)s.%(ext)s\" " + \
"--embed-metadata " + \
"--no-embed-chapters " + \
quote(url)
filename_absolute = execute(youtube_dl_cmd)
return filename_absolute