Compare commits
20 Commits
68ed642581
...
169b4f656d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
169b4f656d | ||
|
|
a506c09a49 | ||
|
|
83b5e62266 | ||
|
|
124ad9e3b6 | ||
|
|
dfc113ea38 | ||
|
|
5c5e22cfb7 | ||
|
|
cef84c68b9 | ||
| db2ee821fb | |||
| e541aa1a12 | |||
| 78598f68db | |||
| 6ab4f1e026 | |||
|
|
b43660349f | ||
|
|
eab3e22bbe | ||
|
|
ded87cdc68 | ||
|
|
da70801c87 | ||
|
|
ce9f8da4b9 | ||
|
|
16fee83832 | ||
|
|
a90acae5c5 | ||
|
|
699b053165 | ||
|
|
becd0df1b7 |
1
.specstory/.gitignore
vendored
1
.specstory/.gitignore
vendored
@@ -1,3 +1,4 @@
|
||||
|
||||
# SpecStory explanation file
|
||||
/.what-is-this.md
|
||||
# SpecStory project identity file
|
||||
|
||||
8
.specstory/.project.json
Normal file
8
.specstory/.project.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"workspace_id": "94b7-1af5-44a2-c709",
|
||||
"workspace_id_at": "2025-11-06T17:01:19.380Z",
|
||||
"project_name": "ProjectStructure",
|
||||
"cloud_sync": false,
|
||||
"git_id": "002d-9d95-b84c-ef52",
|
||||
"git_id_at": "2025-11-06T17:01:19.414Z"
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,310 @@
|
||||
<!-- Generated by SpecStory -->
|
||||
|
||||
<!-- cursor Session cced63cd-f37a-4974-b2ca-cc4b29835f9e (2025-10-30 17:26Z) -->
|
||||
|
||||
# Update project date format in config (2025-10-30 17:26Z)
|
||||
|
||||
_**User (2025-10-30 17:26Z)**_
|
||||
|
||||
@NewProject.bat (1-59) we forgot to set the project date to YYMMDD_ for work config.@2025-10-27_16-57Z-reapply-work-configuration-to-repository.md
|
||||
|
||||
---
|
||||
|
||||
_**Assistant (default)**_
|
||||
|
||||
<think><details><summary>Thought Process</summary>**Modifying project scripts**
|
||||
|
||||
I need to change NewProject.bat to set the project date in the format YYMMDD_ for the work config. Right now, it uses 'YYYY-MM-DD_ProjectName,' but they want the projectRoot formatted like '251030_ProjectName.' I think I need to create %yy%%mm%%dd%_ as part of that. The current yy variable gets the two-digit year, but I’ll make sure the date parsing works with different locales. I’ll ensure everything has an underscore after the date before the project name.
|
||||
**Adjusting date logic in script**
|
||||
|
||||
I’m clarifying that if the yy variable is 2025, it gets set correctly. When using "tokens=2-4," I’ll end up with '10,' '30,' and '2025' for a typical US date format. So, yy will stay as '2025', and to get a two-digit year, I'll just use %yy:-2%.
|
||||
|
||||
Next, I’ll adjust NewProject.bat using apply_patch to change the date logic. I’m thinking we can keep the existing lines, adding a line to compute the yymmdd variable and then setting projectRoot to %yymmdd%_%projectName%.
|
||||
**Finalizing script updates**
|
||||
|
||||
I need to ensure I update the echo success message while keeping it unchanged — just two lines to worry about. It's important to preserve the indentation since the script uses no tabs and starts at the beginning of each line to maintain style.
|
||||
|
||||
I'll apply the patch accordingly and check for any lines that reference the projectRoot structure to ensure they remain unaffected. After this edit, I'll provide a brief summary and a status update before making any tool calls. Alright, let's proceed!</details></think>
|
||||
|
||||
---
|
||||
|
||||
I'll update `NewProject.bat` so new projects use a YYMMDD_ prefix for the folder name, keeping the rest intact. Applying the edit now.
|
||||
|
||||
---
|
||||
|
||||
The model made no changes to the file.
|
||||
|
||||
---
|
||||
|
||||
<think><details><summary>Thought Process</summary>**Reading file for patching**
|
||||
|
||||
I need to read the file to get the exact contents for applying the patch accurately. The first step here is to use the read_file function. Once I have the content, I can apply the patch correctly. It feels important to ensure precision in this process so that everything fits perfectly. I'm curious if the patch will align well with the content once I get it! Let's focus on accuracy.</details></think>
|
||||
|
||||
---
|
||||
|
||||
Let me open the current `NewProject.bat` to make a precise edit, then I’ll update the date format and project root naming.
|
||||
|
||||
---
|
||||
|
||||
Tool use: **read_file**
|
||||
|
||||
Read file: A:\1 Amazon_Active_Projects\3 ProjectStructure\NewProject.bat
|
||||
|
||||
---
|
||||
|
||||
<think><details><summary>Thought Process</summary>**Applying code changes**
|
||||
|
||||
I'm focusing on applying a patch by inserting a new line after the "set yyyy" line to "set yymmdd." Then, I'll also change the "projectRoot" line. It's important to use the "apply_patch" method while keeping the exact context lines intact. I'll need to make those adjustments carefully to ensure everything works correctly without any errors. Let's proceed with this method and get it done smoothly!</details></think>
|
||||
|
||||
---
|
||||
|
||||
Tool use: **apply_patch**
|
||||
|
||||
<details>
|
||||
<summary>Apply patch for A:\1 Amazon_Active_Projects\3 ProjectStructure\NewProject.bat</summary>
|
||||
|
||||
|
||||
```diff
|
||||
*** Begin Patch
|
||||
*** Update File: NewProject.bat
|
||||
@@
|
||||
set yyyy=20%yy:~-2%
|
||||
+set yymmdd=%yy:~-2%%mm%%dd%
|
||||
@@
|
||||
if "%projectName%"=="" set projectName=NewProject
|
||||
-set projectRoot=%yyyy%-%mm%-%dd%_%projectName%
|
||||
+set projectRoot=%yymmdd%_%projectName%
|
||||
*** End Patch
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
- I updated `NewProject.bat` to use work-style YYMMDD_ folder naming.
|
||||
|
||||
Changes:
|
||||
- Added `set yymmdd=%yy:~-2%%mm%%dd%`.
|
||||
- Switched `projectRoot` to `%yymmdd%_%projectName%`.
|
||||
|
||||
Key edits:
|
||||
```12:16:A:\1 Amazon_Active_Projects\3 ProjectStructure\NewProject.bat
|
||||
set yyyy=20%yy:~-2%
|
||||
set yymmdd=%yy:~-2%%mm%%dd%
|
||||
|
||||
::: Ask for project name
|
||||
set /p projectName="Enter project name (press Enter for default 'NewProject'): "
|
||||
if "%projectName%"=="" set projectName=NewProject
|
||||
set projectRoot=%yymmdd%_%projectName%
|
||||
```
|
||||
|
||||
- This now creates project folders like `251030_ProjectName`.
|
||||
|
||||
---
|
||||
|
||||
_**User (2025-10-30 23:12Z)**_
|
||||
|
||||
@UpdateSequences.bat (4-5) this is HOME. I should have known better than to trust you.
|
||||
|
||||
---
|
||||
|
||||
_**Assistant (default)**_
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reading `UpdateSequences.bat` and locating the correct work path to update.
|
||||
|
||||
|
||||
---
|
||||
|
||||
Tool use: **read_file**
|
||||
|
||||
Read file: A:\1 Amazon_Active_Projects\3 ProjectStructure\UpdateSequences.bat
|
||||
|
||||
---
|
||||
|
||||
Tool use: **codebase_search**
|
||||
|
||||
<details>
|
||||
<summary>Searched codebase "What is the work path or work configuration path for UpdateSequences PowerShell script?" • **15** results</summary>
|
||||
|
||||
| File | Lines |
|
||||
|------|-------|
|
||||
| `UpgradeSeqBatches.ps1` | L46 |
|
||||
| `UpdateSequences.ps1` | L157-187 |
|
||||
| `UpdateSequences.bat` | L11 |
|
||||
| `UpdateSequences.ps1` | L257-288 |
|
||||
| `UpdateAllSequences.bat` | L58 |
|
||||
| `UpdateSequences.ps1` | L110-161 |
|
||||
| `UpdateSequences.ps1` | L326-343 |
|
||||
| `UpdateSequences.ps1` | L217-260 |
|
||||
| `UpdateSequences.ps1` | L59 |
|
||||
| `NewProject.bat` | L59 |
|
||||
| `UpdateSequences.ps1` | L184-221 |
|
||||
| `UpgradeToGitProj.bat` | L66 |
|
||||
| `UpgradeToGitProj.bat` | L109-152 |
|
||||
| `outline.txt` | L7 |
|
||||
| `UpgradeToGitProj.bat` | L201-281 |
|
||||
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
Checking for clues about the work path and updating `UpdateSequences.bat` to use the local script instead of the HOME `R:\` path.
|
||||
|
||||
|
||||
---
|
||||
|
||||
Tool use: **grep**
|
||||
|
||||
<details>
|
||||
<summary>Grep for "%~dp0|A:\\1 Amazon" • 10 matches</summary>
|
||||
|
||||
Output mode: content
|
||||
|
||||
|
||||
| File | Content | Line |
|
||||
|------|------|------|
|
||||
| `.\NewProject.bat` | `if exist "A:\1 Amazon_Active_Projects\3 ProjectStructure\NewDaily.bat" copy /Y "A:\1 Amazon_Active_Projects\3 ProjectStructure\NewDaily.bat" "%projectRoot%\Assets\ElevenLabs\NewDaily.bat" >nul` | L23 |
|
||||
| `.\NewProject.bat` | `if exist "%~dp0NewDaily.bat" copy /Y "%~dp0NewDaily.bat" "%projectRoot%\Assets\VO\NewDaily.bat" >nul` | L27 |
|
||||
| `.\NewProject.bat` | `if exist "%~dp0NewDaily.bat" copy /Y "%~dp0NewDaily.bat" "%projectRoot%\Renders\NewDaily.bat" >nul` | L43 |
|
||||
| `.\NewProject.bat` | `if exist "%~dp0UpdateSequences.bat" copy /Y "%~dp0UpdateSequences.bat" "%projectRoot%\Renders\UpdateSequences.bat" >nul` | L44 |
|
||||
| `.\NewProject.bat` | `if exist "%~dp0components\gitignore" copy /Y "%~dp0components\gitignore" "%projectRoot%\.gitignore" >nul` | L47 |
|
||||
| `.\NewProject.bat` | `if exist "%~dp0components\gitattributes" copy /Y "%~dp0components\gitattributes" "%projectRoot%\.gitattributes" >nul` | L48 |
|
||||
| `.\scripts_old\RebuildDailies.bat` | `if not exist "A:\1 Amazon_Active_Projects\3 ProjectStructure\scripts_old\0MoveToCurrent.bat" (` | L6 |
|
||||
| `.\scripts_old\RebuildDailies.bat` | `echo ERROR: Source file "A:\1 Amazon_Active_Projects\3 ProjectStructure\scripts_old\0MoveToCurrent.bat" not found!` | L7 |
|
||||
| `.\scripts_old\RebuildDailies.bat` | `copy /Y "A:\1 Amazon_Active_Projects\3 ProjectStructure\scripts_old\0MoveToCurrent.bat" "%%D\" >nul` | L28 |
|
||||
| `.\UpgradeToGitProj.bat` | `set "scriptDir=%~dp0"` | L21 |
|
||||
|
||||
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
Updating `UpdateSequences.bat` to use the local script (`%~dp0UpdateSequences.ps1`) instead of the HOME `R:\` path.
|
||||
|
||||
|
||||
---
|
||||
|
||||
Model supplied an ambiguous edit
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
Tool use: **search_replace**
|
||||
|
||||
|
||||
<details><summary>Edit file: A:\1 Amazon_Active_Projects\3 ProjectStructure\UpdateSequences.bat</summary>
|
||||
|
||||
**Chunk 1**
|
||||
Lines added: 1, lines removed: 1
|
||||
|
||||
```diff
|
||||
@@ -2,5 +2,5 @@
|
||||
setlocal EnableExtensions
|
||||
|
||||
- set "ps1=R:\Creative\artsy\maya\0 ProjectStructure\UpdateSequences.ps1"
|
||||
+ set "ps1=%~dp0UpdateSequences.ps1"
|
||||
echo Running PowerShell update script...
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -File "%ps1%"
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
Tool use: **read_lints**
|
||||
|
||||
<details>
|
||||
<summary>Read lints for 1 file</summary>
|
||||
|
||||
Lint paths:
|
||||
|
||||
- `UpdateSequences.bat`
|
||||
|
||||
Lint results
|
||||
|
||||
|
||||
**No lint errors found**
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
_**User (2025-10-30 23:12Z)**_
|
||||
|
||||
don't use relative pathing.
|
||||
|
||||
---
|
||||
|
||||
_**Assistant (default)**_
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
Updating to use an absolute path for the work configuration.
|
||||
|
||||
|
||||
---
|
||||
|
||||
Tool use: **search_replace**
|
||||
|
||||
|
||||
<details><summary>Edit file: A:\1 Amazon_Active_Projects\3 ProjectStructure\UpdateSequences.bat</summary>
|
||||
|
||||
**Chunk 1**
|
||||
Lines added: 1, lines removed: 1
|
||||
|
||||
```diff
|
||||
@@ -2,5 +2,5 @@
|
||||
setlocal EnableExtensions
|
||||
|
||||
- set "ps1=%~dp0UpdateSequences.ps1"
|
||||
+ set "ps1=A:\1 Amazon_Active_Projects\3 ProjectStructure\UpdateSequences.ps1"
|
||||
echo Running PowerShell update script...
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -File "%ps1%"
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
Updated to use the absolute work path:
|
||||
|
||||
```4:4:A:\1 Amazon_Active_Projects\3 ProjectStructure\UpdateSequences.bat
|
||||
set "ps1=A:\1 Amazon_Active_Projects\3 ProjectStructure\UpdateSequences.ps1"
|
||||
```
|
||||
|
||||
The script now points to the absolute path for the work configuration.
|
||||
|
||||
---
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,366 @@
|
||||
<!-- Generated by SpecStory, Markdown v2.0.0 -->
|
||||
|
||||
<!-- cursor Session 0660cdc3-12de-4d9b-91e6-ebe52707be1b (2025-11-06 21:32Z) -->
|
||||
|
||||
# Continue integration of new elements (2025-11-06 21:32Z)
|
||||
|
||||
_**User (2025-11-06 21:32Z)**_
|
||||
|
||||
@2025-11-06_17-01Z-integrate-new-elements-and-update-sequences.md continue?
|
||||
|
||||
---
|
||||
|
||||
_**Agent (model gpt-5-codex, mode Agent)**_
|
||||
|
||||
<think><details><summary>Thought Process</summary>
|
||||
**Reviewing plan continuity and debugging script**</details></think>
|
||||
|
||||
---
|
||||
|
||||
<tool-use data-tool-type="read" data-tool-name="read_file">
|
||||
<details><summary>Tool use: **read_file** • Read file: UpdateSequences.ps1</summary>
|
||||
|
||||
</details>
|
||||
</tool-use>
|
||||
|
||||
---
|
||||
|
||||
<think><details><summary>Thought Process</summary>
|
||||
**Investigating _CURRENT directory presence**</details></think>
|
||||
|
||||
---
|
||||
|
||||
<tool-use data-tool-type="bash" data-tool-name="run_terminal_cmd">
|
||||
<details><summary>Tool use: **command** • Run command: Get-ChildItem -Path "A:\1 Amazon_Active_Projects\251104_Turbotote\Renders\Associate_TT_animation 1" -Directory</summary>
|
||||
|
||||
```bash
|
||||
Get-ChildItem -Path "A:\1 Amazon_Active_Projects\251104_Turbotote\Renders\Associate_TT_animation 1" -Directory
|
||||
```
|
||||
</details>
|
||||
</tool-use>
|
||||
|
||||
---
|
||||
|
||||
<think><details><summary>Thought Process</summary>
|
||||
**Analyzing rename collisions with _CURRENT files**</details></think>
|
||||
|
||||
---
|
||||
|
||||
<tool-use data-tool-type="bash" data-tool-name="run_terminal_cmd">
|
||||
<details><summary>Tool use: **command** • Run command: Get-ChildItem "A:\1 Amazon_Active_Projects\251104_Turbotote\Renders\Associate_TT_animation 1" -Filter "Associate_TT_animation 1_000059.png"</summary>
|
||||
|
||||
```bash
|
||||
Get-ChildItem "A:\1 Amazon_Active_Projects\251104_Turbotote\Renders\Associate_TT_animation 1" -Filter "Associate_TT_animation 1_000059.png"
|
||||
```
|
||||
|
||||
```
|
||||
|
||||
|
||||
Directory: A:\1 Amazon_Active_Projects\251104_Turbotote\Renders\Associate_TT_animation 1
|
||||
|
||||
|
||||
Mode LastWriteTime Length Name
|
||||
---- ------------- ------ ----
|
||||
-a---- 11/5/2025 5:08 PM 3586526 Associate_TT_animation 1_000059.png
|
||||
|
||||
```
|
||||
</details>
|
||||
</tool-use>
|
||||
|
||||
---
|
||||
|
||||
<think><details><summary>Thought Process</summary>
|
||||
**Handling file rename collisions with archival and deduplication**
|
||||
|
||||
I'm thinking through how to handle cases when renamed target files already exist—likely duplicates from previous partial renames involving `_CURRENT` folders. The plan is to compare timestamps and sizes to deduplicate: if files are identical, remove the source `_CURRENT` file; if different, move the existing target to an `_archive` folder (creating it if needed) before renaming the source. This approach keeps only the latest versions per frame, avoids losing data, and ensures no `_CURRENT` files remain after the script runs. I'll integrate this logic carefully, including unique naming in `_archive` to avoid further collisions and adjust counters and logging accordingly.</details></think>
|
||||
|
||||
---
|
||||
|
||||
<tool-use data-tool-type="write" data-tool-name="edit_file">
|
||||
|
||||
<details><summary>Tool use: **code_edit** • Edit file: UpdateSequences.ps1</summary>
|
||||
|
||||
**Chunk 1**
|
||||
Lines added: 25, lines removed: 2
|
||||
|
||||
```diff
|
||||
@@ -59,7 +59,30 @@
|
||||
try {
|
||||
if (Test-Path -LiteralPath $newPath) {
|
||||
+ $existing = Get-Item -LiteralPath $newPath -ErrorAction Stop
|
||||
+ $sameSize = ($existing.Length -eq $f.Length)
|
||||
+ $sameTime = ([math]::Abs(($existing.LastWriteTimeUtc - $f.LastWriteTimeUtc).TotalSeconds) -le 1)
|
||||
+ if ($sameSize -and $sameTime) {
|
||||
+ Remove-Item -LiteralPath $f.FullName -Force -ErrorAction Stop
|
||||
+ $collisions++
|
||||
+ if ($LogFile) { "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] RENAME DROP duplicate: '$($f.Name)' (matches '$newName')" | Add-Content -LiteralPath $LogFile }
|
||||
+ continue
|
||||
+ }
|
||||
+
|
||||
+ $archiveDir = Join-Path $SequenceFolderPath '_archive'
|
||||
+ if (-not (Test-Path -LiteralPath $archiveDir)) {
|
||||
+ New-Item -ItemType Directory -Path $archiveDir -Force | Out-Null
|
||||
+ }
|
||||
+
|
||||
+ $archiveName = $existing.Name
|
||||
+ $archivePath = Join-Path $archiveDir $archiveName
|
||||
+ if (Test-Path -LiteralPath $archivePath) {
|
||||
+ $stamp = Get-Date -Format 'yyyyMMdd_HHmmss'
|
||||
+ $baseName = [System.IO.Path]::GetFileNameWithoutExtension($archiveName)
|
||||
+ $archivePath = Join-Path $archiveDir ("{0}_{1}{2}" -f $baseName, $stamp, $existing.Extension)
|
||||
+ }
|
||||
+
|
||||
+ Move-Item -LiteralPath $existing.FullName -Destination $archivePath -Force -ErrorAction Stop
|
||||
$collisions++
|
||||
- if ($LogFile) { "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] RENAME SKIP collision: '$($f.Name)' -> '$newName'" | Add-Content -LiteralPath $LogFile }
|
||||
- continue
|
||||
+ if ($LogFile) { "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] RENAME ARCHIVE existing '$($existing.Name)' -> '$archivePath'" | Add-Content -LiteralPath $LogFile }
|
||||
}
|
||||
Rename-Item -LiteralPath $f.FullName -NewName $newName -ErrorAction Stop
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
</tool-use>
|
||||
|
||||
---
|
||||
|
||||
<think><details><summary>Thought Process</summary>
|
||||
**Verifying archive path handling and script execution**
|
||||
|
||||
I’m checking how `archivePath` is logged—whether to use the full path or just the name—and confirming that `Move-Item` usage is correct without a `-LiteralPath` option for destination. I’m also ensuring date formats are properly quoted and thinking through how to handle filename collisions with `Join-Path`. Next, I plan to run the `UpdateSequences.ps1` script from the appropriate `Renders` folder to confirm everything behaves as expected.</details></think>
|
||||
|
||||
---
|
||||
|
||||
<tool-use data-tool-type="bash" data-tool-name="run_terminal_cmd">
|
||||
<details><summary>Tool use: **command** • Run command: Set-Location "A:\1 Amazon_Active_Projects\251104_Turbotote\Renders"</summary>
|
||||
|
||||
```bash
|
||||
Set-Location "A:\1 Amazon_Active_Projects\251104_Turbotote\Renders"
|
||||
```
|
||||
</details>
|
||||
</tool-use>
|
||||
|
||||
---
|
||||
|
||||
<think><details><summary>Thought Process</summary>
|
||||
**Running the script**</details></think>
|
||||
|
||||
---
|
||||
|
||||
<tool-use data-tool-type="bash" data-tool-name="run_terminal_cmd">
|
||||
<details><summary>Tool use: **command** • Run command: powershell -NoLogo -NoProfile -ExecutionPolicy Bypass -File "A:\1 Amazon_Active_Projects\3 ProjectStructure\UpdateSequences.ps1"</summary>
|
||||
|
||||
```bash
|
||||
powershell -NoLogo -NoProfile -ExecutionPolicy Bypass -File "A:\1 Amazon_Active_Projects\3 ProjectStructure\UpdateSequences.ps1"
|
||||
```
|
||||
|
||||
```
|
||||
[RENAME]|A:\1 Amazon_Active_Projects\251104_Turbotote\Renders\Associate_TT_animation 1|Associate_TT_animation 1|checked=978|renamed=489|collisions=489|errors=0
|
||||
[RENAME]|A:\1 Amazon_Active_Projects\251104_Turbotote\Renders\Associate_TT_animation 2|Associate_TT_animation 2|checked=722|renamed=0|collisions=360|errors=1
|
||||
[RENAME]|A:\1 Amazon_Active_Projects\251104_Turbotote\Renders\Associate_TT_animation 3|Associate_TT_animation 3|checked=468|renamed=0|collisions=234|errors=0
|
||||
=== SUMMARY REPORT ===
|
||||
Sequences scanned: 11
|
||||
Files renamed: 489 (collisions: 1083, errors: 1)
|
||||
Preview MP4s renamed: 0 (collisions: 0, errors: 0)
|
||||
=====================
|
||||
|
||||
```
|
||||
</details>
|
||||
</tool-use>
|
||||
|
||||
---
|
||||
|
||||
<think><details><summary>Thought Process</summary>
|
||||
**Investigating collision handling and renaming**</details></think>
|
||||
|
||||
---
|
||||
|
||||
<tool-use data-tool-type="bash" data-tool-name="run_terminal_cmd">
|
||||
<details><summary>Tool use: **command** • Run command: Get-ChildItem -Path "A:\1 Amazon_Active_Projects\251104_Turbotote\Renders\Associate_TT_animation 1" -File | Select-Object -First 10 | Format-Table Name</summary>
|
||||
|
||||
```bash
|
||||
Get-ChildItem -Path "A:\1 Amazon_Active_Projects\251104_Turbotote\Renders\Associate_TT_animation 1" -File | Select-Object -First 10 | Format-Table Name
|
||||
```
|
||||
|
||||
```
|
||||
|
||||
Name
|
||||
----
|
||||
Associate_TT_animation 1_000035.png
|
||||
Associate_TT_animation 1_000454.png
|
||||
Associate_TT_animation 1_000322.png
|
||||
Associate_TT_animation 1_000485.png
|
||||
Associate_TT_animation 1_000206.png
|
||||
Associate_TT_animation 1_000111.png
|
||||
Associate_TT_animation 1_000049.png
|
||||
Associate_TT_animation 1_000428.png
|
||||
Associate_TT_animation 1_000098.png
|
||||
Associate_TT_animation 1_000201.png
|
||||
|
||||
|
||||
|
||||
```
|
||||
</details>
|
||||
</tool-use>
|
||||
|
||||
---
|
||||
|
||||
<think><details><summary>Thought Process</summary>
|
||||
**Checking other sequences for _CURRENT**</details></think>
|
||||
|
||||
---
|
||||
|
||||
<tool-use data-tool-type="bash" data-tool-name="run_terminal_cmd">
|
||||
<details><summary>Tool use: **command** • Run command: Get-ChildItem -Path "A:\1 Amazon_Active_Projects\251104_Turbotote\Renders\Associate_TT_animation 2" -File | Select-Object -First 10 | Format-Table Name</summary>
|
||||
|
||||
```bash
|
||||
Get-ChildItem -Path "A:\1 Amazon_Active_Projects\251104_Turbotote\Renders\Associate_TT_animation 2" -File | Select-Object -First 10 | Format-Table Name
|
||||
```
|
||||
|
||||
```
|
||||
|
||||
Name
|
||||
----
|
||||
Associate_TT_animation 2_000093.png
|
||||
Associate_TT_animation 2_000355.png
|
||||
Associate_TT_animation 2_000384.png
|
||||
Associate_TT_animation 2_000166.png
|
||||
Associate_TT_animation 2_000271.png
|
||||
Associate_TT_animation 2_000329.png
|
||||
Associate_TT_animation 2_000161.png
|
||||
Associate_TT_animation 2_000276.png
|
||||
Associate_TT_animation 2_000094.png
|
||||
Associate_TT_animation 2_000352.png
|
||||
|
||||
|
||||
|
||||
```
|
||||
</details>
|
||||
</tool-use>
|
||||
|
||||
---
|
||||
|
||||
<think><details><summary>Thought Process</summary>
|
||||
**Analyzing duplicate handling and collisions**</details></think>
|
||||
|
||||
---
|
||||
|
||||
<tool-use data-tool-type="bash" data-tool-name="run_terminal_cmd">
|
||||
<details><summary>Tool use: **command** • Run command: Get-ChildItem "A:\1 Amazon_Active_Projects\251104_Turbotote\Renders\Associate_TT_animation 1\_archive" -File | Select-Object -First 5 | Format-Table Name</summary>
|
||||
|
||||
```bash
|
||||
Get-ChildItem "A:\1 Amazon_Active_Projects\251104_Turbotote\Renders\Associate_TT_animation 1\_archive" -File | Select-Object -First 5 | Format-Table Name
|
||||
```
|
||||
|
||||
```
|
||||
|
||||
Name
|
||||
----
|
||||
Associate_TT_animation 1_000502.png
|
||||
Associate_TT_animation 1_000163.png
|
||||
Associate_TT_animation 1_000274.png
|
||||
Associate_TT_animation 1_000208.png
|
||||
Associate_TT_animation 1_000096.png
|
||||
|
||||
|
||||
|
||||
```
|
||||
</details>
|
||||
</tool-use>
|
||||
|
||||
---
|
||||
|
||||
<think><details><summary>Thought Process</summary>
|
||||
**Preparing to continue modifications**</details></think>
|
||||
|
||||
---
|
||||
|
||||
<tool-use data-tool-type="bash" data-tool-name="run_terminal_cmd">
|
||||
<details><summary>Tool use: **command** • Run command: Set-Location "A:\1 Amazon_Active_Projects\3 ProjectStructure"</summary>
|
||||
|
||||
```bash
|
||||
Set-Location "A:\1 Amazon_Active_Projects\3 ProjectStructure"
|
||||
```
|
||||
</details>
|
||||
</tool-use>
|
||||
|
||||
---
|
||||
|
||||
<think><details><summary>Thought Process</summary>
|
||||
**Reviewing and verifying task progress**</details></think>
|
||||
|
||||
---
|
||||
|
||||
<tool-use data-tool-type="generic" data-tool-name="read_lints">
|
||||
<details>
|
||||
<summary>Tool use: **read_lints** • Read lints for 5 files</summary>
|
||||
|
||||
Lint paths:
|
||||
|
||||
- `UpdateSequences.ps1`
|
||||
- `zip_sequences.py`
|
||||
- `ZipSeqArchv.bat`
|
||||
- `UnzipSeqArchv.bat`
|
||||
- `NewProject.bat`
|
||||
|
||||
Lint results
|
||||
|
||||
|
||||
**No lint errors found**
|
||||
</details>
|
||||
</tool-use>
|
||||
|
||||
---
|
||||
|
||||
<think><details><summary>Thought Process</summary>
|
||||
**Checking git status**</details></think>
|
||||
|
||||
---
|
||||
|
||||
<tool-use data-tool-type="bash" data-tool-name="run_terminal_cmd">
|
||||
<details><summary>Tool use: **command** • Run command: git status -sb</summary>
|
||||
|
||||
```bash
|
||||
git status -sb
|
||||
```
|
||||
|
||||
```
|
||||
## main...origin/main
|
||||
M .specstory/history/2025-11-06_17-01Z-integrate-new-elements-and-update-sequences.md
|
||||
M UpdateSequences.ps1
|
||||
?? .specstory/history/2025-11-06_21-32Z-continue-integration-of-new-elements.md
|
||||
|
||||
```
|
||||
</details>
|
||||
</tool-use>
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
_**User (2025-11-06 21:32Z)**_
|
||||
|
||||
are you just manually renaming them?
|
||||
|
||||
---
|
||||
|
||||
_**Agent (model gpt-5-codex, mode Agent)**_
|
||||
|
||||
<think><details><summary>Thought Process</summary>
|
||||
**Preparing response summaries**</details></think>
|
||||
|
||||
---
|
||||
|
||||
No manual steps. I ran the updated `UpdateSequences.ps1`, which now:
|
||||
|
||||
- Scans each sequence folder, resolving `_CURRENT` to its parent folder name.
|
||||
- Renames every matching frame automatically under that parent name.
|
||||
- Archives any conflicting duplicates into a local `_archive` subfolder so the rename can proceed.
|
||||
|
||||
For `Associate_TT_animation 1`, the folder now only has `Associate_TT_animation 1_*.png`; the `_CURRENT_*` files were either renamed or moved into `Associate_TT_animation 1\_archive`. Let me know if any folder still isn’t following that pattern and I’ll dig deeper.
|
||||
|
||||
---
|
||||
|
||||
19204
.specstory/history/2025-11-10_16-59Z-change-compression-method-to-7z.md
Normal file
19204
.specstory/history/2025-11-10_16-59Z-change-compression-method-to-7z.md
Normal file
File diff suppressed because one or more lines are too long
24
CONFIG.md
Normal file
24
CONFIG.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# ProjectStructure Configuration
|
||||
|
||||
The repository reads user preferences from `config.json` in the ProjectStructure
|
||||
root (copied alongside helper batches when they are deployed to projects).
|
||||
|
||||
## Keys
|
||||
|
||||
| Key | Type | Meaning |
|
||||
| --- | --- | --- |
|
||||
| `dailyFormat` | bool | `true` → daily folders named `YYYY-MM-DD`; `false` → `daily_*` style. |
|
||||
| `structDir` | string | Absolute or relative path to the canonical ProjectStructure directory. Blank values default to the folder containing `config.json`. |
|
||||
| `projectsRoot` | string (optional) | Override for the root directory scanned by `UpgradeSeqBatches.ps1`. Defaults to the parent of `structDir`. |
|
||||
| `zipper` | bool | `true` → use 7‑Zip (if available); `false` → use Python’s built-in zipfile module. |
|
||||
| `compression` | int | Compression level `0-9` (0 = store only, 9 = max). Applies to both zipfile and 7‑Zip. |
|
||||
|
||||
## Notes
|
||||
|
||||
- `UpdateProjectBatches.ps1` copies `config.json` and `ConfigLoader.ps1` to the
|
||||
target project so the helper `.bat` launchers can resolve script locations.
|
||||
- When `zipper` is `true`, the tool searches for `7z`/`7za` on `PATH`. If neither
|
||||
is found it logs a warning and falls back to `zipfile`.
|
||||
- Leaving `structDir` empty is safe—the scripts fall back to the directory that
|
||||
contains `config.json`.
|
||||
|
||||
336
ConfigLoader.ps1
Normal file
336
ConfigLoader.ps1
Normal file
@@ -0,0 +1,336 @@
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$script:LoaderRoot = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$script:ConfigPath = Join-Path -Path $script:LoaderRoot -ChildPath 'config.json'
|
||||
$script:ConfigCache = $null
|
||||
|
||||
function Get-ProjectStructureConfig {
|
||||
if ($null -ne $script:ConfigCache) {
|
||||
return $script:ConfigCache
|
||||
}
|
||||
|
||||
if (Test-Path -LiteralPath $script:ConfigPath) {
|
||||
try {
|
||||
$raw = Get-Content -LiteralPath $script:ConfigPath -Raw -ErrorAction Stop
|
||||
if ($raw.Trim().Length -gt 0) {
|
||||
$script:ConfigCache = $raw | ConvertFrom-Json
|
||||
return $script:ConfigCache
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Warning "Failed to parse config.json: $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
|
||||
$script:ConfigCache = [pscustomobject]@{}
|
||||
return $script:ConfigCache
|
||||
}
|
||||
|
||||
function Get-ConfigValue {
|
||||
param(
|
||||
[Parameter(Mandatory)] [string]$Name,
|
||||
$Default = $null
|
||||
)
|
||||
|
||||
$config = Get-ProjectStructureConfig
|
||||
if ($config.PSObject.Properties.Name -contains $Name) {
|
||||
$value = $config.$Name
|
||||
if ($null -ne $value -and ($value -isnot [string] -or $value.Trim().Length -gt 0)) {
|
||||
return $value
|
||||
}
|
||||
}
|
||||
|
||||
return $Default
|
||||
}
|
||||
|
||||
function Get-StructDirectory {
|
||||
$value = Get-ConfigValue -Name 'structDir'
|
||||
if ($null -eq $value -or [string]::IsNullOrWhiteSpace($value)) {
|
||||
return $script:LoaderRoot
|
||||
}
|
||||
|
||||
if ([System.IO.Path]::IsPathRooted($value)) {
|
||||
$resolved = Resolve-Path -LiteralPath $value -ErrorAction SilentlyContinue
|
||||
if ($null -ne $resolved) { return $resolved.Path }
|
||||
return $value
|
||||
}
|
||||
|
||||
$candidate = Join-Path -Path $script:LoaderRoot -ChildPath $value
|
||||
$resolvedCandidate = Resolve-Path -LiteralPath $candidate -ErrorAction SilentlyContinue
|
||||
if ($null -ne $resolvedCandidate) { return $resolvedCandidate.Path }
|
||||
return $candidate
|
||||
}
|
||||
|
||||
function Get-ProjectsRoot {
|
||||
$value = Get-ConfigValue -Name 'projectsRoot'
|
||||
if ($null -eq $value -or [string]::IsNullOrWhiteSpace($value)) {
|
||||
$structDir = Get-StructDirectory
|
||||
$parent = Split-Path -Parent $structDir
|
||||
if ($null -eq $parent -or $parent.Length -eq 0 -or $parent -eq $structDir) {
|
||||
return $structDir
|
||||
}
|
||||
return $parent
|
||||
}
|
||||
|
||||
if ([System.IO.Path]::IsPathRooted($value)) {
|
||||
$resolved = Resolve-Path -LiteralPath $value -ErrorAction SilentlyContinue
|
||||
if ($null -ne $resolved) { return $resolved.Path }
|
||||
return $value
|
||||
}
|
||||
|
||||
$candidate = Join-Path -Path $script:LoaderRoot -ChildPath $value
|
||||
$resolvedCandidate = Resolve-Path -LiteralPath $candidate -ErrorAction SilentlyContinue
|
||||
if ($null -ne $resolvedCandidate) { return $resolvedCandidate.Path }
|
||||
return $candidate
|
||||
}
|
||||
|
||||
function Get-ProjectPathFromUser {
|
||||
param(
|
||||
[string]$Prompt = "Paste the project path to deploy in"
|
||||
)
|
||||
|
||||
do {
|
||||
$inputPath = Read-Host -Prompt $Prompt
|
||||
if ([string]::IsNullOrWhiteSpace($inputPath)) {
|
||||
Write-Warning "Path cannot be empty. Please try again."
|
||||
continue
|
||||
}
|
||||
|
||||
# Remove quotes if present
|
||||
$inputPath = $inputPath.Trim('"', "'")
|
||||
|
||||
# Try to resolve the path
|
||||
try {
|
||||
if (Test-Path -LiteralPath $inputPath -PathType Container) {
|
||||
$resolved = Resolve-Path -LiteralPath $inputPath -ErrorAction Stop
|
||||
return $resolved.Path
|
||||
}
|
||||
else {
|
||||
Write-Warning "Path does not exist or is not a directory: $inputPath"
|
||||
$retry = Read-Host "Try again? (Y/N)"
|
||||
if ($retry -notmatch '^[Yy]') {
|
||||
return $null
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Warning "Invalid path: $($_.Exception.Message)"
|
||||
$retry = Read-Host "Try again? (Y/N)"
|
||||
if ($retry -notmatch '^[Yy]') {
|
||||
return $null
|
||||
}
|
||||
}
|
||||
} while ($true)
|
||||
}
|
||||
|
||||
function Use-IsoDailyFormat {
|
||||
$dailyFormat = Get-ConfigValue -Name 'dailyFormat' -Default $true
|
||||
|
||||
# Handle backward compatibility with boolean values
|
||||
if ($dailyFormat -is [bool]) {
|
||||
return $dailyFormat
|
||||
}
|
||||
|
||||
# Handle string values
|
||||
if ($dailyFormat -is [string]) {
|
||||
$formatStr = $dailyFormat.Trim()
|
||||
# ISO format contains YYYY-MM-DD pattern
|
||||
if ($formatStr -match 'YYYY-MM-DD' -or $formatStr -match '\d{4}-\d{2}-\d{2}') {
|
||||
return $true
|
||||
}
|
||||
# daily_YYMMDD or other formats are not ISO
|
||||
return $false
|
||||
}
|
||||
|
||||
# Default to false (not ISO format) for unknown types
|
||||
return $false
|
||||
}
|
||||
|
||||
function Use-7Zip {
|
||||
$zipper = Get-ConfigValue -Name 'zipper' -Default $true
|
||||
|
||||
# Handle backward compatibility with boolean values
|
||||
if ($zipper -is [bool]) {
|
||||
return $zipper
|
||||
}
|
||||
|
||||
# Handle string values
|
||||
if ($zipper -is [string]) {
|
||||
$zipperStr = $zipper.Trim().ToLower()
|
||||
return ($zipperStr -eq '7z')
|
||||
}
|
||||
|
||||
# Default to false (use zip) for unknown types
|
||||
return $false
|
||||
}
|
||||
|
||||
function Get-ZipCompressionLevel {
|
||||
$value = Get-ConfigValue -Name 'compression' -Default 9
|
||||
if ($value -is [string]) {
|
||||
$parsed = 0
|
||||
if ([int]::TryParse($value, [ref]$parsed)) {
|
||||
$value = $parsed
|
||||
}
|
||||
}
|
||||
|
||||
if ($value -isnot [int]) {
|
||||
return 9
|
||||
}
|
||||
|
||||
return [Math]::Min(9, [Math]::Max(0, $value))
|
||||
}
|
||||
|
||||
# If script is run directly (not dot-sourced), prompt for project path and deploy
|
||||
# When dot-sourced, InvocationName is '.'; when run directly, it's the script path or name
|
||||
if ($MyInvocation.InvocationName -ne '.') {
|
||||
$projectPath = Get-ProjectPathFromUser
|
||||
if ($null -ne $projectPath) {
|
||||
# Deploy batch files and config to the project
|
||||
$structDir = Get-StructDirectory
|
||||
if (-not (Test-Path -LiteralPath $structDir -PathType Container)) {
|
||||
Write-Error "Configured structDir not found: $structDir"
|
||||
exit 1
|
||||
}
|
||||
|
||||
try {
|
||||
$resolvedProject = (Resolve-Path -LiteralPath $projectPath -ErrorAction Stop).Path
|
||||
}
|
||||
catch {
|
||||
Write-Error "Unable to resolve project directory: $($_.Exception.Message)"
|
||||
exit 1
|
||||
}
|
||||
|
||||
if (-not (Test-Path -LiteralPath $resolvedProject -PathType Container)) {
|
||||
Write-Error "Project path is not a directory: $resolvedProject"
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host "`nDeploying to: $resolvedProject" -ForegroundColor Cyan
|
||||
Write-Host "Struct directory: $structDir" -ForegroundColor Cyan
|
||||
|
||||
$specs = @(
|
||||
@{ Name = 'UpdateSequences.bat'; Source = Join-Path -Path $structDir -ChildPath 'UpdateSequences.bat' },
|
||||
@{ Name = 'UpdateAllSequences.bat'; Source = Join-Path -Path $structDir -ChildPath 'UpdateAllSequences.bat' },
|
||||
@{ Name = 'ZipSeqArchv.bat'; Source = Join-Path -Path $structDir -ChildPath 'ZipSeqArchv.bat' },
|
||||
@{ Name = 'UnzipSeqArchv.bat'; Source = Join-Path -Path $structDir -ChildPath 'UnzipSeqArchv.bat' },
|
||||
@{ Name = 'NewDaily.bat'; Source = Join-Path -Path $structDir -ChildPath 'NewDaily.bat' }
|
||||
)
|
||||
|
||||
# Config files to deploy to projectroot\.config\
|
||||
$configAssets = @(
|
||||
@{ Name = 'config.json'; Source = Join-Path -Path $structDir -ChildPath 'config.json' },
|
||||
@{ Name = 'GetStructDir.ps1'; Source = Join-Path -Path $structDir -ChildPath 'GetStructDir.ps1' }
|
||||
)
|
||||
|
||||
foreach ($spec in $specs) {
|
||||
if (-not (Test-Path -LiteralPath $spec.Source -PathType Leaf)) {
|
||||
Write-Error "Source file not found: $($spec.Source)"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($asset in $configAssets) {
|
||||
if (-not (Test-Path -LiteralPath $asset.Source -PathType Leaf)) {
|
||||
Write-Error "Config asset not found: $($asset.Source)"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
# Ensure .config directory exists in project root and is hidden
|
||||
$projectConfigDir = Join-Path -Path $resolvedProject -ChildPath '.config'
|
||||
if (-not (Test-Path -LiteralPath $projectConfigDir -PathType Container)) {
|
||||
New-Item -Path $projectConfigDir -ItemType Directory -Force | Out-Null
|
||||
Write-Host "Created .config directory: $projectConfigDir" -ForegroundColor Cyan
|
||||
}
|
||||
# Set hidden attribute on .config directory
|
||||
$folder = Get-Item -LiteralPath $projectConfigDir -Force
|
||||
$folder.Attributes = $folder.Attributes -bor [System.IO.FileAttributes]::Hidden
|
||||
|
||||
$touchedDirs = @{}
|
||||
$summary = @()
|
||||
|
||||
foreach ($spec in $specs) {
|
||||
Write-Host "`n=== Updating $($spec.Name) ===" -ForegroundColor Magenta
|
||||
|
||||
$targets = Get-ChildItem -LiteralPath $resolvedProject -Recurse -Filter $spec.Name -File -ErrorAction SilentlyContinue
|
||||
$targets = $targets | Where-Object { $_.FullName -ne $spec.Source }
|
||||
|
||||
if (-not $targets) {
|
||||
Write-Host "No targets found." -ForegroundColor Yellow
|
||||
$summary += [pscustomobject]@{
|
||||
Name = $spec.Name
|
||||
Updated = 0
|
||||
Failed = 0
|
||||
Skipped = 0
|
||||
Total = 0
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
$updated = 0
|
||||
$failed = 0
|
||||
|
||||
foreach ($target in $targets) {
|
||||
try {
|
||||
Copy-Item -Path $spec.Source -Destination $target.FullName -Force
|
||||
Write-Host "[OK] $($target.FullName)" -ForegroundColor Green
|
||||
$updated++
|
||||
|
||||
$targetDir = $target.Directory.FullName
|
||||
if (-not $touchedDirs.ContainsKey($targetDir)) {
|
||||
$touchedDirs[$targetDir] = $true
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Host "[FAIL] $($target.FullName)" -ForegroundColor Red
|
||||
Write-Host " $($_.Exception.Message)" -ForegroundColor DarkRed
|
||||
$failed++
|
||||
}
|
||||
}
|
||||
|
||||
$summary += [pscustomobject]@{
|
||||
Name = $spec.Name
|
||||
Updated = $updated
|
||||
Failed = $failed
|
||||
Skipped = 0
|
||||
Total = @($targets).Count
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "`n=== Summary ===" -ForegroundColor Cyan
|
||||
foreach ($item in $summary) {
|
||||
Write-Host ("{0,-22} Updated: {1,3} Failed: {2,3} Total: {3,3}" -f $item.Name, $item.Updated, $item.Failed, $item.Total)
|
||||
}
|
||||
|
||||
if (($summary | Measure-Object -Property Failed -Sum).Sum -gt 0) {
|
||||
Write-Host "Completed with errors." -ForegroundColor Yellow
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host "All batch files refreshed successfully." -ForegroundColor Green
|
||||
|
||||
# Deploy config files to projectroot\.config\
|
||||
Write-Host "`n=== Deploying config files to .config\ ===" -ForegroundColor Magenta
|
||||
foreach ($asset in $configAssets) {
|
||||
$targetPath = Join-Path -Path $projectConfigDir -ChildPath $asset.Name
|
||||
try {
|
||||
Copy-Item -Path $asset.Source -Destination $targetPath -Force
|
||||
Write-Host "[OK] $targetPath" -ForegroundColor Green
|
||||
}
|
||||
catch {
|
||||
Write-Host "[FAIL] $targetPath" -ForegroundColor Red
|
||||
Write-Host " $($_.Exception.Message)" -ForegroundColor DarkRed
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "`nConfig files deployed successfully." -ForegroundColor Green
|
||||
Write-Host "Deployment completed successfully." -ForegroundColor Green
|
||||
exit 0
|
||||
}
|
||||
else {
|
||||
Write-Host "No project path provided." -ForegroundColor Yellow
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
68
GetStructDir.ps1
Normal file
68
GetStructDir.ps1
Normal file
@@ -0,0 +1,68 @@
|
||||
# Simple helper script to get structDir from project config.json
|
||||
# Reads config.json from .config folder in project root
|
||||
|
||||
param(
|
||||
[string]$ProjectRoot
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($ProjectRoot)) {
|
||||
# Try to determine project root from script location
|
||||
if ($PSScriptRoot) {
|
||||
$ProjectRoot = Split-Path -Parent $PSScriptRoot
|
||||
}
|
||||
elseif ($MyInvocation.MyCommand.Path) {
|
||||
$ProjectRoot = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path)
|
||||
}
|
||||
else {
|
||||
Write-Error "Unable to determine project root. Please provide -ProjectRoot parameter."
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
$configPath = Join-Path -Path $ProjectRoot -ChildPath '.config\config.json'
|
||||
|
||||
if (-not (Test-Path -LiteralPath $configPath -PathType Leaf)) {
|
||||
Write-Error "config.json not found at: $configPath"
|
||||
exit 1
|
||||
}
|
||||
|
||||
try {
|
||||
$config = Get-Content -LiteralPath $configPath -Raw -ErrorAction Stop | ConvertFrom-Json
|
||||
|
||||
if ($config.PSObject.Properties.Name -contains 'structDir') {
|
||||
$structDir = $config.structDir
|
||||
if ($null -ne $structDir -and ($structDir -isnot [string] -or $structDir.Trim().Length -gt 0)) {
|
||||
# If it's an absolute path, resolve it
|
||||
if ([System.IO.Path]::IsPathRooted($structDir)) {
|
||||
$resolved = Resolve-Path -LiteralPath $structDir -ErrorAction SilentlyContinue
|
||||
if ($null -ne $resolved) {
|
||||
Write-Output $resolved.Path
|
||||
exit 0
|
||||
}
|
||||
Write-Output $structDir
|
||||
exit 0
|
||||
}
|
||||
# Relative path - resolve relative to config location
|
||||
$candidate = Join-Path -Path (Split-Path -Parent $configPath) -ChildPath $structDir
|
||||
$resolvedCandidate = Resolve-Path -LiteralPath $candidate -ErrorAction SilentlyContinue
|
||||
if ($null -ne $resolvedCandidate) {
|
||||
Write-Output $resolvedCandidate.Path
|
||||
exit 0
|
||||
}
|
||||
Write-Output $candidate
|
||||
exit 0
|
||||
}
|
||||
}
|
||||
|
||||
# Default: return the directory containing config.json (project root)
|
||||
Write-Output $ProjectRoot
|
||||
exit 0
|
||||
}
|
||||
catch {
|
||||
Write-Error "Failed to read or parse config.json: $($_.Exception.Message)"
|
||||
exit 1
|
||||
}
|
||||
|
||||
@@ -1,5 +1,27 @@
|
||||
@echo off
|
||||
setlocal
|
||||
setlocal EnableExtensions
|
||||
|
||||
set "SCRIPT_DIR=%~dp0"
|
||||
set "CONFIG_LOADER=%SCRIPT_DIR%ConfigLoader.ps1"
|
||||
set "CONFIG_PATH=%SCRIPT_DIR%config.json"
|
||||
|
||||
if not exist "%CONFIG_LOADER%" (
|
||||
echo [ERROR] ConfigLoader.ps1 not found next to NewProject.bat.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if not exist "%CONFIG_PATH%" (
|
||||
echo [ERROR] config.json not found next to NewProject.bat.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
for /f "usebackq delims=" %%I in (`powershell -NoProfile -ExecutionPolicy Bypass -Command ^
|
||||
"Set-StrictMode -Version Latest; $loader = Resolve-Path -LiteralPath '%CONFIG_LOADER%' -ErrorAction Stop; . $loader.Path; Write-Output (Get-StructDirectory)"`) do set "STRUCT_DIR=%%I"
|
||||
|
||||
if not defined STRUCT_DIR (
|
||||
echo [ERROR] Unable to resolve ProjectStructure directory from config.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
:: Get current date in YYYY-MM-DD format
|
||||
for /f "tokens=2-4 delims=/ " %%a in ('date /t') do (
|
||||
@@ -8,18 +30,19 @@ for /f "tokens=2-4 delims=/ " %%a in ('date /t') do (
|
||||
set yy=%%c
|
||||
)
|
||||
set yyyy=20%yy:~-2%
|
||||
set yymmdd=%yy:~-2%%mm%%dd%
|
||||
|
||||
:: Ask for project name
|
||||
set /p projectName="Enter project name (press Enter for default 'NewProject'): "
|
||||
if "%projectName%"=="" set projectName=NewProject
|
||||
set projectRoot=%yyyy%-%mm%-%dd%_%projectName%
|
||||
set projectRoot=%yymmdd%_%projectName%
|
||||
|
||||
:: Create main project directory
|
||||
mkdir "%projectRoot%"
|
||||
|
||||
:: Create Assets structure
|
||||
mkdir "%projectRoot%\Assets\ElevenLabs"
|
||||
if exist "A:\1 Amazon_Active_Projects\3 ProjectStructure\NewDaily.bat" copy /Y "A:\1 Amazon_Active_Projects\3 ProjectStructure\NewDaily.bat" "%projectRoot%\Assets\ElevenLabs\NewDaily.bat" >nul
|
||||
if exist "%STRUCT_DIR%\NewDaily.bat" copy /Y "%STRUCT_DIR%\NewDaily.bat" "%projectRoot%\Assets\ElevenLabs\NewDaily.bat" >nul
|
||||
mkdir "%projectRoot%\Assets\Blends"
|
||||
mkdir "%projectRoot%\Assets\Mocap"
|
||||
mkdir "%projectRoot%\Assets\VO"
|
||||
@@ -39,8 +62,10 @@ mkdir "%projectRoot%\Pr\RnR\RIFE"
|
||||
if not exist "%projectRoot%\Renders" mkdir "%projectRoot%\Renders"
|
||||
|
||||
:: Place helper scripts into Renders
|
||||
if exist "%~dp0NewDaily.bat" copy /Y "%~dp0NewDaily.bat" "%projectRoot%\Renders\NewDaily.bat" >nul
|
||||
if exist "%~dp0UpdateSequences.bat" copy /Y "%~dp0UpdateSequences.bat" "%projectRoot%\Renders\UpdateSequences.bat" >nul
|
||||
set "templateRoot=%STRUCT_DIR%"
|
||||
for %%F in (UpdateSequences.bat ZipSeqArchv.bat UnzipSeqArchv.bat ConfigLoader.ps1 config.json) do (
|
||||
if exist "%templateRoot%\%%F" copy /Y "%templateRoot%\%%F" "%projectRoot%\Renders\%%F" >nul
|
||||
)
|
||||
|
||||
:: Use repo-provided templates for git config files
|
||||
if exist "%~dp0components\gitignore" copy /Y "%~dp0components\gitignore" "%projectRoot%\.gitignore" >nul
|
||||
|
||||
42
UnzipSeqArchv.bat
Normal file
42
UnzipSeqArchv.bat
Normal file
@@ -0,0 +1,42 @@
|
||||
@echo off
|
||||
setlocal EnableExtensions EnableDelayedExpansion
|
||||
|
||||
set "REN_DIR=%~dp0"
|
||||
for %%I in ("%REN_DIR%..") do set "PROJ_ROOT=%%~fI"
|
||||
|
||||
set "CONFIG_DIR=%PROJ_ROOT%\.config"
|
||||
set "CONFIG_PATH=%CONFIG_DIR%\config.json"
|
||||
set "GET_STRUCT_DIR=%CONFIG_DIR%\GetStructDir.ps1"
|
||||
|
||||
if not exist "%CONFIG_PATH%" (
|
||||
echo [ERROR] config.json not found at %CONFIG_PATH%
|
||||
echo Please run ConfigLoader.ps1 to deploy helper files.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if not exist "%GET_STRUCT_DIR%" (
|
||||
echo [ERROR] GetStructDir.ps1 not found at %GET_STRUCT_DIR%
|
||||
echo Please run ConfigLoader.ps1 to deploy helper files.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
for /f "usebackq delims=" %%I in (`powershell -NoProfile -ExecutionPolicy Bypass -Command ^
|
||||
"Set-StrictMode -Version Latest; $structDir = & '%GET_STRUCT_DIR%' -ProjectRoot '%PROJ_ROOT%'; if (-not $structDir) { throw \"Failed to get structDir from GetStructDir.ps1\" }; $pyPath = Join-Path $structDir 'zip_sequences.py'; if (-not (Test-Path -LiteralPath $pyPath)) { throw \"zip_sequences.py not found at $pyPath\" }; Write-Output $pyPath"`) do set "PY_SCRIPT=%%I"
|
||||
|
||||
if not defined PY_SCRIPT (
|
||||
echo [ERROR] Unable to resolve zip_sequences.py path from config.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
pushd "%PROJ_ROOT%" >nul 2>&1
|
||||
|
||||
python "%PY_SCRIPT%" --mode expand --verbose %*
|
||||
set "ERR=!ERRORLEVEL!"
|
||||
|
||||
if not "!ERR!"=="0" (
|
||||
echo Failed to expand render sequence archives (exit code !ERR!).
|
||||
)
|
||||
|
||||
popd >nul 2>&1
|
||||
exit /b !ERR!
|
||||
|
||||
164
UpdateProjectBatches.ps1
Normal file
164
UpdateProjectBatches.ps1
Normal file
@@ -0,0 +1,164 @@
|
||||
# Refresh helper batch scripts within a specific project directory.
|
||||
|
||||
param(
|
||||
[string]$ProjectPath
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
if (-not $PSScriptRoot) {
|
||||
$PSScriptRoot = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
}
|
||||
|
||||
$configLoader = Join-Path -Path $PSScriptRoot -ChildPath 'ConfigLoader.ps1'
|
||||
if (-not (Test-Path -LiteralPath $configLoader)) {
|
||||
throw "Missing ConfigLoader.ps1 in $PSScriptRoot"
|
||||
}
|
||||
. $configLoader
|
||||
|
||||
$structDir = Get-StructDirectory
|
||||
if (-not (Test-Path -LiteralPath $structDir -PathType Container)) {
|
||||
throw "Configured structDir not found: $structDir"
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($ProjectPath)) {
|
||||
$ProjectPath = Read-Host 'Enter the full path to the project directory'
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($ProjectPath)) {
|
||||
Write-Error 'No project directory provided.'
|
||||
exit 1
|
||||
}
|
||||
|
||||
try {
|
||||
$resolvedProject = (Resolve-Path -LiteralPath $ProjectPath -ErrorAction Stop).Path
|
||||
}
|
||||
catch {
|
||||
Write-Error "Unable to resolve project directory: $($_.Exception.Message)"
|
||||
exit 1
|
||||
}
|
||||
|
||||
if (-not (Test-Path -LiteralPath $resolvedProject -PathType Container)) {
|
||||
Write-Error "Project path is not a directory: $resolvedProject"
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host "Project directory: $resolvedProject" -ForegroundColor Cyan
|
||||
Write-Host "Struct directory: $structDir" -ForegroundColor Cyan
|
||||
|
||||
$specs = @(
|
||||
@{ Name = 'UpdateSequences.bat'; Source = Join-Path -Path $structDir -ChildPath 'UpdateSequences.bat' },
|
||||
@{ Name = 'UpdateAllSequences.bat'; Source = Join-Path -Path $structDir -ChildPath 'UpdateAllSequences.bat' },
|
||||
@{ Name = 'ZipSeqArchv.bat'; Source = Join-Path -Path $structDir -ChildPath 'ZipSeqArchv.bat' },
|
||||
@{ Name = 'UnzipSeqArchv.bat'; Source = Join-Path -Path $structDir -ChildPath 'UnzipSeqArchv.bat' },
|
||||
@{ Name = 'NewDaily.bat'; Source = Join-Path -Path $structDir -ChildPath 'NewDaily.bat' }
|
||||
)
|
||||
|
||||
# Config files to deploy to projectroot\.config\
|
||||
$configAssets = @(
|
||||
@{ Name = 'config.json'; Source = Join-Path -Path $structDir -ChildPath 'config.json' },
|
||||
@{ Name = 'GetStructDir.ps1'; Source = Join-Path -Path $structDir -ChildPath 'GetStructDir.ps1' }
|
||||
)
|
||||
|
||||
foreach ($spec in $specs) {
|
||||
if (-not (Test-Path -LiteralPath $spec.Source -PathType Leaf)) {
|
||||
Write-Error "Source file not found: $($spec.Source)"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($asset in $configAssets) {
|
||||
if (-not (Test-Path -LiteralPath $asset.Source -PathType Leaf)) {
|
||||
Write-Error "Config asset not found: $($asset.Source)"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
# Ensure .config directory exists in project root
|
||||
$projectConfigDir = Join-Path -Path $resolvedProject -ChildPath '.config'
|
||||
if (-not (Test-Path -LiteralPath $projectConfigDir -PathType Container)) {
|
||||
New-Item -Path $projectConfigDir -ItemType Directory -Force | Out-Null
|
||||
Write-Host "Created .config directory: $projectConfigDir" -ForegroundColor Cyan
|
||||
}
|
||||
|
||||
$touchedDirs = @{}
|
||||
$summary = @()
|
||||
|
||||
foreach ($spec in $specs) {
|
||||
Write-Host "`n=== Updating $($spec.Name) ===" -ForegroundColor Magenta
|
||||
|
||||
$targets = Get-ChildItem -LiteralPath $resolvedProject -Recurse -Filter $spec.Name -File -ErrorAction SilentlyContinue
|
||||
$targets = $targets | Where-Object { $_.FullName -ne $spec.Source }
|
||||
|
||||
if (-not $targets) {
|
||||
Write-Host "No targets found." -ForegroundColor Yellow
|
||||
$summary += [pscustomobject]@{
|
||||
Name = $spec.Name
|
||||
Updated = 0
|
||||
Failed = 0
|
||||
Skipped = 0
|
||||
Total = 0
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
$updated = 0
|
||||
$failed = 0
|
||||
|
||||
foreach ($target in $targets) {
|
||||
try {
|
||||
Copy-Item -Path $spec.Source -Destination $target.FullName -Force
|
||||
Write-Host "[OK] $($target.FullName)" -ForegroundColor Green
|
||||
$updated++
|
||||
|
||||
$targetDir = $target.Directory.FullName
|
||||
if (-not $touchedDirs.ContainsKey($targetDir)) {
|
||||
$touchedDirs[$targetDir] = $true
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Host "[FAIL] $($target.FullName)" -ForegroundColor Red
|
||||
Write-Host " $($_.Exception.Message)" -ForegroundColor DarkRed
|
||||
$failed++
|
||||
}
|
||||
}
|
||||
|
||||
$summary += [pscustomobject]@{
|
||||
Name = $spec.Name
|
||||
Updated = $updated
|
||||
Failed = $failed
|
||||
Skipped = 0
|
||||
Total = @($targets).Count
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "`n=== Summary ===" -ForegroundColor Cyan
|
||||
foreach ($item in $summary) {
|
||||
Write-Host ("{0,-22} Updated: {1,3} Failed: {2,3} Total: {3,3}" -f $item.Name, $item.Updated, $item.Failed, $item.Total)
|
||||
}
|
||||
|
||||
if (($summary | Measure-Object -Property Failed -Sum).Sum -gt 0) {
|
||||
Write-Host "Completed with errors." -ForegroundColor Yellow
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host "All batch files refreshed successfully." -ForegroundColor Green
|
||||
|
||||
# Deploy config files to projectroot\.config\
|
||||
Write-Host "`n=== Deploying config files to .config\ ===" -ForegroundColor Magenta
|
||||
foreach ($asset in $configAssets) {
|
||||
$targetPath = Join-Path -Path $projectConfigDir -ChildPath $asset.Name
|
||||
try {
|
||||
Copy-Item -Path $asset.Source -Destination $targetPath -Force
|
||||
Write-Host "[OK] $targetPath" -ForegroundColor Green
|
||||
}
|
||||
catch {
|
||||
Write-Host "[FAIL] $targetPath" -ForegroundColor Red
|
||||
Write-Host " $($_.Exception.Message)" -ForegroundColor DarkRed
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "Config files deployed successfully." -ForegroundColor Green
|
||||
|
||||
@@ -1,7 +1,33 @@
|
||||
@echo off
|
||||
setlocal EnableExtensions
|
||||
|
||||
set "ps1=R:\Creative\artsy\maya\0 ProjectStructure\UpdateSequences.ps1"
|
||||
set "script_dir=%~dp0"
|
||||
for %%I in ("%script_dir%..\..") do set "PROJ_ROOT=%%~fI"
|
||||
|
||||
set "CONFIG_DIR=%PROJ_ROOT%\.config"
|
||||
set "CONFIG_PATH=%CONFIG_DIR%\config.json"
|
||||
set "GET_STRUCT_DIR=%CONFIG_DIR%\GetStructDir.ps1"
|
||||
|
||||
if not exist "%CONFIG_PATH%" (
|
||||
echo [ERROR] config.json not found at %CONFIG_PATH%
|
||||
echo Please run ConfigLoader.ps1 to deploy helper files.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if not exist "%GET_STRUCT_DIR%" (
|
||||
echo [ERROR] GetStructDir.ps1 not found at %GET_STRUCT_DIR%
|
||||
echo Please run ConfigLoader.ps1 to deploy helper files.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
for /f "usebackq delims=" %%I in (`powershell -NoProfile -ExecutionPolicy Bypass -Command ^
|
||||
"Set-StrictMode -Version Latest; $structDir = & '%GET_STRUCT_DIR%' -ProjectRoot '%PROJ_ROOT%'; if (-not $structDir) { throw \"Failed to get structDir from GetStructDir.ps1\" }; $ps1Path = Join-Path $structDir 'UpdateSequences.ps1'; if (-not (Test-Path -LiteralPath $ps1Path)) { throw \"UpdateSequences.ps1 not found at $ps1Path\" }; Write-Output $ps1Path"`) do set "ps1=%%I"
|
||||
|
||||
if not defined ps1 (
|
||||
echo [ERROR] Unable to resolve UpdateSequences.ps1 path from config.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo Running PowerShell update script...
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -File "%ps1%"
|
||||
set "rc=%errorlevel%"
|
||||
|
||||
@@ -6,17 +6,18 @@ param(
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
function Get-YoungestTimestamp {
|
||||
param(
|
||||
[Parameter(Mandatory)] [string]$FolderPath
|
||||
)
|
||||
$files = Get-ChildItem -LiteralPath $FolderPath -Recurse -File -ErrorAction SilentlyContinue |
|
||||
Where-Object { $_.FullName -notlike '*\_archive\*' }
|
||||
$young = $files | Sort-Object LastWriteTime -Descending | Select-Object -First 1 -ExpandProperty LastWriteTime
|
||||
if (-not $young) { return [datetime]::Parse('1900-01-01') }
|
||||
return $young
|
||||
if (-not $PSScriptRoot) {
|
||||
$PSScriptRoot = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
}
|
||||
|
||||
$configLoader = Join-Path -Path $PSScriptRoot -ChildPath 'ConfigLoader.ps1'
|
||||
if (-not (Test-Path -LiteralPath $configLoader)) {
|
||||
throw "Missing ConfigLoader.ps1 in $PSScriptRoot"
|
||||
}
|
||||
. $configLoader
|
||||
|
||||
$useIsoDailyFormat = Use-IsoDailyFormat
|
||||
|
||||
function Sync-SequenceFilenames {
|
||||
param(
|
||||
[Parameter(Mandatory)] [string]$SequenceFolderPath,
|
||||
@@ -69,9 +70,32 @@ function Sync-SequenceFilenames {
|
||||
$newPath = Join-Path $SequenceFolderPath $newName
|
||||
try {
|
||||
if (Test-Path -LiteralPath $newPath) {
|
||||
$existing = Get-Item -LiteralPath $newPath -ErrorAction Stop
|
||||
$sameSize = ($existing.Length -eq $f.Length)
|
||||
$sameTime = ([math]::Abs(($existing.LastWriteTimeUtc - $f.LastWriteTimeUtc).TotalSeconds) -le 1)
|
||||
if ($sameSize -and $sameTime) {
|
||||
Remove-Item -LiteralPath $f.FullName -Force -ErrorAction Stop
|
||||
$collisions++
|
||||
if ($LogFile) { "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] RENAME DROP duplicate: '$($f.Name)' (matches '$newName')" | Add-Content -LiteralPath $LogFile }
|
||||
continue
|
||||
}
|
||||
|
||||
$archiveDir = Join-Path $SequenceFolderPath '_archive'
|
||||
if (-not (Test-Path -LiteralPath $archiveDir)) {
|
||||
New-Item -ItemType Directory -Path $archiveDir -Force | Out-Null
|
||||
}
|
||||
|
||||
$archiveName = $existing.Name
|
||||
$archivePath = Join-Path $archiveDir $archiveName
|
||||
if (Test-Path -LiteralPath $archivePath) {
|
||||
$stamp = Get-Date -Format 'yyyyMMdd_HHmmss'
|
||||
$baseName = [System.IO.Path]::GetFileNameWithoutExtension($archiveName)
|
||||
$archivePath = Join-Path $archiveDir ("{0}_{1}{2}" -f $baseName, $stamp, $existing.Extension)
|
||||
}
|
||||
|
||||
Move-Item -LiteralPath $existing.FullName -Destination $archivePath -Force -ErrorAction Stop
|
||||
$collisions++
|
||||
if ($LogFile) { "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] RENAME SKIP collision: '$($f.Name)' -> '$newName'" | Add-Content -LiteralPath $LogFile }
|
||||
continue
|
||||
if ($LogFile) { "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] RENAME ARCHIVE existing '$($existing.Name)' -> '$archivePath'" | Add-Content -LiteralPath $LogFile }
|
||||
}
|
||||
Rename-Item -LiteralPath $f.FullName -NewName $newName -ErrorAction Stop
|
||||
$renamed++
|
||||
@@ -147,200 +171,135 @@ function Rename-SequencePreviewMp4 {
|
||||
}
|
||||
}
|
||||
|
||||
function Resolve-SequenceName {
|
||||
param(
|
||||
[Parameter(Mandatory)] [System.IO.DirectoryInfo]$Directory
|
||||
)
|
||||
if ($Directory.Name -eq '_CURRENT' -and $Directory.Parent) {
|
||||
return $Directory.Parent.Name
|
||||
}
|
||||
return $Directory.Name
|
||||
}
|
||||
|
||||
function Add-SequenceFolder {
|
||||
param(
|
||||
[Parameter(Mandatory)] [System.IO.DirectoryInfo]$Directory,
|
||||
[Parameter(Mandatory)] [hashtable]$Map
|
||||
)
|
||||
if ($Directory.Name -eq '_archive') { return }
|
||||
$fullPath = $Directory.FullName
|
||||
if ($Map.ContainsKey($fullPath)) { return }
|
||||
$Map[$fullPath] = Resolve-SequenceName -Directory $Directory
|
||||
}
|
||||
|
||||
try {
|
||||
$root = (Get-Location).ProviderPath
|
||||
$currentDir = Join-Path $root '_CURRENT'
|
||||
if (-not (Test-Path -LiteralPath $currentDir)) {
|
||||
New-Item -ItemType Directory -Path $currentDir -Force | Out-Null
|
||||
$logFile = $null
|
||||
|
||||
if ($DebugMode) {
|
||||
$logFile = Join-Path $root ("UpdateSequences_{0}.log" -f (Get-Date -Format 'yyyyMMdd_HHmmss'))
|
||||
"[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] === UpdateSequences started in '$root' ===" | Out-File -LiteralPath $logFile -Encoding UTF8
|
||||
}
|
||||
|
||||
$logFile = Join-Path $currentDir '_UpdateSequences.log'
|
||||
$sequenceMap = @{}
|
||||
|
||||
"[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] === UpdateSequences (ps1) started in '$root' ===" | Add-Content -LiteralPath $logFile
|
||||
$primaryPattern = if ($useIsoDailyFormat) { '????-??-??' } else { 'daily_*' }
|
||||
$secondaryPattern = if ($useIsoDailyFormat) { 'daily_*' } else { '????-??-??' }
|
||||
|
||||
# Scan for folders with both conventions: daily_* (work) and YYYY-MM-DD (home)
|
||||
$dailyDirsWork = Get-ChildItem -LiteralPath $root -Directory -Filter 'daily_*' | Where-Object { $_.Name -ne '_archive' }
|
||||
$dailyDirsHome = Get-ChildItem -LiteralPath $root -Directory -Filter '????-??-??' | Where-Object { $_.Name -ne '_archive' }
|
||||
$dailyDirs = @($dailyDirsWork) + @($dailyDirsHome)
|
||||
$primaryDirs = Get-ChildItem -LiteralPath $root -Directory -Filter $primaryPattern -ErrorAction SilentlyContinue |
|
||||
Where-Object { $_.Name -ne '_archive' }
|
||||
foreach ($d in $primaryDirs) {
|
||||
$seqDirs = @(Get-ChildItem -LiteralPath $d.FullName -Directory -ErrorAction SilentlyContinue | Where-Object { $_.Name -ne '_archive' })
|
||||
if ($seqDirs.Count -eq 0) {
|
||||
Add-SequenceFolder -Directory $d -Map $sequenceMap
|
||||
} else {
|
||||
foreach ($s in $seqDirs) {
|
||||
Add-SequenceFolder -Directory $s -Map $sequenceMap
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$mapDaily = @{}
|
||||
$dailiesScanned = 0
|
||||
$secondaryDirs = Get-ChildItem -LiteralPath $root -Directory -Filter $secondaryPattern -ErrorAction SilentlyContinue |
|
||||
Where-Object { $_.Name -ne '_archive' }
|
||||
foreach ($d in $secondaryDirs) {
|
||||
$seqDirs = @(Get-ChildItem -LiteralPath $d.FullName -Directory -ErrorAction SilentlyContinue | Where-Object { $_.Name -ne '_archive' })
|
||||
if ($seqDirs.Count -eq 0) {
|
||||
Add-SequenceFolder -Directory $d -Map $sequenceMap
|
||||
} else {
|
||||
foreach ($s in $seqDirs) {
|
||||
Add-SequenceFolder -Directory $s -Map $sequenceMap
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$directSeqs = Get-ChildItem -LiteralPath $root -Directory -ErrorAction SilentlyContinue |
|
||||
Where-Object {
|
||||
$_.Name -ne '_archive' -and
|
||||
$_.Name -notlike 'daily_*' -and
|
||||
$_.Name -notmatch '^\d{4}-\d{2}-\d{2}$'
|
||||
}
|
||||
foreach ($seq in $directSeqs) {
|
||||
Add-SequenceFolder -Directory $seq -Map $sequenceMap
|
||||
}
|
||||
|
||||
$sequenceFolders = $sequenceMap.GetEnumerator() | ForEach-Object {
|
||||
[pscustomobject]@{
|
||||
Path = $_.Key
|
||||
Name = $_.Value
|
||||
}
|
||||
} | Sort-Object -Property Path
|
||||
|
||||
if (-not $sequenceFolders) {
|
||||
Write-Host "No sequence folders found." -ForegroundColor Yellow
|
||||
if ($logFile) { "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] No sequence folders found." | Add-Content -LiteralPath $logFile }
|
||||
exit 0
|
||||
}
|
||||
|
||||
$totalSequences = 0
|
||||
$filesRenamedTotal = 0
|
||||
$renameCollisions = 0
|
||||
$renameErrors = 0
|
||||
$mp4RenamedTotal = 0
|
||||
$mp4Collisions = 0
|
||||
$mp4Errors = 0
|
||||
foreach ($d in $dailyDirs) {
|
||||
$dailiesScanned++
|
||||
$seqDirs = @(Get-ChildItem -LiteralPath $d.FullName -Directory | Where-Object { $_.Name -ne '_archive' })
|
||||
if ($seqDirs.Count -eq 0) { $seqDirs = @($d) }
|
||||
foreach ($s in $seqDirs) {
|
||||
if (-not (Test-Path -LiteralPath $s.FullName)) { continue }
|
||||
|
||||
$renameResult = Sync-SequenceFilenames -SequenceFolderPath $s.FullName -SequenceName $s.Name -LogFile $logFile
|
||||
if ($DebugMode -or $renameResult.Renamed -gt 0 -or $renameResult.Collisions -gt 0 -or $renameResult.Errors -gt 0) {
|
||||
Write-Host "[RENAME]|$($s.FullName)|$($s.Name)|checked=$($renameResult.Checked)|renamed=$($renameResult.Renamed)|collisions=$($renameResult.Collisions)|errors=$($renameResult.Errors)" -ForegroundColor Cyan
|
||||
}
|
||||
$filesRenamedTotal += $renameResult.Renamed
|
||||
$renameCollisions += $renameResult.Collisions
|
||||
$renameErrors += $renameResult.Errors
|
||||
|
||||
if ($renameResult.Renamed -gt 0 -and $renameResult.FrameCount -gt 0 -and $null -ne $renameResult.MinFrame -and $null -ne $renameResult.MaxFrame) {
|
||||
$mp4Result = Rename-SequencePreviewMp4 -SequenceFolderPath $s.FullName -SequenceName $s.Name -StartFrame $renameResult.MinFrame -EndFrame $renameResult.MaxFrame -LogFile $logFile
|
||||
if ($DebugMode -or $mp4Result.Renamed -gt 0 -or $mp4Result.Collisions -gt 0 -or $mp4Result.Errors -gt 0) {
|
||||
Write-Host "[MP4]|$($s.FullName)|$($s.Name)|renamed=$($mp4Result.Renamed)|collisions=$($mp4Result.Collisions)|errors=$($mp4Result.Errors)" -ForegroundColor Cyan
|
||||
}
|
||||
$mp4RenamedTotal += $mp4Result.Renamed
|
||||
$mp4Collisions += $mp4Result.Collisions
|
||||
$mp4Errors += $mp4Result.Errors
|
||||
}
|
||||
$young = Get-YoungestTimestamp -FolderPath $s.FullName
|
||||
$filesCount = @(Get-ChildItem -LiteralPath $s.FullName -Recurse -File -ErrorAction SilentlyContinue | Where-Object { $_.FullName -notlike '*\_archive\*' }).Count
|
||||
$seqName = $s.Name
|
||||
$cand = [pscustomobject]@{
|
||||
Seq = $seqName
|
||||
SrcFull = $s.FullName
|
||||
Young = $young
|
||||
FilesCount = $filesCount
|
||||
}
|
||||
if ($mapDaily.ContainsKey($seqName)) {
|
||||
if ($mapDaily[$seqName].Young -lt $young) { $mapDaily[$seqName] = $cand }
|
||||
} else {
|
||||
$mapDaily[$seqName] = $cand
|
||||
foreach ($seq in $sequenceFolders) {
|
||||
$totalSequences++
|
||||
$renameResult = Sync-SequenceFilenames -SequenceFolderPath $seq.Path -SequenceName $seq.Name -LogFile $logFile
|
||||
if ($DebugMode -or $renameResult.Renamed -gt 0 -or $renameResult.Collisions -gt 0 -or $renameResult.Errors -gt 0) {
|
||||
Write-Host "[RENAME]|$($seq.Path)|$($seq.Name)|checked=$($renameResult.Checked)|renamed=$($renameResult.Renamed)|collisions=$($renameResult.Collisions)|errors=$($renameResult.Errors)" -ForegroundColor Cyan
|
||||
}
|
||||
$filesRenamedTotal += $renameResult.Renamed
|
||||
$renameCollisions += $renameResult.Collisions
|
||||
$renameErrors += $renameResult.Errors
|
||||
|
||||
if ($renameResult.FrameCount -gt 0 -and $null -ne $renameResult.MinFrame -and $null -ne $renameResult.MaxFrame) {
|
||||
$mp4Result = Rename-SequencePreviewMp4 -SequenceFolderPath $seq.Path -SequenceName $seq.Name -StartFrame $renameResult.MinFrame -EndFrame $renameResult.MaxFrame -LogFile $logFile
|
||||
if ($DebugMode -or $mp4Result.Renamed -gt 0 -or $mp4Result.Collisions -gt 0 -or $mp4Result.Errors -gt 0) {
|
||||
Write-Host "[MP4]|$($seq.Path)|$($seq.Name)|renamed=$($mp4Result.Renamed)|collisions=$($mp4Result.Collisions)|errors=$($mp4Result.Errors)" -ForegroundColor Cyan
|
||||
}
|
||||
$mp4RenamedTotal += $mp4Result.Renamed
|
||||
$mp4Collisions += $mp4Result.Collisions
|
||||
$mp4Errors += $mp4Result.Errors
|
||||
}
|
||||
}
|
||||
|
||||
$mapCurrent = @{}
|
||||
if (Test-Path -LiteralPath $currentDir) {
|
||||
foreach ($c in Get-ChildItem -LiteralPath $currentDir -Directory | Where-Object { $_.Name -ne '_archive' }) {
|
||||
$mapCurrent[$c.Name] = Get-YoungestTimestamp -FolderPath $c.FullName
|
||||
}
|
||||
}
|
||||
|
||||
$lines = @()
|
||||
$lines += "[MAPCOUNT]|count=$($mapDaily.Count)"
|
||||
$total = 0
|
||||
$toCopy = 0
|
||||
foreach ($k in $mapDaily.Keys) {
|
||||
$total++
|
||||
$d = $mapDaily[$k]
|
||||
$curYoung = if ($mapCurrent.ContainsKey($k)) { $mapCurrent[$k] } else { [datetime]::Parse('1900-01-01') }
|
||||
if ($d.Young -gt $curYoung) {
|
||||
$toCopy++
|
||||
$lines += "[COPY]|$($d.SrcFull)|$($d.Seq)|files=$($d.FilesCount)|young=$($d.Young.ToString('s'))"
|
||||
} else {
|
||||
$lines += "[SKIP]|$($d.SrcFull)|$($d.Seq)|reason=notNewer|srcYoung=$($d.Young.ToString('s'))|curYoung=$($curYoung.ToString('s'))"
|
||||
}
|
||||
}
|
||||
|
||||
$lines += "[META]|dailiesScanned=$dailiesScanned|sequencesTotal=$total|toCopy=$toCopy"
|
||||
|
||||
# Print plan to console instead of writing to file
|
||||
Write-Host "=== UPDATE PLAN ===" -ForegroundColor Cyan
|
||||
foreach ($line in $lines) {
|
||||
if ($line -like '[COPY]*') {
|
||||
Write-Host $line -ForegroundColor Green
|
||||
} elseif ($line -like '[SKIP]*') {
|
||||
Write-Host $line -ForegroundColor Yellow
|
||||
} else {
|
||||
Write-Host $line -ForegroundColor White
|
||||
}
|
||||
}
|
||||
Write-Host "==================" -ForegroundColor Cyan
|
||||
|
||||
$sequencesMirrored = 0
|
||||
$mirrorFailures = 0
|
||||
foreach ($line in $lines) {
|
||||
if ($line.StartsWith('[COPY]')) {
|
||||
$parts = $line -split '\|'
|
||||
$srcFull = $parts[1]
|
||||
$seqName = $parts[2]
|
||||
$dstAbs = Join-Path $currentDir $seqName
|
||||
|
||||
if (Test-Path -LiteralPath $dstAbs) {
|
||||
"[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] WIPE: removing existing destination '$dstAbs'" | Add-Content -LiteralPath $logFile
|
||||
try {
|
||||
Remove-Item -LiteralPath $dstAbs -Recurse -Force -ErrorAction Stop
|
||||
}
|
||||
catch {
|
||||
"[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] WIPE ERROR: $($_.Exception.Message)" | Add-Content -LiteralPath $logFile
|
||||
}
|
||||
}
|
||||
if (-not (Test-Path -LiteralPath $dstAbs)) { New-Item -ItemType Directory -Path $dstAbs -Force | Out-Null }
|
||||
"[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] Mirror: '$srcFull' -> '$dstAbs'" | Add-Content -LiteralPath $logFile
|
||||
$robocopyArgs = @(
|
||||
$srcFull,
|
||||
$dstAbs,
|
||||
'/MIR','/MT:8','/R:1','/W:1','/COPY:DAT','/DCOPY:DAT','/FFT','/NFL','/NDL','/NP','/NJH','/NJS','/XD','_archive'
|
||||
)
|
||||
$null = & robocopy @robocopyArgs 2>&1 | Add-Content -LiteralPath $logFile
|
||||
$rc = $LASTEXITCODE
|
||||
if ($rc -lt 8) {
|
||||
$sequencesMirrored++
|
||||
"[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] OK rc=$rc" | Add-Content -LiteralPath $logFile
|
||||
} else {
|
||||
$mirrorFailures++
|
||||
"[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] ERROR rc=$rc" | Add-Content -LiteralPath $logFile
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Print summary report to console
|
||||
Write-Host "=== SUMMARY REPORT ===" -ForegroundColor Magenta
|
||||
Write-Host "Dailies scanned: $dailiesScanned" -ForegroundColor White
|
||||
Write-Host "Sequences found: $total" -ForegroundColor White
|
||||
Write-Host "Planned copies: $toCopy" -ForegroundColor Green
|
||||
Write-Host "Completed OK: $sequencesMirrored" -ForegroundColor Green
|
||||
Write-Host "Completed FAIL: $mirrorFailures" -ForegroundColor Red
|
||||
Write-Host "Sequences scanned: $totalSequences" -ForegroundColor White
|
||||
Write-Host "Files renamed: $filesRenamedTotal (collisions: $renameCollisions, errors: $renameErrors)" -ForegroundColor White
|
||||
Write-Host "Preview MP4s renamed: $mp4RenamedTotal (collisions: $mp4Collisions, errors: $mp4Errors)" -ForegroundColor White
|
||||
|
||||
# Summarize skipped sequences
|
||||
$skippedLines = @()
|
||||
foreach ($line in $lines) {
|
||||
if ($line.StartsWith('[SKIP]')) {
|
||||
$skippedLines += $line
|
||||
}
|
||||
}
|
||||
if ($skippedLines.Count -gt 0) {
|
||||
Write-Host "`n=== SKIPPED SEQUENCES ===" -ForegroundColor Yellow
|
||||
$skippedByReason = @{}
|
||||
foreach ($skip in $skippedLines) {
|
||||
$parts = $skip -split '\|'
|
||||
$reason = $parts[3]
|
||||
$seqName = $parts[2]
|
||||
if (-not $skippedByReason.ContainsKey($reason)) {
|
||||
$skippedByReason[$reason] = @()
|
||||
}
|
||||
$skippedByReason[$reason] += $seqName
|
||||
}
|
||||
|
||||
foreach ($reason in $skippedByReason.Keys) {
|
||||
$seqs = $skippedByReason[$reason]
|
||||
Write-Host "$reason ($($seqs.Count) sequences):" -ForegroundColor Yellow
|
||||
foreach ($seq in $seqs | Sort-Object) {
|
||||
Write-Host " - $seq" -ForegroundColor White
|
||||
}
|
||||
}
|
||||
Write-Host "========================" -ForegroundColor Yellow
|
||||
}
|
||||
Write-Host "=====================" -ForegroundColor Magenta
|
||||
|
||||
"[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] === UpdateSequences (ps1) completed (d=$dailiesScanned seq=$total ok=$sequencesMirrored fail=$mirrorFailures) ===" | Add-Content -LiteralPath $logFile
|
||||
if ($logFile) {
|
||||
"[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] === UpdateSequences completed (seq=$totalSequences renamed=$filesRenamedTotal mp4=$mp4RenamedTotal) ===" | Add-Content -LiteralPath $logFile
|
||||
}
|
||||
|
||||
Write-Host "@$logFile"
|
||||
exit 0
|
||||
}
|
||||
catch {
|
||||
$root = (Get-Location).ProviderPath
|
||||
$currentDir = Join-Path $root '_CURRENT'
|
||||
if (-not (Test-Path -LiteralPath $currentDir)) {
|
||||
New-Item -ItemType Directory -Path $currentDir -Force | Out-Null
|
||||
if ($logFile) {
|
||||
"[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] ERROR: $($_.Exception.Message)" | Add-Content -LiteralPath $logFile
|
||||
}
|
||||
$planFile = Join-Path $currentDir '_UpdatePlan.txt'
|
||||
($_.Exception | Out-String) | Set-Content -LiteralPath ($planFile + '.error.txt') -Encoding ASCII
|
||||
Write-Host "ERROR: $_"
|
||||
Write-Host "ERROR: $_" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
@@ -1,65 +1,8 @@
|
||||
# Update subfolders with the latest UpdateSequences and UpdateAllSequences scripts
|
||||
# Deprecated helper retained for compatibility.
|
||||
|
||||
$sourceBat = "R:\Creative\artsy\maya\0 ProjectStructure\UpdateSequences.bat"
|
||||
$sourceAllBat = "R:\Creative\artsy\maya\0 ProjectStructure\UpdateAllSequences.bat"
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
if (-not (Test-Path $sourceBat)) { Write-Error "Source file not found: $sourceBat"; exit 1 }
|
||||
if (-not (Test-Path $sourceAllBat)) { Write-Error "Source file not found: $sourceAllBat"; exit 1 }
|
||||
|
||||
$root = "R:\Creative\artsy\maya"
|
||||
|
||||
$specs = @(
|
||||
@{ Name = "UpdateSequences.bat"; Source = $sourceBat },
|
||||
@{ Name = "UpdateAllSequences.bat"; Source = $sourceAllBat }
|
||||
)
|
||||
|
||||
$grandTotal = 0
|
||||
$grandUpdated = 0
|
||||
$grandFailed = 0
|
||||
|
||||
foreach ($spec in $specs) {
|
||||
Write-Host "=== Updating $($spec.Name) files ===" -ForegroundColor Cyan
|
||||
Write-Host "Source: $($spec.Source)" -ForegroundColor White
|
||||
Write-Host ""
|
||||
|
||||
$targets = Get-ChildItem -Path $root -Recurse -Filter $spec.Name | Where-Object { $_.FullName -ne $spec.Source }
|
||||
Write-Host "Found $($targets.Count) target files to update:" -ForegroundColor Yellow
|
||||
foreach ($t in $targets) { Write-Host " - $($t.FullName)" -ForegroundColor Gray }
|
||||
Write-Host ""
|
||||
|
||||
$updated = 0
|
||||
$failed = 0
|
||||
foreach ($t in $targets) {
|
||||
try {
|
||||
Copy-Item -Path $spec.Source -Destination $t.FullName -Force
|
||||
Write-Host "✓ Updated: $($t.FullName)" -ForegroundColor Green
|
||||
$updated++
|
||||
}
|
||||
catch {
|
||||
Write-Host "✗ Failed to update: $($t.FullName)" -ForegroundColor Red
|
||||
Write-Host " Error: $($_.Exception.Message)" -ForegroundColor Red
|
||||
$failed++
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "=== $($spec.Name) SUMMARY ===" -ForegroundColor Magenta
|
||||
Write-Host "Successfully updated: $updated" -ForegroundColor Green
|
||||
Write-Host "Failed updates: $failed" -ForegroundColor Red
|
||||
Write-Host "Total targets: $($targets.Count)" -ForegroundColor White
|
||||
Write-Host ""
|
||||
|
||||
$grandTotal += $targets.Count
|
||||
$grandUpdated += $updated
|
||||
$grandFailed += $failed
|
||||
}
|
||||
|
||||
Write-Host "=== OVERALL SUMMARY ===" -ForegroundColor Magenta
|
||||
Write-Host "Total targets across all files: $grandTotal" -ForegroundColor White
|
||||
Write-Host "Total successfully updated: $grandUpdated" -ForegroundColor Green
|
||||
Write-Host "Total failed: $grandFailed" -ForegroundColor Red
|
||||
if ($grandFailed -eq 0) {
|
||||
Write-Host "`n🎉 All files updated successfully!" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "`n⚠️ Some updates failed. See errors above." -ForegroundColor Yellow
|
||||
}
|
||||
Write-Warning 'UpgradeSeqBatches.ps1 has been deprecated.'
|
||||
Write-Warning 'Please run UpdateProjectBatches.ps1 and choose the project that needs refreshed batch files.'
|
||||
exit 1
|
||||
|
||||
@@ -44,7 +44,19 @@ if not exist "%rendersDir%" (
|
||||
)
|
||||
)
|
||||
|
||||
for %%F in (NewDaily.bat UpdateSequences.bat) do (
|
||||
for %%F in (UpdateSequences.bat ZipSeqArchv.bat UnzipSeqArchv.bat) do (
|
||||
if exist "%scriptDir%%%F" (
|
||||
if "%DRY%"=="1" (
|
||||
echo [DRY] copy "%scriptDir%%%F" "%rendersDir%\%%F"
|
||||
) else (
|
||||
copy /Y "%scriptDir%%%F" "%rendersDir%\%%F" >nul
|
||||
)
|
||||
) else (
|
||||
echo [WARN] Missing template: "%scriptDir%%%F"
|
||||
)
|
||||
)
|
||||
|
||||
for %%F in (ConfigLoader.ps1 config.json) do (
|
||||
if exist "%scriptDir%%%F" (
|
||||
if "%DRY%"=="1" (
|
||||
echo [DRY] copy "%scriptDir%%%F" "%rendersDir%\%%F"
|
||||
@@ -122,12 +134,14 @@ if exist "%animDir%" (
|
||||
rem Ensure submodule dir exists and place helper scripts there
|
||||
if "%DRY%"=="1" (
|
||||
if not exist "!_subdir!" echo [DRY] mkdir "!_subdir!"
|
||||
if exist "%scriptDir%NewDaily.bat" echo [DRY] copy "%scriptDir%NewDaily.bat" "!_subdir!\NewDaily.bat"
|
||||
if exist "%scriptDir%UpdateSequences.bat" echo [DRY] copy "%scriptDir%UpdateSequences.bat" "!_subdir!\UpdateSequences.bat"
|
||||
if exist "%scriptDir%ZipSeqArchv.bat" echo [DRY] copy "%scriptDir%ZipSeqArchv.bat" "!_subdir!\ZipSeqArchv.bat"
|
||||
if exist "%scriptDir%UnzipSeqArchv.bat" echo [DRY] copy "%scriptDir%UnzipSeqArchv.bat" "!_subdir!\UnzipSeqArchv.bat"
|
||||
) else (
|
||||
if not exist "!_subdir!" mkdir "!_subdir!" >nul 2>&1
|
||||
if exist "%scriptDir%NewDaily.bat" copy /Y "%scriptDir%NewDaily.bat" "!_subdir!\NewDaily.bat" >nul
|
||||
if exist "%scriptDir%UpdateSequences.bat" copy /Y "%scriptDir%UpdateSequences.bat" "!_subdir!\UpdateSequences.bat" >nul
|
||||
if exist "%scriptDir%ZipSeqArchv.bat" copy /Y "%scriptDir%ZipSeqArchv.bat" "!_subdir!\ZipSeqArchv.bat" >nul
|
||||
if exist "%scriptDir%UnzipSeqArchv.bat" copy /Y "%scriptDir%UnzipSeqArchv.bat" "!_subdir!\UnzipSeqArchv.bat" >nul
|
||||
)
|
||||
for /d %%D in ("%%S\daily_*") do (
|
||||
set "dailyName=%%~nD"
|
||||
|
||||
42
ZipSeqArchv.bat
Normal file
42
ZipSeqArchv.bat
Normal file
@@ -0,0 +1,42 @@
|
||||
@echo off
|
||||
setlocal EnableExtensions EnableDelayedExpansion
|
||||
|
||||
set "REN_DIR=%~dp0"
|
||||
for %%I in ("%REN_DIR%..") do set "PROJ_ROOT=%%~fI"
|
||||
|
||||
set "CONFIG_DIR=%PROJ_ROOT%\.config"
|
||||
set "CONFIG_PATH=%CONFIG_DIR%\config.json"
|
||||
set "GET_STRUCT_DIR=%CONFIG_DIR%\GetStructDir.ps1"
|
||||
|
||||
if not exist "%CONFIG_PATH%" (
|
||||
echo [ERROR] config.json not found at %CONFIG_PATH%
|
||||
echo Please run ConfigLoader.ps1 to deploy helper files.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if not exist "%GET_STRUCT_DIR%" (
|
||||
echo [ERROR] GetStructDir.ps1 not found at %GET_STRUCT_DIR%
|
||||
echo Please run ConfigLoader.ps1 to deploy helper files.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
for /f "usebackq delims=" %%I in (`powershell -NoProfile -ExecutionPolicy Bypass -Command ^
|
||||
"Set-StrictMode -Version Latest; $structDir = & '%GET_STRUCT_DIR%' -ProjectRoot '%PROJ_ROOT%'; if (-not $structDir) { throw \"Failed to get structDir from GetStructDir.ps1\" }; $pyPath = Join-Path $structDir 'zip_sequences.py'; if (-not (Test-Path -LiteralPath $pyPath)) { throw \"zip_sequences.py not found at $pyPath\" }; Write-Output $pyPath"`) do set "PY_SCRIPT=%%I"
|
||||
|
||||
if not defined PY_SCRIPT (
|
||||
echo [ERROR] Unable to resolve zip_sequences.py path from config.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
pushd "%PROJ_ROOT%" >nul 2>&1
|
||||
|
||||
python "%PY_SCRIPT%" --verbose %*
|
||||
set "ERR=!ERRORLEVEL!"
|
||||
|
||||
if not "!ERR!"=="0" (
|
||||
echo Failed to update render sequence archives (exit code !ERR!).
|
||||
)
|
||||
|
||||
popd >nul 2>&1
|
||||
exit /b !ERR!
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
*.zip filter=lfs diff=lfs merge=lfs -text
|
||||
*.fbx filter=lfs diff=lfs merge=lfs -text
|
||||
*.fbxkey filter=lfs diff=lfs merge=lfs -text
|
||||
*.blend2 filter=lfs diff=lfs merge=lfs -text
|
||||
*.mb filter=lfs diff=lfs merge=lfs -text
|
||||
*.ma filter=lfs diff=lfs merge=lfs -text
|
||||
*.tiff filter=lfs diff=lfs merge=lfs -text
|
||||
@@ -40,8 +41,13 @@
|
||||
*.dae filter=lfs diff=lfs merge=lfs -text
|
||||
*.mp3 filter=lfs diff=lfs merge=lfs -text
|
||||
*.mp3 filter=lfs diff=lfs merge=lfs -text
|
||||
*.wav filter=lfs diff=lfs merge=lfs -text
|
||||
*.vdb filter=lfs diff=lfs merge=lfs -text
|
||||
*.aep filter=lfs diff=lfs merge=lfs -text
|
||||
*.aegraphic filter=lfs diff=lfs merge=lfs -text
|
||||
*.prproj filter=lfs diff=lfs merge=lfs -text
|
||||
*.prin filter=lfs diff=lfs merge=lfs -text
|
||||
*.prin filter=lfs diff=lfs merge=lfs -text
|
||||
*.prel filter=lfs diff=lfs merge=lfs -text
|
||||
*.wav filter=lfs diff=lfs merge=lfs -text
|
||||
*.docx filter=lfs diff=lfs merge=lfs -text
|
||||
*.MOV filter=lfs diff=lfs merge=lfs -text
|
||||
*.gif filter=lfs diff=lfs merge=lfs -text
|
||||
@@ -83,12 +83,6 @@ desktop.ini
|
||||
# *.aac
|
||||
# *.ogg
|
||||
|
||||
# Archive files
|
||||
*.zip
|
||||
*.rar
|
||||
*.7z
|
||||
*.tar.gz
|
||||
|
||||
# Log files
|
||||
*.log
|
||||
|
||||
@@ -119,13 +113,18 @@ temp/
|
||||
tmp/
|
||||
cache/
|
||||
|
||||
# image sequences
|
||||
seq/
|
||||
Renders/
|
||||
Renders/**
|
||||
!Renders/_zipped/
|
||||
!Renders/_zipped/**
|
||||
!Renders/**/
|
||||
!Renders/**/*.bat
|
||||
!Renders/**/*.log
|
||||
|
||||
# Premiere
|
||||
*.prlock
|
||||
Adobe Premiere Pro Video Previews/
|
||||
Adobe Premiere Pro Audio Previews/
|
||||
Adobe Premiere Pro (Beta) Video Previews/
|
||||
Adobe Premiere Pro (Beta) Audio Previews/
|
||||
Adobe Premiere Pro (Beta) Audio Previews/
|
||||
|
||||
Pr/RnR/
|
||||
|
||||
7
config.json
Normal file
7
config.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"dailyFormat": "daily_YYMMDD",
|
||||
"structDir": "A:\\1 Amazon_Active_Projects\\3 ProjectStructure",
|
||||
"zipper": "7z",
|
||||
"compression": 9
|
||||
}
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
new script named UpgradeToGitProj. this is to be ran in a project structure that was pre-git.
|
||||
|
||||
1. appends gitignore and gitattributes, initializes git, and installs git lfs
|
||||
- If already initialized, will this just error but continue?
|
||||
2. Manages Renders folder:
|
||||
a. creates folder, adding NewDaily and UpdateSeq scripts
|
||||
b. scans the structure within blends\animations and creates a folder for each submodule, e.g. Horizontal, Shorts, Vertical, etc. If there are no submodules, it just grabs the daily_* folders.
|
||||
c. For each daily_* folder, it copies the contents of each daily_*\seq\ folder into the Renders\submodule\ folder. If it's taking daily_* folders from the root, it just copies the contents of the daily_*\seq\ folder into the Renders\ folder.
|
||||
575
zip_sequences.py
Normal file
575
zip_sequences.py
Normal file
@@ -0,0 +1,575 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Maintain zipped render sequences for Git hooks.
|
||||
|
||||
Default mode scans `Renders/`, produces ZIP archives under `Renders/_zipped/`,
|
||||
and stages any updated archives so commits only track compact files. Switch to
|
||||
`--mode expand` to inflate the tracked archives back into the ignored working
|
||||
directories after checkouts or pulls.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||
from pathlib import Path
|
||||
from typing import Iterator, Sequence
|
||||
|
||||
|
||||
RENDER_ROOT = Path("Renders")
|
||||
ARCHIVE_ROOT = RENDER_ROOT / "_zipped"
|
||||
SEQUENCE_EXTENSIONS = {
|
||||
".png",
|
||||
".jpg",
|
||||
".jpeg",
|
||||
".tif",
|
||||
".tiff",
|
||||
".exr",
|
||||
}
|
||||
STATE_SUFFIX = ".meta.json"
|
||||
DEFAULT_CONFIG = {
|
||||
"zipper": "7z",
|
||||
"compression": 9,
|
||||
"dailyFormat": "daily_YYMMDD",
|
||||
}
|
||||
|
||||
|
||||
def load_config() -> dict:
|
||||
# First try to load from project's .config folder (current working directory)
|
||||
# Then fall back to ProjectStructure repo config (next to zip_sequences.py)
|
||||
cwd = Path.cwd()
|
||||
project_config = cwd / ".config" / "config.json"
|
||||
repo_config = Path(__file__).resolve().with_name("config.json")
|
||||
|
||||
config_paths = [
|
||||
("project", project_config),
|
||||
("repo", repo_config),
|
||||
]
|
||||
|
||||
for source, config_path in config_paths:
|
||||
try:
|
||||
if config_path.exists():
|
||||
text = config_path.read_text(encoding="utf-8")
|
||||
try:
|
||||
data = json.loads(text)
|
||||
if isinstance(data, dict):
|
||||
merged = DEFAULT_CONFIG.copy()
|
||||
merged.update(data)
|
||||
return merged
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
except OSError:
|
||||
continue
|
||||
|
||||
# If no config found, return defaults
|
||||
return DEFAULT_CONFIG.copy()
|
||||
|
||||
|
||||
CONFIG = load_config()
|
||||
zipper_val = CONFIG.get("zipper", "7z")
|
||||
# Handle both old boolean format and new string format
|
||||
if isinstance(zipper_val, bool):
|
||||
ZIPPER_TYPE = "7z" if zipper_val else "zip"
|
||||
else:
|
||||
ZIPPER_TYPE = str(zipper_val).lower()
|
||||
|
||||
COMPRESSION_LEVEL = CONFIG.get("compression", 9)
|
||||
if isinstance(COMPRESSION_LEVEL, str):
|
||||
try:
|
||||
COMPRESSION_LEVEL = int(COMPRESSION_LEVEL)
|
||||
except ValueError:
|
||||
COMPRESSION_LEVEL = 9
|
||||
if not isinstance(COMPRESSION_LEVEL, int):
|
||||
COMPRESSION_LEVEL = 9
|
||||
COMPRESSION_LEVEL = max(0, min(9, COMPRESSION_LEVEL))
|
||||
|
||||
SEVEN_Z_EXE: str | None = None
|
||||
if ZIPPER_TYPE == "7z":
|
||||
SEVEN_Z_EXE = shutil.which("7z") or shutil.which("7za")
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(description="Sync render sequences with zipped archives.")
|
||||
parser.add_argument(
|
||||
"--mode",
|
||||
choices=("zip", "expand"),
|
||||
default="zip",
|
||||
help="zip sequences for commit (default) or expand tracked archives",
|
||||
)
|
||||
parser.add_argument("--jobs", type=int, help="max parallel workers")
|
||||
parser.add_argument("--verbose", action="store_true", help="print extra progress details")
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def max_workers(requested: int | None) -> int:
|
||||
cpu = os.cpu_count() or 1
|
||||
limit = max(1, min(8, cpu))
|
||||
if requested and requested > 0:
|
||||
return min(requested, max(1, cpu))
|
||||
return limit
|
||||
|
||||
|
||||
def log(mode: str, message: str, *, verbose_only: bool = False, verbose: bool = False) -> None:
|
||||
if verbose_only and not verbose:
|
||||
return
|
||||
print(f"[{mode}] {message}")
|
||||
|
||||
|
||||
def is_archive_path(path: Path) -> bool:
|
||||
return any(part == "_archive" for part in path.parts)
|
||||
|
||||
|
||||
def find_sequence_dirs(root: Path) -> Iterator[Path]:
|
||||
for dirpath, dirnames, filenames in os.walk(root):
|
||||
path = Path(dirpath)
|
||||
dirnames[:] = [d for d in dirnames if d != "_archive"]
|
||||
if is_archive_path(path):
|
||||
continue
|
||||
has_frames = any(Path(dirpath, f).suffix.lower() in SEQUENCE_EXTENSIONS for f in filenames)
|
||||
if has_frames:
|
||||
yield path
|
||||
|
||||
|
||||
def iter_sequence_files(seq_dir: Path) -> Iterator[Path]:
|
||||
for dirpath, dirnames, filenames in os.walk(seq_dir):
|
||||
path = Path(dirpath)
|
||||
dirnames[:] = [d for d in dirnames if d != "_archive"]
|
||||
if is_archive_path(path):
|
||||
continue
|
||||
for filename in filenames:
|
||||
yield path / filename
|
||||
|
||||
|
||||
def compute_state(seq_dir: Path) -> dict:
|
||||
entries = []
|
||||
files = sorted(
|
||||
iter_sequence_files(seq_dir),
|
||||
key=lambda p: p.relative_to(seq_dir).as_posix(),
|
||||
)
|
||||
for file_path in files:
|
||||
stat = file_path.stat()
|
||||
entries.append(
|
||||
{
|
||||
"path": file_path.relative_to(seq_dir).as_posix(),
|
||||
"size": stat.st_size,
|
||||
"mtime_ns": stat.st_mtime_ns,
|
||||
}
|
||||
)
|
||||
return {"files": entries}
|
||||
|
||||
|
||||
def current_state(seq_dir: Path) -> dict:
|
||||
if not seq_dir.exists() or not seq_dir.is_dir():
|
||||
return {"files": []}
|
||||
return compute_state(seq_dir)
|
||||
|
||||
|
||||
def load_state(state_path: Path) -> dict | None:
|
||||
if not state_path.exists():
|
||||
return None
|
||||
try:
|
||||
return json.loads(state_path.read_text())
|
||||
except json.JSONDecodeError:
|
||||
return None
|
||||
|
||||
|
||||
def state_changed(seq_state: dict, stored_state: dict | None) -> bool:
|
||||
if stored_state is None:
|
||||
return True
|
||||
return seq_state != stored_state
|
||||
|
||||
|
||||
def archive_path_for(seq_dir: Path) -> Path:
|
||||
rel = seq_dir.relative_to(RENDER_ROOT)
|
||||
suffix = ".7z" if ZIPPER_TYPE == "7z" else ".zip"
|
||||
return (ARCHIVE_ROOT / rel).with_suffix(suffix)
|
||||
|
||||
|
||||
def sequence_dir_for(zip_path: Path) -> Path:
|
||||
rel = zip_path.relative_to(ARCHIVE_ROOT)
|
||||
return (RENDER_ROOT / rel).with_suffix("")
|
||||
|
||||
|
||||
def state_path_for(zip_path: Path) -> Path:
|
||||
return zip_path.with_suffix(zip_path.suffix + STATE_SUFFIX)
|
||||
|
||||
|
||||
def zip_sequence(seq_dir: Path, zip_path: Path) -> None:
|
||||
if ZIPPER_TYPE == "7z":
|
||||
if SEVEN_Z_EXE is None:
|
||||
raise RuntimeError(
|
||||
"7z compression requested but 7z executable not found in PATH. "
|
||||
"Please install 7z (e.g., via Chocolatey: choco install 7zip) "
|
||||
"or set zipper to 'zip' in config.json"
|
||||
)
|
||||
zip_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# If creating a .7z file, remove any existing .zip file for the same sequence
|
||||
if zip_path.suffix == ".7z":
|
||||
old_zip_path = zip_path.with_suffix(".zip")
|
||||
if old_zip_path.exists():
|
||||
old_zip_path.unlink(missing_ok=True)
|
||||
old_state_path = state_path_for(old_zip_path)
|
||||
if old_state_path.exists():
|
||||
old_state_path.unlink(missing_ok=True)
|
||||
|
||||
# Build list of files to archive with relative paths
|
||||
file_list = []
|
||||
for file_path in iter_sequence_files(seq_dir):
|
||||
rel_path = file_path.relative_to(seq_dir).as_posix()
|
||||
file_list.append(rel_path)
|
||||
|
||||
if not file_list:
|
||||
raise RuntimeError(f"No files found to archive in {seq_dir}")
|
||||
|
||||
# Create zip in temporary location first to avoid issues with corrupted existing files
|
||||
temp_zip = None
|
||||
list_file_path = None
|
||||
try:
|
||||
# Create temporary archive file path (but don't create the file - let 7z create it)
|
||||
temp_zip_path = tempfile.mktemp(suffix=".7z", dir=zip_path.parent)
|
||||
temp_zip = Path(temp_zip_path)
|
||||
|
||||
# Create list file with absolute path
|
||||
fd, temp_path = tempfile.mkstemp(suffix=".lst", text=True)
|
||||
list_file_path = Path(temp_path)
|
||||
with os.fdopen(fd, "w", encoding="utf-8") as list_file:
|
||||
for rel_path in file_list:
|
||||
list_file.write(rel_path + "\n")
|
||||
list_file.flush()
|
||||
os.fsync(list_file.fileno()) # Ensure data is written to disk
|
||||
# File is closed here by context manager, small delay to ensure OS releases handle
|
||||
time.sleep(0.1)
|
||||
|
||||
# Use absolute paths for both list file and temp zip
|
||||
list_file_abs = list_file_path.resolve()
|
||||
temp_zip_abs = temp_zip.resolve()
|
||||
# Create archive in temp location first (7z will create it fresh)
|
||||
cmd = [
|
||||
SEVEN_Z_EXE,
|
||||
"a",
|
||||
"-y",
|
||||
"-bb0", # Suppress progress output
|
||||
f"-mx={COMPRESSION_LEVEL}",
|
||||
"-t7z", # Use 7z format, not zip
|
||||
str(temp_zip_abs),
|
||||
f"@{list_file_abs}",
|
||||
]
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
cwd=seq_dir,
|
||||
check=False,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
error_msg = result.stderr.strip() if result.stderr else "Unknown error"
|
||||
if result.stdout:
|
||||
error_msg += f"\nstdout: {result.stdout.strip()}"
|
||||
raise RuntimeError(f"7z compression failed: {error_msg}")
|
||||
|
||||
# Move temp zip to final location, replacing any existing file
|
||||
if zip_path.exists():
|
||||
zip_path.unlink()
|
||||
temp_zip.replace(zip_path)
|
||||
temp_zip = None # Mark as moved so we don't delete it
|
||||
finally:
|
||||
# Clean up temp zip if it wasn't moved
|
||||
if temp_zip and temp_zip.exists():
|
||||
try:
|
||||
temp_zip.unlink(missing_ok=True)
|
||||
except OSError:
|
||||
pass
|
||||
# Clean up list file, with retry in case 7z still has it open
|
||||
if list_file_path and list_file_path.exists():
|
||||
for attempt in range(3):
|
||||
try:
|
||||
list_file_path.unlink(missing_ok=True)
|
||||
break
|
||||
except PermissionError:
|
||||
if attempt < 2:
|
||||
time.sleep(0.1) # Wait 100ms before retry
|
||||
else:
|
||||
# Last attempt failed, just log and continue
|
||||
# The temp file will be cleaned up by the OS eventually
|
||||
pass
|
||||
return
|
||||
|
||||
# Use zipfile (only if ZIPPER_TYPE == "zip")
|
||||
if ZIPPER_TYPE == "zip":
|
||||
from zipfile import ZIP_DEFLATED, ZIP_STORED, ZipFile
|
||||
|
||||
zip_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
if COMPRESSION_LEVEL <= 0:
|
||||
compression = ZIP_STORED
|
||||
zip_kwargs = {}
|
||||
else:
|
||||
compression = ZIP_DEFLATED
|
||||
zip_kwargs = {"compresslevel": COMPRESSION_LEVEL}
|
||||
|
||||
with ZipFile(zip_path, "w", compression=compression, **zip_kwargs) as archive:
|
||||
for file_path in iter_sequence_files(seq_dir):
|
||||
archive.write(file_path, arcname=file_path.relative_to(seq_dir).as_posix())
|
||||
return
|
||||
|
||||
# Unknown ZIPPER_TYPE - fail with clear error
|
||||
raise RuntimeError(
|
||||
f"Unsupported ZIPPER_TYPE: {ZIPPER_TYPE!r}. "
|
||||
f"Expected '7z' or 'zip'. "
|
||||
f"Config zipper value: {CONFIG.get('zipper', 'not set')!r}"
|
||||
)
|
||||
|
||||
|
||||
def expand_sequence(zip_path: Path, seq_state: dict) -> None:
|
||||
target_dir = sequence_dir_for(zip_path)
|
||||
if target_dir.exists():
|
||||
shutil.rmtree(target_dir)
|
||||
target_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
if ZIPPER_TYPE == "7z":
|
||||
if SEVEN_Z_EXE is None:
|
||||
raise RuntimeError(
|
||||
"7z extraction requested but 7z executable not found in PATH. "
|
||||
"Please install 7z or set zipper to 'zip' in config.json"
|
||||
)
|
||||
cmd = [
|
||||
SEVEN_Z_EXE,
|
||||
"x",
|
||||
"-y",
|
||||
str(zip_path),
|
||||
f"-o{target_dir}",
|
||||
]
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
check=False,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
error_msg = result.stderr.strip() if result.stderr else "Unknown error"
|
||||
if result.stdout:
|
||||
error_msg += f"\nstdout: {result.stdout.strip()}"
|
||||
raise RuntimeError(f"7z extraction failed: {error_msg}")
|
||||
elif ZIPPER_TYPE == "zip":
|
||||
from zipfile import ZipFile
|
||||
|
||||
with ZipFile(zip_path, "r") as archive:
|
||||
archive.extractall(target_dir)
|
||||
else:
|
||||
raise RuntimeError(
|
||||
f"Unsupported ZIPPER_TYPE: {ZIPPER_TYPE!r}. "
|
||||
f"Expected '7z' or 'zip'. "
|
||||
f"Config zipper value: {CONFIG.get('zipper', 'not set')!r}"
|
||||
)
|
||||
|
||||
for entry in seq_state.get("files", []):
|
||||
file_path = target_dir / entry["path"]
|
||||
if file_path.exists():
|
||||
os.utime(file_path, ns=(entry["mtime_ns"], entry["mtime_ns"]))
|
||||
|
||||
|
||||
def process_zip(seq_dir: Path, zip_path: Path, state_path: Path, seq_state: dict, *, verbose: bool) -> Sequence[Path]:
|
||||
log("zip", f"{seq_dir} -> {zip_path}", verbose_only=True, verbose=verbose)
|
||||
zip_sequence(seq_dir, zip_path)
|
||||
state_path.write_text(json.dumps(seq_state, indent=2))
|
||||
return (zip_path, state_path)
|
||||
|
||||
|
||||
def process_expand(zip_path: Path, state: dict, *, verbose: bool) -> None:
|
||||
log("expand", f"{zip_path} -> {sequence_dir_for(zip_path)}", verbose_only=True, verbose=verbose)
|
||||
expand_sequence(zip_path, state)
|
||||
|
||||
|
||||
def run_zip(worker_count: int, *, verbose: bool) -> int:
|
||||
work_items: list[tuple[Path, Path, Path, dict]] = []
|
||||
|
||||
if RENDER_ROOT.exists():
|
||||
for seq_dir in find_sequence_dirs(RENDER_ROOT):
|
||||
seq_state = compute_state(seq_dir)
|
||||
if not seq_state["files"]:
|
||||
continue
|
||||
|
||||
# Get the target archive path (will be .7z if ZIPPER_TYPE is "7z")
|
||||
zip_path = archive_path_for(seq_dir)
|
||||
state_path = state_path_for(zip_path)
|
||||
|
||||
# Check if we need to upgrade from .zip to .7z
|
||||
old_zip_path = None
|
||||
if ZIPPER_TYPE == "7z":
|
||||
# Check if an old .zip file exists
|
||||
old_zip_path = zip_path.with_suffix(".zip")
|
||||
if old_zip_path.exists():
|
||||
# Check if the old .zip's metadata matches current state
|
||||
old_state_path = state_path_for(old_zip_path)
|
||||
old_stored_state = load_state(old_state_path)
|
||||
if not state_changed(seq_state, old_stored_state):
|
||||
# Old .zip is up to date, skip conversion
|
||||
continue
|
||||
# Old .zip is out of date, will be replaced with .7z
|
||||
|
||||
# Check if the target archive (e.g., .7z) already exists and is up to date
|
||||
stored_state = load_state(state_path)
|
||||
if not state_changed(seq_state, stored_state):
|
||||
# Target archive is up to date, but we might still need to clean up old .zip
|
||||
if old_zip_path and old_zip_path.exists():
|
||||
# Old .zip exists but we have a newer .7z, remove the old one
|
||||
old_zip_path.unlink(missing_ok=True)
|
||||
old_state_path = state_path_for(old_zip_path)
|
||||
if old_state_path.exists():
|
||||
old_state_path.unlink(missing_ok=True)
|
||||
continue
|
||||
|
||||
work_items.append((seq_dir, zip_path, state_path, seq_state))
|
||||
|
||||
if not work_items:
|
||||
if not RENDER_ROOT.exists():
|
||||
log("zip", "Render root 'Renders' not found; nothing to zip.")
|
||||
else:
|
||||
log("zip", "Archives already up to date; no sequences needed zipping.")
|
||||
return 0
|
||||
|
||||
updated_paths: list[Path] = []
|
||||
|
||||
total = len(work_items)
|
||||
completed = 0
|
||||
|
||||
with ThreadPoolExecutor(max_workers=worker_count) as executor:
|
||||
future_map = {
|
||||
executor.submit(process_zip, seq_dir, zip_path, state_path, seq_state, verbose=verbose): seq_dir
|
||||
for seq_dir, zip_path, state_path, seq_state in work_items
|
||||
}
|
||||
|
||||
for future in as_completed(future_map):
|
||||
updated_paths.extend(future.result())
|
||||
completed += 1
|
||||
seq_dir = future_map[future]
|
||||
rel = seq_dir.relative_to(RENDER_ROOT)
|
||||
log("zip", f"{completed}/{total} {rel}")
|
||||
|
||||
updated_count = len(updated_paths) // 2
|
||||
log("zip", f"Updated {updated_count} sequence archive(s).", verbose=verbose)
|
||||
if updated_paths:
|
||||
log(
|
||||
"zip",
|
||||
"Archives updated. Stage manually with `git add Renders/_zipped`, if desired.",
|
||||
verbose_only=True,
|
||||
verbose=verbose,
|
||||
)
|
||||
|
||||
removed = cleanup_orphan_archives(verbose=verbose)
|
||||
if removed:
|
||||
log("zip", f"Removed {removed} orphan archive(s).", verbose=verbose)
|
||||
|
||||
return updated_count
|
||||
|
||||
|
||||
def run_expand(worker_count: int, *, verbose: bool) -> int:
|
||||
if not ARCHIVE_ROOT.exists():
|
||||
log("expand", "No archives to expand (missing 'Renders/_zipped').")
|
||||
return 0
|
||||
|
||||
work_items: list[tuple[Path, dict]] = []
|
||||
|
||||
# Look for both .zip and .7z archives
|
||||
archive_patterns = ["*.zip", "*.7z"]
|
||||
for pattern in archive_patterns:
|
||||
for zip_path in ARCHIVE_ROOT.rglob(pattern):
|
||||
state_path = state_path_for(zip_path)
|
||||
seq_state = load_state(state_path)
|
||||
if seq_state is None:
|
||||
log("expand", f"Skipping {zip_path} (missing metadata)")
|
||||
continue
|
||||
|
||||
target_dir = sequence_dir_for(zip_path)
|
||||
if current_state(target_dir) == seq_state:
|
||||
continue
|
||||
|
||||
work_items.append((zip_path, seq_state))
|
||||
|
||||
if not work_items:
|
||||
log("expand", "Working folders already match archives; nothing to expand.")
|
||||
return 0
|
||||
|
||||
total = len(work_items)
|
||||
completed = 0
|
||||
|
||||
with ThreadPoolExecutor(max_workers=worker_count) as executor:
|
||||
future_map = {
|
||||
executor.submit(process_expand, zip_path, seq_state, verbose=verbose): zip_path
|
||||
for zip_path, seq_state in work_items
|
||||
}
|
||||
|
||||
for future in as_completed(future_map):
|
||||
future.result()
|
||||
completed += 1
|
||||
zip_path = future_map[future]
|
||||
rel = zip_path.relative_to(ARCHIVE_ROOT)
|
||||
log("expand", f"{completed}/{total} {rel}")
|
||||
|
||||
log("expand", f"Refreshed {len(work_items)} sequence folder(s).", verbose=verbose)
|
||||
return len(work_items)
|
||||
|
||||
|
||||
def cleanup_orphan_archives(*, verbose: bool) -> int:
|
||||
if not ARCHIVE_ROOT.exists():
|
||||
return 0
|
||||
|
||||
removed: list[Path] = []
|
||||
|
||||
# Look for both .zip and .7z archives
|
||||
archive_patterns = ["*.zip", "*.7z"]
|
||||
for pattern in archive_patterns:
|
||||
for zip_path in ARCHIVE_ROOT.rglob(pattern):
|
||||
seq_dir = sequence_dir_for(zip_path)
|
||||
if seq_dir.exists():
|
||||
continue
|
||||
|
||||
rel = zip_path.relative_to(ARCHIVE_ROOT)
|
||||
log("zip", f"Removing orphan archive {rel}", verbose_only=True, verbose=verbose)
|
||||
|
||||
zip_path.unlink(missing_ok=True)
|
||||
state_path = state_path_for(zip_path)
|
||||
if state_path.exists():
|
||||
state_path.unlink()
|
||||
removed.append(zip_path)
|
||||
|
||||
if not removed:
|
||||
return 0
|
||||
|
||||
for parent in sorted({p.parent for p in removed}, key=lambda p: len(p.parts), reverse=True):
|
||||
if not parent.exists():
|
||||
continue
|
||||
while parent != ARCHIVE_ROOT and not any(parent.iterdir()):
|
||||
parent.rmdir()
|
||||
parent = parent.parent
|
||||
|
||||
return len(removed)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
args = parse_args()
|
||||
workers = max_workers(args.jobs)
|
||||
|
||||
if args.mode == "expand":
|
||||
run_expand(workers, verbose=args.verbose)
|
||||
return 0
|
||||
|
||||
updated = run_zip(workers, verbose=args.verbose)
|
||||
return 0 if updated >= 0 else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
raise SystemExit(main())
|
||||
except Exception as exc: # broad to surface unexpected errors cleanly
|
||||
print(f"Sequence sync failed: {exc}", file=sys.stderr)
|
||||
raise
|
||||
|
||||
Reference in New Issue
Block a user