diff --git a/javascript/index.js b/javascript/index.js
index c497e08..a98e45f 100644
--- a/javascript/index.js
+++ b/javascript/index.js
@@ -13,8 +13,8 @@ Promise.resolve().then(async () => {
Infinite Image Browsing
-
-
+
+
diff --git a/scripts/iib/api.py b/scripts/iib/api.py
index c6bdc53..fd6bc7b 100644
--- a/scripts/iib/api.py
+++ b/scripts/iib/api.py
@@ -710,13 +710,21 @@ def infinite_image_browsing_api(app: FastAPI, **kwargs):
@app.get(api_base + "/image_geninfo", dependencies=[Depends(verify_secret)])
async def image_geninfo(path: str):
- return parse_image_info(path).raw_info
+ # 使用 get_exif_data 函数,它已经支持视频文件
+ from scripts.iib.db.update_image_data import get_exif_data
+ try:
+ result = get_exif_data(path)
+ return result.raw_info or ""
+ except Exception as e:
+ logger.error(f"Failed to get geninfo for {path}: {e}")
+ return ""
class GeninfoBatchReq(BaseModel):
paths: List[str]
@app.post(api_base + "/image_geninfo_batch", dependencies=[Depends(verify_secret)])
async def image_geninfo_batch(req: GeninfoBatchReq):
+ from scripts.iib.db.update_image_data import get_exif_data
res = {}
conn = DataBase.get_conn()
for path in req.paths:
@@ -725,9 +733,11 @@ def infinite_image_browsing_api(app: FastAPI, **kwargs):
if img:
res[path] = img.exif
else:
- res[path] = parse_image_info(path).raw_info
+ result = get_exif_data(path)
+ res[path] = result.raw_info or ""
except Exception as e:
- logger.error(e, stack_info=True)
+ logger.error(f"Failed to get geninfo for {path}: {e}", stack_info=True)
+ res[path] = ""
return res
@@ -919,6 +929,7 @@ def infinite_image_browsing_api(app: FastAPI, **kwargs):
cursor: str
folder_paths: List[str] = None
size: Optional[int] = 200
+ random_sort: Optional[bool] = False
@app.post(db_api_base + "/match_images_by_tags", dependencies=[Depends(verify_secret)])
async def match_image_by_tags(req: MatchImagesByTagsReq):
@@ -933,7 +944,8 @@ def infinite_image_browsing_api(app: FastAPI, **kwargs):
tag_dict={"and": req.and_tags, "or": req.or_tags, "not": req.not_tags},
cursor=req.cursor,
folder_paths=folder_paths,
- limit=req.size
+ limit=req.size,
+ random_sort=req.random_sort
)
return {
"files": filter_allowed_files([x.to_file_info() for x in imgs]),
diff --git a/scripts/iib/db/datamodel.py b/scripts/iib/db/datamodel.py
index 3f0b5a5..e7f82da 100644
--- a/scripts/iib/db/datamodel.py
+++ b/scripts/iib/db/datamodel.py
@@ -533,6 +533,7 @@ class ImageTag:
limit: int = 500,
cursor="",
folder_paths: List[str] = None,
+ random_sort: bool = False,
) -> tuple[List[Image], Cursor]:
query = """
SELECT image.id, image.path, image.size,image.date
@@ -583,7 +584,7 @@ class ImageTag:
print(folder_path)
where_clauses.append("(" + " OR ".join(folder_clauses) + ")")
- if cursor:
+ if cursor and not random_sort:
where_clauses.append("(image.date < ?)")
params.append(cursor)
if where_clauses:
@@ -593,7 +594,17 @@ class ImageTag:
query += " HAVING COUNT(DISTINCT tag_id) = ?"
params.append(len(tag_dict["and"]))
- query += " ORDER BY date DESC LIMIT ?"
+ if random_sort:
+ query += " ORDER BY RANDOM() LIMIT ?"
+ # For random sort, use offset-based pagination
+ if cursor:
+ try:
+ offset = int(cursor)
+ query = query.replace("LIMIT ?", f"LIMIT ? OFFSET {offset}")
+ except (ValueError, TypeError):
+ pass # Invalid cursor, start from beginning
+ else:
+ query += " ORDER BY date DESC LIMIT ?"
params.append(limit)
api_cur = Cursor()
with closing(conn.cursor()) as cur:
@@ -610,7 +621,13 @@ class ImageTag:
Image.safe_batch_remove(conn, deleted_ids)
api_cur.has_next = len(rows) >= limit
if images:
- api_cur.next = str(images[-1].date)
+ if random_sort:
+ # For random sort, use offset-based cursor
+ current_offset = int(cursor) if cursor else 0
+ api_cur.next = str(current_offset + len(images))
+ else:
+ # For date sort, use date-based cursor
+ api_cur.next = str(images[-1].date)
return images, api_cur
@classmethod
diff --git a/scripts/iib/db/update_image_data.py b/scripts/iib/db/update_image_data.py
index 720a47f..6d9e18f 100644
--- a/scripts/iib/db/update_image_data.py
+++ b/scripts/iib/db/update_image_data.py
@@ -9,7 +9,9 @@ from scripts.iib.tool import (
is_dev,
get_modified_date,
is_image_file,
- case_insensitive_get
+ case_insensitive_get,
+ get_img_geninfo_txt_path,
+ parse_generation_parameters
)
from scripts.iib.parsers.model import ImageGenerationInfo, ImageGenerationParams
from scripts.iib.logger import logger
@@ -19,6 +21,26 @@ from scripts.iib.plugin import plugin_inst_map
# 定义一个函数来获取图片文件的EXIF数据
def get_exif_data(file_path):
if get_video_type(file_path):
+ # 对于视频文件,尝试读取对应的txt标签文件
+ txt_path = get_img_geninfo_txt_path(file_path)
+ if txt_path:
+ try:
+ with open(txt_path, 'r', encoding='utf-8') as f:
+ content = f.read().strip()
+ if content:
+ # 复用现有解析逻辑,添加视频标识
+ params = parse_generation_parameters(content + "\nSource Identifier: Video Tags")
+ return ImageGenerationInfo(
+ content,
+ ImageGenerationParams(
+ meta=params["meta"],
+ pos_prompt=params["pos_prompt"],
+ extra=params,
+ ),
+ )
+ except Exception as e:
+ if is_dev:
+ logger.error("Failed to read video txt file %s: %s", txt_path, e)
return ImageGenerationInfo()
try:
return parse_image_info(file_path)
diff --git a/vue/src/api/db.ts b/vue/src/api/db.ts
index 08313a8..8691ccd 100644
--- a/vue/src/api/db.ts
+++ b/vue/src/api/db.ts
@@ -43,6 +43,7 @@ export interface MatchImageByTagsReq {
and_tags: TagId[]
or_tags: TagId[]
not_tags: TagId[]
+ random_sort?: boolean
}
export const getImagesByTags = async (req: MatchImageByTagsReq, cursor: string) => {
diff --git a/vue/src/i18n/de.ts b/vue/src/i18n/de.ts
index bae8aa5..45a0d56 100644
--- a/vue/src/i18n/de.ts
+++ b/vue/src/i18n/de.ts
@@ -141,5 +141,7 @@ export const de: Partial = {
randomImageSettingNotification: 'Tipp: Sie können in den globalen Einstellungen steuern, ob die Zufallsbild-Option auf der Startseite angezeigt wird',
mediaType: 'Medientyp',
all: 'Alle',
- video: 'Video'
+ video: 'Video',
+ randomSort: 'Zufällig sortieren',
+ sortByDate: 'Nach Datum sortieren'
}
diff --git a/vue/src/i18n/en.ts b/vue/src/i18n/en.ts
index 577fa00..c578d3f 100644
--- a/vue/src/i18n/en.ts
+++ b/vue/src/i18n/en.ts
@@ -351,5 +351,7 @@ You can specify which snapshot to restore to when starting IIB in the global set
tagOperationFailed: 'Tag operation failed',
mediaType: 'Media Type',
all: 'All',
- video: 'Video'
+ video: 'Video',
+ randomSort: 'Random Sort',
+ sortByDate: 'Sort by Date'
}
diff --git a/vue/src/i18n/zh-hans.ts b/vue/src/i18n/zh-hans.ts
index d8cf36f..1d61ef4 100644
--- a/vue/src/i18n/zh-hans.ts
+++ b/vue/src/i18n/zh-hans.ts
@@ -330,5 +330,7 @@ export const zhHans = {
tagOperationFailed: '标签操作失败',
mediaType: '媒体类型',
all: '全部',
- video: '视频'
+ video: '视频',
+ randomSort: '随机排序',
+ sortByDate: '按日期排序'
}
diff --git a/vue/src/i18n/zh-hant.ts b/vue/src/i18n/zh-hant.ts
index f826e4f..ac1d395 100644
--- a/vue/src/i18n/zh-hant.ts
+++ b/vue/src/i18n/zh-hant.ts
@@ -335,5 +335,7 @@ export const zhHant: Partial = {
tagOperationFailed: '標籤操作失敗',
mediaType: '媒體類型',
all: '全部',
- video: '視頻'
+ video: '視頻',
+ randomSort: '隨機排序',
+ sortByDate: '按日期排序'
}
diff --git a/vue/src/page/TagSearch/MatchedImageGrid.vue b/vue/src/page/TagSearch/MatchedImageGrid.vue
index f964011..ea539ed 100644
--- a/vue/src/page/TagSearch/MatchedImageGrid.vue
+++ b/vue/src/page/TagSearch/MatchedImageGrid.vue
@@ -5,7 +5,7 @@ import '@zanllp/vue-virtual-scroller/dist/vue-virtual-scroller.css'
import { RecycleScroller } from '@zanllp/vue-virtual-scroller'
import { toRawFileUrl } from '@/util/file'
import { getImagesByTags, type MatchImageByTagsReq } from '@/api/db'
-import { nextTick, watch } from 'vue'
+import { nextTick, watch, ref } from 'vue'
import { copy2clipboardI18n } from '@/util'
import fullScreenContextMenu from '@/page/fileTransfer/fullScreenContextMenu.vue'
import { LeftCircleOutlined, RightCircleOutlined } from '@/icon'
@@ -23,7 +23,13 @@ const props = defineProps<{
}>()
-const iter = createImageSearchIter(cursor => getImagesByTags(props.selectedTagIds, cursor))
+// 添加随机排序状态
+const randomSort = ref(true)
+
+// 创建搜索迭代器,根据随机排序状态决定参数
+const iter = createImageSearchIter(cursor => {
+ return getImagesByTags({...props.selectedTagIds, random_sort: randomSort.value}, cursor)
+})
const {
queue,
images,
@@ -67,6 +73,17 @@ watch(
{ immediate: true }
)
+// 监听随机排序状态变化
+watch(
+ randomSort,
+ async () => {
+ await iter.reset()
+ await nextTick()
+ scroller.value?.scrollToItem(0)
+ onScroll() // 重新获取
+ }
+)
+
watch(
() => props,
@@ -114,6 +131,9 @@ const onTiktokViewClick = () => {
+
+ {{ randomSort ? '🎲 ' + $t('randomSort') : '📅 ' + $t('sortByDate') }}
+
{{ $t('tiktokView') }}
{{ $t('saveLoadedImageAsJson') }}
{{ $t('saveAllAsJson') }}