訓練檔結構描述
最後更新:2026-05-29
Your Trainer 是一款 Android 平板的多騎士室內單車應用程式。智慧訓練台控制,本機資料 + 本機控制。一次性購買。
.ytw 訓練檔是純 JSON — 用任何文字編輯器編寫,透過分享面板匯入,並與朋友分享。視覺化編輯器能處理常見情況;當你需要完全掌控時,結構描述就是後備出口。
何時手動編寫
大多數車手永遠不需要這一頁 — 視覺化訓練編輯器和 AI 訓練教練可以涵蓋從 4×8 閾值課表到 microburst 堆疊的一切。當以下情況時才需要結構描述:
- 你想要一種編輯器沒有提供的結構(超長熱身、精細的提示腳本、每段間歇有不同的踏頻目標)。
- 你正在把已有其他工具格式的訓練轉換過來。
- 你想把訓練以單一檔案的形式傳給別人。
- 你想以程式化方式批次產生訓練。
最小範例
最短的有效 .ytw 檔案是一個程式含一段間歇。以 .ytw 副檔名儲存,並分享進 Your Trainer。
{
"programId": "my-sweet-spot",
"programName": "My Sweet Spot 30",
"description": "A short sweet-spot workout.",
"totalDuration": 1800,
"workoutType": "POWER",
"primaryLocale": "en",
"intervals": [
{ "id": "warmup", "duration": 300, "targetPowerPercent": 50, "intensityZone": "Z1", "label": "Warmup", "intervalType": "WARMUP" },
{ "id": "work", "duration": 1200, "targetPowerPercent": 88, "intensityZone": "Z3", "label": "Sweet Spot", "intervalType": "INTERVAL" },
{ "id": "cooldown", "duration": 300, "targetPowerPercent": 50, "intensityZone": "Z1", "label": "Cooldown", "intervalType": "COOLDOWN" }
]
}
頂層欄位
頂層物件描述一個訓練程式。必填欄位會被標示出來。
| 欄位 | 類型 | 說明 |
|---|---|---|
programId 必填 | string | 穩定識別碼。請使用 kebab-case(my-sweet-spot)。這是你資料庫中該訓練的唯一鍵 — 重新匯入具有相同 programId 的檔案會更新既有項目,而不會建立重複項。 |
programName 必填 | string | 車手主要語系下的顯示名稱。 |
description 必填 | string | 顯示在訓練卡片上的一到兩句描述。 |
totalDuration 必填 | integer(秒) | 訓練總長度。App 會在儲存時根據間歇重新計算,所以編寫時即使不一致也沒關係。 |
intervals 必填 | array | 間歇物件或重複群組的有序清單。 |
workoutType | string | 訓練家族:POWER(預設)、HR_ZONE 或 ROUTE。參見訓練類型。 |
variant | string | 家族內的子型:STANDARD(預設)或 RAMP_TEST。 |
primaryLocale | string(BCP-47) | 字串編寫時所使用的語系。預設為 "en"。驅動跨語系回退鏈。 |
category | string | 自由文字分類(例如 "threshold"、"endurance")。可選。 |
difficulty | integer(1–5) | 主觀難度。會顯示在訓練卡片上。 |
isUserCreated | boolean | 對於車手在 App 內編寫的訓練為 true;對於匯入或內建的訓練為 false。預設為 false。 |
isFavorite | boolean | 釘選置頂旗標。車手會在 App 中切換;在分享檔案中通常省略。 |
routeProfile | { distanceMeters, elevationMeters } 的陣列 | ROUTE 訓練的完整爬升曲線。對 POWER 和 HR_ZONE 則為 null。 |
strings | object(locale → LocaleStrings) | 每個語系的名稱、描述、間歇標籤與提示的翻譯。 |
間歇欄位
每個間歇物件描述訓練中的一個區塊。區塊的形態取決於上層的 workoutType — Power 區塊使用功率百分比,HR-Zone 區塊使用區間目標。
| 欄位 | 類型 | 說明 |
|---|---|---|
duration 必填 | integer(秒) | 區塊長度(秒)。 |
targetPowerPercent power 必填 | integer(FTP 的 %) | POWER 訓練的功率目標。與 targetHrZone 互斥。 |
targetPowerEndPercent | integer(FTP 的 %) | 可選的漸進結束功率。當有設定時,目標瓦數會在區塊內從 targetPowerPercent → targetPowerEndPercent 線性插值。 |
targetHrZone hr 必填 | integer(1–5) | HR_ZONE 訓練的心率區間。與 targetPowerPercent 互斥。 |
intensityZone 必填 | string | 視覺區間代碼:Z1–Z5。決定地形視覺化上的顏色。參見訓練區間。 |
intervalType | string | WARMUP、COOLDOWN 或 INTERVAL(預設)。熱身和緩和區塊會被排除在僅計工作區塊的摘要之外(例如僅計工作區塊的平均功率、工作部分的區間時間等)。 |
label 必填 | string | 車手主要語系下顯示在區塊上的文字。跨語系變體位於 strings.<locale>.labels。 |
id | string | 穩定 slug — 用於 strings.<locale>.labels 以及組成提示鍵的鍵值。對於任何隨附翻譯的訓練都建議使用。 |
autoLabel | boolean | 當標籤是由編輯器預設值產生而非車手手動輸入時為 true。自動標籤會由 Your Trainer 自行在地化,不需要在 strings 中為每個語系設置項目。預設為 false。 |
cadenceTarget | integer(RPM) | 區塊的可選踏頻目標(例如 60 用於低踏頻爬坡,100 用於高速旋轉訓練)。 |
cues | CoachingCue 的陣列 | 區塊期間觸發的教練提示。 |
教練提示
教練提示是在騎乘期間出現在駕駛艙上的短文字覆蓋。每個提示有一個相對於其上層間歇的偏移、要顯示的文字,以及保留在螢幕上的時長。
| 欄位 | 類型 | 說明 |
|---|---|---|
offsetSec 必填 | integer(秒) | 從上層間歇起始算起,提示觸發的秒數。 |
text 必填 | string | 訓練主要語系下的提示文字。跨語系變體位於 strings.<locale>.cues,以 <intervalId>:<cueIndex> 為鍵。 |
durationSec | integer(秒) | 提示停留在螢幕上的時長。預設為 5。 |
範例間歇含三個提示(鍵的組成使用上層間歇的 id + 提示在陣列中的索引):
{
"id": "work",
"duration": 600,
"targetPowerPercent": 95,
"intensityZone": "Z4",
"label": "Threshold",
"intervalType": "INTERVAL",
"cues": [
{ "offsetSec": 0, "text": "Settle in — find your rhythm." },
{ "offsetSec": 300, "text": "Halfway. Stay smooth.", "durationSec": 8 },
{ "offsetSec": 540, "text": "One minute. Hold form." }
]
}
在地化字串與回退鏈
strings 區塊承載訓練中每個對車手可見字串的各語系翻譯。每個語系項目都有相同的形態:
"strings": {
"en": {
"name": "Sweet Spot 30",
"description": "A short sweet-spot workout.",
"labels": { "warmup": "Warmup", "work": "Sweet Spot", "cooldown": "Cooldown" },
"cues": { "work:0": "Settle in", "work:1": "Halfway" }
},
"de": {
"name": "Sweet Spot 30",
"description": "Ein kurzes Sweet-Spot-Training.",
"labels": { "warmup": "Aufwärmen", "work": "Sweet Spot", "cooldown": "Ausrollen" },
"cues": { "work:0": "Locker einrollen", "work:1": "Halbzeit" }
}
}
提示鍵遵循 <intervalId>:<cueIndex> 的模式 — 因此 work 間歇上的第一個提示鍵為 work:0。
對於每個車手可見的字串,App 會以這個順序挑選最佳的語系匹配:
strings[<rider-locale>]— 車手自己的語系。strings[primaryLocale]— 作者的語系。strings["en"]— 通用回退。- 頂層欄位(
programName、間歇label、提示text)。
從車手自身語系以外的任何語系顯示的字串,會在訓練卡片和駕駛艙上以斜體呈現,讓車手知道哪些字串尚未翻譯。
strings blocks for additional locales when prompted.重複群組
對於重複性結構,重複群組讓你編寫一次單元,並告訴 App 要播放幾次。重複群組會在匯入時展開為個別區塊,因此車手在騎乘期間能在即將到來的間歇條中看到每個區塊。
{
"intervals": [
{ "id": "warmup", "duration": 600, "targetPowerPercent": 50, "intensityZone": "Z1", "label": "Warmup", "intervalType": "WARMUP" },
{
"repeat": 4,
"intervals": [
{ "id": "on", "duration": 480, "targetPowerPercent": 95, "intensityZone": "Z4", "label": "Threshold" },
{ "id": "off", "duration": 240, "targetPowerPercent": 55, "intensityZone": "Z1", "label": "Recovery" }
]
},
{ "id": "cooldown", "duration": 600, "targetPowerPercent": 50, "intensityZone": "Z1", "label": "Cooldown", "intervalType": "COOLDOWN" }
]
}
上述範例會展開為 1 段熱身 + 4×(閾值 + 恢復)+ 1 段緩和 = 10 個區塊。重複群組可以巢狀,但為了可讀性,平面結構較佳。
訓練類型與變體
workoutType 欄位選擇主要範式;variant 選擇其中的子型。
workoutType | 是什麼 | 所需的間歇形態 |
|---|---|---|
POWER(預設) | 以 FTP 為基準的間歇。訓練台在 ERG 模式下騎乘目標瓦數,或在 SIM 模式下跟隨阻力曲線。 | 每段間歇有 targetPowerPercent(漸進間歇還可選 targetPowerEndPercent)。 |
HR_ZONE | 心率驅動的間歇。訓練台會即時調整瓦數,讓車手的心率維持在目標區間內 — 當心血管負荷是訓練指標時很有用(恢復、基礎、極化訓練)。 | 每段間歇有 targetHrZone(1–5)。在連接 HRM 之前,START 按鈕會顯示 LINK HRM。 |
ROUTE | 以坡度驅動的模擬騎乘。訓練台會跟隨 routeProfile 的爬升曲線;車手自行選擇踏頻與檔位。 | 間歇通常為空,或只包含一個全長佔位符。實際的騎乘內容位於 routeProfile。 |
variant 欄位選擇子型:
STANDARD(預設)— 一般結構化訓練。RAMP_TEST— 標記為 FTP 測試。在騎乘結束時提示車手更新 FTP,並排除在訓練負荷摘要之外(這樣最大努力不會扭曲每週負荷圖表)。
訓練包(.ytwpack)
當你想要一次取得精選的一批訓練時,.ytwpack 格式會將許多 .ytw 檔案與每個訓練包的清單捆綁在一起。.ytwpack 在底層是一個 ZIP 壓縮檔 — 重新命名為 .zip 並解壓即可看到內容,或透過 Your Trainer 的訓練包安裝程式匯入,只需一觸即可安裝每個訓練。
.ytwpack 壓縮檔包含:
- 壓縮檔根目錄下的
manifest.json— 下面描述的訓練包層級的中繼資料。 - 訓練包中每個訓練對應一個
<slug>.ytw,每個檔案都遵循上面記錄的結構描述。
訓練包清單承載足夠的資訊以製作安裝決策表,而無需打開每個訓練。頂層欄位:
| 欄位 | 類型 | 說明 |
|---|---|---|
schema_version 必填 | integer | 目前永遠為 1。 |
pack_id 必填 | string | 穩定的 kebab-case 識別碼(例如 sweet-spot)。 |
name 必填 | string | 顯示在訓練包目錄中的名稱。 |
description 必填 | string | 車手點選安裝之前可見的一到兩句摘要。 |
version 必填 | string(SemVer) | MAJOR.MINOR.PATCH,可選 -prerelease。Patch 用於內容修正;minor 用於新增訓練;major 用於破壞性結構描述變更。會內嵌在發佈的檔名中(v1.0.2.ytwpack)。 |
content_hash 必填 | string | 對所有 .ytw 檔案位元組(以 slug 排序串接)計算的 sha256:。在未變更內容的重複產生間保持穩定;每當內部的訓練變動就會更新。 |
generated_at 必填 | string(ISO 8601) | UTC 時間戳。 |
set 必填 | string | power 或 hr-zone — 這個訓練包所屬的訓練家族。 |
category 必填 | string | 集合內的子分類(例如 sweet-spot)。 |
workout_count 必填 | integer | 訓練包中 .ytw 項目的數量。 |
total_ride_time_seconds 必填 | integer | 訓練包中每個訓練時長的總和。 |
experience_level 必填 | string | 根據內容的難度範圍計算得出 — 為 beginner / intermediate / advanced / mixed 其中之一。線格式為小寫;App 會在顯示時將首字母大寫。 |
hrm_required 必填 | boolean | 若訓練包中有任何訓練使用 HR_ZONE 則為 true。 |
type_mix 必填 | object | 每個分類佔總騎乘時間的百分比(總和為 100)。驅動安裝畫面上 App 內的類型組成圓環圖。 |
duration_histogram 必填 | object | 每個時長區間的訓練數量:0-30、30-60、60-90、90+(分鐘)。驅動安裝畫面的時長圖表。 |
contents 必填 | array | 完整的每訓練項目 — 是資料庫清單項目形態的超集;每項包含 slug、name、duration_seconds、摘要指標,以及用於縮圖渲染的 sparkline 陣列。 |
已下載的 .ytwpack 有兩種安裝路徑:
- 在 Your Trainer 中一觸安裝 — 在分享面板中打開
.ytwpack,Your Trainer 會讀取訓練包清單,向你展示內部內容(類型組成、時長直方圖、總騎乘時間),並一次安裝每個訓練。App 內訓練包安裝程式上線後即可使用。 - 手動解壓 — 重新命名為
.zip(或直接用任何壓縮工具解壓),然後透過分享面板逐一將每個.ytw分享到 Your Trainer。
範例訓練包清單(為了可讀性已截短 contents):
{
"schema_version": 1,
"pack_id": "sweet-spot",
"name": "Sweet Spot",
"description": "26 sweet-spot sessions across classic intervals, sustained stacks, and over-unders.",
"version": "1.0.2",
"content_hash": "sha256:9432a3a76015158dc71ec63…",
"generated_at": "2025-05-28T00:00:00Z",
"set": "power",
"category": "sweet-spot",
"workout_count": 26,
"total_ride_time_seconds": 119340,
"experience_level": "intermediate",
"hrm_required": false,
"type_mix": { "sweet-spot": 100 },
"duration_histogram": { "0-30": 0, "30-60": 5, "60-90": 12, "90+": 9 },
"contents": [
{ "slug": "sweet-spot-3x10min-at-88pct-ftp", "name": "Sweet Spot 3×10min @ 88% FTP", "duration_seconds": 3300, "tss": 55.8, "intensity_factor": 0.764, "sparkline": […] },
{ "slug": "sweet-spot-3x15min-at-90pct-ftp", "name": "Sweet Spot 3×15min @ 90% FTP", "duration_seconds": 4500, "tss": 81.6, "intensity_factor": 0.808, "sparkline": […] }
/* … 24 more workouts … */
]
}
資料庫與訓練包清單
兩份清單與可下載的檔案一同發佈。兩者皆為純 JSON;兩者皆由可直接抓取的 JSON Schema 文件描述。
| URL | 列出什麼 | JSON Schema |
|---|---|---|
library/manifest.json |
資料庫中每個精選的 .ytw — 提供瀏覽 / 搜尋 / 篩選客戶端的每訓練中繼資料。也列出可用的 .ytwpack 下載(檔案路徑、版本、內容雜湊、類型組成摘要、圖示 URL)。 |
/schemas/workout-manifest.json |
packs/manifest.json |
訓練包目錄端點:每個已發佈的 .ytwpack 及其摘要中繼資料。與資料庫清單的 packs 陣列具有相同的每訓練包項目形態;App 內的訓練包資料庫會在車手手動更新時抓取這個。 |
/schemas/workout-manifest.json |
(每個 .ytwpack 內部) |
作為壓縮檔根目錄下 manifest.json 攜帶的每訓練包清單 — 上方表格記錄了其形態。 |
/schemas/workout-pack-manifest.json |
如果你正在打造消費這個資料庫的工具 — 自訂訓練瀏覽器、針對 .zwo 的訓練轉換器、能呈現訓練包的教練儀表板 — 這些是你需要驗證的合約。同樣的每訓練項目形態在兩份清單的 contents / workouts 陣列中都會出現,所以處理其中一個的客戶端也能處理另一個。
完整範例
漸進式間歇
一個 5 分鐘的熱身,透過線性插值從 40 % FTP 漸進到 75 % FTP:
{
"id": "rampup",
"duration": 300,
"targetPowerPercent": 40,
"targetPowerEndPercent": 75,
"intensityZone": "Z1",
"label": "Ramp up",
"intervalType": "WARMUP"
}
高低交替
三組 2 分鐘 95 % FTP / 1 分鐘 105 % FTP,以重複群組表示:
{
"repeat": 3,
"intervals": [
{ "id": "under", "duration": 120, "targetPowerPercent": 95, "intensityZone": "Z4", "label": "Under" },
{ "id": "over", "duration": 60, "targetPowerPercent": 105, "intensityZone": "Z5", "label": "Over" }
]
}
心率區間訓練
30 分鐘 Z2 耐力騎乘,中段加入 3 分鐘 Z4 加速:
{
"programId": "hr-z2-with-surge",
"programName": "Z2 with a Z4 surge",
"description": "Steady Zone 2 with a single 3-minute Zone 4 surge.",
"totalDuration": 1800,
"workoutType": "HR_ZONE",
"primaryLocale": "en",
"intervals": [
{ "id": "warmup", "duration": 300, "targetHrZone": 1, "intensityZone": "Z1", "label": "Warmup", "intervalType": "WARMUP" },
{ "id": "endure1", "duration": 600, "targetHrZone": 2, "intensityZone": "Z2", "label": "Endurance" },
{ "id": "surge", "duration": 180, "targetHrZone": 4, "intensityZone": "Z4", "label": "Surge" },
{ "id": "endure2", "duration": 420, "targetHrZone": 2, "intensityZone": "Z2", "label": "Endurance" },
{ "id": "cooldown","duration": 300, "targetHrZone": 1, "intensityZone": "Z1", "label": "Cooldown", "intervalType": "COOLDOWN" }
]
}
多語言
最小甜蜜點範例,含 EN + DE + NL strings 區塊。同一個訓練,三種原生體驗:
{
"programId": "my-sweet-spot",
"programName": "Sweet Spot 30",
"description": "A short sweet-spot workout.",
"totalDuration": 1800,
"workoutType": "POWER",
"primaryLocale": "en",
"intervals": [
{ "id": "warmup", "duration": 300, "targetPowerPercent": 50, "intensityZone": "Z1", "label": "Warmup", "intervalType": "WARMUP" },
{ "id": "work", "duration": 1200, "targetPowerPercent": 88, "intensityZone": "Z3", "label": "Sweet Spot", "intervalType": "INTERVAL",
"cues": [
{ "offsetSec": 0, "text": "Settle in" },
{ "offsetSec": 600, "text": "Halfway" }
]
},
{ "id": "cooldown", "duration": 300, "targetPowerPercent": 50, "intensityZone": "Z1", "label": "Cooldown", "intervalType": "COOLDOWN" }
],
"strings": {
"en": {
"name": "Sweet Spot 30",
"description": "A short sweet-spot workout.",
"labels": { "warmup": "Warmup", "work": "Sweet Spot", "cooldown": "Cooldown" },
"cues": { "work:0": "Settle in", "work:1": "Halfway" }
},
"de": {
"name": "Sweet Spot 30",
"description": "Ein kurzes Sweet-Spot-Training.",
"labels": { "warmup": "Aufwärmen", "work": "Sweet Spot", "cooldown": "Ausrollen" },
"cues": { "work:0": "Locker einrollen", "work:1": "Halbzeit" }
},
"nl": {
"name": "Sweet Spot 30",
"description": "Een korte sweet-spot-training.",
"labels": { "warmup": "Inrijden", "work": "Sweet Spot", "cooldown": "Uitrijden" },
"cues": { "work:0": "Rustig inrijden", "work:1": "Halverwege" }
}
}
}
常見陷阱
- 間歇缺少
id。執行階段會接受沒有id的間歇,但你會失去翻譯的進入點 —strings.<locale>.labels和提示鍵對應都依賴它。若打算讓訓練帶有strings,請給每段間歇都加上id。 - 提示鍵不一致。提示鍵的模式為
<intervalId>:<cueIndex>— 從零開始計算。work間歇上的第三個提示為work:2,不是work:3。 - 同一段間歇同時有
targetPowerPercent和targetHrZone。兩者互斥 — 根據上層的workoutType保留其中一個。 - 熱身和緩和忘記設定
intervalType。預設為INTERVAL— 請明確設為WARMUP/COOLDOWN,這樣分析層才不會把這些區塊算進工作總計。 - 過高的
totalDuration。編寫期間無害 — Your Trainer 會在儲存時根據intervals重新計算。但在分享前最好還是修正,因為某些外部工具會直接顯示該欄位。 - 錯誤的
intensityZone代碼。必須為字串,且為Z1–Z5其中之一。"Z6"或"3"不會在地形視覺化上呈現正確的顏色。
參考
- AI 訓練教練可從純文字描述產生有效的
.ytwJSON — 可作為手動編輯的起點。 - 用於確定性的程式化撰寫 (無 LLM):Your Trainer MCP — 整合者文件涵蓋
build_workout_from_intent與decompose_workout,它們可組合成此 schema 或由此 schema 分解。 - 對於無法匯入的訓練:疑難排解 → 訓練與路線匯入。
- 區間定義請見:訓練區間。
- 對於不熟悉的術語:詞彙表。
- Your Trainer 內建的訓練涵蓋了結構描述支援的每一種形態 — 在文字編輯器中檢視其中一個(從視覺化編輯器匯出),是快速看到任何欄位實際範例的好方法。
進行程式化的格式規範查詢(欄位表、範例、約束、術語表)時,正規的機器可讀來源是 Your Trainer MCP — 從任何 MCP 客戶端呼叫 get_format_spec、get_canonical_examples、get_format_constraints 或 get_format_glossary。這些工具與驅動本頁的相同知識註冊表共用資料;若工具的回應曾與本頁分歧,則 MCP 為正規,本頁已過時。
JSON Schema(供基於這些格式建構的客戶端使用的機器可讀合約):
/schemas/workout-manifest.json— 資料庫清單 + 每訓練包項目形態(涵蓋library/manifest.json與packs/manifest.json)。/schemas/workout-pack-manifest.json— 每個.ytwpack內攜帶的manifest.json。/schemas/workout-intent.json— 用於組成.ytw的訓練編寫工具所採用的結構化意圖形態。