# A whole season

> Upload a folder tree, follow hundreds of files, collect every output.

One project per season (or per language set), every episode in its input folder. Subfolders are
kept all the way to the outputs.

```
season-1/                         output/
├── ep01.mp4                      ├── season 1/ep01.es.mp4
├── ep02.mp4          ──────▶     ├── season 1/ep02.es.mp4
└── extras/                       └── season 1/extras/
    └── making-of.mp4                 └── making-of.es.mp4
```

## 1. Create the project, stopped

`autoStart: false` lets you upload everything first. The service then sees the whole season in its
first scan and works on it at full parallelism.

```bash
PROJECT=$(curl -s -X POST $API/projects -H "$AUTH" -H 'Content-Type: application/yaml' --data-binary @- <<'YAML' | jq -r .data.id
name: Fishing Channel — Season 1 — es
config:
  sourceMedia: { '@type': S3FolderSource }
  transcription: { '@type': Speechmatics, language: en }
  translations:
    - language: es
      engine: { '@type': Gemini }
      ttsOptions:
        provider: { '@type': Deepdub, voice: auto }
  outputs:
    - '@type': S3FolderOutput
      items:
        - '@type': VideoFile
          ext: mp4
          nameTemplate: '${name}.es.${ext}'
          useOriginalTracks: true
          dubTracks:
            - { language: es, title: Spanish }
options: { autoStart: false }
YAML
)
```

## 2. Upload the season

**AWS CLI**

With the [upload role](/vod-express/uploads/aws-role) set up as the `syncwords-vod` profile:

```bash
INPUT=$(curl -s -H "$AUTH" $API/projects/$PROJECT | jq -r .data.storage.inputPath)
aws s3 sync ./season-1 "${INPUT}season 1/" --profile syncwords-vod \
  --exclude '*' --include '*.mp4' --include '*.mov'
```

**API**

With [`upload.mjs`](/vod-express/uploads/api-uploads#scripts), keeping the folder structure in the names:

```bash
cd season-1
find . -type f \( -name '*.mp4' -o -name '*.mov' \) | sed 's|^\./||' | while read -r f; do
  node ../upload.mjs "$PROJECT" "$f" "season 1/$f"
done
```

## 3. Start

```bash
curl -s -X POST -H "$AUTH" $API/projects/$PROJECT/start | jq .data.status   # "ACTIVE"
```

## 4. Follow progress

```bash
curl -s -H "$AUTH" $API/projects/$PROJECT/files \
  | jq '.data | group_by(.status) | map({(.[0].status): length}) | add'
```

```json
{ "done": 14, "pending": 7, "processing": 1 }
```

Wait for the season, and list what failed:

```bash
until curl -s -H "$AUTH" $API/projects/$PROJECT/files \
  | jq -e 'all(.data[]; .status == "done" or .status == "failed")' > /dev/null; do sleep 60; done

curl -s -H "$AUTH" $API/projects/$PROJECT/files | jq -r '.data[] | select(.status == "failed") | .name'
```

Retry each failed file (its name URL-encoded), then start the project again if it stopped:

```bash
curl -s -H "$AUTH" $API/projects/$PROJECT/files | jq -r '.data[] | select(.status == "failed") | .name | @uri' \
  | while read -r name; do curl -s -X POST -H "$AUTH" "$API/projects/$PROJECT/files/$name/retry"; done
curl -s -X POST -H "$AUTH" $API/projects/$PROJECT/start > /dev/null
```

## 5. Collect the outputs

**AWS CLI**

```bash
OUTPUT=$(curl -s -H "$AUTH" $API/projects/$PROJECT | jq -r .data.storage.outputPath)
aws s3 sync "$OUTPUT" ./season-1-es --profile syncwords-vod
```

**API**

```bash
curl -s -H "$AUTH" $API/projects/$PROJECT/outputs | jq -r '.data[].path' | while read -r path; do
  mkdir -p "season-1-es/$(dirname "$path")"
  url=$(curl -s -G -H "$AUTH" "$API/projects/$PROJECT/outputs/download" --data-urlencode "path=$path" | jq -r .data.url)
  curl -s -o "season-1-es/$path" "$url"
done
```

## Tips for large batches

- **Distinct base names.** `ep01.mp4` and `ep01.mov` in one folder would write the same output.
- **Add episodes later** by uploading into the same project and calling `start`. Files already
  `done` are not processed again.
- **Another language later?** Create a second project for it and upload the season there too;
  changing the first project's config would not reprocess its `done` files.
- **Throughput.** Several files are processed at once, and the next file starts as soon as one
  finishes, so you can upload while earlier episodes are processing.
