├── .gitignore ├── LICENSE.txt ├── README.md ├── pom.xml └── submerge-api ├── META-INF └── MANIFEST.MF ├── pom.xml └── src ├── main └── java │ └── com │ └── github │ └── dnbn │ └── submerge │ └── api │ ├── constant │ └── FontName.java │ ├── parser │ ├── ASSParser.java │ ├── BaseParser.java │ ├── ParserFactory.java │ ├── SRTParser.java │ ├── SubtitleParser.java │ └── exception │ │ ├── InvalidAssSubException.java │ │ ├── InvalidColorCode.java │ │ ├── InvalidFileException.java │ │ ├── InvalidSRTSubException.java │ │ └── InvalidSubException.java │ ├── subtitle │ ├── ass │ │ ├── ASSSub.java │ │ ├── ASSTime.java │ │ ├── Events.java │ │ ├── ScriptInfo.java │ │ └── V4Style.java │ ├── common │ │ ├── SubtitleLine.java │ │ ├── SubtitleTime.java │ │ ├── TimedLine.java │ │ ├── TimedObject.java │ │ └── TimedTextFile.java │ ├── config │ │ ├── Font.java │ │ └── SimpleSubConfig.java │ └── srt │ │ ├── SRTLine.java │ │ ├── SRTSub.java │ │ └── SRTTime.java │ └── utils │ ├── ColorUtils.java │ ├── ConvertionUtils.java │ └── FileUtils.java └── test └── java └── com.github.dnbn.submerge.api ├── Test.java └── TestJunit.java /.gitignore: -------------------------------------------------------------------------------- 1 | .metadata 2 | target/ 3 | *~ 4 | application.properties 5 | application.prod.properties 6 | hibernate.cfg.xml 7 | hibernate.reveng.xml 8 | bin/ 9 | .project 10 | .settings 11 | .classpath 12 | release.properties 13 | .idea 14 | *.iml 15 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) [2015] [Damien Benon] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SubtitleParserForAndroid 2 | Subtitle file parser. support .ass, .srt format 3 | 4 | reference from [submerge](https://github.com/linjonh/submerge) 5 | and then approve it to support android platform at least Android OS API 15, JAVA SDK 1.7. 6 |
because Submerge is for console program and using new JAVA SDK API 1.8, so it not support below android 26.
7 |
8 |
9 | # Usage
10 |
11 | Parsing ASS subtitles:
12 | ```java
13 |
14 | File file = new File("subtitle.ass");
15 |
16 | ASSParser parser = new ASSParser();
17 | ASSSub subtitle = parser.parse(file);
18 |
19 | System.out.println(subtitle.toString());
20 | Parsing SRT subtitles:
21 |
22 | File file = new File("subtitle.srt");
23 | SRTParser parser = new SRTParser();
24 |
25 | SRTSub subtitle = parser.parse(file);
26 |
27 | System.out.println(subtitle.toString());
28 | ```
29 | Using interfaces:
30 | ```java
31 | File file = new File("subtitle.srt");
32 | String extension = FilenameUtils.getExtension(file.getName());
33 |
34 | SubtitleParser parser = ParserFactory.getParser(extension);
35 | TimedTextFile subtitle = parser.parse(file);
36 |
37 | System.out.println(subtitle.toString());
38 | ```
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
60 | * Example of events section:
61 | *
62 | *
122 | * Example of style section:
123 | *
124 | *
275 | * Example of script info section:
276 | *
277 | *
14 | * The field names must be spelled correctly, and are as follows:
15 | *
16 | * Marked, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
17 | *
18 | * The last field will always be the Text field, so that it can contain commas. The format
19 | * line allows new fields to be added to the script format in future, and yet allow old
20 | * versions of the software to read the fields it recognises - even if the field order is
21 | * changed.
22 | */
23 | public class Events extends SubtitleLine
54 | * Higher numbered layers will be drawn over the lower numbered.
55 | */
56 | private int layer;
57 |
58 | /**
59 | * Style name. If it is "Default", then your own *Default style will be subtituted.
60 | *
61 | * However, the Default style used by the script author IS stored in the script even
62 | * though SSA ignores it - so if you want to use it, the information is there - you
63 | * could even change the Name in the Style definition line, so that it will appear in
64 | * the list of "script" styles.
65 | */
66 | private String style;
67 |
68 | /**
69 | * Character name. This is the name of the character who speaks the dialogue. It is
70 | * for information only, to make the script is easier to follow when editing/timing.
71 | */
72 | private String name = StringUtils.EMPTY;
73 |
74 | /**
75 | * 4-figure Left Margin override. The values are in pixels. All zeroes means the
76 | * default margins defined by the style are used.
77 | */
78 | private String marginL = "0000";
79 |
80 | /**
81 | * 4-figure Right Margin override. The values are in pixels. All zeroes means the
82 | * default margins defined by the style are used.
83 | */
84 | private String marginR = "0000";
85 |
86 | /**
87 | * 4-figure Bottom Margin override. The values are in pixels. All zeroes means the
88 | * default margins defined by the style are used.
89 | */
90 | private String marginV = "0000";
91 |
92 | /**
93 | * Transition Effect. This is either empty, or contains information for one of the
94 | * three transition effects implemented in SSA v4.x
95 | *
96 | * The effect names are case sensitive and must appear exactly as shown. The effect
97 | * names do not have quote marks around them.
98 | *
99 | * "Scroll up;y1;y2;delay[;fadeawayheight]"means that the text/picture will scroll up
100 | * the screen. The parameters after the words "Scroll up" are separated by semicolons.
101 | *
102 | * “Banner;delay” means that text will be forced into a single line, regardless of
103 | * length, and scrolled from right to left accross the screen.
104 | */
105 | private String effect = StringUtils.EMPTY;
106 |
107 | /**
108 | * Constructor
109 | *
110 | * @param style style name to apply
111 | * @param time Start Time of the Event
112 | * @param textLines End Time of the Event
113 | */
114 | public Events(String style, ASSTime time, List
59 | *
63 | * [Events]
64 | * Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
65 | * Dialogue: 0,0:02:30.84,0:02:34.70,StlyeOne,,0000,0000,0000,,A text line
66 | * Dialogue: 0,0:02:34.92,0:02:37.54,StyleTwo,,0000,0000,0000,,Another text line
67 | *
68 | *
69 | * @param br: the buffered reader
70 | * @throws IOException
71 | * @throws InvalidAssSubException
72 | * @throws IOException
73 | */
74 | private static Set
121 | *
125 | * [V4+ Styles]
126 | * Format: Name,Fontname,Fontsize,PrimaryColour,SecondaryColour,OutlineColour
127 | * Style: StyleOne,Arial,16,64250,16777215,0
128 | * Style: StyleTwo,Arial,16,16383999,16777215,0
129 | *
130 | *
131 | * @param br: the buffered reader
132 | * @throws IOException
133 | * @throws InvalidAssSubException
134 | */
135 | private static List
274 | *
278 | * [Script Info]
279 | * ScriptType: v4.00+
280 | * Collisions: Normal
281 | * Timer: 100,0000
282 | * Title: My movie title
283 | *
284 | *
285 | * @param br: the buffered reader
286 | * @throws IOException
287 | * @throws InvalidAssSubException
288 | */
289 | private static ScriptInfo parseScriptInfo(BufferedReader br) throws IOException, InvalidAssSubException {
290 |
291 | ScriptInfo scriptInfo = new ScriptInfo();
292 | String line = readFirstTextLine(br);
293 |
294 | while (line != null && !line.startsWith("[")) {
295 |
296 | if (!line.startsWith(COMMENTS_MARK)) {
297 |
298 | String[] split = line.split(ScriptInfo.SEP);
299 | if (split.length > 1) {
300 | String property = StringUtils.deleteWhitespace(split[0]);
301 | property = StringUtils.uncapitalize(property);
302 |
303 | StringBuilder joiner = new StringBuilder();
304 | for (int i = 1; i < split.length; i++) {
305 | joiner.append(split[i])
306 | .append(ScriptInfo.SEP);
307 | }
308 | joiner.delete(joiner.length() - ScriptInfo.SEP.length(), joiner.length());
309 | // StringJoiner joiner = new StringJoiner(ScriptInfo.SEP);
310 | // for (int i = 1; i < split.length; i++) {
311 | // joiner.add(split[i]);
312 | // }
313 | String value = joiner.toString().trim();
314 |
315 | String error = callProperty(ScriptInfo.class, scriptInfo, property, value);
316 |
317 | if (error != null) {
318 | throw new InvalidAssSubException("Script info : " + error);
319 | }
320 |
321 | }
322 |
323 | }
324 |
325 | line = markAndRead(br);
326 | }
327 |
328 | reset(br, line);
329 |
330 | return scriptInfo;
331 | }
332 |
333 | private static String callProperty(Class> clazz, Object object, String property, String value) {
334 | String error = null;
335 |
336 | try {
337 | // System.out.println("clazz = [" + clazz + "], object = [" + object + "], property = [" + property + "], value = [" + value + "]");
338 |
339 | Field declaredField = clazz.getDeclaredField(property);
340 | Class> fieldType = declaredField.getType();
341 |
342 | Method declaredMethod = clazz.getDeclaredMethod("set" + StringUtils.capitalize(property), fieldType);
343 |
344 | String type = fieldType.getSimpleName();
345 | switch (type) {
346 | case "String":
347 | // PropertyUtils.setProperty(object, property, value);
348 | declaredMethod.invoke(object, value);
349 | break;
350 | case "int":
351 | declaredMethod.invoke(object, NumberUtils.toInt(value));
352 |
353 | // PropertyUtils.setProperty(object, property, NumberUtils.toInt(value));
354 | break;
355 | case "boolean":
356 | boolean boolValue = NumberUtils.toInt(value) == -1;
357 | declaredMethod.invoke(object, boolValue);
358 |
359 | // PropertyUtils.setProperty(object, property, boolValue);
360 | break;
361 | case "double":
362 | double doubleValue = NumberUtils.toDouble(value.replace(",", ".").trim());
363 | declaredMethod.invoke(object, doubleValue);
364 | // PropertyUtils.setProperty(object, property, doubleValue);
365 | break;
366 | default:
367 | break;
368 | }
369 | } catch (NoSuchFieldException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
370 | e.printStackTrace();
371 | System.out.println("clazz = [" + clazz + "], property = [" + property + "], value = [" + value + "]");
372 | }
373 |
374 | return error;
375 | }
376 |
377 | /**
378 | * Call a specific property of an object with reflection
379 | *
380 | * @param object: the object to set a property
381 | * @param property: the property to define
382 | * @param value: the value to set
383 | * @return the error message if an error has occured, null otherwise
384 | */
385 | // private static String callProperty(Object object, String property, String value) {
386 | //
387 | // String error = null;
388 | // try {
389 | // PropertyDescriptor descriptor = PropertyUtils.getPropertyDescriptor(object, property);
390 | //
391 | // if (descriptor != null) {
392 | // String type = descriptor.getPropertyType().getSimpleName();
393 | // switch (type) {
394 | // case "String":
395 | // PropertyUtils.setProperty(object, property, value);
396 | // break;
397 | // case "int":
398 | // PropertyUtils.setProperty(object, property, NumberUtils.toInt(value));
399 | // break;
400 | // case "boolean":
401 | // boolean boolValue = NumberUtils.toInt(value) == -1;
402 | // PropertyUtils.setProperty(object, property, boolValue);
403 | // break;
404 | // case "double":
405 | // double doubleValue = NumberUtils.toDouble(value.replace(",", ".").trim());
406 | // PropertyUtils.setProperty(object, property, doubleValue);
407 | // break;
408 | // default:
409 | // break;
410 | // }
411 | // }
412 | //
413 | // } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
414 | // // Property not supported, do nothing
415 | // }
416 | //
417 | // return error;
418 | // }
419 |
420 | /**
421 | * Get the format string definition
422 | *
423 | * @param br: the buffered reader
424 | * @param sectionName: the name of the section to parse
425 | * @return the format string definition
426 | * @throws IOException
427 | * @throws InvalidAssSubException
428 | */
429 | private static String[] findFormat(BufferedReader br, String sectionName) throws IOException,
430 | InvalidAssSubException {
431 |
432 | String line = readFirstTextLine(br);
433 | if (StringUtils.isEmpty(line)) {
434 | throw new InvalidAssSubException("Missing format definition in " + sectionName + " section");
435 | }
436 | if (!line.trim().startsWith(ASSSub.FORMAT)) {
437 | String capitalized = StringUtils.capitalize(sectionName);
438 | throw new InvalidAssSubException(capitalized + " definition must start with 'Format' line");
439 | }
440 | return findInfo(line, ASSSub.FORMAT).split(V4Style.SEP);
441 | }
442 |
443 | /**
444 | * Find the information after ":" in a text line
445 | *
446 | * @param line: the line
447 | * @param search: the information to search
448 | * @return info or null if the info is empty / not found
449 | */
450 | private static String findInfo(String line, String search) {
451 |
452 | String info = null;
453 | String sep = ":";
454 | if (line.trim().toLowerCase().startsWith(search.toLowerCase()) && line.indexOf(sep) > 0) {
455 | info = line.substring(line.indexOf(sep) + 1, line.length()).trim();
456 | }
457 | return StringUtils.isEmpty(info) ? null : info;
458 | }
459 |
460 | }
461 |
--------------------------------------------------------------------------------
/submerge-api/src/main/java/com/github/dnbn/submerge/api/parser/BaseParser.java:
--------------------------------------------------------------------------------
1 | package com.github.dnbn.submerge.api.parser;
2 |
3 | import com.github.dnbn.submerge.api.parser.exception.InvalidFileException;
4 | import com.github.dnbn.submerge.api.parser.exception.InvalidSubException;
5 | import com.github.dnbn.submerge.api.subtitle.common.TimedTextFile;
6 | import com.github.dnbn.submerge.api.utils.FileUtils;
7 | import org.apache.commons.io.IOUtils;
8 | import org.apache.commons.lang.StringUtils;
9 |
10 | import java.io.*;
11 | import java.lang.reflect.ParameterizedType;
12 | import java.lang.reflect.Type;
13 |
14 | public abstract class BaseParserParsableSubtitle
object
65 | *
66 | * @param br: the buffered reader
67 | * @param sub : the subtitle object to fill
68 | * @throws IOException
69 | * @throws InvalidSubException if an error has occured when parsing the subtitle file
70 | */
71 | protected abstract void parse(BufferedReader br, T sub) throws IOException;
72 |
73 | /**
74 | * Ignore blank spaces and return the first text line
75 | *
76 | * @param br: the buffered reader
77 | * @throws IOException
78 | */
79 | protected static String readFirstTextLine(BufferedReader br) throws IOException {
80 |
81 | String line = null;
82 | while ((line = br.readLine()) != null) {
83 | if (!StringUtils.isEmpty(line.trim())) {
84 | break;
85 | }
86 | }
87 | return line;
88 | }
89 |
90 | /**
91 | * Remove the byte order mark if exists
92 | *
93 | * @param br: the buffered reader
94 | * @throws IOException
95 | */
96 | private static void skipBom(BufferedReader br) throws IOException {
97 |
98 | br.mark(4);
99 | if (BOM_MARKER != br.read()) {
100 | br.reset();
101 | }
102 | }
103 |
104 | /**
105 | * Reset the reader at the previous mark if the current line is a new section
106 | *
107 | * @param br: the reader
108 | * @param line: the current line
109 | * @throws IOException
110 | */
111 | protected static void reset(BufferedReader br, String line) throws IOException {
112 |
113 | if (line != null && line.startsWith("[")) {
114 | br.reset();
115 | }
116 | }
117 |
118 | /**
119 | * Mark the position in the reader and read the next text line
120 | *
121 | * @param br: the buffered reader
122 | * @return the next text line
123 | * @throws IOException
124 | */
125 | protected static String markAndRead(BufferedReader br) throws IOException {
126 |
127 | br.mark(32);
128 | return readFirstTextLine(br);
129 | }
130 |
131 | }
132 |
--------------------------------------------------------------------------------
/submerge-api/src/main/java/com/github/dnbn/submerge/api/parser/ParserFactory.java:
--------------------------------------------------------------------------------
1 | package com.github.dnbn.submerge.api.parser;
2 |
3 | import org.apache.commons.lang.NotImplementedException;
4 |
5 | public final class ParserFactory {
6 |
7 | /**
8 | * Return the subtitle parser for the subtitle format matching the extension
9 | *
10 | * @param extension the subtitle extention
11 | * @return the subtitle parser, null if no matching parser
12 | */
13 | public static SubtitleParser getParser(String extension) {
14 |
15 | SubtitleParser parser = null;
16 | String lowerExt = extension.toLowerCase();
17 |
18 | if ("ass".equals(lowerExt) || "ssa".equals(lowerExt)) {
19 | parser = new ASSParser();
20 | } else if ("srt".equalsIgnoreCase(lowerExt)) {
21 | parser = new SRTParser();
22 | } else {
23 | throw new NotImplementedException(extension + " format not supported");
24 | }
25 |
26 | return parser;
27 | }
28 |
29 | /**
30 | * Private constructor
31 | */
32 | private ParserFactory() {
33 |
34 | throw new AssertionError();
35 | }
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/submerge-api/src/main/java/com/github/dnbn/submerge/api/parser/SRTParser.java:
--------------------------------------------------------------------------------
1 | package com.github.dnbn.submerge.api.parser;
2 |
3 | import java.io.BufferedReader;
4 | import java.io.IOException;
5 | import java.util.ArrayList;
6 | import java.util.List;
7 |
8 | import org.apache.commons.lang.StringUtils;
9 |
10 | import com.github.dnbn.submerge.api.parser.exception.InvalidSRTSubException;
11 | import com.github.dnbn.submerge.api.parser.exception.InvalidSubException;
12 | import com.github.dnbn.submerge.api.subtitle.srt.SRTLine;
13 | import com.github.dnbn.submerge.api.subtitle.srt.SRTSub;
14 | import com.github.dnbn.submerge.api.subtitle.srt.SRTTime;
15 |
16 | /**
17 | * Parse SRT subtitles
18 | */
19 | public final class SRTParser extends BaseParser
35 | *
36 | * Example of SRT line:
37 | *
38 | *
39 | * 1
40 | * 00:02:46,813 --> 00:02:50,063
41 | * A text line
42 | *
43 | *
44 | * @param br
45 | * @return SRTLine the line extracted, null if no SRTLine found
46 | * @throws IOException
47 | * @throws InvalidSRTSubException
48 | */
49 | private static SRTLine firstIn(BufferedReader br) throws IOException, InvalidSRTSubException {
50 |
51 | String idLine = readFirstTextLine(br);
52 | String timeLine = br.readLine();
53 |
54 | if (idLine == null || timeLine == null) {
55 | return null;
56 | }
57 |
58 | int id = parseId(idLine);
59 | SRTTime time = parseTime(timeLine);
60 |
61 | ListASSSub
represents a SubStation Alpha subtitle
15 | *
16 | */
17 | public class ASSSub implements TimedTextFile {
18 |
19 | /**
20 | * Serial
21 | */
22 | private static final long serialVersionUID = 8812933867812351549L;
23 |
24 |
25 | /**
26 | * Format
27 | */
28 | public static final String FORMAT = "Format";
29 |
30 | /**
31 | * Events section
32 | */
33 | private static final String EVENTS = "[Events]";
34 |
35 | /**
36 | * Styles section
37 | */
38 | private static final String V4_STYLES = "[V4+ Styles]";
39 |
40 | /**
41 | * Script info section
42 | */
43 | private static final String SCRIPT_INFO = "[Script Info]";
44 |
45 | /**
46 | * Line separator
47 | */
48 | private static final String NEW_LINE = "\n";
49 |
50 | /**
51 | * Key / Value info separator. Ex : "Color: red"
52 | */
53 | public static final String SEP = ": ";
54 |
55 | /**
56 | * Subtitle name
57 | */
58 | private String filename;
59 |
60 | /**
61 | * Headers and general information about the script
62 | */
63 | private ScriptInfo scriptInfo = new ScriptInfo();
64 |
65 | /**
66 | * Style definitions required by the script
67 | */
68 | private ListASSTime
represents a SubStation Alpha time : meaning the time at
12 | * which the text will appear and disappear onscreen
13 | */
14 | public class ASSTime extends SubtitleTime {
15 |
16 | /**
17 | * Serial
18 | */
19 | private static final long serialVersionUID = -8393452818120120069L;
20 |
21 | /**
22 | * The time pattern
23 | */
24 | public static final String TIME_PATTERN = "H:mm:ss.SS";
25 | private static final String TS_PATTERN = "%02d:%02d:%02d.%02d";
26 |
27 | /**
28 | * The time pattern formatter
29 | */
30 | // public static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern(TIME_PATTERN);
31 |
32 | /**
33 | * Constructor
34 | */
35 | public ASSTime(long start, long end) {
36 | super(start, end);
37 | }
38 |
39 | /**
40 | * Constructor
41 | */
42 | public ASSTime() {
43 | super();
44 | }
45 | @Override
46 | public String toString() {
47 | StringBuilder sb = new StringBuilder();
48 | sb.append(format(this.start));
49 | sb.append(" ");
50 | sb.append(format(this.end));
51 | return sb.toString();
52 | }
53 | /**
54 | * Convert a LocalTime
to string
55 | *
56 | * @param time: the time to format
57 | * @return the formatted time
58 | */
59 | public static String format(long time) {
60 | // if (time != null) {
61 | // return time.format(FORMATTER);
62 | // } else {
63 | // System.out.println("time = [" + time + "]");
64 | // return "00:00:00";
65 | // }
66 | long ms = time % 1000;
67 | long s = time / 1000;
68 | long hh = s / 60 / 60;
69 | long min = (s / 60) % 60;
70 | long sec = s % 60;
71 |
72 | return String.format(TS_PATTERN, hh, min, sec, ms);
73 | }
74 |
75 | /**
76 | * @param in string like 10:23:56.90 最小为0.01秒
77 | * @return long milliseconds
78 | */
79 | public static long fromString(String in) {
80 | String[] split1 = in.split(":");
81 | long hours = Long.parseLong(split1[0].trim());
82 | long minutes = Long.parseLong(split1[1].trim());
83 | long seconds = 0;
84 | long millies = 0;
85 | if (split1.length > 2) {
86 | String[] split = split1[2].split("\\.");//ASS format
87 | seconds = Long.parseLong(split[0].trim());
88 | if (split.length > 1) {
89 | millies = Long.parseLong(split[1].trim());
90 | } else {
91 | System.out.println("in = [" + in + "]");
92 | }
93 | } else {
94 | System.out.println("in = [" + in + "] split:" + Arrays.toString(split1));
95 | }
96 | return hours * 60 * 60 * 1000 + minutes * 60 * 1000 + seconds * 1000 + millies;
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/submerge-api/src/main/java/com/github/dnbn/submerge/api/subtitle/ass/Events.java:
--------------------------------------------------------------------------------
1 | package com.github.dnbn.submerge.api.subtitle.ass;
2 |
3 | import com.github.dnbn.submerge.api.subtitle.common.SubtitleLine;
4 | import org.apache.commons.lang.StringUtils;
5 |
6 | import java.util.List;
7 |
8 | /**
9 | * Contain the subtitle text, their timings, and how it should be displayed. The fields
10 | * which appear in each Dialogue line are defined by a Format: line, which must appear
11 | * before any events in the section. The format line specifies how SSA will interpret all
12 | * following Event lines.
13 | * ScriptInfo
section contains headers and general information about the
8 | * script
9 | */
10 | public class ScriptInfo implements Serializable {
11 |
12 | /**
13 | * Serial
14 | */
15 | private static final long serialVersionUID = -6613873382621648995L;
16 |
17 | /**
18 | * Timer declaration
19 | */
20 | private static final String TIMER = "Timer";
21 |
22 | /**
23 | * PlayDepth declaration
24 | */
25 | private static final String PLAY_DEPTH = "PlayDepth";
26 |
27 | /**
28 | * PlayResX declaration
29 | */
30 | private static final String PLAY_RES_X = "PlayResX";
31 |
32 | /**
33 | * PlayResY declaration
34 | */
35 | private static final String PLAY_RES_Y = "PlayResY";
36 |
37 | /**
38 | * Collisions declaration
39 | */
40 | private static final String COLLISIONS = "Collisions";
41 |
42 | /**
43 | * Script Type declaration
44 | */
45 | private static final String SCRIPT_TYPE = "ScriptType";
46 |
47 | /**
48 | * Update Details declaration
49 | */
50 | private static final String UPDATE_DETAILS = "Update Details";
51 |
52 | /**
53 | * Script Updated By declaration
54 | */
55 | private static final String SCRIPT_UPDATED_BY = "Script Updated By";
56 |
57 | /**
58 | * Synch Point declaration
59 | */
60 | private static final String SYNCH_POINT = "Synch Point";
61 |
62 | /**
63 | * Original Timing declaration
64 | */
65 | private static final String ORIGINAL_TIMING = "Original Timing";
66 |
67 | /**
68 | * Original Editing declaration
69 | */
70 | private static final String ORIGINAL_EDITING = "Original Editing";
71 |
72 | /**
73 | * Original Translation declaration
74 | */
75 | private static final String ORIGINAL_TRANSLATION = "Original Translation";
76 |
77 | /**
78 | * Original Script declaration
79 | */
80 | private static final String ORIGINAL_SCRIPT = "Original Script";
81 |
82 | /**
83 | * Title declaration
84 | */
85 | private static final String TITLE = "Title";
86 |
87 | /**
88 | * Separator
89 | */
90 | public static final String SEP = ": ";
91 |
92 | /**
93 | * New line separator
94 | */
95 | private static final String NEW_LINE = "\n";
96 |
97 | /**
98 | * Decimal time formater
99 | */
100 | private static final DecimalFormat timeFormatter = new DecimalFormat("#.0000");
101 |
102 | public enum Collision {
103 |
104 | /**
105 | * position subtitles in the position specified by the "margins"
106 | */
107 | NORMAL("Normal"),
108 |
109 | /**
110 | * subtitles will be shifted upwards to make room for subsequent overlapping
111 | * subtitles
112 | */
113 | REVERSE("Reverse");
114 |
115 | private String type;
116 |
117 | Collision(String type) {
118 | this.type = type;
119 | }
120 |
121 | @Override
122 | public String toString() {
123 | return this.type;
124 | }
125 |
126 | }
127 |
128 | /**
129 | * This is a description of the script. If the original author(s) did not provide this
130 | * information then StringBuilder
if the value is not null
256 | *
257 | * @param sb: the string builder
258 | * @param desc: the description
259 | * @param val: the value
260 | */
261 | private static void appendNotNull(StringBuilder sb, String desc, String val) {
262 | if (val != null) {
263 | sb.append(desc).append(SEP).append(val).append(NEW_LINE);
264 | }
265 | }
266 |
267 | /**
268 | * Append a value in a StringBuilder
if the value is positive
269 | *
270 | * @param sb: the string builder
271 | * @param desc: the description
272 | * @param val: the value
273 | */
274 | private static void appendPositive(StringBuilder sb, String desc, int val) {
275 | if (val > 0) {
276 | sb.append(desc).append(SEP).append(Integer.toString(val)).append(NEW_LINE);
277 | }
278 | }
279 |
280 | // ===================== getter and setter start =====================
281 |
282 | public String getTitle() {
283 | return this.title;
284 | }
285 |
286 | public void setTitle(String title) {
287 | this.title = title;
288 | }
289 |
290 | public String getOriginalScript() {
291 | return this.originalScript;
292 | }
293 |
294 | public void setOriginalScript(String originalScript) {
295 | this.originalScript = originalScript;
296 | }
297 |
298 | public String getOriginalTranslation() {
299 | return this.originalTranslation;
300 | }
301 |
302 | public void setOriginalTranslation(String originalTranslation) {
303 | this.originalTranslation = originalTranslation;
304 | }
305 |
306 | public String getOriginalEditing() {
307 | return this.originalEditing;
308 | }
309 |
310 | public void setOriginalEditing(String originalEditing) {
311 | this.originalEditing = originalEditing;
312 | }
313 |
314 | public String getOriginalTiming() {
315 | return this.originalTiming;
316 | }
317 |
318 | public void setOriginalTiming(String originalTiming) {
319 | this.originalTiming = originalTiming;
320 | }
321 |
322 | public String getSynchPoint() {
323 | return this.synchPoint;
324 | }
325 |
326 | public void setSynchPoint(String synchPoint) {
327 | this.synchPoint = synchPoint;
328 | }
329 |
330 | public String getOriginalScriptChecking() {
331 | return this.originalScriptChecking;
332 | }
333 |
334 | public void setOriginalScriptChecking(String originalScriptChecking) {
335 | this.originalScriptChecking = originalScriptChecking;
336 | }
337 |
338 | public String getScriptUpdatedBy() {
339 | return this.scriptUpdatedBy;
340 | }
341 |
342 | public void setScriptUpdatedBy(String scriptUpdatedBy) {
343 | this.scriptUpdatedBy = scriptUpdatedBy;
344 | }
345 |
346 | public String getUserDetails() {
347 | return this.userDetails;
348 | }
349 |
350 | public void setUserDetails(String userDetails) {
351 | this.userDetails = userDetails;
352 | }
353 |
354 | public String getScriptType() {
355 | return this.scriptType;
356 | }
357 |
358 | public void setScriptType(String scriptType) {
359 | this.scriptType = scriptType;
360 | }
361 |
362 | public Collision getCollisions() {
363 | return this.collisions;
364 | }
365 |
366 | public void setCollisions(Collision collisions) {
367 | this.collisions = collisions;
368 | }
369 |
370 | public int getPlayResY() {
371 | return this.playResY;
372 | }
373 |
374 | public void setPlayResY(int playResY) {
375 | this.playResY = playResY;
376 | }
377 |
378 | public int getPlayResX() {
379 | return this.playResX;
380 | }
381 |
382 | public void setPlayResX(int playResX) {
383 | this.playResX = playResX;
384 | }
385 |
386 | public int getPlayDepth() {
387 | return this.playDepth;
388 | }
389 |
390 | public void setPlayDepth(int playDepth) {
391 | this.playDepth = playDepth;
392 | }
393 |
394 | public double getTimer() {
395 | return this.timer;
396 | }
397 |
398 | public void setTimer(double timer) {
399 | this.timer = timer;
400 | }
401 |
402 | }
403 |
--------------------------------------------------------------------------------
/submerge-api/src/main/java/com/github/dnbn/submerge/api/subtitle/ass/V4Style.java:
--------------------------------------------------------------------------------
1 | package com.github.dnbn.submerge.api.subtitle.ass;
2 |
3 | import java.io.Serializable;
4 |
5 | /**
6 | * Styles define the appearance and position of subtitles. All styles used by the script
7 | * are are defined by a Style line in the script.
8 | *
9 | * Any of the the settings in the Style, (except shadow/outline type and depth) can
10 | * overridden by control codes in the subtitle text.
11 | *
12 | * The fields which appear in each Style definition line are named in a special line with
13 | * the line type “Format:”. The Format line must appear before any Styles - because it
14 | * defines how SSA will interpret the Style definition lines. The field names listed in
15 | * the format line must be correctly spelled!
16 | *
17 | * The fields are as follows:
18 | *
19 | * Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, TertiaryColour, BackColour,
20 | * Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle,
21 | * Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
22 | *
23 | * The format line allows new fields to be added to the script format in future, and yet
24 | * allow old versions of the software to read the fields it recognises - even if the field
25 | * order is changed.
26 | */
27 | public class V4Style implements Serializable {
28 |
29 | /**
30 | * Serial
31 | */
32 | private static final long serialVersionUID = -4910432063071707768L;
33 |
34 | /**
35 | * Style declaration
36 | */
37 | public static final String STYLE = "Style: ";
38 |
39 | /**
40 | * Format declaration
41 | */
42 | public static final String FORMAT_STRING = "Name,Fontname,Fontsize,PrimaryColour,"
43 | + "SecondaryColour,OutlineColour,BackColour,Bold,Italic,Underline,"
44 | + "StrikeOut,ScaleX,ScaleY,Spacing,Angle,BorderStyle,Outline,Shadow,"
45 | + "Alignment,MarginL,MarginR,MarginV,Encoding";
46 |
47 | /**
48 | * Separator
49 | */
50 | public static final String SEP = ",";
51 |
52 | /**
53 | * The name of the Style. Case sensitive. Cannot include commas.
54 | */
55 | private String name;
56 |
57 | /**
58 | * The fontname as used by Windows. Case-sensitive.
59 | */
60 | private String fontname = "Arial";
61 |
62 | /**
63 | * The font size
64 | */
65 | private int fontsize;
66 |
67 | /**
68 | * A long integer BGR (blue-green-red) value. ie. the byte order in the hexadecimal
69 | * equivelent of this number is BBGGRR
70 | *
71 | * The color format contains the alpha channel, too. (AABBGGRR)
72 | */
73 | private int primaryColour;
74 |
75 | /**
76 | * long integer BGR (blue-green-red) value. ie. the byte order in the hexadecimal
77 | * equivelent of this number is BBGGRR
78 | *
79 | * This colour may be used instead of the Primary colour when a subtitle is
80 | * automatically shifted to prevent an onscreen collsion, to distinguish the different
81 | * subtitles.
82 | *
83 | * The color format contains the alpha channel, too. (AABBGGRR)
84 | */
85 | private int secondaryColour = 16777215; // #FFFFFF (white)
86 |
87 | /**
88 | * A long integer BGR (blue-green-red) value. ie. the byte order in the hexadecimal
89 | * equivelent of this number is BBGGRR
90 | *
91 | * This colour may be used instead of the Primary or Secondary colour when a subtitle
92 | * is automatically shifted to prevent an onscreen collsion, to distinguish the
93 | * different subtitles.
94 | *
95 | * The color format contains the alpha channel, too. (AABBGGRR)
96 | */
97 | private int outlineColour;
98 |
99 | /**
100 | * This is the colour of the subtitle outline or shadow, if these are used. A long
101 | * integer BGR (blue-green-red) value. ie. the byte order in the hexadecimal
102 | * equivelent of this number is BBGGRR.
103 | *
104 | * The color format contains the alpha channel, too. (AABBGGRR)
105 | */
106 | private int backColour;
107 |
108 | /**
109 | * This defines whether text is bold (true) or not (false). -1 is True, 0 is False.
110 | * This is independant of the Italic attribute - you can have have text which is both
111 | * bold and italic.
112 | */
113 | private boolean bold;
114 |
115 | /**
116 | * This defines whether text is italic (true) or not (false). -1 is True, 0 is False.
117 | * This is independant of the bold attribute - you can have have text which is both
118 | * bold and italic.
119 | */
120 | private boolean italic;
121 |
122 | /**
123 | * -1 is True, 0 is False
124 | */
125 | private boolean underline;
126 |
127 | /**
128 | * -1 is True, 0 is False
129 | */
130 | private boolean strikeOut;
131 |
132 | /**
133 | * Modifies the width of the font. [percent]
134 | */
135 | private int scaleX = 100;
136 |
137 | /**
138 | * Modifies the height of the font. [percent]
139 | */
140 | private int scaleY = 100;
141 |
142 | /**
143 | * Extra space between characters. [pixels]
144 | */
145 | private int spacing;
146 |
147 | /**
148 | * The origin of the rotation is defined by the alignment. Can be a floating point
149 | * number. [degrees]
150 | */
151 | private double angle;
152 |
153 | /**
154 | * 1=Outline + drop shadow, 3=Opaque box
155 | */
156 | private int borderStyle = 1;
157 |
158 | /**
159 | * If BorderStyle is 1, then this specifies the width of the outline around the text,
160 | * in pixels. Values may be 0, 1, 2, 3 or 4.
161 | */
162 | private int outline = 2;
163 |
164 | /**
165 | * If BorderStyle is 1, then this specifies the depth of the drop shadow behind the
166 | * text, in pixels. Values may be 0, 1, 2, 3 or 4. Drop shadow is always used in
167 | * addition to an outline - SSA will force an outline of 1 pixel if no outline width
168 | * is given.
169 | */
170 | private int shadow;
171 |
172 | /**
173 | * This sets how text is "justified" within the Left/Right onscreen margins, and also
174 | * the vertical placing. Values may be 1=Left, 2=Centered, 3=Right. Add 4 to the value
175 | * for a "Toptitle". Add 8 to the value for a "Midtitle". eg. 5 = left-justified
176 | * toptitle
177 | */
178 | private int alignment = 2;
179 |
180 | /**
181 | * This defines the Left Margin in pixels. It is the distance from the left-hand edge
182 | * of the screen.The three onscreen margins (MarginL, MarginR, MarginV) define areas
183 | * in which the subtitle text will be displayed.
184 | */
185 | private int marginL = 10;
186 |
187 | /**
188 | * This defines the Right Margin in pixels. It is the distance from the right-hand
189 | * edge of the screen. The three onscreen margins (MarginL, MarginR, MarginV) define
190 | * areas in which the subtitle text will be displayed.
191 | */
192 | private int marginR = 10;
193 |
194 | /**
195 | * This defines the vertical Left Margin in pixels. For a subtitle, it is the distance
196 | * from the bottom of the screen. For a toptitle, it is the distance from the top of
197 | * the screen. For a midtitle, the value is ignored - the text will be vertically
198 | * centred
199 | */
200 | private int marginV = 10;
201 |
202 | /**
203 | * This specifies the font character set or encoding and on multi-lingual Windows
204 | * installations it provides access to characters used in multiple than one languages.
205 | * It is usually 0 (zero) for English (Western, ANSI) Windows.
206 | *
207 | * When the file is Unicode, this field is useful during file format conversions.
208 | */
209 | private int encoding;
210 |
211 | /**
212 | * Default constructor
213 | */
214 | public V4Style() {
215 | }
216 |
217 | /**
218 | * Constructor
219 | *
220 | * @param name: the style name
221 | */
222 | public V4Style(String name) {
223 | this.name = name;
224 | }
225 |
226 | @Override
227 | public String toString() {
228 | StringBuilder sb = new StringBuilder();
229 | sb.append(STYLE);
230 | sb.append(this.name).append(SEP);
231 | sb.append(this.fontname).append(SEP);
232 | sb.append(this.fontsize).append(SEP);
233 | sb.append(this.primaryColour).append(SEP);
234 | sb.append(this.secondaryColour).append(SEP);
235 | sb.append(this.outlineColour).append(SEP);
236 | sb.append(this.backColour).append(SEP);
237 | sb.append(this.bold ? -1 : 0).append(SEP);
238 | sb.append(this.italic ? -1 : 0).append(SEP);
239 | sb.append(this.underline ? -1 : 0).append(SEP);
240 | sb.append(this.strikeOut ? -1 : 0).append(SEP);
241 | sb.append(this.scaleX).append(SEP);
242 | sb.append(this.scaleY).append(SEP);
243 | sb.append(this.spacing).append(SEP);
244 | sb.append(this.angle).append(SEP);
245 | sb.append(this.borderStyle).append(SEP);
246 | sb.append(this.outline).append(SEP);
247 | sb.append(this.shadow).append(SEP);
248 | sb.append(this.alignment).append(SEP);
249 | sb.append(this.marginL).append(SEP);
250 | sb.append(this.marginR).append(SEP);
251 | sb.append(this.marginV).append(SEP);
252 | sb.append(this.encoding);
253 | return sb.toString();
254 | }
255 |
256 | // ===================== getter and setter start =====================
257 |
258 | public String getName() {
259 | return this.name;
260 | }
261 |
262 | public void setName(String name) {
263 | this.name = name;
264 | }
265 |
266 | public String getFontname() {
267 | return this.fontname;
268 | }
269 |
270 | public void setFontname(String fontname) {
271 | this.fontname = fontname;
272 | }
273 |
274 | public int getFontsize() {
275 | return this.fontsize;
276 | }
277 |
278 | public void setFontsize(int fontsize) {
279 | this.fontsize = fontsize;
280 | }
281 |
282 | public int getPrimaryColour() {
283 | return this.primaryColour;
284 | }
285 |
286 | public void setPrimaryColour(int primaryColour) {
287 | this.primaryColour = primaryColour;
288 | }
289 |
290 | public int getSecondaryColour() {
291 | return this.secondaryColour;
292 | }
293 |
294 | public void setSecondaryColour(int secondaryColour) {
295 | this.secondaryColour = secondaryColour;
296 | }
297 |
298 | public int getOutlineColour() {
299 | return this.outlineColour;
300 | }
301 |
302 | public void setOutlineColor(int outlineColor) {
303 | this.outlineColour = outlineColor;
304 | }
305 |
306 | public int getBackColour() {
307 | return this.backColour;
308 | }
309 |
310 | public void setBackColour(int backColour) {
311 | this.backColour = backColour;
312 | }
313 |
314 | public boolean isBold() {
315 | return this.bold;
316 | }
317 |
318 | public void setBold(boolean bold) {
319 | this.bold = bold;
320 | }
321 |
322 | public boolean isItalic() {
323 | return this.italic;
324 | }
325 |
326 | public void setItalic(boolean italic) {
327 | this.italic = italic;
328 | }
329 |
330 | public boolean isUnderline() {
331 | return this.underline;
332 | }
333 |
334 | public void setUnderline(boolean underline) {
335 | this.underline = underline;
336 | }
337 |
338 | public boolean isStrikeOut() {
339 | return this.strikeOut;
340 | }
341 |
342 | public void setStrikeOut(boolean strikeOut) {
343 | this.strikeOut = strikeOut;
344 | }
345 |
346 | public void setOutlineColour(int outlineColour) {
347 | this.outlineColour = outlineColour;
348 | }
349 |
350 | public int getScaleX() {
351 | return this.scaleX;
352 | }
353 |
354 | public void setScaleX(int scaleX) {
355 | this.scaleX = scaleX;
356 | }
357 |
358 | public int getScaleY() {
359 | return this.scaleY;
360 | }
361 |
362 | public void setScaleY(int scaleY) {
363 | this.scaleY = scaleY;
364 | }
365 |
366 | public int getSpacing() {
367 | return this.spacing;
368 | }
369 |
370 | public void setSpacing(int spacing) {
371 | this.spacing = spacing;
372 | }
373 |
374 | public double getAngle() {
375 | return this.angle;
376 | }
377 |
378 | public void setAngle(double angle) {
379 | this.angle = angle;
380 | }
381 |
382 | public int getOutline() {
383 | return this.outline;
384 | }
385 |
386 | public void setOutline(int outline) {
387 | this.outline = outline;
388 | }
389 |
390 | public int getShadow() {
391 | return this.shadow;
392 | }
393 |
394 | public void setShadow(int shadow) {
395 | this.shadow = shadow;
396 | }
397 |
398 | public int getAlignment() {
399 | return this.alignment;
400 | }
401 |
402 | public void setAlignment(int alignment) {
403 | this.alignment = alignment;
404 | }
405 |
406 | public int getMarginL() {
407 | return this.marginL;
408 | }
409 |
410 | public void setMarginL(int marginL) {
411 | this.marginL = marginL;
412 | }
413 |
414 | public int getMarginR() {
415 | return this.marginR;
416 | }
417 |
418 | public void setMarginR(int marginR) {
419 | this.marginR = marginR;
420 | }
421 |
422 | public int getMarginV() {
423 | return this.marginV;
424 | }
425 |
426 | public void setMarginV(int marginV) {
427 | this.marginV = marginV;
428 | }
429 |
430 | public int getEncoding() {
431 | return this.encoding;
432 | }
433 |
434 | public void setEncoding(int encoding) {
435 | this.encoding = encoding;
436 | }
437 |
438 | public int getBorderStyle() {
439 | return this.borderStyle;
440 | }
441 |
442 | public void setBorderStyle(int borderStyle) {
443 | this.borderStyle = borderStyle;
444 | }
445 |
446 | }
447 |
--------------------------------------------------------------------------------
/submerge-api/src/main/java/com/github/dnbn/submerge/api/subtitle/common/SubtitleLine.java:
--------------------------------------------------------------------------------
1 | package com.github.dnbn.submerge.api.subtitle.common;
2 |
3 | import java.util.ArrayList;
4 | import java.util.Comparator;
5 | import java.util.List;
6 |
7 | public class SubtitleLineLocalTime
to string
38 | *
39 | * @param time: the time to format
40 | * @return the formatted time
41 | */
42 | public static String format(long time) {
43 | //
44 | // int hr = time.get(ChronoField.HOUR_OF_DAY);
45 | // int min = time.get(ChronoField.MINUTE_OF_HOUR);
46 | // int sec = time.get(ChronoField.SECOND_OF_MINUTE);
47 | // int ms = time.get(ChronoField.MILLI_OF_SECOND);
48 | long ms = time % 1000;
49 | long s = time / 1000;
50 | long hh = s / 60 / 60;
51 | long min = (s / 60) % 60;
52 | long sec = s % 60;
53 |
54 | return String.format(TS_PATTERN, hh, min, sec, ms);
55 | }
56 |
57 |
58 | /**
59 | * @param in string like 10:23:56,909
60 | * @return long milliseconds
61 | */
62 | public static long fromString(String in) {
63 | String[] split1 = in.split(":");
64 | long hours = Long.parseLong(split1[0].trim());
65 | long minutes = Long.parseLong(split1[1].trim());
66 | long seconds = 0;
67 | long millies = 0;
68 | if (split1.length > 2) {
69 | String[] split = split1[2].split(",");//SRT format
70 | seconds = Long.parseLong(split[0].trim());
71 | if (split.length > 1) {
72 | millies = Long.parseLong(split[1].trim());
73 | } else {
74 | System.out.println("in = [" + in + "]");
75 | }
76 | } else {
77 | System.out.println("in = [" + in + "] split1:" + Arrays.toString(split1));
78 | }
79 | return hours * 60 * 60 * 1000 + minutes * 60 * 1000 + seconds * 1000 + millies;
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/submerge-api/src/main/java/com/github/dnbn/submerge/api/utils/ColorUtils.java:
--------------------------------------------------------------------------------
1 | package com.github.dnbn.submerge.api.utils;
2 |
3 |
4 | import com.github.dnbn.submerge.api.parser.exception.InvalidColorCode;
5 |
6 |
7 | public final class ColorUtils {
8 |
9 | /**
10 | * Convert the hexadecimal color code to BGR code
11 | *
12 | * @param hex
13 | * @return
14 | */
15 | public static int hexToRGB(String hex) {
16 | // TODO: 2019/1/4 revert alpha
17 | System.out.println("hex = [" + hex + "]");
18 | // int in = Color.parseColor(hex);
19 | // int red = (in) & 0xFF;
20 | // int green = (in >> 8) & 0xFF;
21 | // int blue = (in >> 16) & 0xFF;
22 | // return (red << 16) | (blue) | (green << 8);
23 | return 0;
24 | }
25 |
26 | /**
27 | * Convert a &HAABBGGRR to hexadecimal
28 | *
29 | * @param haabbggrr: the color code
30 | * @return the hexadecimal code
31 | * @throws InvalidColorCode
32 | */
33 | public static String HAABBGGRRToHex(String haabbggrr) {
34 | if (haabbggrr.length() != 10) {
35 | throw new InvalidColorCode("Invalid pattern, must be &HAABBGGRR");
36 | }
37 | StringBuilder sb = new StringBuilder();
38 | sb.append("#");
39 | sb.append(haabbggrr.substring(8));
40 | sb.append(haabbggrr, 6, 8);
41 | sb.append(haabbggrr, 4, 6);
42 | sb.append(haabbggrr, 2, 4);
43 | return sb.toString().toLowerCase();
44 | }
45 |
46 | /**
47 | * Convert a &HBBGGRR to hexadecimal
48 | *
49 | * @param hbbggrr: the color code
50 | * @return the hexadecimal code
51 | * @throws InvalidColorCode
52 | */
53 | public static String HBBGGRRToHex(String hbbggrr) {
54 | if (hbbggrr.length() != 8) {
55 | throw new InvalidColorCode("Invalid pattern, must be &HBBGGRR");
56 | }
57 | StringBuilder sb = new StringBuilder();
58 | sb.append("#");
59 | sb.append(hbbggrr.substring(6));
60 | sb.append(hbbggrr, 4, 6);
61 | sb.append(hbbggrr, 2, 4);
62 | return sb.toString().toLowerCase();
63 | }
64 |
65 | /**
66 | * Convert a &HAABBGGRR to BGR
67 | *
68 | * @param haabbggrr: the color code
69 | * @return the BGR code
70 | * @throws InvalidColorCode
71 | */
72 | public static int HAABBGGRRToRGB(String haabbggrr) {
73 | return hexToRGB(HAABBGGRRToHex(haabbggrr));
74 | }
75 |
76 | /**
77 | * Convert a &HBBGGRR to BGR
78 | *
79 | * @param hbbggrr: the color code
80 | * @return the BGR code
81 | * @throws InvalidColorCode
82 | */
83 | public static int HBBGGRRToRGB(String hbbggrr) {
84 | return hexToRGB(HBBGGRRToHex(hbbggrr));
85 | }
86 |
87 | }
88 |
--------------------------------------------------------------------------------
/submerge-api/src/main/java/com/github/dnbn/submerge/api/utils/ConvertionUtils.java:
--------------------------------------------------------------------------------
1 | package com.github.dnbn.submerge.api.utils;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 |
6 | import org.apache.commons.lang.StringUtils;
7 |
8 | import com.github.dnbn.submerge.api.subtitle.ass.ASSTime;
9 | import com.github.dnbn.submerge.api.subtitle.ass.Events;
10 | import com.github.dnbn.submerge.api.subtitle.ass.V4Style;
11 | import com.github.dnbn.submerge.api.subtitle.common.TimedLine;
12 | import com.github.dnbn.submerge.api.subtitle.common.TimedObject;
13 | import com.github.dnbn.submerge.api.subtitle.config.Font;
14 | import com.github.dnbn.submerge.api.subtitle.config.SimpleSubConfig;
15 |
16 | public class ConvertionUtils {
17 |
18 | private static final String RGX_XML_TAG = "<[^>]+>";
19 | private static final String RGX_ASS_FORMATTING = "\\{[^\\}]*\\}";
20 | private static final String SRT_ITALIC_CLOSE = "\\";
21 | private static final String SRT_ITALIC_OPEN = "\\";
22 | private static final String ASS_ITALIC_CLOSE = "\\{\\\\i0\\}";
23 | private static final String ASS_ITALIC_OPEN = "\\{\\\\i1\\}";
24 |
25 | /**
26 | * Create an Events
object from a timed line
27 | *
28 | * @param line: a timed line
29 | * @param style: the style name
30 | * @return the corresponding Events
31 | */
32 | public static Events createEvent(TimedLine line, String style) {
33 |
34 | ListV4Style
object from SubInput
47 | *
48 | * @param config: the configuration object
49 | * @return the corresponding style
50 | */
51 | public static V4Style createV4Style(SimpleSubConfig config) {
52 |
53 | V4Style style = new V4Style(config.getStyleName());
54 | Font font = config.getFontconfig();
55 | style.setFontname(font.getName());
56 | style.setFontsize(font.getSize());
57 | style.setAlignment(config.getAlignment());
58 | style.setPrimaryColour(ColorUtils.hexToRGB(font.getColor()));
59 | style.setOutlineColor(ColorUtils.hexToRGB(font.getOutlineColor()));
60 | style.setOutline(font.getOutlineWidth());
61 | style.setMarginV(config.getVerticalMargin());
62 | return style;
63 | }
64 |
65 | /**
66 | * Format a text line to be srt compliant
67 | *
68 | * @param textLine the text line
69 | * @return the formatted text line
70 | */
71 | public static String toSRTString(String textLine) {
72 |
73 | String formatted = textLine.replaceAll(ASS_ITALIC_OPEN, SRT_ITALIC_OPEN);
74 | formatted = formatted.replaceAll(ASS_ITALIC_CLOSE, SRT_ITALIC_CLOSE);
75 | formatted = formatted.replaceAll(RGX_ASS_FORMATTING, StringUtils.EMPTY);
76 |
77 | return formatted;
78 | }
79 |
80 | /**
81 | * Format a text line to be ass compliant
82 | *
83 | * @param textLine the text line
84 | * @return
85 | */
86 | public static String toASSString(String textLine) {
87 |
88 | String formatted = textLine.replaceAll(SRT_ITALIC_OPEN, ASS_ITALIC_OPEN);
89 | formatted = formatted.replaceAll(SRT_ITALIC_CLOSE, ASS_ITALIC_CLOSE);
90 |
91 | return formatted.replaceAll(RGX_XML_TAG, StringUtils.EMPTY);
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/submerge-api/src/main/java/com/github/dnbn/submerge/api/utils/FileUtils.java:
--------------------------------------------------------------------------------
1 | package com.github.dnbn.submerge.api.utils;
2 |
3 | import org.apache.commons.io.IOUtils;
4 | import org.mozilla.universalchardet.UniversalDetector;
5 |
6 | import java.io.File;
7 | import java.io.FileInputStream;
8 | import java.io.IOException;
9 | import java.io.InputStream;
10 |
11 |
12 | public class FileUtils {
13 |
14 | /**
15 | * Detect charset encoding of a file
16 | *
17 | * @param file: the file to detect encoding from
18 | * @return the charset encoding
19 | * @throws IOException
20 | */
21 | public static String guessEncoding(File file) throws IOException {
22 | try (FileInputStream is = new FileInputStream(file)) {
23 | return guessEncoding(is);
24 | }
25 | }
26 |
27 | /**
28 | * Detect charset encoding of an input stream
29 | *
30 | * @param is: the InputStream to detect encoding from
31 | * @return the charset encoding
32 | * @throws IOException
33 | */
34 | public static String guessEncoding(InputStream is) throws IOException {
35 | return guessEncoding(IOUtils.toByteArray(is));
36 | }
37 |
38 | /**
39 | * Detect charset encoding of a byte array
40 | *
41 | * @param bytes: the byte array to detect encoding from
42 | * @return the charset encoding
43 | */
44 | public static String guessEncoding(byte[] bytes) {
45 | UniversalDetector detector = new UniversalDetector(null);
46 |
47 | detector.handleData(bytes, 0, bytes.length);
48 | detector.dataEnd();
49 |
50 | String encoding = detector.getDetectedCharset();
51 | detector.reset();
52 |
53 | if (encoding == null || "MACCYRILLIC".equals(encoding)) {
54 | // juniversalchardet incorrectly detects windows-1256 as MACCYRILLIC
55 | // If encoding is MACCYRILLIC or null, we use ICU4J
56 | // CharsetMatch detected = new CharsetDetector().setText(bytes).detect();
57 | // if (detected != null) {
58 | // encoding = detected.getName();
59 | // } else {
60 | encoding = "UTF-8";
61 | // }
62 | }
63 |
64 | return encoding;
65 | }
66 |
67 | }
68 |
--------------------------------------------------------------------------------
/submerge-api/src/test/java/com.github.dnbn.submerge.api/Test.java:
--------------------------------------------------------------------------------
1 | package com.github.dnbn.submerge.api;
2 |
3 | import com.github.dnbn.submerge.api.parser.ASSParser;
4 | import com.github.dnbn.submerge.api.parser.SRTParser;
5 | import com.github.dnbn.submerge.api.subtitle.ass.ASSSub;
6 | import com.github.dnbn.submerge.api.subtitle.common.TimedLine;
7 | import com.github.dnbn.submerge.api.subtitle.srt.SRTSub;
8 |
9 | import java.io.File;
10 | import java.util.ArrayList;
11 | import java.util.Arrays;
12 | import java.util.List;
13 | import java.util.Set;
14 |
15 | public class Test {
16 |
17 | private static boolean isSrt;
18 |
19 | public static void main(String args[]) {
20 |
21 | Set extends TimedLine> timedLines = getTimedLines();
22 | printTimeLine(timedLines, isSrt);
23 |
24 | }
25 |
26 | public static Set extends TimedLine> getTimedLines() {
27 | File assFile = new File("E:\\有字幕的视频\\Game.of.Thrones.S06E10.1080p.HDTV.x264-BATV.简体.ass");
28 | File srtFile = new File("C:\\Users\\jianyou.lin\\Downloads\\interstellar.(2014).chi.1cd.(7601884)\\interstellar.srt");
29 | isSrt = true;
30 |
31 |
32 | // for (TimedLine next : parse.getTimedLines()) {
33 | // LocalTime start = next.getTime().getStart();
34 | // LocalTime end = next.getTime().getEnd();
35 | // List