Downloader Studio
DownloaderStudio

Documentation développeur

Comprendre, construire et publier Downloader Studio

Cette documentation couvre l'architecture réelle du code, le fonctionnement interne de l'application, les dépendances utilisées et le processus de build multiplateforme. Utilisez le menu à gauche pour naviguer par section.

PyQt5 5.15 Python 3.10 – 3.13 yt-dlp MIT + Commons Clause

01Vue d'ensemble

Downloader Studio est une application de bureau écrite en Python avec PyQt5 pour l'interface graphique et yt-dlp comme moteur d'extraction/téléchargement. Elle permet de rechercher des médias, de les mettre en file d'attente et de les télécharger en audio ou vidéo, avec post-traitement FFmpeg pour les conversions de format.

Points clés

  • Interface à onglets latéraux : Recherche, File d'attente, Paramètres, Logs.
  • Mode flottant minimaliste activable avec Ctrl+M.
  • Thèmes clair et sombre appliqués globalement via une feuille de styles Qt générée en Python.
  • Interface traduite en 4 langues (FR, EN, DE, ES), reconstruite dynamiquement au changement de langue.
  • Paramètres persistés en JSON dans le dossier de données utilisateur.

Le point d'entrée est main.py : il instancie QApplication, affiche un splash screen, puis construit la fenêtre principale Downloader définie dans app/ui/main_window.py.

02Architecture du projet

Arborescence des fichiers

downloader-studio/
├── main.py                        # Point d'entrée (QApplication, splash screen)
├── config.py                      # Constantes globales (version, chemins, langues)
├── requirements.txt                # Dépendances runtime
├── app/
│   ├── signals.py                  # SignalManager global (ex: language_changed)
│   ├── core/
│   │   ├── download_engine.py      # File d'attente + intégration yt-dlp
│   │   └── update_checker.py       # Vérification de mise à jour via GitHub Releases
│   ├── ui/
│   │   ├── main_window.py          # Fenêtre principale, sidebar, thèmes, settings
│   │   ├── minimal_window.py        # Mode flottant compact (Ctrl+M)
│   │   └── pages/
│   │       ├── search_page.py      # Recherche + résultats + ajout à la file
│   │       ├── queue_page.py       # File d'attente, progression, historique
│   │       ├── settings_page.py    # Langue, thème, sources, formats, qualité
│   │       └── logs_page.py        # Journal d'activité + export
│   └── utils/
│       ├── icons.py                # Icônes SVG générées à la volée (QIcon)
│       └── resources.py            # Résolution de chemins (dev / PyInstaller / Inno)
├── i18n/
│   ├── __init__.py                 # Façade: t(), set_language(), get_translator()
│   └── translations.py             # Dictionnaire TRANSLATIONS + classe Translator
├── assets/                         # Logo, icônes, ressources visuelles
├── docs/                           # Ce site (landing page + documentation)
├── packaging/
│   ├── build_exe.py                 # Script de build complet (PyInstaller + Inno Setup)
│   └── Downloader_Studio.iss        # Script Inno Setup (installateur Windows)

Séparation des responsabilités

L'application suit une séparation classique UI / logique métier :

  • UI (app/ui/) : widgets PyQt5, mise en page, thèmes, événements utilisateur. Ne contient aucune logique de téléchargement directe.
  • Core (app/core/) : DownloadEngine gère la file d'attente et orchestre des DownloadWorker (QThread) qui appellent yt-dlp. Aucune dépendance à PyQt Widgets, uniquement à QThread/pyqtSignal pour communiquer avec l'UI de façon thread-safe.
  • i18n (i18n/) : dictionnaire de chaînes centralisé, aucune chaîne UI n'est codée en dur dans les widgets.
  • Utils (app/utils/) : fonctions transverses (icônes, résolution de chemins) sans état.

Résolution des chemins (resources.py)

resource_path() gère trois contextes d'exécution différents pour que le code fonctionne identiquement en dev et une fois packagé :

ContexteDétectionBase de chemin utilisée
Script Python normalsys.frozen absentRacine du dépôt (3 niveaux au-dessus du fichier)
Exécutable PyInstaller (--onefile)sys.frozen et sys._MEIPASSDossier d'extraction temporaire PyInstaller
Exécutable installé (Inno Setup / onedir)sys.frozen sans _MEIPASSDossier contenant l'exécutable

03Fonctionnement de l'application

Cycle de vie du téléchargement

1. Recherche

search_page.py appelle DownloadEngine.search_youtube() ou search_soundcloud(), qui interrogent yt-dlp en mode extraction seule (ytsearch10: / scsearch10:).

2. File d'attente

Chaque résultat sélectionné devient un DownloadTask (URL, format, qualité). add_task() refuse les doublons déjà en file ou en cours.

3. Téléchargement

_process_next() lance un DownloadWorker (QThread) par tâche, séquentiellement. Il configure les options yt-dlp selon le format demandé.

4. Post-traitement

Pour l'audio (MP3/WAV/M4A), un postprocessor FFmpeg extrait et convertit la piste. Pour la vidéo, un format combiné vidéo+audio est sélectionné selon la qualité choisie.

Sources supportées

L'interface expose deux cases à cocher de recherche : YouTube et SoundCloud. Le moteur reconnaît en plus Twitch et TikTok par simple collage d'URL (pas de recherche par mot-clé pour ces deux sources) ; la détection se fait par correspondance de domaine dans search_page.py.

SourceRecherche par mot-cléCollage d'URL directe
YouTubeOuiOui
SoundCloudOuiOui
TwitchNonOui
TikTokNonOui

Résolution des lecteurs intégrés (iframe)

Certains sites "portail" n'exposent pas directement une page lisible par yt-dlp : la vidéo réelle vit dans un <iframe>. Quand yt-dlp échoue avec une erreur Unsupported URL, resolve_embedded_media_url() télécharge la page HTML, cherche le premier src d'iframe qui ressemble à un lecteur, puis retente le téléchargement sur cette URL résolue.

Formats et qualité

FormatTypeTraitement
mp3AudioExtraction FFmpeg, qualité configurable (ex. 320 kbps)
wavAudioExtraction FFmpeg, qualité maximale (sans perte)
m4aAudioExtraction FFmpeg, qualité configurable
mp4VidéoSélection du meilleur flux vidéo+audio selon la résolution max choisie (480p à 4K)

Mode minimaliste

Ctrl+M bascule vers minimal_window.py : une fenêtre flottante compacte qui garde l'essentiel (coller une URL, lancer le téléchargement, ouvrir le dossier de sortie) sans afficher l'interface complète.

Persistance des paramètres

À la fermeture, save_app_settings() écrit un fichier JSON contenant le thème, la langue, le dossier de sortie, les sources activées, les formats activés et les préférences de qualité. Il est relu au démarrage par load_app_settings().

Clé JSONRôle
themeThème actif (dark ou light)
languageLangue active de l'interface
output_dirDossier de destination des téléchargements
sources_enabledSources de recherche activées/désactivées
enabled_formatsFormats proposés dans l'UI
quality / audio_qualityQualité vidéo et bitrate audio par défaut

Le fichier est stocké dans %LOCALAPPDATA%\Downloader Studio\downloader_settings.json (voir config.py, USER_DATA_DIR). Il est ignoré par Git.

04Internationalisation (i18n)

Toutes les chaînes affichées passent par i18n/translations.py. La classe Translator charge un dictionnaire par langue depuis TRANSLATIONS et expose une fonction globale t(key) utilisée partout dans l'UI.

# i18n/translations.py
TRANSLATIONS = {
    "fr": { "search": "Recherche", "queue": "File d'attente", ... },
    "en": { "search": "Search", "queue": "Queue", ... },
    "de": { ... },
    "es": { ... },
}

class Translator:
    def get(self, key, default=None): ...
    def set_language(self, language): ...

def t(key, default=None):
    return _translator.get(key, default)

Quand la langue change (page Paramètres), un signal global signal_manager.language_changed (app/signals.py) est émis. Chaque page abonnée reconstruit ses widgets texte pour éviter de garder des libellés dans l'ancienne langue.

CodeLangueStatut
frFrançaisLangue par défaut
enEnglishComplète
deDeutschComplète
esEspañolComplète

05Dépendances

Dépendances runtime (requirements.txt)

PaquetVersion épingléeRôle
PyQt55.15.11Framework d'interface graphique (fenêtres, widgets, thèmes)
PyQt5-Qt55.15.2Binaires Qt5 requis par PyQt5
PyQt5-sip12.18.0Bindings SIP entre Python et Qt5
yt-dlp2026.6.9Extraction et téléchargement de médias (YouTube, SoundCloud, Twitch, TikTok, etc.)
requests2.31.0Appels HTTP : résolution d'iframes, vérification des mises à jour GitHub

Dépendance externe non-Python

OutilRôleOrigine
FFmpeg (ffmpeg + ffprobe)Extraction et conversion audio, mux vidéo+audioDétecté localement, sinon téléchargé automatiquement depuis gyan.dev (build release-essentials) au moment du build ou de l'installation

Dépendances de build (non incluses dans requirements.txt)

OutilRôle
pyinstallerGénère l'exécutable onedir à partir de main.py
Inno Setup 6Compile l'installateur Windows depuis packaging/Downloader_Studio.iss

Python 3.14/3.15 non supportés pour le build release. Les specs PyInstaller du projet lèvent une erreur explicite en dehors de la plage 3.10 – 3.13 : ces versions récentes ont produit des exécutables qui plantent au lancement avec Failed to load Python DLL.

06Build & publication

Build local (Windows)

py -3.13 -m venv .venv-build
.\.venv-build\Scripts\python -m pip install --upgrade pip
.\.venv-build\Scripts\python -m pip install -r requirements.txt pyinstaller

# Build complet : PyInstaller + FFmpeg + installateur Inno Setup
.\.venv-build\Scripts\python packaginguild_exe.py

# Depuis Git Bash
./build.sh

packaging/build_exe.py exécute dans l'ordre :

  1. Validation de la version Python (3.10–3.13 final).
  2. Build PyInstaller en mode onedir (voir pourquoi ci-dessous).
  3. Récupération de FFmpeg (cache local, installation système, ou téléchargement).
  4. Compilation de l'installateur avec Inno Setup 6 (si détecté).

Pourquoi onedir plutôt que onefile

Le mode onefile extrait Python dans %TEMP%\_MEI... à chaque lancement. Un antivirus ou une politique de droits peut bloquer ou supprimer python313.dll à ce moment-là, provoquant Failed to load Python DLL. Le mode onedir garde le runtime Python, les DLL Qt et FFmpeg à côté de l'exécutable installé, donc le lancement ne dépend plus d'une extraction temporaire.

Build multiplateforme (CI GitHub Actions)

.github/workflows/release.yml se déclenche sur un tag v*.*.* et construit en parallèle les trois plateformes, puis publie tout sur une GitHub Release :

PlateformeRunnerArtefacts produits
Windowswindows-latestZIP portable + installateur Inno Setup (_Setup.exe)
Linuxubuntu-latestAppImage + archive .tar.gz
macOSmacos-latestBundle .app zippé (non signé)
# Déclencher une release complète
git tag v2.17.0
git push origin v2.17.0

Mise à jour automatique

app/core/update_checker.py interroge l'API GitHub Releases (/releases/latest) au démarrage. Il compare la version distante à APP_VERSION (config.py), puis sélectionne l'asset correspondant au système d'exploitation courant (sys.platform) grâce à une correspondance de suffixe de nom de fichier, avec repli générique si aucun asset spécifique à la plateforme n'est trouvé.

07Configuration

config.py centralise les constantes globales de l'application.

ConstanteExempleUsage
APP_VERSION2.17.0Version affichée et comparée par le vérificateur de mise à jour
APP_GITHUB_REPObatsave/downloader-studioDépôt utilisé pour les releases et la doc
USER_DATA_DIR%LOCALAPPDATA%\Downloader StudioDossier des données utilisateur (settings, logs)
DEFAULT_OUTPUT_DIR%USERPROFILE%\DownloadsDossier de téléchargement par défaut
LANGUAGES{"fr", "en", "de", "es"}Langues disponibles dans les paramètres
THEMES_AVAILABLE["dark", "light"]Thèmes disponibles

08Limites & avertissements

  • Downloader Studio n'est affilié à aucune des plateformes qu'il interroge (YouTube, SoundCloud, Twitch, TikTok).
  • Le téléchargement dépend entièrement de yt-dlp : toute évolution des sites cibles peut casser l'extraction jusqu'à mise à jour de la dépendance.
  • Respectez les conditions d'utilisation des plateformes et les droits d'auteur du contenu téléchargé.
  • Le build release nécessite CPython 3.10–3.13 ; toute autre version est bloquée volontairement.

← Retour au site

Developer documentation

Understand, build, and ship Downloader Studio

This documentation covers the actual code architecture, the app's internal behavior, the dependencies it relies on, and the multiplatform build process. Use the left menu to jump between sections.

PyQt5 5.15 Python 3.10 – 3.13 yt-dlp MIT + Commons Clause

01Overview

Downloader Studio is a desktop application written in Python, using PyQt5 for the GUI and yt-dlp as the extraction/download engine. It lets users search for media, queue it up, and download audio or video with FFmpeg post-processing for format conversion.

Key points

  • Sidebar-tabbed interface: Search, Queue, Settings, Logs.
  • Minimal floating mode toggled with Ctrl+M.
  • Dark and light themes applied globally via a Python-generated Qt stylesheet.
  • UI translated into 4 languages (FR, EN, DE, ES), rebuilt dynamically on language change.
  • Settings persisted as JSON in the user data directory.

The entry point is main.py: it creates the QApplication, shows a splash screen, then builds the Downloader main window defined in app/ui/main_window.py.

02Project architecture

File tree

downloader-studio/
├── main.py                        # Entry point (QApplication, splash screen)
├── config.py                      # Global constants (version, paths, languages)
├── requirements.txt                # Runtime dependencies
├── app/
│   ├── signals.py                  # Global SignalManager (e.g. language_changed)
│   ├── core/
│   │   ├── download_engine.py      # Queue + yt-dlp integration
│   │   └── update_checker.py       # Update check via GitHub Releases
│   ├── ui/
│   │   ├── main_window.py          # Main window, sidebar, themes, settings
│   │   ├── minimal_window.py        # Compact floating mode (Ctrl+M)
│   │   └── pages/
│   │       ├── search_page.py      # Search + results + add to queue
│   │       ├── queue_page.py       # Queue, progress, history
│   │       ├── settings_page.py    # Language, theme, sources, formats, quality
│   │       └── logs_page.py        # Activity log + export
│   └── utils/
│       ├── icons.py                # SVG icons generated on the fly (QIcon)
│       └── resources.py            # Path resolution (dev / PyInstaller / Inno)
├── i18n/
│   ├── __init__.py                 # Facade: t(), set_language(), get_translator()
│   └── translations.py             # TRANSLATIONS dict + Translator class
├── assets/                         # Logo, icons, visual assets
├── docs/                           # This site (landing page + documentation)
├── packaging/
│   ├── build_exe.py                 # Full build script (PyInstaller + Inno Setup)
│   └── Downloader_Studio.iss        # Inno Setup script (Windows installer)

Separation of concerns

The app follows a classic UI / business-logic split:

  • UI (app/ui/): PyQt5 widgets, layout, themes, user events. No direct download logic.
  • Core (app/core/): DownloadEngine manages the queue and drives DownloadWorker instances (QThread) that call yt-dlp. No dependency on PyQt Widgets, only on QThread/pyqtSignal for thread-safe UI communication.
  • i18n (i18n/): centralized string dictionary, no UI string is hardcoded in widgets.
  • Utils (app/utils/): stateless cross-cutting helpers (icons, path resolution).

Path resolution (resources.py)

resource_path() handles three different execution contexts so the code behaves identically in dev and once packaged:

ContextDetectionPath base used
Plain Python scriptsys.frozen absentRepo root (3 levels above the file)
PyInstaller executable (--onefile)sys.frozen and sys._MEIPASSPyInstaller's temporary extraction folder
Installed executable (Inno Setup / onedir)sys.frozen without _MEIPASSFolder containing the executable

03How it works

Download lifecycle

1. Search

search_page.py calls DownloadEngine.search_youtube() or search_soundcloud(), which query yt-dlp in extract-only mode (ytsearch10: / scsearch10:).

2. Queue

Each selected result becomes a DownloadTask (URL, format, quality). add_task() rejects duplicates already queued or in progress.

3. Download

_process_next() starts one DownloadWorker (QThread) per task, sequentially. It builds yt-dlp options based on the requested format.

4. Post-processing

For audio (MP3/WAV/M4A), an FFmpeg postprocessor extracts and converts the track. For video, a combined video+audio format is selected based on the chosen quality.

Supported sources

The UI exposes two search checkboxes: YouTube and SoundCloud. The engine also recognizes Twitch and TikTok by pasting a direct URL (no keyword search for these two); detection is done via domain matching in search_page.py.

SourceKeyword searchDirect URL paste
YouTubeYesYes
SoundCloudYesYes
TwitchNoYes
TikTokNoYes

Embedded player resolution (iframe)

Some "portal" sites don't expose a page yt-dlp can read directly: the real video lives inside an <iframe>. When yt-dlp fails with Unsupported URL, resolve_embedded_media_url() downloads the HTML page, looks for the first iframe src that looks like a player, then retries the download on that resolved URL.

Formats and quality

FormatTypeProcessing
mp3AudioFFmpeg extraction, configurable quality (e.g. 320 kbps)
wavAudioFFmpeg extraction, maximum (lossless) quality
m4aAudioFFmpeg extraction, configurable quality
mp4VideoBest combined video+audio stream selected by max resolution (480p to 4K)

Minimal mode

Ctrl+M switches to minimal_window.py: a compact floating window that keeps the essentials (paste a URL, start the download, open the output folder) without showing the full interface.

Settings persistence

On close, save_app_settings() writes a JSON file with the theme, language, output folder, enabled sources, enabled formats, and quality preferences. It's read back on startup by load_app_settings().

JSON keyPurpose
themeActive theme (dark or light)
languageActive UI language
output_dirDownload destination folder
sources_enabledEnabled/disabled search sources
enabled_formatsFormats offered in the UI
quality / audio_qualityDefault video quality and audio bitrate

The file is stored at %LOCALAPPDATA%\Downloader Studio\downloader_settings.json (see config.py, USER_DATA_DIR). It's ignored by Git.

04Internationalization (i18n)

Every displayed string goes through i18n/translations.py. The Translator class loads a per-language dictionary from TRANSLATIONS and exposes a global t(key) function used throughout the UI.

# i18n/translations.py
TRANSLATIONS = {
    "fr": { "search": "Recherche", "queue": "File d'attente", ... },
    "en": { "search": "Search", "queue": "Queue", ... },
    "de": { ... },
    "es": { ... },
}

class Translator:
    def get(self, key, default=None): ...
    def set_language(self, language): ...

def t(key, default=None):
    return _translator.get(key, default)

When the language changes (Settings page), a global signal signal_manager.language_changed (app/signals.py) is emitted. Each subscribed page rebuilds its text widgets to avoid keeping labels in the old language.

CodeLanguageStatus
frFrenchDefault language
enEnglishComplete
deGermanComplete
esSpanishComplete

05Dependencies

Runtime dependencies (requirements.txt)

PackagePinned versionRole
PyQt55.15.11GUI framework (windows, widgets, themes)
PyQt5-Qt55.15.2Qt5 binaries required by PyQt5
PyQt5-sip12.18.0SIP bindings between Python and Qt5
yt-dlp2026.6.9Media extraction and download (YouTube, SoundCloud, Twitch, TikTok, etc.)
requests2.31.0HTTP calls: iframe resolution, GitHub update checks

Non-Python external dependency

ToolRoleSource
FFmpeg (ffmpeg + ffprobe)Audio extraction/conversion, video+audio muxingDetected locally, otherwise downloaded automatically from gyan.dev (release-essentials build) at build or install time

Build-time dependencies (not in requirements.txt)

ToolRole
pyinstallerBuilds the onedir executable from main.py
Inno Setup 6Compiles the Windows installer from packaging/Downloader_Studio.iss

Python 3.14/3.15 are not supported for release builds. The project's PyInstaller specs raise an explicit error outside the 3.10 – 3.13 range: those newer versions have produced executables that crash on launch with Failed to load Python DLL.

06Build & release

Local build (Windows)

py -3.13 -m venv .venv-build
.\.venv-build\Scripts\python -m pip install --upgrade pip
.\.venv-build\Scripts\python -m pip install -r requirements.txt pyinstaller

# Full build: PyInstaller + FFmpeg + Inno Setup installer
.\.venv-build\Scripts\python packaginguild_exe.py

# From Git Bash
./build.sh

packaging/build_exe.py runs, in order:

  1. Python version validation (3.10–3.13 final).
  2. PyInstaller build in onedir mode (see why below).
  3. FFmpeg retrieval (local cache, system install, or download).
  4. Installer compilation with Inno Setup 6 (if detected).

Why onedir instead of onefile

onefile mode extracts Python into %TEMP%\_MEI... on every launch. An antivirus or permission policy can block or delete python313.dll at that moment, causing Failed to load Python DLL. onedir mode keeps the Python runtime, Qt DLLs, and FFmpeg next to the installed executable, so launching no longer depends on a temporary extraction.

Multiplatform build (GitHub Actions CI)

.github/workflows/release.yml triggers on a v*.*.* tag and builds all three platforms in parallel, then publishes everything to a GitHub Release:

PlatformRunnerArtifacts produced
Windowswindows-latestPortable ZIP + Inno Setup installer (_Setup.exe)
Linuxubuntu-latestAppImage + .tar.gz archive
macOSmacos-latestZipped .app bundle (unsigned)
# Trigger a full release
git tag v2.17.0
git push origin v2.17.0

Automatic updates

app/core/update_checker.py queries the GitHub Releases API (/releases/latest) on startup. It compares the remote version against APP_VERSION (config.py), then selects the asset matching the current OS (sys.platform) via filename suffix matching, with a generic fallback if no platform-specific asset is found.

07Configuration

config.py centralizes the app's global constants.

ConstantExampleUsage
APP_VERSION2.17.0Displayed version, compared by the update checker
APP_GITHUB_REPObatsave/downloader-studioRepo used for releases and docs
USER_DATA_DIR%LOCALAPPDATA%\Downloader StudioUser data folder (settings, logs)
DEFAULT_OUTPUT_DIR%USERPROFILE%\DownloadsDefault download folder
LANGUAGES{"fr", "en", "de", "es"}Languages available in settings
THEMES_AVAILABLE["dark", "light"]Available themes

08Limitations & disclaimers

  • Downloader Studio is not affiliated with any platform it queries (YouTube, SoundCloud, Twitch, TikTok).
  • Downloading fully depends on yt-dlp: any change on the target sites can break extraction until the dependency is updated.
  • Respect each platform's terms of service and the copyright of downloaded content.
  • Release builds require CPython 3.10–3.13; any other version is intentionally blocked.

← Back to the site

Entwicklerdokumentation

Downloader Studio verstehen, bauen und veröffentlichen

Diese Dokumentation beschreibt die tatsächliche Code-Architektur, das interne Verhalten der App, die verwendeten Abhängigkeiten und den plattformübergreifenden Build-Prozess. Nutze das Menü links, um zwischen den Abschnitten zu springen.

PyQt5 5.15 Python 3.10 – 3.13 yt-dlp MIT + Commons Clause

01Überblick

Downloader Studio ist eine Desktop-Anwendung in Python, mit PyQt5 für die Oberfläche und yt-dlp als Extraktions-/Download-Engine. Nutzer können Medien suchen, in eine Warteschlange stellen und als Audio oder Video herunterladen, mit FFmpeg-Nachbearbeitung für Formatkonvertierung.

Wichtige Punkte

  • Seitenleisten-Oberfläche: Suche, Warteschlange, Einstellungen, Logs.
  • Minimalistischer Schwebemodus, umschaltbar mit Strg+M.
  • Helles und dunkles Design, global über ein in Python erzeugtes Qt-Stylesheet angewendet.
  • Oberfläche in 4 Sprachen (FR, EN, DE, ES), bei Sprachwechsel dynamisch neu aufgebaut.
  • Einstellungen als JSON im Benutzerdatenordner gespeichert.

Einstiegspunkt ist main.py: Es erstellt die QApplication, zeigt einen Splashscreen und baut dann das Hauptfenster Downloader aus app/ui/main_window.py auf.

02Projektarchitektur

Dateibaum

downloader-studio/
├── main.py                        # Einstiegspunkt (QApplication, Splashscreen)
├── config.py                      # Globale Konstanten (Version, Pfade, Sprachen)
├── requirements.txt                # Laufzeitabhängigkeiten
├── app/
│   ├── signals.py                  # Globaler SignalManager (z. B. language_changed)
│   ├── core/
│   │   ├── download_engine.py      # Warteschlange + yt-dlp-Integration
│   │   └── update_checker.py       # Update-Prüfung über GitHub Releases
│   ├── ui/
│   │   ├── main_window.py          # Hauptfenster, Seitenleiste, Designs, Einstellungen
│   │   ├── minimal_window.py        # Kompakter Schwebemodus (Strg+M)
│   │   └── pages/
│   │       ├── search_page.py      # Suche + Ergebnisse + zur Warteschlange hinzufügen
│   │       ├── queue_page.py       # Warteschlange, Fortschritt, Verlauf
│   │       ├── settings_page.py    # Sprache, Design, Quellen, Formate, Qualität
│   │       └── logs_page.py        # Aktivitätsprotokoll + Export
│   └── utils/
│       ├── icons.py                # Zur Laufzeit erzeugte SVG-Icons (QIcon)
│       └── resources.py            # Pfadauflösung (Dev / PyInstaller / Inno)
├── i18n/
│   ├── __init__.py                 # Fassade: t(), set_language(), get_translator()
│   └── translations.py             # TRANSLATIONS-Dict + Translator-Klasse
├── assets/                         # Logo, Icons, visuelle Assets
├── docs/                           # Diese Website (Landing Page + Dokumentation)
├── packaging/
│   ├── build_exe.py                 # Vollständiges Build-Skript (PyInstaller + Inno Setup)
│   └── Downloader_Studio.iss        # Inno-Setup-Skript (Windows-Installer)

Trennung der Zuständigkeiten

Die App folgt einer klassischen UI/Geschäftslogik-Trennung:

  • UI (app/ui/): PyQt5-Widgets, Layout, Designs, Benutzerereignisse. Keine direkte Download-Logik.
  • Core (app/core/): DownloadEngine verwaltet die Warteschlange und steuert DownloadWorker-Instanzen (QThread), die yt-dlp aufrufen. Keine Abhängigkeit von PyQt-Widgets, nur von QThread/pyqtSignal für Thread-sichere UI-Kommunikation.
  • i18n (i18n/): zentrales String-Wörterbuch, keine UI-Zeichenkette ist fest in Widgets kodiert.
  • Utils (app/utils/): zustandslose Querschnittshilfen (Icons, Pfadauflösung).

Pfadauflösung (resources.py)

resource_path() behandelt drei verschiedene Ausführungskontexte, damit der Code in der Entwicklung und nach dem Packaging identisch funktioniert:

KontextErkennungVerwendete Pfadbasis
Normales Python-Skriptsys.frozen fehltRepo-Wurzel (3 Ebenen über der Datei)
PyInstaller-Executable (--onefile)sys.frozen und sys._MEIPASSTemporärer Extraktionsordner von PyInstaller
Installierte Executable (Inno Setup / onedir)sys.frozen ohne _MEIPASSOrdner, der die Executable enthält

03Funktionsweise

Download-Lebenszyklus

1. Suche

search_page.py ruft DownloadEngine.search_youtube() oder search_soundcloud() auf, die yt-dlp im reinen Extraktionsmodus abfragen (ytsearch10: / scsearch10:).

2. Warteschlange

Jedes ausgewählte Ergebnis wird zu einem DownloadTask (URL, Format, Qualität). add_task() lehnt bereits eingereihte oder laufende Duplikate ab.

3. Download

_process_next() startet pro Aufgabe nacheinander einen DownloadWorker (QThread). Er konfiguriert die yt-dlp-Optionen je nach gewünschtem Format.

4. Nachbearbeitung

Bei Audio (MP3/WAV/M4A) extrahiert und konvertiert ein FFmpeg-postprocessor die Spur. Bei Video wird ein kombinierter Video+Audio-Stream je nach gewählter Qualität ausgewählt.

Unterstützte Quellen

Die Oberfläche bietet zwei Such-Kontrollkästchen: YouTube und SoundCloud. Die Engine erkennt zusätzlich Twitch und TikTok durch direktes Einfügen einer URL (keine Stichwortsuche für diese beiden); die Erkennung erfolgt über Domain-Übereinstimmung in search_page.py.

QuelleStichwortsucheDirekte URL-Eingabe
YouTubeJaJa
SoundCloudJaJa
TwitchNeinJa
TikTokNeinJa

Auflösung eingebetteter Player (iframe)

Manche "Portal"-Seiten stellen keine direkt von yt-dlp lesbare Seite bereit: Das eigentliche Video steckt in einem <iframe>. Wenn yt-dlp mit Unsupported URL fehlschlägt, lädt resolve_embedded_media_url() die HTML-Seite herunter, sucht nach der ersten iframe-src, die wie ein Player aussieht, und versucht den Download erneut mit dieser aufgelösten URL.

Formate und Qualität

FormatTypVerarbeitung
mp3AudioFFmpeg-Extraktion, konfigurierbare Qualität (z. B. 320 kbps)
wavAudioFFmpeg-Extraktion, maximale (verlustfreie) Qualität
m4aAudioFFmpeg-Extraktion, konfigurierbare Qualität
mp4VideoBester kombinierter Video+Audio-Stream nach gewählter Maximalauflösung (480p bis 4K)

Minimalmodus

Strg+M wechselt zu minimal_window.py: ein kompaktes Schwebefenster, das nur das Wesentliche behält (URL einfügen, Download starten, Zielordner öffnen), ohne die vollständige Oberfläche anzuzeigen.

Speicherung der Einstellungen

Beim Schließen schreibt save_app_settings() eine JSON-Datei mit Design, Sprache, Zielordner, aktivierten Quellen, aktivierten Formaten und Qualitätspräferenzen. Sie wird beim Start von load_app_settings() wieder eingelesen.

JSON-SchlüsselZweck
themeAktives Design (dark oder light)
languageAktive UI-Sprache
output_dirZielordner für Downloads
sources_enabledAktivierte/deaktivierte Suchquellen
enabled_formatsIn der UI angebotene Formate
quality / audio_qualityStandard-Videoqualität und Audio-Bitrate

Die Datei liegt unter %LOCALAPPDATA%\Downloader Studio\downloader_settings.json (siehe config.py, USER_DATA_DIR). Sie wird von Git ignoriert.

04Internationalisierung (i18n)

Jede angezeigte Zeichenkette läuft über i18n/translations.py. Die Klasse Translator lädt ein sprachspezifisches Wörterbuch aus TRANSLATIONS und stellt eine globale Funktion t(key) bereit, die überall in der UI verwendet wird.

# i18n/translations.py
TRANSLATIONS = {
    "fr": { "search": "Recherche", "queue": "File d'attente", ... },
    "en": { "search": "Search", "queue": "Queue", ... },
    "de": { ... },
    "es": { ... },
}

class Translator:
    def get(self, key, default=None): ...
    def set_language(self, language): ...

def t(key, default=None):
    return _translator.get(key, default)

Bei Sprachwechsel (Einstellungsseite) wird ein globales Signal signal_manager.language_changed (app/signals.py) ausgelöst. Jede abonnierte Seite baut ihre Text-Widgets neu auf, um Beschriftungen in der alten Sprache zu vermeiden.

CodeSpracheStatus
frFranzösischStandardsprache
enEnglischVollständig
deDeutschVollständig
esSpanischVollständig

05Abhängigkeiten

Laufzeitabhängigkeiten (requirements.txt)

PaketFixierte VersionRolle
PyQt55.15.11GUI-Framework (Fenster, Widgets, Designs)
PyQt5-Qt55.15.2Von PyQt5 benötigte Qt5-Binärdateien
PyQt5-sip12.18.0SIP-Bindings zwischen Python und Qt5
yt-dlp2026.6.9Medienextraktion und -download (YouTube, SoundCloud, Twitch, TikTok usw.)
requests2.31.0HTTP-Aufrufe: iframe-Auflösung, GitHub-Update-Prüfung

Externe Nicht-Python-Abhängigkeit

ToolRolleHerkunft
FFmpeg (ffmpeg + ffprobe)Audio-Extraktion/-Konvertierung, Video+Audio-MuxingLokal erkannt, sonst automatisch von gyan.dev heruntergeladen (release-essentials-Build) beim Build oder bei der Installation

Build-Abhängigkeiten (nicht in requirements.txt)

ToolRolle
pyinstallerErzeugt die onedir-Executable aus main.py
Inno Setup 6Kompiliert den Windows-Installer aus packaging/Downloader_Studio.iss

Python 3.14/3.15 werden für Release-Builds nicht unterstützt. Die PyInstaller-Specs des Projekts werfen außerhalb des Bereichs 3.10 – 3.13 einen expliziten Fehler: Diese neueren Versionen haben Executables erzeugt, die beim Start mit Failed to load Python DLL abstürzen.

06Build & Veröffentlichung

Lokaler Build (Windows)

py -3.13 -m venv .venv-build
.\.venv-build\Scripts\python -m pip install --upgrade pip
.\.venv-build\Scripts\python -m pip install -r requirements.txt pyinstaller

# Vollständiger Build: PyInstaller + FFmpeg + Inno-Setup-Installer
.\.venv-build\Scripts\python packaginguild_exe.py

# Über Git Bash
./build.sh

packaging/build_exe.py führt der Reihe nach aus:

  1. Prüfung der Python-Version (3.10–3.13 final).
  2. PyInstaller-Build im onedir-Modus (siehe unten, warum).
  3. Beschaffung von FFmpeg (lokaler Cache, Systeminstallation oder Download).
  4. Kompilierung des Installers mit Inno Setup 6 (falls erkannt).

Warum onedir statt onefile

Der onefile-Modus extrahiert Python bei jedem Start nach %TEMP%\_MEI.... Ein Antivirenprogramm oder eine Rechterichtlinie kann python313.dll in diesem Moment blockieren oder löschen, was zu Failed to load Python DLL führt. Der onedir-Modus behält die Python-Laufzeit, Qt-DLLs und FFmpeg neben der installierten Executable, sodass der Start nicht mehr von einer temporären Extraktion abhängt.

Plattformübergreifender Build (GitHub Actions CI)

.github/workflows/release.yml wird bei einem Tag v*.*.* ausgelöst und baut alle drei Plattformen parallel, um dann alles in einer GitHub Release zu veröffentlichen:

PlattformRunnerErzeugte Artefakte
Windowswindows-latestPortables ZIP + Inno-Setup-Installer (_Setup.exe)
Linuxubuntu-latestAppImage + .tar.gz-Archiv
macOSmacos-latestGezipptes .app-Bundle (unsigniert)
# Eine vollständige Release auslösen
git tag v2.17.0
git push origin v2.17.0

Automatische Updates

app/core/update_checker.py fragt beim Start die GitHub-Releases-API (/releases/latest) ab. Es vergleicht die entfernte Version mit APP_VERSION (config.py) und wählt dann das zum aktuellen Betriebssystem passende Asset (sys.platform) über eine Dateinamen-Suffix-Übereinstimmung aus, mit generischem Fallback, falls kein plattformspezifisches Asset gefunden wird.

07Konfiguration

config.py zentralisiert die globalen Konstanten der App.

KonstanteBeispielVerwendung
APP_VERSION2.17.0Angezeigte Version, vom Update-Checker verglichen
APP_GITHUB_REPObatsave/downloader-studioRepo für Releases und Dokumentation
USER_DATA_DIR%LOCALAPPDATA%\Downloader StudioBenutzerdatenordner (Einstellungen, Logs)
DEFAULT_OUTPUT_DIR%USERPROFILE%\DownloadsStandard-Download-Ordner
LANGUAGES{"fr", "en", "de", "es"}In den Einstellungen verfügbare Sprachen
THEMES_AVAILABLE["dark", "light"]Verfügbare Designs

08Einschränkungen & Hinweise

  • Downloader Studio ist nicht mit den abgefragten Plattformen verbunden (YouTube, SoundCloud, Twitch, TikTok).
  • Der Download hängt vollständig von yt-dlp ab: Änderungen an den Zielseiten können die Extraktion brechen, bis die Abhängigkeit aktualisiert wird.
  • Beachte die Nutzungsbedingungen der Plattformen und das Urheberrecht der heruntergeladenen Inhalte.
  • Release-Builds erfordern CPython 3.10–3.13; jede andere Version wird absichtlich blockiert.

← Zurück zur Website

Documentación para desarrolladores

Entender, compilar y publicar Downloader Studio

Esta documentación cubre la arquitectura real del código, el funcionamiento interno de la aplicación, las dependencias utilizadas y el proceso de compilación multiplataforma. Usa el menú de la izquierda para navegar entre secciones.

PyQt5 5.15 Python 3.10 – 3.13 yt-dlp MIT + Commons Clause

01Visión general

Downloader Studio es una aplicación de escritorio escrita en Python, con PyQt5 para la interfaz gráfica y yt-dlp como motor de extracción/descarga. Permite buscar medios, encolarlos y descargarlos en audio o video, con post-procesamiento FFmpeg para la conversión de formato.

Puntos clave

  • Interfaz con pestañas laterales: Búsqueda, Cola, Configuración, Logs.
  • Modo flotante minimalista activable con Ctrl+M.
  • Temas claro y oscuro aplicados globalmente mediante una hoja de estilos Qt generada en Python.
  • Interfaz traducida a 4 idiomas (FR, EN, DE, ES), reconstruida dinámicamente al cambiar de idioma.
  • Configuración persistida en JSON en el directorio de datos del usuario.

El punto de entrada es main.py: crea la QApplication, muestra una pantalla de bienvenida y luego construye la ventana principal Downloader definida en app/ui/main_window.py.

02Arquitectura del proyecto

Árbol de archivos

downloader-studio/
├── main.py                        # Punto de entrada (QApplication, splash screen)
├── config.py                      # Constantes globales (versión, rutas, idiomas)
├── requirements.txt                # Dependencias en tiempo de ejecución
├── app/
│   ├── signals.py                  # SignalManager global (ej. language_changed)
│   ├── core/
│   │   ├── download_engine.py      # Cola + integración con yt-dlp
│   │   └── update_checker.py       # Verificación de actualizaciones vía GitHub Releases
│   ├── ui/
│   │   ├── main_window.py          # Ventana principal, barra lateral, temas, configuración
│   │   ├── minimal_window.py        # Modo flotante compacto (Ctrl+M)
│   │   └── pages/
│   │       ├── search_page.py      # Búsqueda + resultados + agregar a la cola
│   │       ├── queue_page.py       # Cola, progreso, historial
│   │       ├── settings_page.py    # Idioma, tema, fuentes, formatos, calidad
│   │       └── logs_page.py        # Registro de actividad + exportación
│   └── utils/
│       ├── icons.py                # Íconos SVG generados al vuelo (QIcon)
│       └── resources.py            # Resolución de rutas (dev / PyInstaller / Inno)
├── i18n/
│   ├── __init__.py                 # Fachada: t(), set_language(), get_translator()
│   └── translations.py             # Diccionario TRANSLATIONS + clase Translator
├── assets/                         # Logo, íconos, recursos visuales
├── docs/                           # Este sitio (landing page + documentación)
├── packaging/
│   ├── build_exe.py                 # Script de compilación completo (PyInstaller + Inno Setup)
│   └── Downloader_Studio.iss        # Script de Inno Setup (instalador de Windows)

Separación de responsabilidades

La aplicación sigue una separación clásica de UI / lógica de negocio:

  • UI (app/ui/): widgets de PyQt5, diseño, temas, eventos de usuario. Sin lógica de descarga directa.
  • Core (app/core/): DownloadEngine gestiona la cola y coordina instancias de DownloadWorker (QThread) que llaman a yt-dlp. Sin dependencia de PyQt Widgets, solo de QThread/pyqtSignal para comunicación segura entre hilos con la UI.
  • i18n (i18n/): diccionario de cadenas centralizado, ninguna cadena de la UI está escrita directamente en los widgets.
  • Utils (app/utils/): utilidades transversales sin estado (íconos, resolución de rutas).

Resolución de rutas (resources.py)

resource_path() gestiona tres contextos de ejecución distintos para que el código funcione igual en desarrollo y una vez empaquetado:

ContextoDetecciónBase de ruta usada
Script Python normalSin sys.frozenRaíz del repositorio (3 niveles por encima del archivo)
Ejecutable PyInstaller (--onefile)sys.frozen y sys._MEIPASSCarpeta temporal de extracción de PyInstaller
Ejecutable instalado (Inno Setup / onedir)sys.frozen sin _MEIPASSCarpeta que contiene el ejecutable

03Funcionamiento

Ciclo de vida de la descarga

1. Búsqueda

search_page.py llama a DownloadEngine.search_youtube() o search_soundcloud(), que consultan yt-dlp en modo de solo extracción (ytsearch10: / scsearch10:).

2. Cola

Cada resultado seleccionado se convierte en un DownloadTask (URL, formato, calidad). add_task() rechaza duplicados ya en cola o en curso.

3. Descarga

_process_next() inicia un DownloadWorker (QThread) por tarea, de forma secuencial. Configura las opciones de yt-dlp según el formato solicitado.

4. Post-procesamiento

Para audio (MP3/WAV/M4A), un postprocessor de FFmpeg extrae y convierte la pista. Para video, se selecciona un formato combinado video+audio según la calidad elegida.

Fuentes compatibles

La interfaz expone dos casillas de búsqueda: YouTube y SoundCloud. El motor también reconoce Twitch y TikTok pegando directamente una URL (sin búsqueda por palabra clave para estas dos); la detección se hace por coincidencia de dominio en search_page.py.

FuenteBúsqueda por palabra clavePegado de URL directa
YouTube
SoundCloud
TwitchNo
TikTokNo

Resolución de reproductores incrustados (iframe)

Algunos sitios "portal" no exponen directamente una página legible por yt-dlp: el video real vive dentro de un <iframe>. Cuando yt-dlp falla con Unsupported URL, resolve_embedded_media_url() descarga la página HTML, busca el primer src de iframe que parezca un reproductor y reintenta la descarga con esa URL resuelta.

Formatos y calidad

FormatoTipoProcesamiento
mp3AudioExtracción FFmpeg, calidad configurable (ej. 320 kbps)
wavAudioExtracción FFmpeg, calidad máxima (sin pérdida)
m4aAudioExtracción FFmpeg, calidad configurable
mp4VideoMejor flujo combinado video+audio según la resolución máxima elegida (480p a 4K)

Modo minimalista

Ctrl+M cambia a minimal_window.py: una ventana flotante compacta que conserva lo esencial (pegar una URL, iniciar la descarga, abrir la carpeta de salida) sin mostrar la interfaz completa.

Persistencia de la configuración

Al cerrar, save_app_settings() escribe un archivo JSON con el tema, el idioma, la carpeta de salida, las fuentes activadas, los formatos activados y las preferencias de calidad. Se vuelve a leer al iniciar mediante load_app_settings().

Clave JSONPropósito
themeTema activo (dark o light)
languageIdioma activo de la interfaz
output_dirCarpeta de destino de las descargas
sources_enabledFuentes de búsqueda activadas/desactivadas
enabled_formatsFormatos ofrecidos en la UI
quality / audio_qualityCalidad de video y bitrate de audio predeterminados

El archivo se guarda en %LOCALAPPDATA%\Downloader Studio\downloader_settings.json (ver config.py, USER_DATA_DIR). Git lo ignora.

04Internacionalización (i18n)

Todas las cadenas mostradas pasan por i18n/translations.py. La clase Translator carga un diccionario por idioma desde TRANSLATIONS y expone una función global t(key) usada en toda la UI.

# i18n/translations.py
TRANSLATIONS = {
    "fr": { "search": "Recherche", "queue": "File d'attente", ... },
    "en": { "search": "Search", "queue": "Queue", ... },
    "de": { ... },
    "es": { ... },
}

class Translator:
    def get(self, key, default=None): ...
    def set_language(self, language): ...

def t(key, default=None):
    return _translator.get(key, default)

Cuando cambia el idioma (página de Configuración), se emite una señal global signal_manager.language_changed (app/signals.py). Cada página suscrita reconstruye sus widgets de texto para evitar mantener etiquetas en el idioma anterior.

CódigoIdiomaEstado
frFrancésIdioma predeterminado
enInglésCompleto
deAlemánCompleto
esEspañolCompleto

05Dependencias

Dependencias en tiempo de ejecución (requirements.txt)

PaqueteVersión fijadaRol
PyQt55.15.11Framework de interfaz gráfica (ventanas, widgets, temas)
PyQt5-Qt55.15.2Binarios de Qt5 requeridos por PyQt5
PyQt5-sip12.18.0Bindings SIP entre Python y Qt5
yt-dlp2026.6.9Extracción y descarga de medios (YouTube, SoundCloud, Twitch, TikTok, etc.)
requests2.31.0Llamadas HTTP: resolución de iframes, verificación de actualizaciones en GitHub

Dependencia externa no Python

HerramientaRolOrigen
FFmpeg (ffmpeg + ffprobe)Extracción/conversión de audio, mux de video+audioDetectado localmente; si no, descargado automáticamente desde gyan.dev (build release-essentials) durante la compilación o instalación

Dependencias de compilación (no incluidas en requirements.txt)

HerramientaRol
pyinstallerGenera el ejecutable onedir a partir de main.py
Inno Setup 6Compila el instalador de Windows desde packaging/Downloader_Studio.iss

Python 3.14/3.15 no son compatibles con el build de release. Los specs de PyInstaller del proyecto lanzan un error explícito fuera del rango 3.10 – 3.13: estas versiones más recientes han producido ejecutables que fallan al iniciar con Failed to load Python DLL.

06Compilación y publicación

Compilación local (Windows)

py -3.13 -m venv .venv-build
.\.venv-build\Scripts\python -m pip install --upgrade pip
.\.venv-build\Scripts\python -m pip install -r requirements.txt pyinstaller

# Compilación completa: PyInstaller + FFmpeg + instalador Inno Setup
.\.venv-build\Scripts\python packaginguild_exe.py

# Desde Git Bash
./build.sh

packaging/build_exe.py ejecuta, en orden:

  1. Validación de la versión de Python (3.10–3.13 final).
  2. Compilación con PyInstaller en modo onedir (ver por qué abajo).
  3. Obtención de FFmpeg (caché local, instalación del sistema o descarga).
  4. Compilación del instalador con Inno Setup 6 (si se detecta).

Por qué onedir en lugar de onefile

El modo onefile extrae Python en %TEMP%\_MEI... en cada inicio. Un antivirus o una política de permisos puede bloquear o eliminar python313.dll en ese momento, provocando Failed to load Python DLL. El modo onedir mantiene el runtime de Python, las DLL de Qt y FFmpeg junto al ejecutable instalado, por lo que el inicio ya no depende de una extracción temporal.

Compilación multiplataforma (CI de GitHub Actions)

.github/workflows/release.yml se activa con una etiqueta v*.*.* y compila las tres plataformas en paralelo, luego publica todo en una GitHub Release:

PlataformaRunnerArtefactos generados
Windowswindows-latestZIP portable + instalador Inno Setup (_Setup.exe)
Linuxubuntu-latestAppImage + archivo .tar.gz
macOSmacos-latestPaquete .app comprimido (sin firmar)
# Activar una release completa
git tag v2.17.0
git push origin v2.17.0

Actualizaciones automáticas

app/core/update_checker.py consulta la API de GitHub Releases (/releases/latest) al iniciar. Compara la versión remota con APP_VERSION (config.py) y luego selecciona el activo correspondiente al sistema operativo actual (sys.platform) mediante coincidencia de sufijo de nombre de archivo, con reserva genérica si no se encuentra un activo específico de la plataforma.

07Configuración

config.py centraliza las constantes globales de la aplicación.

ConstanteEjemploUso
APP_VERSION2.17.0Versión mostrada, comparada por el verificador de actualizaciones
APP_GITHUB_REPObatsave/downloader-studioRepositorio usado para releases y documentación
USER_DATA_DIR%LOCALAPPDATA%\Downloader StudioCarpeta de datos del usuario (configuración, logs)
DEFAULT_OUTPUT_DIR%USERPROFILE%\DownloadsCarpeta de descarga predeterminada
LANGUAGES{"fr", "en", "de", "es"}Idiomas disponibles en la configuración
THEMES_AVAILABLE["dark", "light"]Temas disponibles

08Límites y avisos

  • Downloader Studio no está afiliado a ninguna de las plataformas que consulta (YouTube, SoundCloud, Twitch, TikTok).
  • La descarga depende totalmente de yt-dlp: cualquier cambio en los sitios de destino puede romper la extracción hasta que se actualice la dependencia.
  • Respeta los términos de uso de las plataformas y los derechos de autor del contenido descargado.
  • El build de release requiere CPython 3.10–3.13; cualquier otra versión se bloquea intencionalmente.

← Volver al sitio