Skip to content

Commit 2afe653

Browse files
committed
release: v0.8.8 混合流升级与图片标题修复
1 parent 7104533 commit 2afe653

10 files changed

Lines changed: 1119 additions & 267 deletions

File tree

README.md

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
TikLocal's main features include:
1010

11-
* **A TikTok-like swipe-up browsing experience** that allows you to easily and quickly browse local video files.
11+
* **A TikTok-like swipe-up browsing experience** with a mixed feed of local videos and images.
1212
* **A file manager-like directory browsing** feature that allows you to easily find and manage local video files.
1313
* **A Pinterest-like grid layout** feature that allows you to enjoy local images.
1414
* **Support for light and dark modes** to suit your personal preferences.
@@ -19,7 +19,7 @@ TikLocal is suitable for the following use cases:
1919

2020
* You don't trust TikTok's teen mode and want to provide your child with completely controllable video content.
2121
* You want to browse and manage your local videos and images locally, but don't want to use third-party cloud services.
22-
* You want to use a TikTok-style video browsing experience on your phone or tablet.
22+
* You want to use a TikTok-style mixed media browsing experience on your phone or tablet.
2323
* You want to use a Pinterest-style image browsing experience on your phone or tablet.
2424

2525
## How to use
@@ -105,13 +105,27 @@ brew install yt-dlp gallery-dl ffmpeg
105105
sudo apt install yt-dlp gallery-dl ffmpeg
106106
```
107107

108+
### Home Mixed Feed
109+
110+
The home page (`/`) now uses a mixed immersive feed:
111+
112+
- Videos and images are mixed in one swipe flow (video-first density, randomized order)
113+
- Image cards support in-feed AI caption/tags panel
114+
- Image cards support circular magnifier (2.5x / 5x)
115+
- Image cards do **not** auto-advance; swipe manually to move next/previous
116+
108117
### Configuration
109118

110119
TikLocal provides some configuration options that you can adjust to your needs.
111120

112121
* **Light and dark modes:** You can choose to use light or dark mode.
113122
* **Video playback speed:** You can adjust the video playback speed.
114123

124+
## Documentation
125+
126+
- Docs index: `docs/README.md`
127+
- Release notes: `docs/release_notes.md`
128+
115129

116130
## TODO
117131

README_zh.md

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
TikLocal 的主要功能包括:
1010

11-
* 提供类似 **Tiktok****上下滑动浏览** 体验,让您可以轻松快速地浏览本地短视频文件
11+
* 提供类似 **Tiktok****上下滑动浏览** 体验,首页可混合浏览本地视频与图片
1212
* 提供类似 **普通文件管理器****目录浏览** 功能,让您可以方便地查找和管理本地短视频文件。
1313
* 提供类似 **Pinterest****网格布局** 功能,让您可以欣赏本地图片。
1414
* 支持 **浅色和暗色模式**,满足您的个人喜好。
@@ -19,7 +19,7 @@ TikLocal 适用于以下场景:
1919

2020
* 您不相信Tiktok的青少年模式, 想给你的小孩提供完全可控的短视频内容。
2121
* 您想在本地浏览和管理您的短视频和图片文件,但不想使用第三方云服务。
22-
* 您想在手机或 Pad 上使用 Tiktok 式的短视频浏览体验
22+
* 您想在手机或 Pad 上使用 Tiktok 式的视频+图片混合浏览体验
2323
* 您想在手机或 Pad 上使用 Pinterest 式的图片浏览体验。
2424

2525
## 如何使用
@@ -103,13 +103,27 @@ brew install yt-dlp gallery-dl ffmpeg
103103
sudo apt install yt-dlp gallery-dl ffmpeg
104104
```
105105

106+
### 首页混合流(Feed)
107+
108+
首页(`/`)已升级为混合沉浸流:
109+
110+
- 视频与图片在同一条滑动流中混排(视频主导密度,顺序随机化)
111+
- 图片条目支持 AI 标题/标签面板(站内生成与展示)
112+
- 图片条目支持圆形放大镜(2.5x / 5x)
113+
- 图片条目不自动跳转,需手动滑动切换
114+
106115
### 配置
107116

108117
TikLocal 提供了一些配置选项,您可以根据自己的需要进行调整。
109118

110119
* 浅色模式/暗色模式:您可以选择使用浅色模式或暗色模式。
111120
* 视频播放速度:您可以调整视频播放速度。
112121

122+
## 文档
123+
124+
- 文档索引:`docs/README.md`
125+
- 版本记录:`docs/release_notes.md`
126+
113127

114128
## TODO
115129

docs/README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# 文档索引
2+
3+
## 说明
4+
5+
本目录用于沉淀 TikLocal 的关键设计决策、版本变更与实现约束,减少后续迭代时的重复讨论与回归风险。
6+
7+
## 文档列表
8+
9+
- `docs/mixed-feed-design.md`:首页混合流(视频+图片)设计与实现说明。
10+
- `docs/release_notes.md`:版本发布记录与未发布变更清单。

docs/mixed-feed-design.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# 首页混合流(视频+图片)设计说明
2+
3+
- 状态: 已落地
4+
- 更新时间: 2026-02-22
5+
6+
## 背景/目标
7+
8+
- 首页原先是纯视频沉浸流,图片内容被分流到 Gallery,导致首页素材类型单一。
9+
- 目标是在不引入复杂配置的前提下,升级为统一的混合流,保持“视频主导”节奏,同时提升新鲜感。
10+
11+
## 结论/方案
12+
13+
- 后端新增 `GET /api/feed/mix`,统一返回 typed items(`video` / `image`)。
14+
- 前端首页改为单一 Feed 状态机,按条目类型切换播放与交互能力。
15+
- 图片条目复用 Gallery 的关键体验:AI 标题/标签、圆形放大镜、单击专注模式(仅隐藏左下信息层)。
16+
17+
## 关键决策与理由
18+
19+
- 混排策略使用“目标比例 + 轻随机 + 连续段约束”,而不是固定 `4V+1I`:降低用户可预测性,保持内容新鲜感。
20+
- 图片不自动切换:避免打断阅读图片细节,改为手动滑动控制节奏。
21+
- 视频与图片工具分离:视频不显示 AI 按钮,图片不显示倍速按钮,减少认知噪音。
22+
- 专注模式只隐藏左下 AI 信息层:保留右侧操作按钮,避免关键操作入口丢失。
23+
24+
## 实施要点
25+
26+
- 后端:
27+
1. 从推荐服务分别拉取视频与图片候选池。
28+
2. 基于 seed 的伪随机混排,保证分页行为稳定且可复现。
29+
3. 输出统一结构(`type/name/media_url/thumb_url/detail_url`)。
30+
- 前端:
31+
1. 统一 Feed 渲染(`video``img` 元素共用切换流程)。
32+
2. 按类型启用控件:仅视频显示进度/倍速,仅图片显示 AI/放大镜。
33+
3. 放大镜按 `object-fit: contain` 的真实内容框计算取样,避免横向压扁。
34+
35+
## 影响范围
36+
37+
- 首页沉浸流入口:`/`
38+
- API:`/api/feed/mix`
39+
- 交互行为:单击、双击、滑动、图片专注、图片放大与 AI 信息层展示。
40+
41+
## 风险与权衡
42+
43+
- 混排随机性提高后,单次浏览的可解释性会下降。
44+
- 采用固定内置比例(不放到 settings)降低了配置复杂度,但牺牲了用户级个性化调节。
45+
- 前端状态机复杂度上升,需要测试覆盖关键类型切换场景。
46+
47+
## 后续事项
48+
49+
- [ ] 增加混合流比例/连续段统计的轻量调试日志(仅开发模式)。
50+
- [ ] 增加首页混合流端到端测试(类型切换、控件显隐、手势行为)。
51+
- [ ] 评估后续“图片组(carousel)”条目形态,在不破坏当前手势模型下演进。
52+
53+
## 相关文件/模块
54+
55+
- `tiklocal/app.py`
56+
- `tiklocal/templates/tiktok.html`
57+
- `tests/test_feed_mix.py`
58+
- `docs/release_notes.md`

docs/release_notes.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
11
# Release Notes
22

3+
## Unreleased
4+
5+
## v0.8.8 (2026-02-22)
6+
- 首页沉浸流升级为混合媒体 Feed:在同一滑动流中混排视频与图片,替代原纯视频首页链路。
7+
- 新增 `/api/feed/mix`,统一返回 typed media items(`video` / `image`),并使用“目标比率 + 轻随机约束”混排,避免固定节奏可预测性。
8+
- 首页图片条目复用 Gallery 关键交互:AI 标题/标签面板、2.5x/5x 圆形放大镜、单击专注模式(仅隐藏左下信息层)。
9+
- 交互收敛:视频不显示 AI 按钮,图片不显示倍速按钮;图片不再自动计时切换,改为手动滑动切换。
10+
- 修复首页放大镜取样计算:按 `object-fit: contain` 的真实内容框计算,避免横向压扁。
11+
- 修复首页图片 AI 标题/标签不显示问题:调整 `currentCaptionUri` 生命周期,避免异步回写被错误丢弃。
12+
- 清理首页混合流过期代码:移除无效 `controls-active` 状态切换与不可触发的播放图标点击监听。
13+
- 新增 `tests/test_feed_mix.py` 覆盖混合 Feed API 基本行为,并新增混合流设计文档索引。
14+
315
## v0.8.7 (2026-02-21)
416
- 新增“来源回跳”能力:下载成功后将文件与原始 URL 建立映射,并新增 `~/.tiklocal/download_sources.json` 持久化来源索引。
517
- 新增来源解析三层兜底:优先来源映射,其次 `.info.json``webpage_url/original_url`),最后按文件名结构推断平台链接(x/youtube/tiktok/instagram)。

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "TikLocal"
3-
version = "0.8.7"
3+
version = "0.8.8"
44
description = "A local media server that combines the features of TikTok and Pinterest"
55
authors = ["ChanMo <chan.mo@outlook.com>"]
66
readme = "README.md"

tests/test_feed_mix.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import pytest
2+
3+
from tiklocal.app import create_app
4+
5+
6+
@pytest.fixture
7+
def client(tmp_path, monkeypatch):
8+
media_root = tmp_path / "media"
9+
media_root.mkdir(parents=True, exist_ok=True)
10+
(media_root / "v1.mp4").write_bytes(b"00")
11+
(media_root / "v2.mp4").write_bytes(b"00")
12+
(media_root / "nested").mkdir(parents=True, exist_ok=True)
13+
(media_root / "nested" / "v 3.mp4").write_bytes(b"00")
14+
15+
(media_root / "i1.jpg").write_bytes(b"00")
16+
(media_root / "i2.png").write_bytes(b"00")
17+
18+
data_root = tmp_path / "tiklocal-data"
19+
monkeypatch.setenv("MEDIA_ROOT", str(media_root))
20+
monkeypatch.setenv("TIKLOCAL_INSTANCE", str(data_root))
21+
22+
app = create_app({"TESTING": True, "MEDIA_ROOT": media_root})
23+
return app.test_client()
24+
25+
26+
def test_mix_feed_returns_typed_items(client):
27+
res = client.get("/api/feed/mix?page=1&size=12&seed=fixed-seed")
28+
assert res.status_code == 200
29+
data = res.get_json()
30+
31+
assert isinstance(data, dict)
32+
assert "items" in data
33+
assert "seed" in data
34+
assert "has_more" in data
35+
36+
items = data["items"]
37+
assert isinstance(items, list)
38+
assert len(items) > 0
39+
40+
types = {item.get("type") for item in items}
41+
assert "video" in types
42+
assert "image" in types
43+
44+
for item in items:
45+
assert "name" in item
46+
assert "media_url" in item
47+
assert "detail_url" in item
48+
assert item["type"] in {"video", "image"}
49+
if item["type"] == "video":
50+
assert item["detail_url"].startswith("/detail/")
51+
else:
52+
assert item["detail_url"].startswith("/image?uri=")
53+
54+
55+
def test_mix_feed_falls_back_to_videos_when_no_images(tmp_path, monkeypatch):
56+
media_root = tmp_path / "media"
57+
media_root.mkdir(parents=True, exist_ok=True)
58+
(media_root / "only-video.mp4").write_bytes(b"00")
59+
(media_root / "only-video-2.mp4").write_bytes(b"00")
60+
61+
data_root = tmp_path / "tiklocal-data"
62+
monkeypatch.setenv("MEDIA_ROOT", str(media_root))
63+
monkeypatch.setenv("TIKLOCAL_INSTANCE", str(data_root))
64+
65+
app = create_app({"TESTING": True, "MEDIA_ROOT": media_root})
66+
test_client = app.test_client()
67+
68+
res = test_client.get("/api/feed/mix?page=1&size=8&seed=fixed-seed")
69+
assert res.status_code == 200
70+
data = res.get_json()
71+
72+
items = data["items"]
73+
assert len(items) > 0
74+
assert all(item["type"] == "video" for item in items)

0 commit comments

Comments
 (0)