diff --git a/app/ytdl.py b/app/ytdl.py index fe52a75..6bfa2e7 100644 --- a/app/ytdl.py +++ b/app/ytdl.py @@ -488,8 +488,30 @@ class Download: info_dict = st.get('info_dict') if isinstance(st, dict) else None if not isinstance(info_dict, dict): return None + vcodec = str(info_dict.get('vcodec') or '').lower() acodec = str(info_dict.get('acodec') or '').lower() + + # Check requested_formats when top-level codecs are both "none" + # (common when yt-dlp uses separate downloaders for video+audio streams) + if (not vcodec or vcodec == 'none') and (not acodec or acodec == 'none'): + requested = info_dict.get('requested_formats') + if isinstance(requested, list) and requested: + has_video = any( + str(f.get('vcodec') or '').lower() not in ('', 'none') + for f in requested if isinstance(f, dict) + ) + has_audio = any( + str(f.get('acodec') or '').lower() not in ('', 'none') + for f in requested if isinstance(f, dict) + ) + if has_video and has_audio: + return 'media' + if has_video: + return 'video' + if has_audio: + return 'audio' + if vcodec and vcodec != 'none' and (not acodec or acodec == 'none'): return 'video' if acodec and acodec != 'none' and (not vcodec or vcodec == 'none'): @@ -517,6 +539,9 @@ class Download: phase = self._download_phase_from_status(st) if phase: status['download_phase'] = phase + log.debug(f"put_status: status={status.get('status')}, phase={phase}, " + f"vcodec={st.get('info_dict', {}).get('vcodec') if isinstance(st.get('info_dict'), dict) else 'N/A'}, " + f"acodec={st.get('info_dict', {}).get('acodec') if isinstance(st.get('info_dict'), dict) else 'N/A'}") self.status_queue.put(status) def put_status_postprocessor(d): @@ -605,11 +630,11 @@ class Download: ) ret = yt_dlp.YoutubeDL(params=ytdl_params).download([self.info.url]) - self.status_queue.put({'status': 'finished' if ret == 0 else 'error'}) + self.status_queue.put({'status': 'finished' if ret == 0 else 'error', 'download_phase': None}) log.info(f"Finished download for: {self.info.title}") except yt_dlp.utils.YoutubeDLError as exc: log.error(f"Download error for {self.info.title}: {str(exc)}") - self.status_queue.put({'status': 'error', 'msg': str(exc)}) + self.status_queue.put({'status': 'error', 'msg': str(exc), 'download_phase': None}) async def start(self, notifier): log.info(f"Preparing download for: {self.info.title}") @@ -759,7 +784,7 @@ class Download: self.info.status = status['status'] self.info.msg = status.get('msg') if 'download_phase' in status: - self.info.download_phase = status.get('download_phase') + self.info.download_phase = status['download_phase'] if 'downloaded_bytes' in status: total = status.get('total_bytes') or status.get('total_bytes_estimate') if total: diff --git a/docker-compose.local.yml b/docker-compose.local.yml index cb0c080..7b7ecf4 100644 --- a/docker-compose.local.yml +++ b/docker-compose.local.yml @@ -2,25 +2,121 @@ services: mytube: build: context: . + args: + TARGETARCH: amd64 image: mytube - container_name: mytube-local + container_name: mytube + restart: unless-stopped ports: - "8081:8081" volumes: - - ./downloads:/downloads + # Required: download directory (matches DOWNLOAD_DIR, STATE_DIR, TEMP_DIR) + - /path/to/downloads:/downloads + + # Optional: environment configuration file + # - /path/to/.env:/app/.env:ro + + # Optional: yt-dlp global options file + # - /path/to/ytdl-options.json:/config/ytdl-options.json:ro + + # Optional: yt-dlp presets file + # - /path/to/ytdl-presets.json:/config/ytdl-presets.json:ro + + # Optional: HTTPS certificates (if HTTPS=true) + # - /path/to/ssl/crt.pem:/ssl/crt.pem:ro + # - /path/to/ssl/key.pem:/ssl/key.pem:ro + + # Optional: custom robots.txt + # - /path/to/robots.txt:/etc/robots.txt:ro + environment: - DOWNLOAD_DIR: /downloads - STATE_DIR: /downloads/.metube - TEMP_DIR: /downloads - HOST: 0.0.0.0 - PORT: "8081" - PUBLIC_MODE: "false" - CUSTOM_DIRS: "true" - CREATE_CUSTOM_DIRS: "true" - LOGLEVEL: INFO + ENV_FILE: "" + + # === Container Setup === + # PUID: "1000" + # PGID: "1000" + # UMASK: "022" + # DEFAULT_THEME: auto + # LOGLEVEL: INFO + # ENABLE_ACCESSLOG: "false" + + # === Directory Configuration === + # DOWNLOAD_DIR: /downloads + # STATE_DIR: /downloads/.metube + # TEMP_DIR: /downloads + # AUDIO_DOWNLOAD_DIR: /audio + + # === Web Server === + # HOST: 0.0.0.0 + # PORT: "8081" + # PUBLIC_MODE: "false" + # URL_PREFIX: /mytube/ + # PUBLIC_HOST_URL: download/ + # PUBLIC_HOST_AUDIO_URL: audio_download/ + # HTTPS: "false" + # CERTFILE: /ssl/crt.pem + # KEYFILE: /ssl/key.pem + # CORS_ALLOWED_ORIGINS: "" + # ROBOTS_TXT: "" + + # === Download Behavior === + # MAX_CONCURRENT_DOWNLOADS: "3" + # DELETE_FILE_ON_TRASHCAN: "false" + # DEFAULT_OPTION_PLAYLIST_ITEM_LIMIT: "0" + # SUBSCRIPTION_DEFAULT_CHECK_INTERVAL: "60" + # SUBSCRIPTION_SCAN_PLAYLIST_END: "50" + # SUBSCRIPTION_MAX_SEEN_IDS: "50000" + # CLEAR_COMPLETED_AFTER: "0" + + # === File Naming & Output === + # OUTPUT_TEMPLATE: "%(title)s.%(ext)s" + # OUTPUT_TEMPLATE_CHAPTER: "%(title)s - %(section_number)02d - %(section_title)s.%(ext)s" + # OUTPUT_TEMPLATE_PLAYLIST: "%(playlist_title)s/%(title)s.%(ext)s" + # OUTPUT_TEMPLATE_CHANNEL: "%(channel)s/%(title)s.%(ext)s" + + # === yt-dlp Options === + # YTDL_OPTIONS: "{}" + # YTDL_OPTIONS_FILE: /config/ytdl-options.json + # YTDL_OPTIONS_PRESETS: "{}" + # YTDL_OPTIONS_PRESETS_FILE: /config/ytdl-presets.json + # ALLOW_YTDL_OPTIONS_OVERRIDES: "false" + + # === Directory Features === + # CUSTOM_DIRS: "true" + # CREATE_CUSTOM_DIRS: "true" + # DOWNLOAD_DIRS_INDEXABLE: "false" + # CUSTOM_DIRS_EXCLUDE_REGEX: "(^|/)[.@].*$" + # CHOWN_DIRS: "true" + + # === Custom Environment File === + # ENV_FILE: /path/to/custom.env + healthcheck: - test: ["CMD", "curl", "-fsS", "http://localhost:8081/"] + test: ["CMD", "curl", "-fsS", "http://localhost:${PORT:-8081}/"] interval: 30s timeout: 5s retries: 3 start_period: 20s + + # Optional: set working user + # user: "${PUID:-1000}:${PGID:-1000}" + + # Optional: network configuration + # networks: + # - default + + # Optional: resource limits + # deploy: + # resources: + # limits: + # cpus: '1' + # memory: 1G + # reservations: + # cpus: '0.5' + # memory: 512M + +# Optional: define custom network +# networks: +# default: +# name: mytube-network +# driver: bridge