Merge pull request #451 from arabcoders/dev
[FEAT] add custom download fields to simple mode.
This commit is contained in:
commit
618ce4d387
8 changed files with 101 additions and 9 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -39,3 +39,4 @@ build
|
|||
version.txt
|
||||
test_impl.py
|
||||
./eslint.config.js
|
||||
.pytest_cache
|
||||
|
|
|
|||
13
README.md
13
README.md
|
|
@ -99,8 +99,17 @@ For simple API documentation, you can refer to the [API documentation](API.md).
|
|||
|
||||
# Disclaimer
|
||||
|
||||
This project is not affiliated with yt-dlp, or any other service. It's a personal project that was created to
|
||||
make downloading videos from the internet easier. It's not intended to be used for piracy or any other illegal activities.
|
||||
This project is not affiliated with yt-dlp or any other service.
|
||||
|
||||
It’s a personal project designed to make downloading videos from the internet more convenient. It’s not intended for
|
||||
piracy or any unlawful use.
|
||||
|
||||
This project was built primarily for my own needs and preferences. The UI might not be the most polished or visually
|
||||
refined, but I’m happy with it as it is. You can, however, create and load your own UI for complete customization. I
|
||||
plan to refactor the UI/UX in the future using [Nuxt/ui](https://ui.nuxt.com/).
|
||||
|
||||
Contributions are welcome, but I may decline changes that don’t align with my vision for the project. Unsolicited pull
|
||||
requests may be ignored. For suggestions or feature requests, please open a discussion or join the Discord server.
|
||||
|
||||
# Social contact
|
||||
|
||||
|
|
|
|||
|
|
@ -370,3 +370,4 @@ div.is-centered {
|
|||
padding: 0;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
<div class="is-block" style="word-break: all;" v-if="props.title">
|
||||
<span style="font-size: 120%;">{{ props.title }}</span>
|
||||
</div>
|
||||
<figure class="image is-3by1 is-hidden-mobile">
|
||||
<figure :class="['image', thumbnail_ratio, 'is-hidden-mobile']">
|
||||
<img @load="e => pImg(e)" :src="url" :alt="props.title" @error="clearCache"
|
||||
:crossorigin="props.privacy ? 'anonymous' : 'use-credentials'"
|
||||
:referrerpolicy="props.privacy ? 'no-referrer' : 'origin'" />
|
||||
|
|
@ -20,6 +20,7 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useStorage } from '@vueuse/core'
|
||||
import { disableOpacity, enableOpacity } from '~/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
|
|
@ -31,6 +32,7 @@ const props = defineProps<{
|
|||
|
||||
const cache = useSessionCache()
|
||||
const toast = useNotification()
|
||||
const thumbnail_ratio = useStorage<'is-16by9' | 'is-3by1'>('thumbnail_ratio', 'is-3by1')
|
||||
const url = ref<string | null>(null)
|
||||
const error = ref(false)
|
||||
const isPreloading = ref(false)
|
||||
|
|
|
|||
|
|
@ -266,7 +266,7 @@
|
|||
</div>
|
||||
</header>
|
||||
<div v-if="showThumbnails" class="card-image">
|
||||
<figure class="image is-3by1">
|
||||
<figure :class="['image', thumbnail_ratio]">
|
||||
<span v-if="'finished' === item.status && item.filename" @click="playVideo(item)" class="play-overlay">
|
||||
<div class="play-icon"></div>
|
||||
<img @load="(e: Event) => pImg(e)" :src="getImage(item)" v-if="getImage(item)" />
|
||||
|
|
@ -475,6 +475,7 @@ const direction = useStorage<'asc' | 'desc'>('sortCompleted', 'desc')
|
|||
const display_style = useStorage<'grid' | 'list'>('display_style', 'grid')
|
||||
const bg_enable = useStorage<boolean>('random_bg', true)
|
||||
const bg_opacity = useStorage<number>('random_bg_opacity', 0.95)
|
||||
const thumbnail_ratio = useStorage<'is-16by9' | 'is-3by1'>('thumbnail_ratio', 'is-3by1')
|
||||
|
||||
const selectedElms = ref<string[]>([])
|
||||
const masterSelectAll = ref(false)
|
||||
|
|
|
|||
|
|
@ -198,7 +198,7 @@
|
|||
</div>
|
||||
</header>
|
||||
<div v-if="showThumbnails" class="card-image">
|
||||
<figure class="image is-3by1">
|
||||
<figure :class="['image', thumbnail_ratio]">
|
||||
<span v-if="isEmbedable(item.url)" @click="embed_url = getEmbedable(item.url) as string"
|
||||
class="play-overlay">
|
||||
<div class="play-icon embed-icon"></div>
|
||||
|
|
@ -336,6 +336,7 @@ const hideThumbnail = useStorage('hideThumbnailQueue', false)
|
|||
const display_style = useStorage('display_style', 'grid')
|
||||
const bg_enable = useStorage('random_bg', true)
|
||||
const bg_opacity = useStorage('random_bg_opacity', 0.95)
|
||||
const thumbnail_ratio = useStorage<'is-16by9' | 'is-3by1'>('thumbnail_ratio', 'is-3by1')
|
||||
|
||||
const selectedElms = ref<string[]>([])
|
||||
const masterSelectAll = ref(false)
|
||||
|
|
|
|||
|
|
@ -104,6 +104,21 @@
|
|||
<p class="help is-bold"> Show videos thumbnail if available.</p>
|
||||
</div>
|
||||
|
||||
<div class="field" v-if="show_thumbnail">
|
||||
<label class="label is-unselectable">Thumbnail Aspect Ratio</label>
|
||||
<div class="control">
|
||||
<label for="ratio_16by9" class="radio">
|
||||
<input id="ratio_16by9" type="radio" v-model="thumbnail_ratio" value="is-16by9">
|
||||
<span> 16:9</span>
|
||||
</label>
|
||||
<label for="ratio_3by1" class="radio">
|
||||
<input id="ratio_3by1" type="radio" v-model="thumbnail_ratio" value="is-3by1">
|
||||
<span> 3:1</span>
|
||||
</label>
|
||||
</div>
|
||||
<p class="help is-bold">Choose the aspect ratio for thumbnail display.</p>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="label" for="allow_toasts">Show notifications</label>
|
||||
<div class="control">
|
||||
|
|
@ -164,6 +179,7 @@ const allow_toasts = useStorage<boolean>('allow_toasts', true)
|
|||
const toast_position = useStorage<POSITION>('toast_position', POSITION.TOP_RIGHT)
|
||||
const toast_dismiss_on_click = useStorage<boolean>('toast_dismiss_on_click', true)
|
||||
const show_thumbnail = useStorage<boolean>('show_thumbnail', true)
|
||||
const thumbnail_ratio = useStorage<'is-16by9' | 'is-3by1'>('thumbnail_ratio', 'is-3by1')
|
||||
const separator = useStorage<string>('url_separator', separators[0]?.value ?? ',')
|
||||
const simpleMode = useStorage<boolean>('simple_mode', useConfigStore().app.simple_mode || false)
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -13,8 +13,9 @@
|
|||
</label>
|
||||
<div class="field has-addons">
|
||||
<div class="control">
|
||||
<button type="button" class="button is-info" @click="() => showPresets = !showPresets">
|
||||
<span class="icon"><i class="fas" :class="showPresets ? 'fa-chevron-up' : 'fa-chevron-down'" /></span>
|
||||
<button type="button" class="button is-info" @click="() => showExtras = !showExtras"
|
||||
v-tooltip="showExtras ? 'Hide extra options' : 'Show extra options'">
|
||||
<span class="icon"><i class="fas" :class="showExtras ? 'fa-chevron-up' : 'fa-chevron-down'" /></span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="control is-expanded">
|
||||
|
|
@ -29,7 +30,7 @@
|
|||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field has-addons" v-if="showPresets">
|
||||
<div class="field has-addons" v-if="showExtras">
|
||||
<div class="control">
|
||||
<label class="button is-static">
|
||||
<span class="icon"><i class="fas fa-sliders" /></span>
|
||||
|
|
@ -55,6 +56,20 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="columns is-multiline is-mobile" v-if="showExtras && configStore.dl_fields.length > 0">
|
||||
<div class="column is-6-tablet is-12-mobile">
|
||||
<DLInput id="force_download" type="bool" label="Force download"
|
||||
v-model="dlFields['--no-download-archive']" icon="fa-solid fa-download"
|
||||
:disabled="!socketStore.isConnected || addInProgress" description="Ignore archive and re-download." />
|
||||
</div>
|
||||
<div class="column is-6-tablet is-12-mobile" v-for="(fi, index) in sortedDLFields"
|
||||
:key="fi.id || `dlf-${index}`">
|
||||
<DLInput :id="fi?.id || `dlf-${index}`" :type="fi.kind" :description="fi.description" :label="fi.name"
|
||||
:icon="fi.icon" v-model="dlFields[fi.field]" :field="fi.field"
|
||||
:disabled="!socketStore.isConnected || addInProgress" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
|
|
@ -213,7 +228,8 @@ const videoItem = ref<StoreItem | null>(null)
|
|||
const formUrl = ref<string>('')
|
||||
const formPreset = ref<{ preset: string }>({ preset: app.value.default_preset || '' })
|
||||
const addInProgress = ref<boolean>(false)
|
||||
const showPresets = ref<boolean>(false)
|
||||
const showExtras = ref<boolean>(false)
|
||||
const dlFields = useStorage<Record<string, any>>('dl_fields', {})
|
||||
const show_thumbnail = useStorage<boolean>('show_thumbnail', true)
|
||||
|
||||
const sortByNewest = (items: StoreItem[]): StoreItem[] => items.slice().sort((a, b) => (b.timestamp ?? 0) - (a.timestamp ?? 0))
|
||||
|
|
@ -247,9 +263,50 @@ const addDownload = async (): Promise<void> => {
|
|||
return
|
||||
}
|
||||
|
||||
let cli = ''
|
||||
|
||||
const dlFieldsExtra = ['--no-download-archive']
|
||||
|
||||
const is_valid = (dl_field: string): boolean => {
|
||||
if (dlFieldsExtra.includes(dl_field)) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (configStore.dl_fields && configStore.dl_fields.length > 0) {
|
||||
return configStore.dl_fields.some(f => dl_field === f.field)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
if (dlFields.value && Object.keys(dlFields.value).length > 0) {
|
||||
const joined = []
|
||||
for (const [key, value] of Object.entries(dlFields.value)) {
|
||||
if (false === is_valid(key)) {
|
||||
continue
|
||||
}
|
||||
|
||||
if ([undefined, null, '', false].includes(value as any)) {
|
||||
continue
|
||||
}
|
||||
|
||||
const keyRegex = new RegExp(`(^|\\s)${key}(\\s|$)`)
|
||||
if (cli && keyRegex.test(cli)) {
|
||||
continue
|
||||
}
|
||||
|
||||
joined.push(true === value ? `${key}` : `${key} ${value}`)
|
||||
}
|
||||
|
||||
if (joined.length > 0) {
|
||||
cli = joined.join(' ')
|
||||
}
|
||||
}
|
||||
|
||||
const payload: item_request[] = [{
|
||||
url,
|
||||
preset: formPreset.value.preset || app.value.default_preset,
|
||||
cli: cli || '',
|
||||
auto_start: true,
|
||||
}]
|
||||
|
||||
|
|
@ -274,6 +331,7 @@ const addDownload = async (): Promise<void> => {
|
|||
|
||||
formUrl.value = ''
|
||||
formPreset.value.preset = app.value.default_preset || ''
|
||||
dlFields.value = {}
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Failed to add download.'
|
||||
toast.error(message)
|
||||
|
|
@ -544,6 +602,9 @@ const showMessage = (item: StoreItem) => {
|
|||
return (item.msg?.length || 0) > 0
|
||||
}
|
||||
|
||||
// eslint-disable-next-line vue/no-side-effects-in-computed-properties
|
||||
const sortedDLFields = computed(() => configStore.dl_fields.sort((a, b) => (a.order || 0) - (b.order || 0)))
|
||||
|
||||
const connectionStatusColor = computed(() => {
|
||||
switch (socketStore.connectionStatus) {
|
||||
case 'connected':
|
||||
|
|
|
|||
Loading…
Reference in a new issue