@@ -0,0 +1,288 @@
param (
[ string ] $ProjectRoot = ( Split-Path -Parent $PSScriptRoot ) ,
[ string ] $PackageDate = ( [ DateTimeOffset ] :: UtcNow . ToOffset ( [ TimeSpan ] :: FromHours ( 8 ) ) . ToString ( 'yyyy-MM-dd' ) ) ,
[ string ] $PackageName = '' ,
[ switch ] $RefreshDescriptions
)
$ErrorActionPreference = 'Stop'
Add-Type -AssemblyName System . Drawing
$ProjectRoot = [ IO.Path ] :: GetFullPath ( $ProjectRoot )
if ( -not $PackageName ) { $PackageName = '素材网上传包_' + $PackageDate }
if ( $PackageName -notmatch '^素材网上传包_[^/\\:]+$' ) { throw 'PackageName必须是素材网上传包_开头的单个目录名' }
$dest = Join-Path $ProjectRoot " 06_AI动漫制作/07_交付与发行/ $PackageName "
$existingNames = @ { }
if ( Test-Path -LiteralPath $dest ) {
if ( -not $RefreshDescriptions ) { throw " 目标已存在,为保留已有交付请另用PackageName: $dest " }
$prior = Get-Content -LiteralPath " $dest /素材清单.json " -Raw | ConvertFrom-Json
if ( $prior . schema_version -ne 1 -or $prior . project -ne '炉火照彻诸神沉默的年代' ) { throw '不是本工具的交付包' }
foreach ( $item in @ ( $prior . items ) + @ ( Get-Content -LiteralPath " $dest /外部参考清单.json " -Raw | ConvertFrom-Json ) ) { $existingNames [ $item . sha256 ] = $item . filename }
}
$notes = Get-Content -LiteralPath ( Join-Path $PSScriptRoot 'image_catalog_notes.json' ) -Raw | ConvertFrom-Json -AsHashtable
$episodeRoot = Join-Path $ProjectRoot '06_AI动漫制作/05_生产素材/正片/S01E01_H3_V01'
$manifest = Get-Content -LiteralPath " $episodeRoot /source_manifest.json " -Raw | ConvertFrom-Json
$shotMap = @ { } ; foreach ( $shot in $manifest . shots ) { $shotMap [ $shot . id ] = $shot }
$sceneNames = @ { 1 = '前世租屋' ; 2 = '化工厂交班与更衣' ; 3 = '事故与救人' ; 4 = '婴儿与泉堂外景' ; 5 = '孤儿院照料婴儿' ; 6 = '泉堂祝福与沉默' ; 7 = '孤儿院日常' ; 8 = '大公府出发' ; 9 = '大圣堂会面' ; 10 = '勇者装备交付' ; 12 = '少年与送货' }
$linked = @ { }
$indexPath = Join-Path $ProjectRoot '06_AI动漫制作/02_视觉圣经/角色设定/角色参考图索引.md'
foreach ( $m in [ regex ] :: Matches ( [ IO.File ] :: ReadAllText ( $indexPath ) , '\[([^\]]+)\]\(([^)]+\.png)\)' ) ) {
$target = [ IO.Path ] :: GetFullPath ( ( Join-Path ( Split-Path $indexPath ) $m . Groups [ 2 ] . Value ) )
$linked [ $target ] = $m . Groups [ 1 ] . Value
}
$reviewNotes = @ { }
$reviewDoc = Join-Path $ProjectRoot '06_AI动漫制作/02_视觉圣经/角色设定/2026-09-29_其余角色逐张生成_V08.md'
foreach ( $line in Get-Content -LiteralPath $reviewDoc ) {
if ( $line -match '^\| \d+ \| ([^|]+) \| ([^|]+) \|' ) { $reviewNotes [ $Matches [ 1 ] . Trim ( ) ] = $Matches [ 2 ] . Trim ( ) }
}
Push-Location $ProjectRoot
try { $paths = @ ( rg - -files - -hidden - -no -ignore -g '*.png' -g '*.jpg' -g '*.jpeg' -g '*.webp' -g '*.gif' -g '*.bmp' -g '!.git/**' -g '!06_AI动漫制作/07_交付与发行/**' ) } finally { Pop-Location }
$sources = [ Collections.Generic.List[object] ] :: new ( )
foreach ( $rel in $paths ) {
$sources . Add ( [ pscustomobject ] @ { Full = ( Join-Path $ProjectRoot $rel ) ; Rel = $rel . Replace ( '\' , '/' ) ; External = ( $rel -match '画风参考/|画风参考\\|用户提供' ) ; Origin = '项目' } )
}
$desktop = 'C:\Users\zhang\Desktop\AI短剧\角色管理'
if ( Test-Path -LiteralPath $desktop ) {
foreach ( $f in Get-ChildItem -LiteralPath $desktop -File -Recurse | Where-Object Extension -in @ ( '.png' , '.jpg' , '.jpeg' , '.webp' ) ) {
$sources . Add ( [ pscustomobject ] @ { Full = $f . FullName ; Rel = ( '桌面角色管理/' + [ IO.Path ] :: GetRelativePath ( $desktop , $f . FullName ) . Replace ( '\' , '/' ) ) ; External = $true ; Origin = '桌面来源待核参考' } )
}
}
foreach ( $s in $sources ) { $s | Add-Member Hash ( Get-FileHash -LiteralPath $s . Full -Algorithm SHA256 ) . Hash . ToLowerInvariant ( ) }
$groups = @ ( $sources | Group-Object Hash )
$generated = [ Collections.Generic.List[object] ] :: new ( )
$external = [ Collections.Generic.List[object] ] :: new ( )
$copyJobs = [ Collections.Generic.List[object] ] :: new ( )
function CleanName([string]$value ) {
$clean = [ regex ] :: Replace ( $value , '[<>:"/\\|?*\x00-\x1f]' , '_' )
$clean = [ regex ] :: Replace ( $clean , '\s+' , '_' ) . Trim ( '.' , ' ' )
if ( $clean . Length -gt 88 ) { $clean = $clean . Substring ( 0 , 88 ) }
return $clean
}
function ShortText([string]$value,[int]$max=1200 ) {
$value = ( $value -replace '\r?\n' , ' ' -replace '\s+' , ' ' ) . Trim ( )
if ( $value . Length -gt $max ) { return $value . Substring ( 0 , $max ) + '…' }
return $value
}
foreach ( $group in $groups ) {
$ordered = @ ( $group . Group | Sort-Object @ { Expression = { $_ . External } } , @ { Expression = { $_ . Rel -match '^99_|post_frames|review|rejected' } } , Rel )
$s = $ordered [ 0 ] ; $rel = $s . Rel ; $f = Get-Item -LiteralPath $s . Full ; $stem = $f . BaseName
$isExternal = @ ( $ordered | Where-Object { -not $_ . External } ) . Count -eq 0
$category = '生成辅助图' ; $status = '生产素材待审' ; $use = '构图、画面连续性与图生视频输入参考;入正式资产库前审看。'
$prefix = '02' ; $title = $stem ; $detail = '' ; $warn = '图片存在不等于已通过艺术或连续性验收。' ; $context = '' ; $shotId = '' ; $age = '未在本图单独核定'
$evidence = [ Collections.Generic.List[string] ] :: new ( ) ; $evidence . Add ( $rel )
$assetId = if ( $stem -match '^(CHR|REF|BG|MAP)-' ) { $stem } else { '' }
$isArchived = $rel . StartsWith ( '99_' )
if ( $isExternal ) {
$category = '外部参考' ; $status = '非本项目生成/来源授权待核' ; $prefix = '90'
$title = '外部参考|' + $stem
$detail = if ( $rel -match '画风参考|用户提供' ) { '用户提供的既有动画画风截图,仅作线条、色彩和光影讨论。' } else { '桌面角色管理目录中的原有参考图;未找到足够证据将其认定为本项目新生成的原创素材。' }
$warn = '不要按本项目原创生成图公开发布或出售;来源及授权待核实。' ; $use = '私人参考索引,不默认上传。'
} elseif ( $rel -match 'post_frames/' ) {
$category = '视频逐帧中间图' ; $prefix = '06' ; $status = '后期中间图/非独立原画'
$title = ( [ regex ] :: Match ( $rel , 'E01-S\d+-C\d+' ) . Value ) + '| ' + $stem
$detail = '视频后期逐帧序列中的单帧,供屏幕内容合成、画面检查或后期追溯;相邻文件通常属于同一镜头,不应全部作为不同原创设定图。'
$warn = '帧序号是文件编号,不直接推算时间码;原视频帧率与提取起点须另查。' ; $use = '技术归档;默认预览隐藏,不建议逐张公开上架。'
} elseif ( $stem -match '^(review|compare|waveform)|_sheet$|检查|抽帧|light_controlled_review' -or $rel -match '/scenes/' ) {
$category = if ( $stem -eq 'waveform' ) { '音频波形图' } else { '检查拼图或抽帧' }
$prefix = '05' ; $status = '质检辅助/非独立原画'
$title = if ( $rel -match 'E01-S\d+-C\d+' ) { $Matches [ 0 ] + '| ' + $stem } elseif ( $stem -eq 'waveform' ) { ( Split-Path ( Split-Path $s . Full ) -Leaf ) + '|波形图' } else { $stem }
$detail = '制作过程中生成的检查、比较或预览图片,用于画面节奏、构图或技术效果对照。'
$warn = '可能含多帧、标注或技术图表;不能当作单帧设定图或完整成片。' ; $use = '内部审看与制作过程记录。'
}
if ( -not $isExternal -and ( $rel -match '/角色设定/' -or $stem -match '^CHR-|^REF-' -or $isArchived ) ) {
$category = '角色参考图' ; $prefix = '01' ; $status = 'REVIEW/候选未定稿'
if ( $linked . ContainsKey ( $s . Full ) ) { $title = $linked [ $s . Full ] }
elseif ( $rel -match '/([^/]+)/(?:01_参考图|02_生成候选)/' ) { $title = $Matches [ 1 ] + '| ' + $stem }
$detail = '用于角色身份、服装、种族轮廓和年龄版本对照的生成候选。'
$warn = '图中自发纹章、文字与装饰不自动成为小说设定;没有完整侧背、表情和跨镜测试。'
$parent = Split-Path ( Split-Path $s . Full )
$design = Join-Path $parent '视觉设计方案.md'
if ( -not ( Test-Path -LiteralPath $design ) -and $rel -match '(0[12]_转生[前后]角色/.*)/(01_参考图|02_生成候选)/' ) {
$design = Join-Path $ProjectRoot ( '06_AI动漫制作/02_视觉圣经/角色设定/' + $Matches [ 1 ] + '/视觉设计方案.md' )
}
if ( Test-Path -LiteralPath $design ) {
$bullets = @ ( Get-Content -LiteralPath $design | Where-Object { $_ -match '^- (性别|视觉年龄|骨相|体型|服装|道具|动作|不可漂移|种族|发色|瞳色)' } )
$bullets = @ ( $bullets | Where-Object { $_ -notmatch '\d+岁|真实年龄' } )
$context = '通用角色卡供对照,旧版服装条目以本图生成记录覆盖:' + ( ShortText ( $bullets -join ' ' ) 1700 )
$evidence . Add ( [ IO.Path ] :: GetRelativePath ( $ProjectRoot , $design ) . Replace ( '\' , '/' ) )
}
foreach ( $k in @ ( $reviewNotes . Keys | Sort-Object Length -Descending ) ) { if ( $title -like " * $k * " -and $stem -match 'V08' ) { $detail + = ' 生成记录的画面描述与初检:' + $reviewNotes [ $k ] ; $evidence . Add ( [ IO.Path ] :: GetRelativePath ( $ProjectRoot , $reviewDoc ) . Replace ( '\' , '/' ) ) ; break } }
}
foreach ( $k in @ ( $notes . Keys | Sort-Object Length -Descending ) ) {
if ( -not $isExternal -and $stem . Contains ( $k ) ) { $title = $notes [ $k ] [ 0 ] + '| ' + $stem ; $detail = $notes [ $k ] [ 1 ] ; $warn = $notes [ $k ] [ 2 ] ; break }
}
if ( -not $isExternal -and $stem -match '^BG-' ) { $category = '场景设定图' ; $prefix = '02' ; $status = 'REVIEW/候选未定稿' }
if ( -not $isExternal -and $stem -match '^MAP-' ) { $category = '世界地图' ; $prefix = '03' ; $status = 'REVIEW/布局草案' ; $use = '地理布局、商路和行军关系讨论,不用于精确测距。' }
if ( $stem -match '(?:-|_)A(\d+)(?:-|_)' ) { $age = $Matches [ 1 ] + '岁(文件所标版本;归档图不保证符合现行时间线)' }
if ( $isArchived ) {
$prefix = '04' ; $status = 'ARCHIVED/历史版本'
$warn = '已被后续方案替代或仍有问题,不作为当前生产底图。 ' + $warn
$use = '设计迭代追溯、旧版对比;上传时必须保留“历史版本”标签。'
if ( $rel -match '服饰修订前' ) { $warn + = ' 此批衣装与世界观时代、阶层要求不符,后续已经修订。' }
if ( $rel -match '魔王比例修订前' ) { $warn + = ' 魔王头身比例或双视图比例有问题;当前参照V07而不是此图。' }
if ( $rel -match '魔王外貌微调前' ) { $warn + = ' 此图早于V07头部缩小及衣装前片修订。' }
if ( $rel -match '地图候选' ) { $warn + = ' 北部直连关系不足,当前已改为V03。' }
if ( $rel -match '角色细节修订前' ) { $warn + = ' 这是V08B之前的局部问题版本;萨尔克靴/手、神父年龄或奈芙拉肢体读图需参照修订。' }
}
if ( $rel -match '(E01-S\d+-C\d+)' ) {
$shotId = $Matches [ 1 ]
if ( $shotMap . ContainsKey ( $shotId ) ) {
$sh = $shotMap [ $shotId ] ; $sceneName = $sceneNames [ [int ] $sh . scene ]
$context = " 原镜头: $shotId ;场景: $sceneName ;景别机位: $( $sh . framing ) ;计划画面与表演: $( $sh . action_performance ) ;空间光线: $( $sh . space_light ) ;声音关系: $( $sh . sound ) 。这是原制作说明,不保证每项均已在图片中正确实现。 "
$evidence . Add ( '06_AI动漫制作/05_生产素材/正片/S01E01_H3_V01/source_manifest.json' )
if ( $category -eq '生成辅助图' ) { $title = " $shotId | $sceneName | $stem " ; $detail = " 第一集对应镜头的生成首帧或返修底图。 " + $sh . action_performance }
}
$readyPath = Join-Path ( Split-Path $s . Full ) 'ready.json'
if ( Test-Path -LiteralPath $readyPath ) {
$ready = Get-Content -LiteralPath $readyPath -Raw | ConvertFrom-Json
if ( $ready . frame_qc . notes ) { $warn + = ' 原首帧审看记录:' + $ready . frame_qc . notes }
}
}
if ( $rel -match 'rejected|first_v1' ) { $status = '历史或返修前版本/复核' ; $warn + = ' 文件位于返修或旧版路径,不按最新版自动使用。' }
if ( -not $detail ) { $detail = '本项目制作图片。独立画面内容尚无充分文字记录,请结合原图核对后补充;不从文件名编造人物身份或事件。' }
if ( $rel -match '视频测试' -and -not $shotId ) {
if ( $rel -match '男主收手机' ) { $context = '前世男主在化工厂更衣柜前收手机的测试段;母亲中秋消息通过屏幕文字表现,不另配母亲旁白。' }
elseif ( $rel -match '泉堂夜景|SC06_40秒' ) { $context = '泉堂祝福测试段,855年的45岁马提亚斯、13岁阿黛尔与婴儿;冷窗光与暖烛光,神像保持静止与沉默。' }
elseif ( $rel -match '开篇40秒' ) { $context = '前世租屋开篇40秒柔光测试,风扇、水杯、电脑和29岁男主的日常。测试版不自动替代正片同名镜头。' }
$use + = ' 测试素材与正式镜头身份分开登记。'
}
if ( $rel -match '视频测试/.+/(?:frames/)?(C0[1-8])(?:R2)?(?:_|/|\.)' -and $rel -match 'SC06_40秒|开篇40秒' ) {
$testId = $Matches [ 1 ]
$testMatch = [ regex ] :: Match ( $rel , '/(C0[1-8])(?:R2)?(?:_|/|\.)' )
$testId = $testMatch . Groups [ 1 ] . Value
if ( $rel -match 'SC06_40秒' ) {
$testText = @ { C01 = '马提亚斯怀抱婴儿来到泉堂神像前,阿黛尔持烛在旁,左窗冷光与右侧暖室构成静默空间。' ; C02 = '45岁神父怀抱婴儿祷告的近景,银白反光打断话语,眼神抬向神像方向。' ; C03 = '灰石神像水纹与婴儿祝福印记的近景,光是细微回应,不是神像活化或女神开口。' ; C04 = '13岁阿黛尔双手持烛的近景,视线由烛火转向画外神父与神像,表现好奇和探寻。' ; C05 = '神父等待回应的表情近景,抱婴姿势稳定,期待逐渐转为接受沉默。' ; C06 = '神父将目光重新落回婴儿、少女跟随注视的双人场面,祝福余光退去,回到实际照料。' ; C07 = '泉堂仪式之后的延续场面,以仍在燃烧的烛火和人物实际照料维持日常,没有女神额外发言。' ; C08 = '祝福事件后的记录与报告场面,供边境发现向大圣堂上报的段落衔接。' }
} else {
$testText = @ { C01 = '租屋窗边风扇、半杯水与薄荷盆栽近景,冷暖柔光表现普通生活。' ; C02 = '前世男主从左肩后看电脑分类结果,屏幕中的猫是静态图片,鼠标与错误识别服务轻微幽默。' ; C03 = '29岁男主看向右侧电脑的侧面近景,克制笑意与自然疲倦构成普通青年形象。' ; C04 = '双手与笔记本的俯拍近景,未完成计算、铅笔和纸页用于呈现广泛但仍在试错的爱好。' ; C05 = '男主听见闹钟后停下电脑操作的侧面中景,收手和肩部放松表示结束折腾、准备休息。' ; C06 = '男主在窗边查看稀疏的第三盆薄荷,杯与土壤形成浇水动作起点,植物不神奇生长。' ; C07 = '浇水后拉窗帘、准备走向床铺的租屋全景,窗、家具和人物动线保持连续。' }
}
if ( $testText . ContainsKey ( $testId ) ) {
if ( $category -eq '生成辅助图' ) { $detail = $testText [ $testId ] + ' 这是该测试镜头的输入或辅助静图,动态完成度另查视频。' ; $title = $testId + '| ' + $ ( if ( $rel -match 'SC06_40秒' ) { '泉堂祝福测试' } else { '前世租屋测试' } ) + '| ' + $stem }
else { $detail + = ' 对应镜头内容:' + $testText [ $testId ] }
}
}
$img = [ Drawing.Image ] :: FromFile ( $s . Full )
try { $width = $img . Width ; $height = $img . Height ; $format = $f . Extension . ToLowerInvariant ( ) } finally { $img . Dispose ( ) }
$hash = $group . Name
$imageName = if ( $existingNames . ContainsKey ( $hash ) ) { $existingNames [ $hash ] } else { ( CleanName ( " $prefix " + '__' + $title ) ) + '__' + $hash . Substring ( 0 , 10 ) + $format }
$folder = if ( $isExternal ) { '外部参考_不默认上传' } else { '图片' }
$outRel = " $folder / $imageName "
$srcRels = @ ( $ordered . Rel )
$description = " 《炉火照彻诸神沉默的年代》素材。 $detail " + $ ( if ( $context ) { " 设定/制作背景(非逐项视觉验收): $context " } ) + " 状态: $status 。用途: $use 注意: $warn "
$record = [ pscustomobject][ordered ] @ {
asset_id = ( 'IMG-' + $hash . Substring ( 0 , 16 ) ) ; original_asset_id = $assetId ; filename = $imageName ; image_path = $outRel ;
title = $title ; category = $category ; status = $status ; description = $description ; content_description = $detail ;
context_notes = $context ; usage = $use ; limitations = $warn ; age_version = $age ; shot_id = $shotId ;
tags = @ ( '炉火照彻诸神沉默的年代' , 'AI动漫' , $category , $status ) ; width = $width ; height = $height ; bytes = $f . Length ;
sha256 = $hash ; original_filename = $f . Name ; source_paths = $srcRels ; evidence_paths = @ ( $evidence ) ;
description_basis = '依据项目索引、已有生成记录及制作数据整理;不是全图重新逐张验收' ;
public_release_approved = $false ; rights_note = '本包不作版权或商业授权保证;外部参考与生成素材分别标记' ;
default_gallery_visible = ( $prefix -in @ ( '01' , '02' , '03' ) ) ; duplicate_source_count = $srcRels . Count
}
$copyJobs . Add ( [ pscustomobject ] @ { Source = $s . Full ; Relative = $outRel ; Record = $record } )
if ( $isExternal ) { $external . Add ( $record ) } else { $generated . Add ( $record ) }
}
$generated = @ ( $generated | Sort-Object category , title , asset_id )
$external = @ ( $external | Sort-Object title )
foreach ( $folder in @ ( " $dest /图片 " , " $dest /外部参考_不默认上传 " ) ) { if ( -not ( Test-Path -LiteralPath $folder ) ) { New-Item -ItemType Directory -Path $folder | Out-Null } }
$utf8 = [ Text.UTF8Encoding ] :: new ( $false )
foreach ( $job in $copyJobs ) {
$target = Join-Path $dest $job . Relative
if ( Test-Path -LiteralPath $target ) {
if ( ( Get-FileHash -LiteralPath $target -Algorithm SHA256 ) . Hash . ToLowerInvariant ( ) -ne $job . Record . sha256 ) { throw " 图片内容已改变,不覆盖: $target " }
} else { Copy-Item -LiteralPath $job . Source -Destination $target }
$r = $job . Record
$text = @"
标 题 : $( $r . title )
素 材 编 号 : $( $r . asset_id )
原 资 产 编 号 : $( $r . original_asset_id )
图 片 文 件 : $( $r . filename )
分 类 : $( $r . category )
状 态 : $( $r . status )
年 龄 版 本 : $( $r . age_version )
镜 头 编 号 : $( $r . shot_id )
【 内 容 说 明 】
$( $r . content_description )
【 设 定 与 制 作 背 景 】
$( $r . context_notes )
【 建 议 用 途 】
$( $r . usage )
【 版 本 问 题 与 使 用 限 制 】
$( $r . limitations )
【 尺 寸 与 来 源 】
尺 寸 : $( $r . width ) × $( $r . height ) 像 素
文 件 字 节 数 : $( $r . bytes )
S H A - 2 5 6 : $( $r . sha256 )
原 文 件 名 : $( $r . original_filename )
全 部 来 源 :
$( $r . source_paths -join [ Environment ] :: NewLine )
说 明 依 据 : $( $r . description_basis )
公 开 发 布 批 准 : 尚 未 确 认 ; 本 包 仅 供 整 理 和 选 择 上 传
来 源 授 权 说 明 : $( $r . rights_note )
【 素 材 网 站 可 用 简 介 】
$( $r . description )
标 签 : $( $r . tags -join ', ' )
"@
[ IO.File ] :: WriteAllText ( [ IO.Path ] :: ChangeExtension ( $target , '.txt' ) , $text , $utf8 )
}
$catalog = [ pscustomobject ] @ { schema_version = 1 ; project = '炉火照彻诸神沉默的年代' ; packaged_date = $PackageDate ; source_image_files = $sources . Count ; project_source_files = $paths . Count ; unique_project_images = $generated . Count ; unique_external_references = $external . Count ; duplicate_copies_removed = $sources . Count - $groups . Count ; items = $generated }
[ IO.File ] :: WriteAllText ( " $dest /素材清单.json " , ( $catalog | ConvertTo-Json -Depth 12 ) , $utf8 )
[ IO.File ] :: WriteAllText ( " $dest /外部参考清单.json " , ( @ ( $external ) | ConvertTo-Json -Depth 12 ) , $utf8 )
$csv = @ ( $generated | ForEach-Object { [ pscustomobject][ordered ] @ { 素材编号 = $_ . asset_id ; 文件名 = $_ . filename ; 图片路径 = $_ . image_path ; 标题 = $_ . title ; 分类 = $_ . category ; 版本状态 = $_ . status ; 详细说明 = $_ . description ; 标签 = ( $_ . tags -join '; ' ) ; 宽度 = $_ . width ; 高度 = $_ . height ; 字节数 = $_ . bytes ; 原资产编号 = $_ . original_asset_id ; 年龄版本 = $_ . age_version ; 镜头编号 = $_ . shot_id ; 原路径 = ( $_ . source_paths -join '; ' ) ; SHA256 = $_ . sha256 ; 公开发布已批准 = '否' ; 说明依据 = $_ . description_basis } } )
$csv | Export-Csv -LiteralPath " $dest /素材清单.csv " -NoTypeInformation -Encoding utf8BOM
$generated | Where-Object default_gallery_visible | Select-Object asset_id , filename , title , category , status | Export-Csv -LiteralPath " $dest /优先审看清单.csv " -NoTypeInformation -Encoding utf8BOM
$sourceMap = foreach ( $r in @ ( $generated ) + @ ( $external ) ) { foreach ( $p in $r . source_paths ) { [ pscustomobject ] @ { 原路径 = $p ; 目标路径 = $r . image_path ; SHA256 = $r . sha256 ; 处理 = '原文件保留;内容相同仅输出一份' } } }
$sourceMap | Export-Csv -LiteralPath " $dest /原图到上传文件映射.csv " -NoTypeInformation -Encoding utf8BOM
$escapedJson = ( @ ( $generated ) | ConvertTo-Json -Depth 10 -Compress ) . Replace ( '<' , '\u003c' )
$template = [ IO.File ] :: ReadAllText ( ( Join-Path $PSScriptRoot 'image_catalog_gallery.html' ) )
[ IO.File ] :: WriteAllText ( " $dest /图片预览目录.html " , $template . Replace ( '__CATALOG_DATA__' , $escapedJson ) , $utf8 )
$counts = ( $generated | Group-Object category | ForEach-Object { " - $( $_ . Name ) : $( $_ . Count ) 张 " } ) -join [ Environment ] :: NewLine
$readme = @"
# 素 材 网 上 传 包
项 目 : 《 炉 火 照 彻 诸 神 沉 默 的 年 代 》 ; 整 理 日 期 : $PackageDate 。
# # 直 接 使 用
- * * 图 片 / * * : $( $generated . Count ) 张 内 容 去 重 后 的 本 项 目 图 片 全 部 集 中 在 这 一 个 文 件 夹 ; 每 张 旁 边 有 同 名 T X T 详 细 说 明 。
- * * 图 片 预 览 目 录 . h t m l * * : 双 击 可 离 线 打 开 , 按 标 题 / 说 明 / 分 类 搜 索 ; 默 认 只 显 示 当 前 人 物 、 地 图 与 生 产 底 图 , 勾 选 可 显 示 历 史 和 中 间 图 。
- * * 素 材 清 单 . c s v * * : U T F - 8 B O M , 通 用 网 站 批 量 导 入 字 段 ; 用 表 格 软 件 打 开 时 选 U T F - 8 。
- * * 素 材 清 单 . j s o n * * : 包 含 图 片 、 标 题 、 完 整 说 明 、 标 签 、 尺 寸 、 来 源 、 版 本 、 S H A - 2 5 6 。
- * * 优 先 审 看 清 单 . c s v * * : 便 于 先 审 核 候 选 原 画 , 不 代 表 已 批 准 公 开 发 布 。
- * * 原 图 到 上 传 文 件 映 射 . c s v * * : 原 路 径 到 唯 一 图 片 的 映 射 , 不 遗 漏 重 复 副 本 来 源 。
- * * 外 部 参 考 _ 不 默 认 上 传 / * * : $( $external . Count ) 张 外 部 截 图 或 来 源 未 核 的 桌 面 参 考 , 另 附 说 明 , 不 混 入 生 成 图 库 。
# # 盘 点 与 范 围
扫 描 仓 库 已 有 图 片 $( $paths . Count ) 个 , 加 已 知 桌 面 角 色 参 考 $( $sources . Count - $paths . Count ) 个 。 以 文 件 内 容 S H A - 2 5 6 去 重 ; 相 同 画 面 但 字 节 不 同 的 图 片 仍 分 别 保 留 , 不 做 相 似 图 删 除 。
合 计 保 留 $( $groups . Count ) 份 唯 一 图 片 , 其 中 本 项 目 $( $generated . Count ) 张 、 外 部 参 考 $( $external . Count ) 张 ; 去 掉 $( $sources . Count - $groups . Count ) 个 重 复 副 本 的 额 外 拷 贝 , 原 文 件 全 部 不 动 。 仅 整 理 本 地 落 盘 项 目 文 件 , 未 声 称 扫 描 整 台 电 脑 、 所 有 临 时 生 成 目 录 或 服 务 器 全 部 图 片 。
没 有 重 画 、 裁 切 、 压 缩 或 放 大 图 片 , 目 标 图 片 与 来 源 字 节 一 致 ; 没 有 上 传 网 站 、 提 交 或 推 送 。
# # 本 项 目 图 片 分 类
$counts
# # 上 传 前 注 意
1 . 当 前 人 设 仍 为 R E V I E W , 不 因 整 理 自 动 成 为 定 稿 。 旧 版 图 保 留 A R C H I V E D ; 同 名 人 物 的 年 龄 版 不 能 混 用 。
2 . 说 明 来 自 原 生 成 记 录 、 项 目 设 定 与 镜 头 资 料 ; 没 有 声 称 重 新 逐 张 通 过 视 觉 质 检 。 设 定 背 景 是 目 标 , 不 保 证 画 面 正 确 画 出 所 有 细 节 。
3 . 逐 帧 图 、 波 形 、 拼 图 和 检 查 图 只 宜 技 术 归 档 , 不 能 包 装 成 独 立 原 创 设 定 图 。 网 站 可 用 分 类 过 滤 。
4 . 外 部 动 画 截 图 和 桌 面 来 源 待 核 参 考 不 进 入 主 导 入 表 。 生 成 素 材 的 具 体 授 权 仍 需 按 你 的 实 际 服 务 与 用 途 确 认 , 本 包 不 宣 称 授 权 已 经 审 核 。
5 . 不 上 传 工 作 流 、 服 务 地 址 、 凭 据 或 服 务 器 配 置 ; 图 片 保 留 原 字 节 , P N G 可 能 仍 有 原 生 元 数 据 , 公 开 发 布 前 按 素 材 站 隐 私 要 求 检 查 。
6 . C S V 为 通 用 字 段 , 并 非 已 适 配 某 个 站 点 接 口 ; 导 入 时 映 射 “ 图 片 路 径 、 标 题 、 详 细 说 明 、 标 签 ” 。 T X T 可 直 接 复 制 文 案 。
"@
[ IO.File ] :: WriteAllText ( " $dest /README.md " , $readme , $utf8 )
$failures = [ Collections.Generic.List[string] ] :: new ( )
foreach ( $job in $copyJobs ) {
$target = Join-Path $dest $job . Relative
if ( ( Get-FileHash -LiteralPath $target -Algorithm SHA256 ) . Hash . ToLowerInvariant ( ) -ne $job . Record . sha256 ) { $failures . Add ( $job . Relative ) }
if ( -not ( Test-Path -LiteralPath ( [ IO.Path ] :: ChangeExtension ( $target , '.txt' ) ) ) ) { $failures . Add ( 'missing description ' + $job . Relative ) }
}
$validation = [ pscustomobject ] @ { image_files = $groups . Count ; main_images = $generated . Count ; external_images = $external . Count ; descriptions = $groups . Count ; csv_rows = @ ( Import-Csv -LiteralPath " $dest /素材清单.csv " ) . Count ; source_mapping_rows = @ ( $sourceMap ) . Count ; hash_mismatches = @ ( $failures ) ; source_files_untouched = $true }
[ IO.File ] :: WriteAllText ( " $dest /校验报告.json " , ( $validation | ConvertTo-Json -Depth 5 ) , $utf8 )
[ pscustomobject ] @ { destination = $dest ; validation = $validation ; categories = @ ( $generated | Group-Object category | Select-Object Count , Name ) } | ConvertTo-Json -Depth 6