diff --git a/Dockerfile b/Dockerfile index 28bc986..d678a03 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,9 +10,12 @@ FROM python:3.8-alpine WORKDIR /app -COPY Pipfile* docker-entrypoint.sh ./ +COPY Pipfile* docker-entrypoint.sh . -RUN chmod +x docker-entrypoint.sh && \ +# Use sed to strip carriage-return characters from the entrypoint script (in case building on Windows) +# Install dependencies +RUN sed -i 's/\r$//g' docker-entrypoint.sh && \ + chmod +x docker-entrypoint.sh && \ apk add --update ffmpeg aria2 coreutils shadow su-exec && \ apk add --update --virtual .build-deps gcc g++ musl-dev && \ pip install --no-cache-dir pipenv && \ @@ -32,6 +35,7 @@ ENV UMASK=022 ENV DOWNLOAD_DIR /downloads ENV STATE_DIR /downloads/.metube +ENV TEMP_DIR /downloads VOLUME /downloads EXPOSE 8081 CMD [ "./docker-entrypoint.sh" ] diff --git a/README.md b/README.md index 1b70cf5..abaca29 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ ![Build Status](https://github.com/alexta69/metube/actions/workflows/main.yml/badge.svg) ![Docker Pulls](https://img.shields.io/docker/pulls/alexta69/metube.svg) -Web GUI for youtube-dl (using the [yt-dlp](https://github.com/yt-dlp/yt-dlp) fork) with playlist support. Allows you to download videos from YouTube and dozens of other sites (https://github.com/yt-dlp/yt-dlp/blob/master/supportedsites.md). +Web GUI for youtube-dl (using the [yt-dlp](https://github.com/yt-dlp/yt-dlp) fork) with playlist support. Allows you to download videos from YouTube and [dozens of other sites](https://github.com/yt-dlp/yt-dlp/blob/master/supportedsites.md). ![screenshot1](https://github.com/alexta69/metube/raw/master/screenshot.gif) @@ -12,6 +12,7 @@ Web GUI for youtube-dl (using the [yt-dlp](https://github.com/yt-dlp/yt-dlp) for ```bash docker run -d -p 8081:8081 -v /path/to/downloads:/downloads ghcr.io/alexta69/metube ``` + ## Run using docker-compose ```yaml @@ -40,6 +41,9 @@ Certain values can be set via environment variables, using the `-e` parameter on * __CUSTOM_DIRS__: whether to enable downloading videos into custom directories within the __DOWNLOAD_DIR__ (or __AUDIO_DOWNLOAD_DIR__). When enabled, a drop-down appears next to the Add button to specify the download directory. Defaults to `true`. * __CREATE_CUSTOM_DIRS__: whether to support automatically creating directories within the __DOWNLOAD_DIR__ (or __AUDIO_DOWNLOAD_DIR__) if they do not exist. When enabled, the download directory selector becomes supports free-text input, and the specified directory will be created recursively. Defaults to `true`. * __STATE_DIR__: path to where the queue persistence files will be saved. Defaults to `/downloads/.metube` in the docker image, and `.` otherwise. +* __TEMP_DIR__: path where intermediary download files will be saved. Defaults to `/downloads` in the docker image, and `.` otherwise. + * Set this to an SSD or RAM filesystem (e.g., `tmpfs`) for better performance + * __Note__: Using a RAM filesystem may prevent downloads from being resumed * __DELETE_FILE_ON_TRASHCAN__: if `true`, downloaded files are deleted on the server, when they are trashed from the "Completed" section of the UI. Defaults to `false`. * __URL_PREFIX__: base path for the web server (for use when hosting behind a reverse proxy). Defaults to `/`. * __OUTPUT_TEMPLATE__: the template for the filenames of the downloaded videos, formatted according to [this spec](https://github.com/yt-dlp/yt-dlp/blob/master/README.md#output-template). Defaults to `%(title)s.%(ext)s`. @@ -47,11 +51,13 @@ Certain values can be set via environment variables, using the `-e` parameter on * __YTDL_OPTIONS__: Additional options to pass to youtube-dl, in JSON format. [See available options here](https://github.com/yt-dlp/yt-dlp/blob/master/yt_dlp/YoutubeDL.py#L183). They roughly correspond to command-line options, though some do not have exact equivalents here, for example `--recode-video` has to be specified via `postprocessors`. Also note that dashes are replaced with underscores. The following example value for `YTDL_OPTIONS` embeds English subtitles and chapter markers (for videos that have them), and also changes the permissions on the downloaded video: -``` + +```json {"writesubtitles": true, "subtitleslangs": ["en", "-live_chat"], "postprocessors": [{"key": "Exec", "exec_cmd": "chmod 0664", "when": "after_move"}, {"key": "FFmpegEmbedSubtitle", "already_have_subtitle": false}, {"key": "FFmpegMetadata", "add_chapters": true}]} ``` ## Using browser cookies + In case you need to use your browser's cookies with MeTube, for example to download restricted or private videos: * Add the following to your docker-compose.yml: @@ -62,9 +68,10 @@ In case you need to use your browser's cookies with MeTube, for example to downl environment: - YTDL_OPTIONS={"cookiefile":"/cookies/cookies.txt"} ``` + * Install in your browser an extension to extract cookies: - * [Firefox](https://addons.mozilla.org/en-US/firefox/addon/export-cookies-txt/) - * [Chrome](https://chrome.google.com/webstore/detail/get-cookiestxt-locally/cclelndahbckbenkjhflpdbgdldlbecc) + * [Firefox](https://addons.mozilla.org/en-US/firefox/addon/export-cookies-txt/) + * [Chrome](https://chrome.google.com/webstore/detail/get-cookiestxt-locally/cclelndahbckbenkjhflpdbgdldlbecc) * Extract the cookies you need with the extension and rename the file `cookies.txt` * Drop the file in the folder you configured in the docker-compose.yml above * Restart the container @@ -88,6 +95,7 @@ javascript:!function(){xhr=new XMLHttpRequest();xhr.open("POST","https://metube. ``` [shoonya75](https://github.com/shoonya75) has contributed a Firefox version: + ```javascript javascript:(function(){xhr=new XMLHttpRequest();xhr.open("POST","https://metube.domain.com/add");xhr.send(JSON.stringify({"url":document.location.href,"quality":"best"}));xhr.onload=function(){if(xhr.status==200){alert("Sent to metube!")}else{alert("Send to metube failed. Check the javascript console for clues.")}}})(); ``` diff --git a/app/main.py b/app/main.py index 1faa7a2..7b3817b 100644 --- a/app/main.py +++ b/app/main.py @@ -17,6 +17,7 @@ class Config: _DEFAULTS = { 'DOWNLOAD_DIR': '.', 'AUDIO_DOWNLOAD_DIR': '%%DOWNLOAD_DIR', + 'TEMP_DIR': '%%DOWNLOAD_DIR', 'DOWNLOAD_DIRS_INDEXABLE': 'false', 'CUSTOM_DIRS': 'true', 'CREATE_CUSTOM_DIRS': 'true', @@ -192,4 +193,5 @@ app.on_response_prepare.append(on_prepare) if __name__ == '__main__': logging.basicConfig(level=logging.DEBUG) + log.info(f"Listening on {config.HOST}:{config.PORT}") web.run_app(app, host=config.HOST, port=config.PORT, reuse_port=True) diff --git a/app/ytdl.py b/app/ytdl.py index 84fa4e6..2c52f72 100644 --- a/app/ytdl.py +++ b/app/ytdl.py @@ -42,8 +42,9 @@ class DownloadInfo: class Download: manager = None - def __init__(self, download_dir, output_template, output_template_chapter, quality, format, ytdl_opts, info): + def __init__(self, download_dir, temp_dir, output_template, output_template_chapter, quality, format, ytdl_opts, info): self.download_dir = download_dir + self.temp_dir = temp_dir self.output_template = output_template self.output_template_chapter = output_template_chapter self.format = get_format(format, quality) @@ -81,7 +82,7 @@ class Download: 'quiet': True, 'no_color': True, #'skip_download': True, - 'paths': {"home": self.download_dir}, + 'paths': {"home": self.download_dir, "temp": self.temp_dir}, 'outtmpl': { "default": self.output_template, "chapter": self.output_template_chapter }, 'format': self.format, 'socket_timeout': 30, @@ -159,7 +160,7 @@ class PersistentQueue: def load(self): for k, v in self.saved_items(): - self.dict[k] = Download(None, None, None, None, None, {}, v) + self.dict[k] = Download(None, None, None, None, None, None, {}, v) def exists(self, key): return key in self.dict @@ -258,7 +259,7 @@ class DownloadQueue: for property, value in entry.items(): if property.startswith("playlist"): output = output.replace(f"%({property})s", str(value)) - self.queue.put(Download(dldirectory, output, output_chapter, quality, format, self.config.YTDL_OPTIONS, dl)) + self.queue.put(Download(dldirectory, self.config.TEMP_DIR, output, output_chapter, quality, format, self.config.YTDL_OPTIONS, dl)) self.event.set() await self.notifier.added(dl) return {'status': 'ok'} diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index ccf58d7..d58692c 100644 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -2,15 +2,15 @@ echo "Setting umask to ${UMASK}" umask ${UMASK} -echo "Creating download directory ${DOWNLOAD_DIR} and state directory ${STATE_DIR}" -mkdir -p "${DOWNLOAD_DIR}" "${STATE_DIR}" +echo "Creating download directory (${DOWNLOAD_DIR}), state directory (${STATE_DIR}), and temp dir (${TEMP_DIR})" +mkdir -p "${DOWNLOAD_DIR}" "${STATE_DIR}" "${TEMP_DIR}" if [ `id -u` -eq 0 ] && [ `id -g` -eq 0 ]; then if [ "${UID}" -eq 0 ]; then echo "Warning: it is not recommended to run as root user, please check your setting of the UID environment variable" fi echo "Changing ownership of download and state directories to ${UID}:${GID}" - chown -R "${UID}":"${GID}" /app "${DOWNLOAD_DIR}" "${STATE_DIR}" + chown -R "${UID}":"${GID}" /app "${DOWNLOAD_DIR}" "${STATE_DIR}" "${TEMP_DIR}" echo "Running MeTube as user ${UID}:${GID}" su-exec "${UID}":"${GID}" python3 app/main.py else