diff --git a/deploy/Dockerfile.runtime-update b/deploy/Dockerfile.runtime-update index 905fba3..7b46d9a 100644 --- a/deploy/Dockerfile.runtime-update +++ b/deploy/Dockerfile.runtime-update @@ -29,4 +29,4 @@ CMD ["python", "-m", "uvicorn", "pigway_frame_flow.main:app", "--host", "0.0.0.0 ARG BUILD_COMMIT ARG BUILD_TIME ENV BUILD_COMMIT=$BUILD_COMMIT BUILD_TIME=$BUILD_TIME -LABEL org.opencontainers.image.title="Pigway Frame Flow" org.opencontainers.image.version="0.1.13" org.opencontainers.image.revision=$BUILD_COMMIT +LABEL org.opencontainers.image.title="Pigway Frame Flow" org.opencontainers.image.version="0.1.14" org.opencontainers.image.revision=$BUILD_COMMIT diff --git a/docs/releases/v0.1.14.md b/docs/releases/v0.1.14.md new file mode 100644 index 0000000..1cbc385 --- /dev/null +++ b/docs/releases/v0.1.14.md @@ -0,0 +1,5 @@ +# v0.1.14 + +底部文字仅显示日期、已命名人物(使用 & 连接)、文件夹主题及地点,不显示原始文件名。原始文件名仍用于图库和诊断记录。 + +上传传输区分明确的 DNS/连接建立失败与已建立连接中断:前者尚未发送 HTTP 写入,最多重试三次,失败后结束该次操作,不进入阻塞后续写入的 UNKNOWN;读超时或连接中断等不确定结果继续保留核对保护,不盲目重放上传。既有未知操作通过只读设备/图库核验后使用原有 acknowledge 流程关闭,保留原始错误和审计记录。 diff --git a/pigway_frame_flow/api/app.py b/pigway_frame_flow/api/app.py index 391d67e..1f181fd 100644 --- a/pigway_frame_flow/api/app.py +++ b/pigway_frame_flow/api/app.py @@ -126,7 +126,7 @@ def create_app(settings=None, start_worker=True): app = FastAPI( title="Pigway Frame Flow API", docs_url=None, - version="0.1.13", + version="0.1.14", lifespan=lifespan, responses=error_responses, ) @@ -1032,7 +1032,7 @@ def create_app(settings=None, start_worker=True): supports_upload=True, ), ], - version="0.1.13", + version="0.1.14", mock=settings.mock, write_enabled=settings.write_enabled, api_key_configured=bool(settings.api_key) diff --git a/pigway_frame_flow/display/service.py b/pigway_frame_flow/display/service.py index 601f6d8..727f1a4 100644 --- a/pigway_frame_flow/display/service.py +++ b/pigway_frame_flow/display/service.py @@ -296,8 +296,6 @@ class DisplayService: data, name, caption = self.photo_sources.read( content.source_id, item["id"], target_size=size ) - if source.folder_caption: - caption = {**(caption or {}), "filename": name} if ( source.minimum_sharpness or source.minimum_brightness diff --git a/pigway_frame_flow/drivers/kokonna/client.py b/pigway_frame_flow/drivers/kokonna/client.py index 36f4d06..465b358 100644 --- a/pigway_frame_flow/drivers/kokonna/client.py +++ b/pigway_frame_flow/drivers/kokonna/client.py @@ -10,6 +10,7 @@ from collections import deque from urllib.parse import quote import requests +from urllib3.exceptions import MaxRetryError, NewConnectionError from pigway_frame_flow.errors import ErrorCode as E from pigway_frame_flow.errors import FlowError @@ -124,7 +125,15 @@ class SafeTransport: ), "request_error", ) - not_sent = isinstance(exc, requests.ConnectTimeout) + # A socket/DNS establishment failure precedes the HTTP write. A + # dropped established connection still has an uncertain outcome. + not_sent = isinstance(exc, requests.ConnectTimeout) or any( + isinstance(cause, MaxRetryError) + and isinstance(cause.reason, NewConnectionError) + for cause in exc.args + ) + if not_sent and category == "connection_error": + category = "connect_error" uncertain = write and not not_sent raise FlowError( E.WRITE_RESULT_UNKNOWN if uncertain else E.CLOUD_UNAVAILABLE, diff --git a/pigway_frame_flow/models.py b/pigway_frame_flow/models.py index 0888f75..855fee7 100644 --- a/pigway_frame_flow/models.py +++ b/pigway_frame_flow/models.py @@ -366,7 +366,7 @@ class SettingsView(BaseModel): class Health(BaseModel): status: str - version: str = "0.1.13" + version: str = "0.1.14" class Readiness(BaseModel): @@ -396,7 +396,7 @@ class RestoreRequest(StrictModel): class Version(BaseModel): product: str = "Pigway Frame Flow" - version: str = "0.1.13" + version: str = "0.1.14" api_version: str = "v1" commit: str = "development" build_time: str = "unknown" diff --git a/pigway_frame_flow/providers/photo_sources.py b/pigway_frame_flow/providers/photo_sources.py index 3d568ba..5fa5c32 100644 --- a/pigway_frame_flow/providers/photo_sources.py +++ b/pigway_frame_flow/providers/photo_sources.py @@ -621,7 +621,6 @@ class PhotoSources: ) if source.folder_caption: caption = dict(item.get("caption") or {}) - caption["filename"] = item["name"] try: caption["people"] = " & ".join( self.synology.item_people(item["id"], space=source.people_space) @@ -631,7 +630,10 @@ class PhotoSources: logging.getLogger(__name__).warning( "Photos person labels unavailable; keeping photo caption" ) - item = {**item, "caption": caption} + item = { + **item, + "caption": {k: v for k, v in caption.items() if v} or None, + } elif source.kind in ("google-drive", "alipan", "baidu"): from pigway_frame_flow.providers.cloud_drives import CloudDrives diff --git a/pigway_frame_flow/renderers/photo_caption.py b/pigway_frame_flow/renderers/photo_caption.py index 32958c4..6922f77 100644 --- a/pigway_frame_flow/renderers/photo_caption.py +++ b/pigway_frame_flow/renderers/photo_caption.py @@ -41,7 +41,7 @@ def render_captioned_photo( draw = ImageDraw.Draw(canvas) values = [ " ".join(str(caption.get(key, "")).split()) - for key in ("date", "people", "title", "place", "filename") + for key in ("date", "people", "title", "place") ] text = " · ".join(value for value in values if value) font_size = layout.font_size diff --git a/pigway_frame_flow/web/i18n.js b/pigway_frame_flow/web/i18n.js index b76ce35..7ab9d4b 100644 --- a/pigway_frame_flow/web/i18n.js +++ b/pigway_frame_flow/web/i18n.js @@ -20,8 +20,8 @@ Object.assign(MESSAGES.en,{officialContentTitle:'KoKonna official widgets and su Object.assign(MESSAGES.zh,{photoSources:'照片数据源',sourceHint:'通过 Synology Photos API 管理照片来源、人物和筛选规则。浏览与预览不会改变画框。',addSource:'添加数据源',browse:'浏览照片',randomPreview:'随机选图预览',sourceJob:'定时随机照片',noSources:'暂无照片,请添加数据源或检查目录。',sourceKind:'数据源类型',sourceLocation:'目录 / WebDAV URL',sourceLocationHint:'Synology Photos:所选空间内的目录路径,. 表示整个空间。按需获取 XL 预览图,不下载原图。其他云盘使用对应目录地址或 ID。',sourceCredential:'WebDAV 凭据环境变量名',sourceCredentialHint:'仅填写变量名,禁止填写密码。管理员通过环境变量或 Secret 配置 JSON username/password。',sourceRecursive:'包含子目录',sourceImages:'可用照片',sourceBrowseHint:'按需读取目录,点击文件名才生成预览;不会下载全部原图。',previousPage:'上一页',nextPage:'下一页',sourcePreviewHint:'这是已选中照片的预览,显示时使用同一张照片。',randomPhoto:'随机照片',sourceJobHint:'每次执行重新随机选图。默认暂停;启用后会按时改变画框显示。'}); Object.assign(MESSAGES.en,{photoSources:'Photo sources',sourceHint:'Manage photo sources, people and filters through the Synology Photos API. Browsing and previewing do not change the frame.',addSource:'Add source',browse:'Browse photos',randomPreview:'Preview random photo',sourceJob:'Schedule random photos',noSources:'No photos yet. Add a source or check the folder.',sourceKind:'Source type',sourceLocation:'Folder / WebDAV URL',sourceLocationHint:'Synology Photos: folder path within the selected space; . means the whole space. Downloads XL previews on demand, not originals. Other drives use their folder URL or ID.',sourceCredential:'WebDAV credential environment name',sourceCredentialHint:'Enter a variable name, never a password. Admin configures JSON username/password via environment or Secret.',sourceRecursive:'Include subfolders',sourceImages:'Available photos',sourceBrowseHint:'Catalog loaded on demand. Click a filename to preview; originals are not downloaded in bulk.',previousPage:'Previous page',nextPage:'Next page',sourcePreviewHint:'Display uses this exact previewed photo.',randomPhoto:'Random photo',sourceJobHint:'Choose a fresh random photo each run. Paused by default; enabling changes the frame on schedule.'}); -Object.assign(MESSAGES.zh,{organizedPhotos:'仅日期开头文件夹中的照片',folderCaption:'照片下方显示文件夹文字',folderCaptionHint:'底部单行显示日期、已命名人物、主题、@ 后的地点和文件名。子目录沿用最近的日期文件夹;没有日期文件夹时保留文件名,未命名人物不显示。'}); -Object.assign(MESSAGES.en,{organizedPhotos:'Only photos in date-prefixed folders',folderCaption:'Show folder caption below the photo',folderCaptionHint:'One footer line shows date, named people, title, place after @ and filename. Subfolders inherit their nearest dated folder; otherwise the filename is kept. Unnamed people are omitted.'}); +Object.assign(MESSAGES.zh,{organizedPhotos:'仅日期开头文件夹中的照片',folderCaption:'照片下方显示文件夹文字',folderCaptionHint:'底部单行显示日期、已命名人物、主题和 @ 后的地点。子目录沿用最近的日期文件夹;未命名人物不显示,不显示原始文件名。'}); +Object.assign(MESSAGES.en,{organizedPhotos:'Only photos in date-prefixed folders',folderCaption:'Show folder caption below the photo',folderCaptionHint:'One footer line shows date, named people, title and place after @. Subfolders inherit their nearest dated folder; unnamed people and original filenames are omitted.'}); Object.assign(MESSAGES.zh,{});Object.assign(MESSAGES.en,{}); diff --git a/pigway_frame_flow/web/index.html b/pigway_frame_flow/web/index.html index f0d4c39..f9192b5 100644 --- a/pigway_frame_flow/web/index.html +++ b/pigway_frame_flow/web/index.html @@ -1,3 +1,3 @@