### Queue Downloads with Duplicate Detection Source: https://context7.com/deniscerri/ytdlnis/llms.txt Queues a list of DownloadItems, applying duplicate detection policies. Handles results, including duplicate IDs and messages. Can also schedule downloads for a future start time. ```kotlin viewLifecycleOwner.lifecycleScope.launch { val result = downloadViewModel.queueDownloads( items = listOf(downloadItem), ignoreDuplicates = false ) if (result.duplicateDownloadIDs.isNotEmpty()) { // Show user a dialog listing duplicates result.duplicateDownloadIDs.forEach { ids -> println("Duplicate: downloadID=${ids.downloadItemID}, historyID=${ids.historyItemID}") } } if (result.message.isNotBlank()) { Toast.makeText(requireContext(), result.message, Toast.LENGTH_LONG).show() } } ``` ```kotlin // Queue with a future start time (scheduled download) downloadItem.downloadStartTime = System.currentTimeMillis() + TimeUnit.HOURS.toMillis(2) downloadViewModel.queueDownloads(listOf(downloadItem)) // → item gets status "Scheduled"; WorkManager uses setInitialDelay() ``` -------------------------------- ### Initialize and Execute yt-dlp with RuntimeManager Source: https://context7.com/deniscerri/ytdlnis/llms.txt Initializes the RuntimeManager and demonstrates executing a yt-dlp command to download audio. Call init() once in Application.onCreate(). Execute commands from a background thread or coroutine. Handles progress, cancellation, and exceptions. ```kotlin RuntimeManager.init(applicationContext) val request = YTDLRequest("https://www.youtube.com/watch?v=dQw4w9WgXcQ") .addOption("-x") .addOption("--audio-format", "mp3") .addOption("--audio-quality", "0") .addOption("-o", "/sdcard/Music/%(title)s.%(ext)s") try { val response: ExecuteResponse = RuntimeManager.execute( request = request, processId = "download-42", callback = { progress, etaMs, line -> println("$progress% | ETA ${etaMs}ms | $line") } ) println("Exit: ${response.exitCode}, took ${response.elapsedTime}ms") println(response.out) } catch (e: ExecuteException) { System.err.println("yt-dlp failed: ${e.message}") } catch (e: RuntimeManager.CanceledException) { println("Download was cancelled by the user") } RuntimeManager.destroyProcessById("download-42") ``` -------------------------------- ### Build yt-dlp Commands with YTDLRequest Source: https://context7.com/deniscerri/ytdlnis/llms.txt Demonstrates building yt-dlp commands for single and multiple URL downloads, including options for subtitles and audio format. Inspects options and appends raw arguments. ```kotlin val request = YTDLRequest("https://www.youtube.com/watch?v=abc123") .addOption("--write-subs") .addOption("--embed-subs") .addOption("--sub-langs", "en.*") .addOption("--merge-output-format", "mkv") .addOption("-o", "/sdcard/Videos/%(uploader)s - %(title)s.%(ext)s") println(request.buildCommand()) val batch = YTDLRequest(listOf( "https://soundcloud.com/artist/track1", "https://soundcloud.com/artist/track2" )).addOption("-x") .addOption("--audio-format", "flac") println(batch.hasOption("-x")) println(batch.getOption("--audio-format")) val withExtras = YTDLRequest("https://example.com/video") .addCommands(listOf("--no-playlist", "--socket-timeout", "30")) ``` -------------------------------- ### Load BotGuard Client Instance Source: https://github.com/deniscerri/ytdlnis/blob/main/app/src/main/assets/po_token.html Use this factory method to create and initialize a BotGuardClient. Ensure the VM and its 'a' property are available in the global scope. ```javascript function loadBotGuard(challengeData) { this.vm = this[challengeData.globalName]; this.program = challengeData.program; this.vmFunctions = {}; this.syncSnapshotFunction = null; if (!this.vm) throw new Error('[BotGuardClient]: VM not found in the global object'); if (!this.vm.a) throw new Error('[BotGuardClient]: Could not load program'); const vmFunctionsCallback = function ( asyncSnapshotFunction, shutdownFunction, passEventFunction, checkCameraFunction ) { this.vmFunctions = { asyncSnapshotFunction: asyncSnapshotFunction, shutdownFunction: shutdownFunction, passEventFunction: passEventFunction, checkCameraFunction: checkCameraFunction }; }; this.syncSnapshotFunction = this.vm.a(this.program, vmFunctionsCallback, true, this.userInteractionElement, function () {/* no-op */ }, [ [], [] ])[0] // an asynchronous function runs in the background and it will eventually call // `vmFunctionsCallback`, however we need to manually tell JavaScript to pass // control to the things running in the background by interrupting this async // function in any way, e.g. with a delay of 1ms. The loop is most probably not // needed but is there just because. return new Promise(function (resolve, reject) { i = 0 refreshIntervalId = setInterval(function () { if (!!this.vmFunctions.asyncSnapshotFunction) { resolve(this) clearInterval(refreshIntervalId); } if (i >= 10000) { reject("asyncSnapshotFunction is null even after 10 seconds") clearInterval(refreshIntervalId); } i += 1; }, 1); }) } ``` -------------------------------- ### Run BotGuard Initialization and Snapshot Source: https://github.com/deniscerri/ytdlnis/blob/main/app/src/main/assets/po_token.html This function orchestrates the loading of BotGuard and taking a snapshot. It first executes any provided interpreter JavaScript, then loads the client, and finally calls the snapshot method. ```javascript function runBotGuard(challengeData) { const interpreterJavascript = challengeData.interpreterJavascript.privateDoNotAccessOrElseSafeScriptWrappedValue; if (interpreterJavascript) { new Function(interpreterJavascript)(); } else throw new Error('Could not load VM'); const webPoSignalOutput = [] return loadBotGuard({ globalName: challengeData.globalName, globalObj: this, program: challengeData.program }).then(function (botguard) { return botguard.snapshot({ webPoSignalOutput: webPoSignalOutput }) }).then(function (botguardResponse) { return { webPoSignalOutput: webPoSignalOutput, botguardResponse: botguardResponse } }) } ``` -------------------------------- ### RuntimeManager.init() and RuntimeManager.execute() Source: https://context7.com/deniscerri/ytdlnis/llms.txt Initializes the RuntimeManager and executes a yt-dlp request. The execute method is blocking and should be called from a background thread or coroutine. It provides progress updates via a callback and returns the execution response or throws an exception on failure or cancellation. ```APIDOC ## RuntimeManager Initialization and Execution ### Description Initializes the RuntimeManager singleton and executes a yt-dlp command. The `execute` method is blocking and should be called from a background thread or coroutine. It accepts a `YTDLRequest`, a unique `processId` for cancellation, and a callback for progress updates. ### Method `RuntimeManager.init(context: Context)` `RuntimeManager.execute(request: YTDLRequest, processId: String, callback: (progress: Float, etaMs: Long, line: String) -> Unit): ExecuteResponse` ### Parameters #### `RuntimeManager.init` Parameters - **context** (Context) - Required - The application context for initialization. #### `RuntimeManager.execute` Parameters - **request** (YTDLRequest) - Required - The request object containing URLs and yt-dlp options. - **processId** (String) - Required - A unique identifier for the process, used for cancellation. - **callback** (Function) - Optional - A lambda function to receive progress updates (progress percentage, estimated time remaining in milliseconds, and log line). ### Request Example ```kotlin // Initialize once in Application.onCreate() RuntimeManager.init(applicationContext) // Build a request: download the best audio from a URL val request = YTDLRequest("https://www.youtube.com/watch?v=dQw4w9WgXcQ") .addOption("-x") // extract audio .addOption("--audio-format", "mp3") .addOption("--audio-quality", "0") .addOption("-o", "/sdcard/Music/%(title)s.%(ext)s") // Execute synchronously (call from a coroutine / background thread) try { val response: ExecuteResponse = RuntimeManager.execute( request = request, processId = "download-42", // unique ID for cancellation callback = { progress, etaMs, line -> println("$progress% | ETA ${etaMs}ms | $line") } ) println("Exit: ${response.exitCode}, took ${response.elapsedTime}ms") println(response.out) } catch (e: ExecuteException) { System.err.println("yt-dlp failed: ${e.message}") } catch (e: RuntimeManager.CanceledException) { println("Download was cancelled by the user") } ``` ### Response #### Success Response (ExecuteResponse) - **exitCode** (Int) - The exit code of the yt-dlp process. - **elapsedTime** (Long) - The total time taken for the process in milliseconds. - **out** (String) - The standard output from the yt-dlp process. #### Error Responses - **ExecuteException** - Thrown if the yt-dlp process fails. - **RuntimeManager.CanceledException** - Thrown if the process is cancelled. ``` -------------------------------- ### DownloadViewModel Lifecycle Methods Source: https://context7.com/deniscerri/ytdlnis/llms.txt Provides methods to manage the lifecycle of individual downloads, including pausing, resuming, and canceling. ```APIDOC ## DownloadViewModel Lifecycle Methods ### Description The ViewModel exposes methods to pause individual downloads (kills the yt-dlp process and sets status to `Paused`), resume them (re-queues at the top), cancel all active/queued work via WorkManager, and observe per-bucket counts as Kotlin `Flow`. ### Methods #### pauseDownload ##### Description Pauses a specific download. ##### Method Signature `suspend fun pauseDownload(itemId: Long)` ##### Parameters - **itemId** (Long) - Required - The ID of the download to pause. ##### Request Example ```kotlin viewLifecycleOwner.lifecycleScope.launch { downloadViewModel.pauseDownload(itemId = 42L) } ``` #### resumeDownload ##### Description Resumes a specific download, re-queuing it at the top. ##### Method Signature `fun resumeDownload(itemID: Long)` ##### Parameters - **itemID** (Long) - Required - The ID of the download to resume. ##### Request Example ```kotlin downloadViewModel.resumeDownload(itemID = 42L) ``` #### pauseAllDownloads ##### Description Pauses all currently active downloads. ##### Method Signature `suspend fun pauseAllDownloads()` ##### Request Example ```kotlin viewLifecycleOwner.lifecycleScope.launch { downloadViewModel.pauseAllDownloads() } ``` #### resumeAllDownloads ##### Description Resumes all previously paused downloads. ##### Method Signature `fun resumeAllDownloads()` ##### Request Example ```kotlin downloadViewModel.resumeAllDownloads() ``` #### cancelAllDownloads ##### Description Cancels all active and queued downloads, keeping records. ##### Method Signature `fun cancelAllDownloads()` ##### Request Example ```kotlin downloadViewModel.cancelAllDownloads() ``` #### deleteAll ##### Description Cancels all downloads and deletes all associated records. ##### Method Signature `fun deleteAll()` ##### Request Example ```kotlin downloadViewModel.deleteAll() ``` #### activeDownloadsCount ##### Description Observes the count of active downloads reactively. ##### Method Signature `val activeDownloadsCount: Flow` ##### Response Example ```kotlin downloadViewModel.activeDownloadsCount .onEach { count -> badge.text = count.toString() } .launchIn(viewLifecycleOwner.lifecycleScope) ``` ``` -------------------------------- ### YTDLRequest and YTDLOptions Source: https://context7.com/deniscerri/ytdlnis/llms.txt Builds yt-dlp commands by chaining options and URLs. The `buildCommand()` method generates the argument list suitable for `RuntimeManager.execute()`. ```APIDOC ## YTDLRequest and YTDLOptions Command Builder ### Description `YTDLRequest` and `YTDLOptions` provide a fluent API for constructing yt-dlp command-line arguments. They allow specifying URLs, options, and raw commands, and can generate the final command list for execution. ### Method `YTDLRequest(url: String)` `YTDLRequest(urls: List)` `YTDLRequest.addOption(name: String, value: String = "")` `YTDLRequest.addOptions(options: List)` `YTDLRequest.addCommands(commands: List)` `YTDLRequest.buildCommand(): List` `YTDLRequest.hasOption(name: String): Boolean` `YTDLRequest.getOption(name: String): String?` ### Parameters #### `YTDLRequest` Constructor Parameters - **url** (String) - A single URL for the download. - **urls** (List) - A list of URLs for batch downloading. #### `YTDLRequest` Method Parameters - **name** (String) - The name of the yt-dlp option (e.g., "-x", "--audio-format"). - **value** (String) - The value for the option, if applicable. Defaults to an empty string if the option does not take a value. - **options** (List) - A list of options to add. - **commands** (List) - A list of raw yt-dlp arguments to append to the command. ### Request Example ```kotlin // Single URL – video download with subtitle embedding val request = YTDLRequest("https://www.youtube.com/watch?v=abc123") .addOption("--write-subs") .addOption("--embed-subs") .addOption("--sub-langs", "en.*") .addOption("--merge-output-format", "mkv") .addOption("-o", "/sdcard/Videos/%(uploader)s - %(title)s.%(ext)s") println(request.buildCommand()) // [--write-subs, --embed-subs, --sub-langs, en.*, --merge-output-format, mkv, // -o, /sdcard/Videos/%(uploader)s - %(title)s.%(ext)s, // https://www.youtube.com/watch?v=abc123] // Multiple URLs – batch download val batch = YTDLRequest(listOf( "https://soundcloud.com/artist/track1", "https://soundcloud.com/artist/track2" )).addOption("-x") .addOption("--audio-format", "flac") // Inspect options at runtime println(batch.hasOption("-x")) // true println(batch.getOption("--audio-format")) // "flac" // Append raw yt-dlp arguments not covered by addOption() val withExtras = YTDLRequest("https://example.com/video") .addCommands(listOf("--no-playlist", "--socket-timeout", "30")) ``` ``` -------------------------------- ### RuntimeManager.updateYTDL Source: https://context7.com/deniscerri/ytdlnis/llms.txt Fetches the latest yt-dlp release, compares it with the local version, downloads if necessary, and updates SharedPreferences. Supports different update channels. ```APIDOC ## RuntimeManager.updateYTDL — update yt-dlp binary `updateYTDL` fetches the latest release from GitHub's API, compares it against the locally stored version tag, downloads the new binary if necessary, and persists the new version in `SharedPreferences`. ### Usage ```kotlin // Update from the stable channel (default) val status = RuntimeManager.updateYTDL(applicationContext) when (status) { RuntimeManager.UpdateStatus.DONE -> println("yt-dlp updated to ${RuntimeManager.versionName(applicationContext)}") RuntimeManager.UpdateStatus.ALREADY_UP_TO_DATE -> println("Already on latest: ${RuntimeManager.version(applicationContext)}") null -> println("Update skipped (appContext was null)") } // Update from the nightly channel try { RuntimeManager.updateYTDL(applicationContext, RuntimeManager.UpdateChannel.NIGHTLY) } catch (e: ExecuteException) { Log.e("Update", "Failed: ${e.message}") } ``` ### Channels - **STABLE**: `RuntimeManager.UpdateChannel.STABLE` (Defaults to `https://api.github.com/repos/yt-dlp/yt-dlp/releases/latest`) - **NIGHTLY**: `RuntimeManager.UpdateChannel.NIGHTLY` (Nightly builds repo) - **MASTER**: `RuntimeManager.UpdateChannel.MASTER` (Master builds repo) ``` -------------------------------- ### yt-dlp Format Selection Arguments Source: https://github.com/deniscerri/ytdlnis/blob/main/CHANGELOG.md Arguments for yt-dlp to handle format selection, including embedding subtitles and specifying preferred languages. ```bash write-subs and write-auto-subs and --compat-options no-keep-subs ``` ```bash -S ``` ```bash ba[lang^=...] ``` -------------------------------- ### Create yt-dlp CommandTemplate for Audio Downloads Source: https://context7.com/deniscerri/ytdlnis/llms.txt Defines a CommandTemplate to append SponsorBlock removal to YouTube audio downloads. It specifies the content for yt-dlp and a URL regex to match YouTube domains. ```kotlin val sbTemplate = CommandTemplate( id = 0, title = "YouTube SponsorBlock", content = "--sponsorblock-remove sponsor,selfpromo", useAsExtraCommand = false, useAsExtraCommandAudio = true, useAsExtraCommandVideo = false, useAsExtraCommandDataFetching = false, preferredCommandTemplate = false, urlRegex = mutableListOf("youtube\\.com", "youtu\\.be") ) ``` -------------------------------- ### Add Player Client Arguments Source: https://github.com/deniscerri/ytdlnis/blob/main/fastlane/metadata/android/en-US/changelogs/107090000.txt Specifies player client arguments for data fetching and downloading to utilize a wider range of formats. ```bash player_client=default,mediaconnect,android ``` -------------------------------- ### Manage All Downloads Lifecycle Source: https://context7.com/deniscerri/ytdlnis/llms.txt Provides methods to pause, resume, and cancel all active and queued downloads simultaneously. Also includes a method to cancel and delete all records. ```kotlin // Pause ALL active downloads at once viewLifecycleOwner.lifecycleScope.launch { downloadViewModel.pauseAllDownloads() } ``` ```kotlin // Resume all paused downloads downloadViewModel.resumeAllDownloads() ``` ```kotlin // Cancel all active + queued downloads and keep records downloadViewModel.cancelAllDownloads() ``` ```kotlin // Cancel AND delete all records downloadViewModel.deleteAll() ``` -------------------------------- ### Update yt-dlp Binary with RuntimeManager Source: https://context7.com/deniscerri/ytdlnis/llms.txt Fetches the latest yt-dlp release, compares it with the local version, downloads if necessary, and saves the new version. Supports stable, nightly, and master channels. Handles potential null appContext. ```kotlin val status = RuntimeManager.updateYTDL(applicationContext) when (status) { RuntimeManager.UpdateStatus.DONE -> println("yt-dlp updated to ${RuntimeManager.versionName(applicationContext)}") RuntimeManager.UpdateStatus.ALREADY_UP_TO_DATE -> println("Already on latest: ${RuntimeManager.version(applicationContext)}") null -> println("Update skipped (appContext was null)") } ``` ```kotlin try { RuntimeManager.updateYTDL(applicationContext, RuntimeManager.UpdateChannel.NIGHTLY) } catch (e: ExecuteException) { Log.e("Update", "Failed: ${e.message}") } ``` ```kotlin // Available channels RuntimeManager.UpdateChannel.STABLE // https://api.github.com/repos/yt-dlp/yt-dlp/releases/latest RuntimeManager.UpdateChannel.NIGHTLY // nightly builds repo RuntimeManager.UpdateChannel.MASTER // master builds repo ``` -------------------------------- ### Android Intent Integration for Downloads Source: https://context7.com/deniscerri/ytdlnis/llms.txt Allows third-party applications to initiate downloads by sending intents with specific extras. Supports 'audio', 'video', or 'command' types and background processing. ```kotlin // Send a URL from another app (Kotlin example using startActivity) val intent = Intent(Intent.ACTION_SEND).apply { type = "text/plain" putExtra(Intent.EXTRA_TEXT, "https://www.youtube.com/watch?v=dQw4w9WgXcQ") putExtra("TYPE", "audio") // "audio" | "video" | "command" putExtra("BACKGROUND", true) // skip the bottom-sheet card entirely setPackage("com.deniscerri.ytdl") } context.startActivity(intent) ``` ```text // ── Tasker example (Send Intent task) ──────────────────────────── // Action: android.intent.action.SEND // Cat: Default // Mime Type: text/plain // Extra: android.intent.extra.TEXT : https://example.com/video // Extra: TYPE : video // Extra: BACKGROUND : true ``` ```shell // ── adb shell example ──────────────────────────────────────────── // adb shell am start \ // -a android.intent.action.SEND \ // -t text/plain \ // --es android.intent.extra.TEXT "https://youtu.be/abc" \ // --es TYPE audio \ // --ez BACKGROUND true \ // -n com.deniscerri.ytdl/.receiver.ShareActivity ``` -------------------------------- ### Create Preferred yt-dlp CommandTemplate for Custom Invocation Source: https://context7.com/deniscerri/ytdlnis/llms.txt Defines a preferred CommandTemplate for custom yt-dlp invocations, specifying the video format and merge output format. This template is used when the download type is 'command'. ```kotlin val customTemplate = CommandTemplate( id = 0, title = "Best 1080p h264", content = "-f \"bestvideo[height<=1080][vcodec^=avc]+bestaudio/best\" --merge-output-format mp4", useAsExtraCommand = false, useAsExtraCommandAudio = false, useAsExtraCommandVideo = false, useAsExtraCommandDataFetching = false, preferredCommandTemplate = true, urlRegex = mutableListOf() ) ``` -------------------------------- ### Create DownloadItem for Audio Download Source: https://context7.com/deniscerri/ytdlnis/llms.txt Constructs a DownloadItem entity for an audio download, specifying metadata, format, preferences, and download path. Room auto-generates the ID. ```kotlin // Construct a DownloadItem for an audio download val audioItem = DownloadItem( id = 0, // auto-generated by Room url = "https://soundcloud.com/artist/track", title = "My Track", author = "Artist Name", thumb = "https://cdn.example.com/thumb.jpg", duration = "3:45", type = DownloadType.audio, format = Format(format_id = "bestaudio", container = "mp3"), container = "mp3", downloadSections = "", allFormats = mutableListOf(), downloadPath = "/sdcard/Music", website = "soundcloud.com", downloadSize = "", playlistTitle = "", audioPreferences = AudioPreferences( embedThumb = true, cropThumb = false, splitByChapters = false, sponsorBlockFilters = arrayListOf(), bitrate = "320" ), videoPreferences = VideoPreferences(), // defaults are fine for audio extraCommands = "--no-playlist", customFileNameTemplate = "% (uploader).30B - %(title).170B", SaveThumb = false, status = "Queued", downloadStartTime = 0L, logID = null ) ``` -------------------------------- ### DownloadViewModel.queueDownloads Source: https://context7.com/deniscerri/ytdlnis/llms.txt Enqueues downloads with duplicate detection. Accepts a list of DownloadItems, applies duplicate-detection policies, and hands them off for processing. ```APIDOC ## queueDownloads ### Description Enqueues downloads with duplicate detection. Accepts a list of `DownloadItem`s, applies the duplicate-detection policy configured in settings (`url_type`, `download_archive`, or `config`), marks items as `Queued` or `Scheduled`, and hands them off to `DownloadRepository.startDownloadWorker()`. ### Method Signature `suspend fun queueDownloads(items: List, ignoreDuplicates: Boolean = true): QueueResult` ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body - **items** (List) - Required - The list of items to queue for download. - **ignoreDuplicates** (Boolean) - Optional - If true, skips duplicate detection. Defaults to true. ### Request Example ```kotlin viewLifecycleOwner.lifecycleScope.launch { val result = downloadViewModel.queueDownloads( items = listOf(downloadItem), ignoreDuplicates = false ) if (result.duplicateDownloadIDs.isNotEmpty()) { // Show user a dialog listing duplicates result.duplicateDownloadIDs.forEach { ids -> println("Duplicate: downloadID=${ids.downloadItemID}, historyID=${ids.historyItemID}") } } if (result.message.isNotBlank()) { Toast.makeText(requireContext(), result.message, Toast.LENGTH_LONG).show() } } // Queue with a future start time (scheduled download) downloadItem.downloadStartTime = System.currentTimeMillis() + TimeUnit.HOURS.toMillis(2) downloadViewModel.queueDownloads(listOf(downloadItem)) // → item gets status "Scheduled"; WorkManager uses setInitialDelay() ``` ### Response #### Success Response (QueueResult) - **duplicateDownloadIDs** (List) - A list of IDs for duplicate downloads detected. - **message** (String) - A message indicating the result of the operation. #### Response Example ```json { "duplicateDownloadIDs": [ { "downloadItemID": 123, "historyItemID": 456 } ], "message": "Downloads queued successfully." } ``` ``` -------------------------------- ### Create Queued DownloadItem from ResultItem Source: https://context7.com/deniscerri/ytdlnis/llms.txt Converts a ResultItem into a DownloadItem, applying user preferences from SharedPreferences for paths, containers, and other settings. Queues the download using DownloadViewModel. ```kotlin // Inside a Fragment or Activity that has a DownloadViewModel val downloadViewModel: DownloadViewModel by viewModels() // Assume `result` was previously populated by ResultViewModel.fetchInfo() val result: ResultItem = /* ... */ // Build a DownloadItem honouring all user preferences val downloadItem = downloadViewModel.createDownloadItemFromResult( result = result, givenType = DownloadType.video ) // Inspect the produced item println(downloadItem.downloadPath) // e.g. "/sdcard/Movies" println(downloadItem.container) // e.g. "mp4" println(downloadItem.videoPreferences.embedSubs) // true if pref is set println(downloadItem.customFileNameTemplate) // Queue it immediately viewLifecycleOwner.lifecycleScope.launch { downloadViewModel.queueDownloads(listOf(downloadItem)) } ``` -------------------------------- ### DownloadViewModel.createDownloadItemFromResult Source: https://context7.com/deniscerri/ytdlnis/llms.txt Converts a ResultItem into a DownloadItem, applying user preferences read from SharedPreferences for paths, containers, and other settings. ```APIDOC ## DownloadViewModel.createDownloadItemFromResult — create a queued download `createDownloadItemFromResult` converts a `ResultItem` (the metadata returned from a yt-dlp info-fetch) into a fully configured `DownloadItem` by reading all user preferences from `SharedPreferences` (paths, containers, subtitle settings, SponsorBlock filters, filename templates, etc.). ### Usage ```kotlin // Inside a Fragment or Activity that has a DownloadViewModel val downloadViewModel: DownloadViewModel by viewModels() // Assume `result` was previously populated by ResultViewModel.fetchInfo() val result: ResultItem = /* ... */ // Build a DownloadItem honouring all user preferences val downloadItem = downloadViewModel.createDownloadItemFromResult( result = result, givenType = DownloadType.video ) // Inspect the produced item println(downloadItem.downloadPath) // e.g. "/sdcard/Movies" println(downloadItem.container) // e.g. "mp4" println(downloadItem.videoPreferences.embedSubs) // true if pref is set println(downloadItem.customFileNameTemplate) // Queue it immediately viewLifecycleOwner.lifecycleScope.launch { downloadViewModel.queueDownloads(listOf(downloadItem)) } ``` ```