live.voxport.podcast.episode
Schema Diff
+2 -2
Compatibility Analysis
Breaking Changes Detected
3 breaking changes, 2 non-breaking changes.
Breaking Changes (3)
- RemovedVertex RemovedVertex { vertex_id: "live.voxport.podcast.series" }
- RemovedEdge RemovedEdge { src: "live.voxport.podcast.episode:body.series", tgt: "live.voxport.podcast.series", kind: "ref", name: None }
- ConstraintTightened ConstraintTightened { vertex_id: "live.voxport.podcast.episode:body.series", sort: "ref", old_value: "live.voxport.podcast.series", new_value: "live.voxport.podcast.series#seriesRef" }
Non-Breaking Changes (2)
- AddedVertex AddedVertex { vertex_id: "live.voxport.podcast.series#seriesRef" }
- AddedEdge AddedEdge { src: "live.voxport.podcast.episode:body.series", tgt: "live.voxport.podcast.series#seriesRef", kind: "ref", name: None }
Migration Guidance
Removed Elements
RemovedVertex { vertex_id: "live.voxport.podcast.series" }
Added Elements
AddedVertex { vertex_id: "live.voxport.podcast.series#seriesRef" }
Constraint Changes
- ConstraintTightened ConstraintTightened { vertex_id: "live.voxport.podcast.episode:body.series", sort: "ref", old_value: "live.voxport.podcast.series", new_value: "live.voxport.podcast.series#seriesRef" }
Additional Notes
- Breaking: RemovedEdge { src: "live.voxport.podcast.episode:body.series", tgt: "live.voxport.podcast.series", kind: "ref", name: None }
- Non-breaking: AddedEdge { src: "live.voxport.podcast.episode:body.series", tgt: "live.voxport.podcast.series#seriesRef", kind: "ref", name: None }
1
1
{
2
2
"$type": "com.atproto.lexicon.schema",
3
3
"defs": {
4
4
"chapter": {
5
5
"properties": {
6
6
"endTime": {
7
7
"description": "End time in seconds (optional)",
8
8
"type": "integer"
9
9
},
10
10
"hidden": {
11
11
"description": "Whether the chapter is hidden from the table of contents (Podcasting 2.0 toc:false)",
12
12
"type": "boolean"
13
13
},
14
14
"image": {
15
15
"format": "uri",
16
16
"type": "string"
17
17
},
18
18
"startTime": {
19
19
"description": "Start time in seconds",
20
20
"type": "integer"
21
21
},
22
22
"title": {
23
23
"maxLength": 256,
24
24
"type": "string"
25
25
},
26
26
"url": {
27
27
"description": "Chapter link URL (Podcasting 2.0 chapter url)",
28
28
"format": "uri",
29
29
"type": "string"
30
30
}
31
31
},
32
32
"required": [
33
33
"title",
34
34
"startTime"
35
35
],
36
36
"type": "object"
37
37
},
38
38
"main": {
39
39
"description": "A podcast episode belonging to a series",
40
40
"key": "tid",
41
41
"record": {
42
42
"properties": {
43
43
"audio": {
44
44
"accept": [
45
45
"audio/mpeg",
46
46
"audio/mp4",
47
47
"audio/wav",
48
48
"audio/ogg",
49
49
"audio/aac"
50
50
],
51
51
"description": "Audio file reference",
52
52
"maxSize": 524288000,
53
53
"type": "blob"
54
54
},
55
55
"audioLength": {
56
56
"description": "Byte size of the audio file when using audioUrl. Required for a valid RSS enclosure — without this, a firehose consumer cannot reconstruct the feed from records alone.",
57
57
"minimum": 0,
58
58
"type": "integer"
59
59
},
60
60
"audioType": {
61
61
"description": "MIME type of the audio file when using audioUrl. Pairs with audioLength for RSS enclosure reconstruction.",
62
62
"knownValues": [
63
63
"audio/mpeg",
64
64
"audio/mp4",
65
65
"audio/wav",
66
66
"audio/ogg",
67
67
"audio/aac"
68
68
],
69
69
"maxLength": 64,
70
70
"type": "string"
71
71
},
72
72
"audioUrl": {
73
73
"description": "External audio URL (if not using blob)",
74
74
"format": "uri",
75
75
"type": "string"
76
76
},
77
77
"blueskyPostRef": {
78
78
"description": "Reference to the Atmosphere cross-post (app.bsky.feed.post) for this episode. Set when the episode is auto-cross-posted, enabling custom feed operators to discover the cross-post from the episode record.",
79
79
"ref": "com.atproto.repo.strongRef",
80
80
"type": "ref"
81
81
},
82
82
"chapters": {
83
83
"description": "Episode chapters/timestamps",
84
84
"items": {
85
85
"ref": "#chapter",
86
86
"type": "ref"
87
87
},
88
88
"maxLength": 100,
89
89
"type": "array"
90
90
},
91
91
"createdAt": {
92
92
"format": "datetime",
93
93
"type": "string"
94
94
},
95
95
"duration": {
96
96
"description": "Duration in seconds",
97
97
"minimum": 0,
98
98
"type": "integer"
99
99
},
100
100
"episodeNumber": {
101
101
"description": "Episode number in series",
102
102
"minimum": 1,
103
103
"type": "integer"
104
104
},
105
105
"episodeType": {
106
106
"default": "full",
107
107
"description": "Episode type per RSS spec",
108
108
"knownValues": [
109
109
"full",
110
110
"trailer",
111
111
"bonus"
112
112
],
113
113
"maxLength": 16,
114
114
"type": "string"
115
115
},
116
116
"explicit": {
117
117
"description": "Whether episode is explicit",
118
118
"type": "boolean"
119
119
},
120
120
"image": {
121
121
"accept": [
122
122
"image/png",
123
123
"image/jpeg"
124
124
],
125
125
"description": "Episode-specific cover art",
126
126
"maxSize": 2097152,
127
127
"type": "blob"
128
128
},
129
129
"importedGuid": {
130
130
"description": "Original GUID from RSS feed (for imported episodes)",
131
131
"maxLength": 2048,
132
132
"type": "string"
133
133
},
134
134
"persons": {
135
135
"description": "People associated with this episode — hosts, guests, co-hosts (Podcasting 2.0 podcast:person)",
136
136
"items": {
137
137
"ref": "#person",
138
138
"type": "ref"
139
139
},
140
140
"maxLength": 50,
141
141
"type": "array"
142
142
},
143
143
"publishedAt": {
144
144
"description": "Publication date (null = draft)",
145
145
"format": "datetime",
146
146
"type": "string"
147
147
},
148
148
"seasonNumber": {
149
149
"description": "Season number",
150
150
"minimum": 1,
151
151
"type": "integer"
152
152
},
153
153
"series": {
154
-
"description": "Reference to parent series",
155
-
"ref": "live.voxport.podcast.series",
154
+
"description": "Reference to parent series (uri + optional cid)",
155
+
"ref": "live.voxport.podcast.series#seriesRef",
156
156
"type": "ref"
157
157
},
158
158
"soundbites": {
159
159
"description": "Short clip markers for apps to surface (Podcasting 2.0 podcast:soundbite)",
160
160
"items": {
161
161
"ref": "#soundbite",
162
162
"type": "ref"
163
163
},
164
164
"maxLength": 20,
165
165
"type": "array"
166
166
},
167
167
"summary": {
168
168
"description": "Episode description/show notes (supports markdown)",
169
169
"maxLength": 10000,
170
170
"type": "string"
171
171
},
172
172
"tags": {
173
173
"description": "Episode tags/keywords",
174
174
"items": {
175
175
"maxLength": 64,
176
176
"type": "string"
177
177
},
178
178
"maxLength": 20,
179
179
"type": "array"
180
180
},
181
181
"title": {
182
182
"description": "Episode title",
183
183
"maxLength": 256,
184
184
"type": "string"
185
185
},
186
186
"transcript": {
187
187
"description": "Episode transcript (singular — deprecated, prefer transcripts array)",
188
188
"ref": "#transcript",
189
189
"type": "ref"
190
190
},
191
191
"transcripts": {
192
192
"description": "Episode transcripts — multiple entries for different languages/formats (Podcasting 2.0 podcast:transcript). Preferred over the singular transcript field.",
193
193
"items": {
194
194
"ref": "#transcript",
195
195
"type": "ref"
196
196
},
197
197
"maxLength": 20,
198
198
"type": "array"
199
199
},
200
200
"updatedAt": {
201
201
"format": "datetime",
202
202
"type": "string"
203
203
}
204
204
},
205
205
"required": [
206
206
"series",
207
207
"title",
208
208
"createdAt"
209
209
],
210
210
"type": "object"
211
211
},
212
212
"type": "record"
213
213
},
214
214
"person": {
215
215
"properties": {
216
216
"did": {
217
217
"description": "ATProto DID if the person has a verified identity",
218
218
"format": "did",
219
219
"type": "string"
220
220
},
221
221
"group": {
222
222
"description": "Group from the Podcast Taxonomy Project (e.g. cast, writing, visuals). Defaults to 'cast' if absent.",
223
223
"maxLength": 64,
224
224
"type": "string"
225
225
},
226
226
"handle": {
227
227
"description": "ATProto handle (e.g. alice.bsky.social) if the person has a Bluesky account",
228
228
"format": "handle",
229
229
"type": "string"
230
230
},
231
231
"image": {
232
232
"description": "Profile image URL (maps to podcast:person img attribute)",
233
233
"format": "uri",
234
234
"type": "string"
235
235
},
236
236
"name": {
237
237
"description": "Full name or alias of the person (spec: max 128 chars)",
238
238
"maxLength": 128,
239
239
"type": "string"
240
240
},
241
241
"role": {
242
242
"description": "Role from the Podcast Taxonomy Project (e.g. host, guest, co-host). Defaults to 'host' if absent.",
243
243
"maxLength": 64,
244
244
"type": "string"
245
245
},
246
246
"url": {
247
247
"description": "External profile URL (maps to podcast:person href attribute)",
248
248
"format": "uri",
249
249
"type": "string"
250
250
}
251
251
},
252
252
"required": [
253
253
"name"
254
254
],
255
255
"type": "object"
256
256
},
257
257
"soundbite": {
258
258
"properties": {
259
259
"duration": {
260
260
"description": "Duration in fractional seconds (e.g. '60.0', '42.25'). Matches Podcasting 2.0 spec exactly.",
261
261
"maxLength": 16,
262
262
"type": "string"
263
263
},
264
264
"startTime": {
265
265
"description": "Start time in fractional seconds (e.g. '73.0', '1234.5'). Matches Podcasting 2.0 spec exactly.",
266
266
"maxLength": 16,
267
267
"type": "string"
268
268
},
269
269
"title": {
270
270
"description": "Title/label for the soundbite (spec: max 128 chars)",
271
271
"maxLength": 128,
272
272
"type": "string"
273
273
}
274
274
},
275
275
"required": [
276
276
"startTime",
277
277
"duration"
278
278
],
279
279
"type": "object"
280
280
},
281
281
"transcript": {
282
282
"properties": {
283
283
"language": {
284
284
"description": "Language of the transcript (defaults to feed language if absent)",
285
285
"format": "language",
286
286
"type": "string"
287
287
},
288
288
"rel": {
289
289
"description": "If 'captions', the file is a closed captions file with time codes",
290
290
"knownValues": [
291
291
"captions"
292
292
],
293
293
"maxLength": 16,
294
294
"type": "string"
295
295
},
296
296
"type": {
297
297
"description": "MIME type of the transcript file",
298
298
"knownValues": [
299
299
"text/plain",
300
300
"text/html",
301
301
"text/vtt",
302
302
"application/x-subrip",
303
303
"application/json"
304
304
],
305
305
"maxLength": 64,
306
306
"type": "string"
307
307
},
308
308
"url": {
309
309
"description": "URL of the transcript file",
310
310
"format": "uri",
311
311
"type": "string"
312
312
}
313
313
},
314
314
"required": [
315
315
"url",
316
316
"type"
317
317
],
318
318
"type": "object"
319
319
}
320
320
},
321
321
"description": "A podcast episode belonging to a series",
322
322
"id": "live.voxport.podcast.episode",
323
323
"lexicon": 1
324
324
}