The Structured Subtitle Format (SSF) is a format inspired from ASS2, yet completely different, which is not backwards-compatible with the SSA lineage of subtitle formats.
从ASS的变化
Margin is specified in all 4 directions instead of MarginV (vertical margin), MarginL and MarginR.
Subtitles may be positioned relative to the frame border (traditional behaviour) or use video display as coordinate base, including potential black bands around the frame.
Script comments using C-style (/* ... */) or C++-style (//) comments
新的特性
Always uses Unicode (UTF-8 signature, UCS-2/UTF-16 BOM)
A new syntax, more verbose yet heavier, somewhat inspired from CSS
The format is designed to be streamable, meaning styles are constantly being reminded in the media samples part, in addition to the media file header (packetization).
Text can be rendered along a graphics path, maybe using a Bézier curve technique with control points.
Text strings may be defined for later re-use
1 Structured Subtitle Format 1.0 (boring name, need a better one)
2 ------------------------------
3
4 The encoding must be utf-8/16le/16be with the BOM at the beginning.
5
6 Parsing is prefered to be stream oriented, which means:
7 - new-line characters in text do not differ from the rest of the white-space characters
8 - forward references are not allowed
9
10 Comments
11 --------
12
13 // a comment, ends at the end of the line
14 /* this is a comment, too */
15
16 Syntax
17 ------
18
19 Fortunatelly, there is only one general way to define something. The elements are broken
20 into lines for better understanding, but they can be compressed into one single line as well,
21 whichever suits your preference. The term "type" could be read as "attribute" or "member of
22 a structure" too.
23
24 [!]
25 [type[.type[..]]]
26 [#name]
27 [: | =]
28 [refs | quoted string | num | bool | raw]
29 ;
30
31 As you can see nearly everything is optional, even the terminating semi-colon is not required
32 when a closing bracket ends the definition anyway. However, either type or #name must be given,
33 otherwise there would be no meaning of the expression.
34
35 "!":
36 - gives the definition high priority, normal priority types of the same kind cannot override it:
37 #a {!t: 123;};
38 #b {t: 234;};
39 #c a b; // t of c equals to 123
40 - works on references too:
41 !#a {t: 123;};
42 #b {t: 234;};
43 #ab a b;
44 #c ab; // t of c still equals to 123
45
46 "type" and "name":
47 - alphanumeric or underscore characters only, without spaces
48 - cannot start with a number
49 - case-sensitive
50
51 "type":
52 - type is optional but can be inherited through referencing other names which happen to have a type already
53 color#c1 {a: 0x80;};
54 #c2: c1; // c2 also has the type of color now
55 #c3: c2; // c3 too
56 - if there is a type already additional references cannot change it
57 - when mixing different types (not recommended) the first one decides
58 - there is one special type which have an important but a limited use: @ (see 'subtitle' for an example usage)
59 - it is not parsed for references or nested definitions
60 - the content of the following block {...} is saved as-is for the application
61 - cannot be global or named and therefore referenced
62 - { and } have to be escaped with \ inside the block (other block specific characters may as well)
63 - type.type.type.... is equal to writing: type {type {type {...};};};
64
65 "name":
66 - every name is globally visible
67 - redefining a name is forbidden, unless it was predefined by the application
68 - using the type as the name (type#type) can change the type's default values
69 - however, the scope is important:
70
71 subtitle#subtitle {style.font.size: 20;};
72 style#style {font.size: 30;};
73 style#s1 {font.face: "Arial";};
74 style#s2 : s1 {font.color: red;};
75 subtitle#a {style: s2 {font.weight: "normal";};};
76
77 Here font.size equals to 20, because it inherits from subtitle#subtitle.style instead of the
78 global style#style, additionally it gets the properties of s2-s1 and the inline def. If it also
79 inherited properties from style#style through s2-s1 indirectly, there would be two default base
80 definitions and the order of overriding eachother would not be clear.
81
82 subtitle#a.style
83 <- subtitle#subtitle.style
84 <- s2 <- s1 <-NOT- style#style
85 <- {font.weight: "normal";}
86
87 "refs":
88 - combination of any names or nested definitions separated by spaces
89 - forward references are not allowed
90 - referencing a value type directly is not allowed: (TODO: this may change)
91 OK:
92 color#c1: {a: 12;};
93 color#c2: c1;
94 BAD:
95 #twelve: 12;
96 color#c2: {a: twelve;};
97 - 'name' must have been defined by a parent node
98 OK:
99 #c1: {a: 12;};
100 style#s1 {color: c1;};
101 style#s2 {color: c1;};
102 BAD:
103 style#s1 {color#c1: {a: 12;};};
104 style#s2 {color: c1;}; // c1 cannot be accessed here
105
106 "quoted string" or 'quoted string':
107 - \ escapes characters, including " and '
108 - cannot contain any new-line characters
109
110 "num":
111 - decimal, hexadecimal (prefixed with: 0x), float [+ unit (optional, see 'time' below for an example)]
112 - there are numbers with restricted range or special formatting:
113 - degrees: mod 360
114 - percent: must be between 0 and 1
115 - time: [+] [:[:[.]]] | h | m | s | ms
116
117 "bool":
118 - "true" | "false" | "on" | "off" | "yes" | "no" | 1 | 0
119 - unrecognizable values will result in unpredictable behaviour, since there can't be a default fallback value
120
121 Recognized types and attributes
122 -------------------------------
123
124 file
125 {
126 format: ; // identifies the file format ("ssf")
127 version: ; // file format version (1)
128 language: ; // iso6392
129 title: ;
130 year: ;
131 author: ;
132 };
133
134 color
135 {
136 // a, r, g, b: 0 - 255 or 0x00 - 0xff
137
138 a: ;
139 r: ;
140 g: ;
141 b: ;
142 };
143
144 point
145 {
146 x: ;
147 y: ;
148 };
149
150 size
151 {
152 cx: ;
153 cy: ;
154 };
155
156 rect
157 {
158 t: ;
159 r: ;
160 b: ;
161 l: ;
162 };
163
164 align
165 {
166 // when given in percent, 0 means top/left, 1 bottom/right, 0.5 middle/center
167
168 v: ["top" | "middle" | "bottom" | ];
169 h: ["left" | "center" | "right" | ];
170 };
171
172 angle
173 {
174 x: ;
175 y: ;
176 z: ;
177 };
178
179 frame
180 {
181 reference: ["video" | "window"];
182 resolution: ;
183 };
184
185 direction
186 {
187 primary: ["right" | "left" | "down" | "up"];
188 secondary: ["right" | "left" | "down" | "up"]; // must be perpendicular to primary
189 };
190
191 placement
192 {
193 clip: ["none" | "frame" | ]; // anything drawn outside this rectangle is clipped, negative or larger than 'resolution' values are not allowed for rect
194 margin: ; // "top" "right" "bottom" "left" are also valid values for the rect members, they refer to the values of the "frame" rect (0, 0, frame.resolution.cx, frame.resolution.cy)
195 align: ;
196 pos: ["auto" | ]; // absolute values, pos.x or pos.y can be animated only when both the source and destination style defined it
197 offset: ; // relative to pos, unlike pos this can be applied to fragments of the text as an override
198 angle: ; // origin of rotation is the alignment point, unless it is overriden
199 org: ["auto" | ]; // override for the origin
200 path: ; // a series of x y coord pairs (at least two points)
201 };
202
203 font
204 {
205 face: ;
206 size: ;
207 weight: ["normal" | "bold" | "thin" | ];
208 color: ;
209 underline: ;
210 strikethrough: ;
211 italic: ;
212 spacing: ;
213 scale: ;
214 kerning: ;
215 };
216
217 background
218 {
219 color: ;
220 size: ;
221 type: ["outline" | "enlarge" | "box"];
222 blur: ; // number of passes
223
224 // "enlarge" can be computed faster, but because it follows the path of the original outline it is
225 // not rounded and size > 1 can make it look pretty bad if it intersects with itself.
226 };
227
228 shadow
229 {
230 color: ;
231 depth: ;
232 angle: ;
233 blur: ; // number of passes
234 };
235
236 fill
237 {
238 color: ;
239 width: ;
240
241 // It cannot be applied to the same text multiple times as an override.
242 //
243 // #k1 {fill.width:1; time {start: +0; stop: +1s;}};
244 // #k2 {fill.width:1; time {start: 0; stop: 2s;}};
245 //
246 // OK:
247 // [k1] {Hello}
248 //
249 // BAD:
250 // [k1] {Wo[k2]{r}ld!}
251 //
252 };
253
254 time
255 {
256 id: [ | ];
257 start: