Merge 2141db22a8 into aa8d9eb712
This commit is contained in:
commit
3ead3bca4c
5 changed files with 129 additions and 5 deletions
|
|
@ -62,12 +62,12 @@ Certain values can be set via environment variables, using the `-e` parameter on
|
|||
* __YTDL_OPTIONS__: Additional options to pass to yt-dlp, in JSON format. [See available options here](https://github.com/yt-dlp/yt-dlp/blob/master/yt_dlp/YoutubeDL.py#L220). 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. You may find [this script](https://github.com/yt-dlp/yt-dlp/blob/master/devscripts/cli_to_api.py) helpful for converting from command line options to `YTDL_OPTIONS`.
|
||||
* __YTDL_OPTIONS_FILE__: A path to a JSON file that will be loaded and used for populating `YTDL_OPTIONS` above. Please note that if both `YTDL_OPTIONS_FILE` and `YTDL_OPTIONS` are specified, the options in `YTDL_OPTIONS` take precedence.
|
||||
* __ROBOTS_TXT__: A path to a `robots.txt` file mounted in the container
|
||||
* __DOWNLOAD_MODE__ :This flag controls how downloads are scheduled and executed. Options are `sequential`, `concurrent`, and `limited`. Defaults to `limited`:
|
||||
* `sequential`: Downloads are processed one at a time. A new download won’t start until the previous one has finished. This mode is useful for conserving system resources or ensuring downloads occur in a strict order.
|
||||
* __DOWNLOAD_MODE__ :This flag controls how downloads are scheduled and executed. Options are `sequential`, `concurrent`, and `limited`. Defaults to `limited`. Can also be configured in the UI:
|
||||
* `sequential`: Downloads are processed one at a time. A new download won't start until the previous one has finished. This mode is useful for conserving system resources or ensuring downloads occur in a strict order.
|
||||
* `concurrent`: Downloads are started immediately as they are added, with no built-in limit on how many run simultaneously. This mode may overwhelm your system if too many downloads start at once.
|
||||
* `limited`: Downloads are started concurrently but are capped by a concurrency limit. In this mode, a semaphore is used so that at most a fixed number of downloads run at any given time.
|
||||
* **MAX\_CONCURRENT\_DOWNLOADS** This flag is used only when **DOWNLOAD\_MODE** is set to **limited**.
|
||||
It specifies the maximum number of simultaneous downloads allowed. For example, if set to `5`, then at most five downloads will run concurrently, and any additional downloads will wait until one of the active downloads completes. Defaults to `3`.
|
||||
It specifies the maximum number of simultaneous downloads allowed. For example, if set to `5`, then at most five downloads will run concurrently, and any additional downloads will wait until one of the active downloads completes. Defaults to `3`. Can also be configured in the UI.
|
||||
* __LOGLEVEL__: Log level, can be set to `DEBUG`, `INFO`, `WARNING`, `ERROR`, `CRITICAL` or `NONE`. Defaults to `INFO`.
|
||||
* __ENABLE_ACCESSLOG__: whether to enable access log. Defaults to `false`.
|
||||
|
||||
|
|
|
|||
54
app/main.py
54
app/main.py
|
|
@ -264,11 +264,63 @@ async def history(request):
|
|||
log.info("Sending download history")
|
||||
return web.Response(text=serializer.encode(history))
|
||||
|
||||
@routes.post(config.URL_PREFIX + 'update_download_config')
|
||||
async def update_download_config(request):
|
||||
post = await request.json()
|
||||
download_mode = post.get('download_mode')
|
||||
max_concurrent = post.get('max_concurrent_downloads')
|
||||
|
||||
if download_mode not in ['sequential', 'concurrent', 'limited']:
|
||||
raise web.HTTPBadRequest(text='Invalid download_mode')
|
||||
|
||||
if max_concurrent is not None:
|
||||
try:
|
||||
max_concurrent = int(max_concurrent)
|
||||
if max_concurrent < 1:
|
||||
raise ValueError()
|
||||
except ValueError:
|
||||
raise web.HTTPBadRequest(text='max_concurrent_downloads must be a positive integer')
|
||||
|
||||
# Update config
|
||||
config.DOWNLOAD_MODE = download_mode
|
||||
if max_concurrent is not None:
|
||||
config.MAX_CONCURRENT_DOWNLOADS = max_concurrent
|
||||
|
||||
# Update download queue configuration
|
||||
if download_mode == 'sequential':
|
||||
if not hasattr(dqueue, 'seq_lock'):
|
||||
dqueue.seq_lock = asyncio.Lock()
|
||||
dqueue.semaphore = None
|
||||
elif download_mode == 'limited':
|
||||
dqueue.semaphore = asyncio.Semaphore(int(config.MAX_CONCURRENT_DOWNLOADS))
|
||||
if hasattr(dqueue, 'seq_lock'):
|
||||
delattr(dqueue, 'seq_lock')
|
||||
else: # concurrent
|
||||
dqueue.semaphore = None
|
||||
if hasattr(dqueue, 'seq_lock'):
|
||||
delattr(dqueue, 'seq_lock')
|
||||
|
||||
# Notify all clients of the configuration change
|
||||
await sio.emit('configuration', serializer.encode(config))
|
||||
|
||||
return web.Response(text=serializer.encode({'status': 'ok'}))
|
||||
|
||||
@sio.event
|
||||
async def connect(sid, environ):
|
||||
log.info(f"Client connected: {sid}")
|
||||
await sio.emit('all', serializer.encode(dqueue.get()), to=sid)
|
||||
await sio.emit('configuration', serializer.encode(config), to=sid)
|
||||
# Include download settings in configuration
|
||||
config_dict = {
|
||||
'CUSTOM_DIRS': config.CUSTOM_DIRS,
|
||||
'CREATE_CUSTOM_DIRS': config.CREATE_CUSTOM_DIRS,
|
||||
'DEFAULT_OPTION_PLAYLIST_STRICT_MODE': config.DEFAULT_OPTION_PLAYLIST_STRICT_MODE,
|
||||
'DEFAULT_OPTION_PLAYLIST_ITEM_LIMIT': config.DEFAULT_OPTION_PLAYLIST_ITEM_LIMIT,
|
||||
'PUBLIC_HOST_URL': config.PUBLIC_HOST_URL,
|
||||
'PUBLIC_HOST_AUDIO_URL': config.PUBLIC_HOST_AUDIO_URL,
|
||||
'DOWNLOAD_MODE': config.DOWNLOAD_MODE,
|
||||
'MAX_CONCURRENT_DOWNLOADS': config.MAX_CONCURRENT_DOWNLOADS
|
||||
}
|
||||
await sio.emit('configuration', serializer.encode(config_dict), to=sid)
|
||||
if config.CUSTOM_DIRS:
|
||||
await sio.emit('custom_dirs', serializer.encode(get_custom_dirs()), to=sid)
|
||||
if config.YTDL_OPTIONS_FILE:
|
||||
|
|
|
|||
|
|
@ -207,6 +207,34 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Download Configuration -->
|
||||
<div class="row mt-3" *ngIf="isAdvancedOpen">
|
||||
<div class="col">
|
||||
<h6>Download Configuration</h6>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<label for="downloadMode" class="form-label">Download Mode</label>
|
||||
<select id="downloadMode" class="form-select" [(ngModel)]="downloadMode" (change)="onDownloadModeChange()">
|
||||
<option *ngFor="let mode of availableDownloadModes" [value]="mode.id">{{mode.name}}</option>
|
||||
</select>
|
||||
<div class="form-text">
|
||||
<small>Sequential: Downloads one at a time<br>
|
||||
Limited: Downloads up to the specified number concurrently<br>
|
||||
Unlimited: No limit on concurrent downloads</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6" *ngIf="downloadMode === 'limited'">
|
||||
<label for="maxConcurrent" class="form-label">Max Concurrent Downloads</label>
|
||||
<input type="number" id="maxConcurrent" class="form-control" min="1" max="20"
|
||||
[(ngModel)]="maxConcurrentDownloads" (change)="onMaxConcurrentChange()">
|
||||
<div class="form-text">
|
||||
<small>Maximum number of simultaneous downloads (1-20)</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Advanced Actions -->
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
|
|
|
|||
|
|
@ -44,6 +44,15 @@ export class AppComponent implements AfterViewInit {
|
|||
metubeVersion: string | null = null;
|
||||
isAdvancedOpen = false;
|
||||
|
||||
// Download configuration
|
||||
downloadMode: string = 'limited';
|
||||
maxConcurrentDownloads: number = 3;
|
||||
availableDownloadModes = [
|
||||
{ id: 'sequential', name: 'Sequential (one at a time)' },
|
||||
{ id: 'limited', name: 'Limited concurrent' },
|
||||
{ id: 'concurrent', name: 'Unlimited concurrent' }
|
||||
];
|
||||
|
||||
// Download metrics
|
||||
activeDownloads = 0;
|
||||
queuedDownloads = 0;
|
||||
|
|
@ -196,6 +205,14 @@ export class AppComponent implements AfterViewInit {
|
|||
if (playlistItemLimit !== '0') {
|
||||
this.playlistItemLimit = playlistItemLimit;
|
||||
}
|
||||
|
||||
// Update download configuration
|
||||
if (config['DOWNLOAD_MODE']) {
|
||||
this.downloadMode = config['DOWNLOAD_MODE'];
|
||||
}
|
||||
if (config['MAX_CONCURRENT_DOWNLOADS']) {
|
||||
this.maxConcurrentDownloads = config['MAX_CONCURRENT_DOWNLOADS'];
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -516,4 +533,24 @@ export class AppComponent implements AfterViewInit {
|
|||
|
||||
this.totalSpeed = downloadingItems.reduce((total, item) => total + (item.speed || 0), 0);
|
||||
}
|
||||
|
||||
updateDownloadConfig() {
|
||||
this.downloads.updateDownloadConfig(this.downloadMode, this.maxConcurrentDownloads)
|
||||
.subscribe((status: Status) => {
|
||||
if (status.status === 'error') {
|
||||
alert(`Error updating download configuration: ${status.msg}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onDownloadModeChange() {
|
||||
this.updateDownloadConfig();
|
||||
}
|
||||
|
||||
onMaxConcurrentChange() {
|
||||
if (this.maxConcurrentDownloads < 1) {
|
||||
this.maxConcurrentDownloads = 1;
|
||||
}
|
||||
this.updateDownloadConfig();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -157,5 +157,12 @@ export class DownloadsService {
|
|||
return Array.from(this.queue.values()).map(download => download.url);
|
||||
}
|
||||
|
||||
|
||||
public updateDownloadConfig(downloadMode: string, maxConcurrentDownloads: number): Observable<Status> {
|
||||
return this.http.post<Status>('update_download_config', {
|
||||
download_mode: downloadMode,
|
||||
max_concurrent_downloads: maxConcurrentDownloads
|
||||
}).pipe(
|
||||
catchError(this.handleHTTPError)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue