diff --git a/docs/verification/v1.2.0/pkg-notarization.json b/docs/verification/v1.2.0/pkg-notarization.json new file mode 100644 index 0000000..69e107d --- /dev/null +++ b/docs/verification/v1.2.0/pkg-notarization.json @@ -0,0 +1,6 @@ +{ + "name": "大鱼人像美化-1.2.0.pkg", + "id": "96a3d1be-a751-4fb8-b062-33a09c01ddf2", + "status": "Accepted", + "createdDate": "2026-10-21T17:39:18.173Z" +} diff --git a/docs/verification/v1.2.0/pkg-verification.json b/docs/verification/v1.2.0/pkg-verification.json new file mode 100644 index 0000000..720ed6f --- /dev/null +++ b/docs/verification/v1.2.0/pkg-verification.json @@ -0,0 +1,36 @@ +{ + "package": "releases/1.2.0/大鱼人像美化-1.2.0.pkg", + "bytes": 5011005, + "sha256": "5bd77c28b3a7e54f3d4060be1f16bcb145d9bc4d6b9cfb9eb137c01130440243", + "version": "1.2.0", + "build": "14", + "app_files_compared": 34, + "app_byte_and_symlink_equality": true, + "template_files": { + "大鱼人像美化.moef": "e9fa97dc14323d0daa7d3d61b06a78ba4000a70ab9f5e67ebffd750d49df1e15", + "Drop Zone.tiff": "a7f8e3a2a431dbb3ce9e777040bb55e701cfe9ab9632e8cbb10359732bc8692e", + "large.png": "a02ddb255b1ae255c749bb837a759e2dee1c3b8ffb9bdc1167957761bd5a1b97", + "small.png": "1552b838dd88f60dee0795e20aaca652f9c558ecefc93500e895fbff77c2a4d5" + }, + "locked_relative_paths": [ + "大鱼人像美化/large.png", + "大鱼人像美化/small.png" + ], + "script_sha256": { + "preinstall": "b0562f7705c84a2f34e31df67a4f0d429e60083bab122dad99f56a4b266c0174", + "postinstall": "eeada4e07a284726d70772591c481f6ae12bfda12ed9ae3035d1089bca0bd59b" + }, + "isolated_script_tests": [ + "fresh install", + "replace old locked thumbnail", + "partial copy failure restores old bytes and locks" + ], + "isolation_changes": [ + "Console identity/home redirected to task fixture", + "chown replaced with true", + "FxPlug verification skipped; formal app signature and discovery verified separately" + ], + "real_installer_execution": false, + "app_and_template_installed": "APP installed through signed delivery workflow; user thumbnail directory updated separately, not by running this PKG", + "fonts": "No editable third-party font dependency; promotional lettering baked into PNG. No optional font ZIP needed." +} diff --git a/releases/1.2.0/article.html b/releases/1.2.0/article.html new file mode 100644 index 0000000..6cd05ad --- /dev/null +++ b/releases/1.2.0/article.html @@ -0,0 +1,66 @@ + +

大鱼人像美化是一款适用于 Final Cut Pro 与 Motion 的免费开源人像插件,把磨皮、美白和脸型调整放进同一块原生卡片面板。插件会识别当前画面中的人脸并自动跟随,无需先点击分析;你可以按素材需要调整强度,拖动滑杆时持续查看效果。

+ + +
大鱼人像美化古风宣传封面,展示自动瘦脸、自动跟随与自动美颜
古风宣传插画;面部线条用于示意瘦脸与跟随功能。
+ + +

可以做什么

+ + + + + +

自动识别负责定位处理区域,效果强度由你控制。建议先从较小的数值开始;多人交叉、严重遮挡和极端侧脸的结果需要结合具体素材检查。

+ + +

版本与下载

+ + +

当前版本 1.2.0(14),支持 macOS 13 或更高版本,包含 Apple Silicon 与 Intel 两种架构。下载包包含 FxPlug4 程序和一个配套 FCP 效果模板。APP 与 PKG 均已完成 Developer ID 正式签名、Apple 公证和票据粘附;安装包为所有用户免费资源。

+ + +

本次更新加入古风封面与锁定缩略图、免费开源说明页和主 APP 三秒自动退出。人脸处理与面板交互沿用 1.1.2 已验收实现,不需要重新运行手动分析。

+ + +

安装

+ + +

安装前先保存工作并退出 Final Cut Pro 和 Motion,安装期间保持关闭。运行本页提供的 PKG,按安装器提示完成安装;它会把程序安装到 /Library/Plug-Ins/FxPlug/beatygril.app,把配套效果安装到当前登录用户的 Motion Templates 目录,并自动验证和修复系统插件注册。

+ + +

本插件免费开源,无需激活码、付费注册或账号授权。主 APP 打开时显示说明图,窗口显示三秒后自动退出;宿主中的滤镜由独立服务运行。安装器还会重新锁定两张缩略图,避免被宿主自动替换。

+ + +
主程序说明页:免费开源,自动注册,无需激活码,启动三秒后自动退出
+ + +

使用

+ + +

Final Cut Pro:打开效果浏览器,找到大鱼磨皮瘦脸分类,将大鱼人像美化添加到人像片段。Motion:在原生滤镜资源库的大鱼分类中选择大鱼人像美化。

+ + +

在检查器中按需调节磨皮、美白和脸型。每项左侧的眼睛可暂时停用该项,数值会保留;右侧回转箭头用于重置。人脸会随画面自动更新,开启检测框预览可以检查跟随位置。多人模式建议先用短片段观察稳定性。

+ + +

常见问题

+ + +

如果效果没有出现或显示红色警告,先退出 Final Cut Pro 和 Motion,再打开一次主 APP,等待其自动退出,然后重新启动宿主。可在“终端”执行下面这条命令:

+ + +
open '/Library/Plug-Ins/FxPlug/beatygril.app'
+ + +

该流程可重新触发系统注册,并不能解决所有报错。仍异常时请核对 macOS、宿主版本及安装包完整性;不要只复制单个 .moef 文件,配套程序与 Drop Zone.tiff 也需要保留。当前仅提供上述一个效果模板。

+ + +

源码与开源许可

+ + +

源码仓库:大鱼人像美化 · paintfcp-fxplug。仓库已设为公开属性;目前大鱼站的统一 Git 网关仍要求登录并校验会员。为方便所有读者获取源码,另外提供 1.2.0 源码快照 ZIP(免费、免 Git 登录),可直接下载研究和自行编译。

+ + +

项目原创代码采用 MIT 许可,源码包含 Xcode 工程、Objective-C / Metal 实现、模板及构建与验证说明。Apple SDK、运行框架及样本按各自许可使用,详见源码中的 LICENSE 和 THIRD_PARTY_NOTICES.md。自行编译需要 Xcode 与 FxPlug SDK;证书和签名私钥不包含在源码中。

+ diff --git a/releases/1.2.0/大鱼人像美化-1.2.0.pkg b/releases/1.2.0/大鱼人像美化-1.2.0.pkg new file mode 100644 index 0000000..80662e4 Binary files /dev/null and b/releases/1.2.0/大鱼人像美化-1.2.0.pkg differ diff --git a/scripts/publish-release.py b/scripts/publish-release.py new file mode 100644 index 0000000..3b620ee --- /dev/null +++ b/scripts/publish-release.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 +"""Publish this reviewed release through the connected Dayu article publisher. +Secrets are read from macOS Keychain into memory; no auth material is persisted. +""" +from pathlib import Path +import base64,hashlib,html,json,mimetypes,ssl,subprocess,urllib.error,urllib.parse,urllib.request,uuid +ROOT=Path(__file__).resolve().parent.parent +STATE=ROOT/'build/publish-operation.json';STATE.parent.mkdir(exist_ok=True) +state=json.loads(STATE.read_text()) if STATE.exists() else {'operation':str(uuid.uuid4())} +def save():STATE.write_text(json.dumps(state,ensure_ascii=False,indent=2)+'\n') +save() +secret=subprocess.run(['security','find-generic-password','-s','dayu-article-publisher','-a','dayu','-w'],text=True,capture_output=True,check=True).stdout.strip() +auth='Basic '+base64.b64encode(('dayu:'+secret).encode()).decode() +class NoRedirect(urllib.request.HTTPRedirectHandler): + def redirect_request(self,*args,**kwargs):return None +opener=urllib.request.build_opener(NoRedirect,urllib.request.HTTPSHandler(context=ssl.create_default_context(cafile='/etc/ssl/cert.pem'))) +def request(path,method='GET',body=None,headers=None): + assert path.startswith('/wp-json/') and '\n' not in path + h={'Authorization':auth,'Accept':'application/json'};h.update(headers or {}) + if isinstance(body,dict):body=json.dumps(body,ensure_ascii=False).encode();h['Content-Type']='application/json' + req=urllib.request.Request('https://dayu.media'+path,data=body,headers=h,method=method) + with opener.open(req,timeout=180) as f:return json.load(f) +def upload(name,path,mime): + if name in state:return state[name] + filename=path.name + matches=request('/wp/v2/media?search='+urllib.parse.quote(path.stem)+'&per_page=30&context=edit') + for item in matches: + if urllib.parse.unquote(item.get('source_url','')).endswith('/'+filename): + # Only reuse a byte-identical previous upload. + with urllib.request.urlopen(item['source_url'],context=ssl.create_default_context(cafile='/etc/ssl/cert.pem'),timeout=90) as r:data=r.read() + if hashlib.sha256(data).digest()==hashlib.sha256(path.read_bytes()).digest(): + state[name]={'id':item['id'],'url':item['source_url']};save();return state[name] + result=request('/wp/v2/media','POST',path.read_bytes(),{'Content-Type':mime,'Content-Disposition':"attachment; filename*=UTF-8''"+urllib.parse.quote(filename)}) + state[name]={'id':result['id'],'url':result['source_url']};save();return state[name] +status=request('/dayu-publisher/v1/status');assert status['can_publish'] and status['can_upload'] and 'seo' in status['fields'] +source=upload('source',ROOT/'build/Dayu-Portrait-1.2.0-source.zip','application/zip') +card=upload('card',ROOT/'beatygril/beatygril/Wrapper Application/Resources/LauncherInfo.png','image/png') +content=(ROOT/'releases/1.2.0/article.html').read_text().replace('{{source_url}}',html.escape(source['url'],quote=True)).replace('{{card_url}}',html.escape(card['url'],quote=True)) +seo={ + 'title':{'enabled':True,'value':'大鱼人像美化 1.2.0|FCP / Motion 免费开源自动美颜插件'}, + 'description':{'enabled':True,'value':'大鱼人像美化是适用于 Final Cut Pro 和 Motion 的免费开源 FxPlug4 插件,支持磨皮、美白、自动人脸跟随及瘦脸调整。提供 Apple 签名公证 PKG 安装包和源码,安装自动注册,无需激活码,并附具体安装、使用及红色警告排查说明。'}, + 'keywords':{'enabled':True,'value':'大鱼人像美化,FCP美颜插件,Motion磨皮,自动瘦脸,自动跟随,FxPlug4,免费开源'} +} +payload={'title':'大鱼人像美化 1.2.0:FCP / Motion 免费开源自动美颜插件','slug':'dayu-portrait-beauty','status':'publish','content':content,'excerpt':'磨皮、美白、瘦脸与自动人脸跟随,原生卡片面板实时预览。免费开源,自动注册,无需激活码;提供签名公证安装包和源码。','categories':[3],'tags':[], 'resource':{'cao_status':True,'cao_price':0,'cao_vip_rate':1,'cao_close_novip_pay':False,'cao_expire_day':0,'cao_pwd':'','cao_info':[{'title':'版本','desc':'1.2.0(14)'},{'title':'宿主','desc':'Final Cut Pro / Motion'},{'title':'系统','desc':'macOS 13 或更高版本'},{'title':'架构','desc':'Apple Silicon / Intel'},{'title':'授权','desc':'免费开源,无需激活码'}]},'seo':seo} +(ROOT/'releases/1.2.0/article-payload.json').write_text(json.dumps(payload,ensure_ascii=False,indent=2)+'\n') +if 'post_id' not in state: + existing=request('/wp/v2/posts?slug=dayu-portrait-beauty&context=edit');assert not existing,'Matching post exists; inspect and continue its ID instead of creating a duplicate' + boundary='DayuRelease'+uuid.uuid4().hex;parts=[] + def part(name,data,filename=None,mime=None): + head='--'+boundary+'\r\nContent-Disposition: form-data; name="'+name+'"' + if filename:head+='; filename="'+filename+'"' + head+='\r\n' + if mime:head+='Content-Type: '+mime+'\r\n' + parts.append(head.encode()+b'\r\n'+data+b'\r\n') + part('payload',json.dumps(payload,ensure_ascii=False).encode()) + part('package',(ROOT/'releases/1.2.0/大鱼人像美化-1.2.0.pkg').read_bytes(),'Dayu-Portrait-1.2.0.pkg','application/octet-stream') + part('cover',(ROOT/'Assets/Artwork/portrait-cover.png').read_bytes(),'Dayu-Portrait-Cover.png','image/png') + body=b''.join(parts)+('--'+boundary+'--\r\n').encode() + result=request('/dayu-publisher/v1/posts','POST',body,{'Content-Type':'multipart/form-data; boundary='+boundary,'Idempotency-Key':state['operation']}) + state['response']=result;save() + # Response schema may vary; resolve the exact saved slug through standard REST. + rows=request('/wp/v2/posts?slug=dayu-portrait-beauty&context=edit');assert len(rows)==1 + state['post_id']=rows[0]['id'];state['url']=rows[0]['link'];save() +saved=request('/dayu-publisher/v1/posts/'+str(state['post_id'])) +(ROOT/'docs/verification/v1.2.0/published-article.json').write_text(json.dumps(saved,ensure_ascii=False,indent=2)+'\n') +print(json.dumps({'post_id':state['post_id'],'url':state['url'],'source':source,'card':card,'snapshot_saved':True},ensure_ascii=False)) diff --git a/scripts/verify-release-package.py b/scripts/verify-release-package.py new file mode 100644 index 0000000..4783654 --- /dev/null +++ b/scripts/verify-release-package.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 +"""Verify an expanded release payload and run template-only update/rollback in isolation.""" +from pathlib import Path +import hashlib,json,os,plistlib,shutil,subprocess,sys +root=Path(__file__).resolve().parent.parent +expanded=root/'build/pkg-expanded/Payload.pkg';payload=expanded/'Payload' +formal=Path('/Library/Plug-Ins/FxPlug/beatygril.app');packed=payload/'Library/Plug-Ins/FxPlug/beatygril.app' +sha=lambda p:hashlib.sha256(p.read_bytes()).hexdigest() +def files(p): + return {str(x.relative_to(p)):('symlink:'+os.readlink(x) if x.is_symlink() else sha(x)) for x in p.rglob('*') if x.is_file() or x.is_symlink()} +assert files(formal)==files(packed),'APP differs from signed installed product' +subprocess.run(['codesign','--verify','--deep','--strict',str(packed)],check=True) +subprocess.run(['xcrun','stapler','validate',str(packed)],check=True,capture_output=True) +source=root/'Templates/大鱼人像美化';template=next(payload.rglob('大鱼人像美化.moef')).parent +runtime=['大鱼人像美化.moef','Drop Zone.tiff','large.png','small.png'] +assert sorted(x.name for x in template.iterdir())==sorted(runtime) +assert all(sha(source/n)==sha(template/n) for n in runtime) +post=(expanded/'Scripts/postinstall').read_text();pre=(expanded/'Scripts/preinstall').read_text() +for p in [expanded/'Scripts/postinstall',expanded/'Scripts/preinstall']: + subprocess.run(['/bin/zsh','-n',str(p)],check=True) +locks=[l for l in post.splitlines() if l.startswith('/usr/bin/chflags uchg "${FINAL_TARGET}')] +assert len(locks)==2 and any('/large.png' in l for l in locks) and any('/small.png' in l for l in locks) +assert 'Template Backups' in post and 'restore_original_locks' in post +# Keep the exact generated functions and template body. Only identity lookup, +# chown and external FxPlug registration are replaced to confine execution. +base=root/'build/pkg-isolation';base.mkdir(exist_ok=False) +volume=base/'volume';home=base/'user';home.mkdir() +start=post.index('CURRENT_ACTIVE=0') +isolated='#!/bin/zsh\nset -eu\nTARGET_VOLUME="'+str(volume)+'"\nCONSOLE_USER="'+os.getlogin()+'"\nCONSOLE_UID="'+str(os.getuid())+'"\nCONSOLE_GROUP="staff"\nUSER_HOME="'+str(home)+'"\n'+post[start:] +isolated=isolated[:isolated.index('fx_verify_app "${TARGET_VOLUME%/}/Library/Plug-Ins/FxPlug/beatygril.app"')]+'exit 0\n' +isolated=isolated.replace('/usr/sbin/chown -R','/usr/bin/true') +new_copy=next(l for l in isolated.splitlines() if l.startswith('/usr/bin/ditto "${TARGET_VOLUME%/}')) +def refill(): + target=volume/template.parent.relative_to(payload) + if target.exists(): + subprocess.run(['chflags','-R','nouchg',str(target)],check=True);shutil.rmtree(target) + target.parent.mkdir(parents=True,exist_ok=True);shutil.copytree(template.parent,target,symlinks=True) +def run_script(name,text,success): + p=base/name;p.write_text(text) + proc=subprocess.run(['/bin/zsh',str(p)],text=True,capture_output=True) + (base/(name+'.output')).write_text(proc.stdout+proc.stderr) + assert (proc.returncode==0)==success,(name,proc.returncode,proc.stderr) + return proc.returncode +final=home/'Movies/Motion Templates.localized/Effects.localized/大鱼磨皮瘦脸/大鱼人像美化' +refill();run_script('fresh-install.zsh',isolated,True) +assert all(sha(final/n)==sha(source/n) for n in runtime) +assert all((final/n).stat().st_flags & 2 for n in ['large.png','small.png']) +# Locked old images are overwritten, then protected again. +subprocess.run(['chflags','nouchg',str(final/'large.png')],check=True) +(final/'large.png').write_bytes(b'old locked thumbnail');subprocess.run(['chflags','uchg',str(final/'large.png')],check=True) +refill();run_script('update-locked.zsh',isolated,True) +assert all(sha(final/n)==sha(source/n) for n in runtime) +assert all((final/n).stat().st_flags & 2 for n in ['large.png','small.png']) +# Simulate a failed payload copy after corrupting one new file; the actual EXIT +# trap must restore the previous bytes and previous immutable flags. +original=files(final);refill() +failing=isolated.replace(new_copy,'/bin/echo partial > "${FINAL_TARGET}/大鱼人像美化/large.png"\n/usr/bin/false',1) +run_script('failed-copy-rollback.zsh',failing,False) +assert files(final)==original +assert all((final/n).stat().st_flags & 2 for n in ['large.png','small.png']) +backups=list((home/'Library/Application Support/PKGForge/Template Backups').rglob('.locked-paths')) +assert backups and all('/Movies/' not in str(x) for x in backups) +package=root/'releases/1.2.0/大鱼人像美化-1.2.0.pkg' +report={'package':str(package.relative_to(root)),'bytes':package.stat().st_size,'sha256':sha(package),'version':'1.2.0','build':'14','app_files_compared':len(files(formal)),'app_byte_and_symlink_equality':True,'template_files':{n:sha(template/n) for n in runtime},'locked_relative_paths':['大鱼人像美化/large.png','大鱼人像美化/small.png'],'script_sha256':{'preinstall':sha(expanded/'Scripts/preinstall'),'postinstall':sha(expanded/'Scripts/postinstall')},'isolated_script_tests':['fresh install','replace old locked thumbnail','partial copy failure restores old bytes and locks'],'isolation_changes':['Console identity/home redirected to task fixture','chown replaced with true','FxPlug verification skipped; formal app signature and discovery verified separately'],'real_installer_execution':False,'app_and_template_installed':'APP installed through signed delivery workflow; user thumbnail directory updated separately, not by running this PKG','fonts':'No editable third-party font dependency; promotional lettering baked into PNG. No optional font ZIP needed.'} +(root/'docs/verification/v1.2.0/pkg-verification.json').write_text(json.dumps(report,ensure_ascii=False,indent=2)+'\n');print(json.dumps(report,ensure_ascii=False,indent=2)) +# This fixture belongs solely to this verifier; remove its locks before cleanup. +subprocess.run(['chflags','-R','nouchg',str(base)],check=True);shutil.rmtree(base)