diff options
| author | RblSb <msrblsb@gmail.com> | 2021-07-12 18:32:53 +0300 |
|---|---|---|
| committer | RblSb <msrblsb@gmail.com> | 2021-07-12 18:32:53 +0300 |
| commit | 6397cc81227c52ad0caf43885c20c00175cf1cc4 (patch) | |
| tree | 83794c1ab6dc9bed4056a15fd819c54219088f08 | |
| parent | afc010aeb7b7bdf750eaeb5c47db0fe345b492f5 (diff) | |
Basic ass subs support
| -rw-r--r-- | res/client.js | 108 | ||||
| -rw-r--r-- | src/client/players/RawSubs.hx | 151 |
2 files changed, 212 insertions, 47 deletions
diff --git a/res/client.js b/res/client.js index acc335f..11b6094 100644 --- a/res/client.js +++ b/res/client.js @@ -407,6 +407,25 @@ StringTools.rtrim = function(s) { StringTools.trim = function(s) { return StringTools.ltrim(StringTools.rtrim(s)); }; +StringTools.lpad = function(s,c,l) { + if(c.length <= 0) { + return s; + } + var buf_b = ""; + l -= s.length; + while(buf_b.length < l) buf_b += c == null ? "null" : "" + c; + buf_b += s == null ? "null" : "" + s; + return buf_b; +}; +StringTools.rpad = function(s,c,l) { + if(c.length <= 0) { + return s; + } + var buf_b = ""; + buf_b = "" + (s == null ? "null" : "" + s); + while(buf_b.length < l) buf_b += c == null ? "null" : "" + c; + return buf_b; +}; StringTools.replace = function(s,sub,by) { return s.split(sub).join(by); }; @@ -2701,9 +2720,10 @@ client_players_RawSubs.loadSubs = function(item,video) { if(client_JsApi.hasSubtitleSupport(ext)) { return; } - var url = "/proxy?url=" + client_players_RawSubs.encodeURI(item.subs); + var url = "/proxy?url=" + encodeURI(item.subs); switch(ext) { case "ass": + client_players_RawSubs.parseAss(video,url); break; case "srt": client_players_RawSubs.parseSrt(video,url); @@ -2740,11 +2760,91 @@ client_players_RawSubs.parseSrt = function(video,url) { data += "" + sub.time + "\n"; data += "" + sub.text + "\n\n"; } - haxe_Log.trace(data,{ fileName : "src/client/players/RawSubs.hx", lineNumber : 64, className : "client.players.RawSubs", methodName : "parseSrt"}); var url = "data:text/plain;base64," + haxe_crypto_Base64.encode(haxe_io_Bytes.ofString(data)); client_players_RawSubs.onParsed(video,"SRT subtitles",url); }); }; +client_players_RawSubs.parseAss = function(video,url) { + window.fetch(url).then(function(response) { + return response.text(); + }).then(function(text) { + var subs = []; + var lines = StringTools.replace(text,"\r\n","\n").split("\n"); + var matchFormat = new EReg("^Format:",""); + var matchDialogue = new EReg("^Dialogue:",""); + var blockTags_r = new RegExp("\\{\\\\[^}]*\\}","g".split("u").join("")); + var tags_r = new RegExp("\\\\[^ ]+","g".split("u").join("")); + var drawingMode = new EReg("\\\\p[124]",""); + var eventStart = false; + var formatFound = false; + var ids_h = Object.create(null); + var subsCounter = 1; + var _g = 0; + while(_g < lines.length) { + var line = StringTools.trim(lines[_g++]); + if(!eventStart) { + eventStart = StringTools.startsWith(line,"[Events]"); + continue; + } + if(!formatFound) { + formatFound = matchFormat.match(line); + if(!formatFound) { + continue; + } + var list = line.replace(matchFormat.r,"").split(","); + var _g1 = 0; + var _g2 = list.length; + while(_g1 < _g2) { + var i = _g1++; + ids_h[StringTools.trim(list[i])] = i; + } + ids_h["_length"] = list.length; + } + if(!matchDialogue.match(line)) { + continue; + } + var list1 = line.replace(matchDialogue.r,"").split(","); + while(list1.length > ids_h["_length"]) { + var el = list1.pop(); + list1[list1.length - 1] += el; + } + var result = new Array(list1.length); + var _g3 = 0; + var _g11 = list1.length; + while(_g3 < _g11) { + var i1 = _g3++; + result[i1] = StringTools.trim(list1[i1]); + } + list1 = result; + var text = result[ids_h["Text"]]; + if(drawingMode.match(text)) { + text = ""; + } + text = text.replace(blockTags_r,""); + text = text.replace(tags_r,""); + subs.push({ counter : subsCounter, start : client_players_RawSubs.convertAssTime(result[ids_h["Start"]]), end : client_players_RawSubs.convertAssTime(result[ids_h["End"]]), text : text}); + ++subsCounter; + } + var data = "WEBVTT\n\n"; + var _g = 0; + while(_g < subs.length) { + var sub = subs[_g]; + ++_g; + data += "" + sub.counter + "\n"; + data += "" + sub.start + " --> " + sub.end + "\n"; + data += "" + sub.text + "\n\n"; + } + var url = "data:text/plain;base64," + haxe_crypto_Base64.encode(haxe_io_Bytes.ofString(data)); + client_players_RawSubs.onParsed(video,"ASS subtitles",url); + }); +}; +client_players_RawSubs.convertAssTime = function(time) { + if(!client_players_RawSubs.assTimeStamp.match(time)) { + return "" + StringTools.lpad("" + 0,"0",2) + ":" + StringTools.lpad("" + 0,"0",2) + ":" + StringTools.lpad("" + 0,"0",2) + "." + HxOverrides.substr(StringTools.rpad("" + 0,"0",3),0,3); + } + var time = { h : Std.parseInt(client_players_RawSubs.assTimeStamp.matched(1)), m : Std.parseInt(client_players_RawSubs.assTimeStamp.matched(2)), s : Std.parseInt(client_players_RawSubs.assTimeStamp.matched(3)), ms : Std.parseInt(client_players_RawSubs.assTimeStamp.matched(4))}; + return "" + StringTools.lpad("" + time.h,"0",2) + ":" + StringTools.lpad("" + time.m,"0",2) + ":" + StringTools.lpad("" + time.s,"0",2) + "." + HxOverrides.substr(StringTools.rpad("" + time.ms,"0",3),0,3); +}; client_players_RawSubs.onParsed = function(video,name,dataUrl) { var trackEl = window.document.createElement("track"); trackEl.label = name; @@ -2754,9 +2854,6 @@ client_players_RawSubs.onParsed = function(video,name,dataUrl) { trackEl.track.mode = "showing"; video.appendChild(trackEl); }; -client_players_RawSubs.encodeURI = function(data) { - return encodeURI(data); -}; var client_players_Youtube = function(main,player) { this.matchSeconds = new EReg("([0-9]+)S",""); this.matchMinutes = new EReg("([0-9]+)M",""); @@ -3783,6 +3880,7 @@ client_JsApi.videoChange = []; client_JsApi.videoRemove = []; client_JsApi.onceListeners = []; client_Settings.isSupported = false; +client_players_RawSubs.assTimeStamp = new EReg("([0-9]+):([0-9][0-9]):([0-9][0-9]).([0-9][0-9])",""); haxe_crypto_Base64.CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; haxe_crypto_Base64.BYTES = haxe_io_Bytes.ofString(haxe_crypto_Base64.CHARS); js_youtube_Youtube.isLoadedAPI = false; diff --git a/src/client/players/RawSubs.hx b/src/client/players/RawSubs.hx index 4fcf3b6..cf37baa 100644 --- a/src/client/players/RawSubs.hx +++ b/src/client/players/RawSubs.hx @@ -25,6 +25,7 @@ class RawSubs { switch ext { case "ass": + parseAss(video, url); case "srt": parseSrt(video, url); case "vtt": @@ -61,57 +62,102 @@ class RawSubs { data += '${sub.time}\n'; data += '${sub.text}\n\n'; } - trace(data); final textBase64 = "data:text/plain;base64,"; final url = textBase64 + Base64.encode(Bytes.ofString(data)); onParsed(video, "SRT subtitles", url); }); } - // function saveSubs() { - // final options = [ - // for (i in 0...subsSelect.options.length) - // subsSelect.item(i) - // ]; - // var inputId = (cast el("#id-offset") : InputElement).value; - // var id = Std.parseInt(inputId); - // if (id == null) id = 1; - // final data:Array<String> = options.map(element -> { - // final value = element.textContent; - // final firstTime = Std.parseFloat(value.split("|")[0]); - // final secondTime = Std.parseFloat(value.split("|")[1]); - // // 00:00:00,498 --> 00:00:02,827 - // var time = '$id\n'; - // time += srvTimeFormat(stringDuration(firstTime)); - // time += " --> "; - // time += srvTimeFormat(stringDuration(secondTime)); - // time += '\ntext$id\n'; - // id++; - // return time; - // }); - // final data = data.join("\n"); - // Utils.saveFile("subs.srv", TextPlain, data); - // } + static function parseAss(video:VideoElement, url:String):Void { + window.fetch(url).then(response -> { + return response.text(); + }).then(text -> { + final subs:Array<{ + counter:Int, + start:String, + end:String, + text:String, + }> = []; + final lines = text.replace("\r\n", "\n").split("\n"); + final matchFormat = ~/^Format:/; + final matchDialogue = ~/^Dialogue:/; + final blockTags = ~/\{\\[^}]*\}/g; + final tags = ~/\\[^ ]+/g; + final drawingMode = ~/\\p[124]/; + var eventStart = false; + var formatFound = false; + final ids:Map<String, Int> = []; + var subsCounter = 1; + for (rawLine in lines) { + final line = rawLine.trim(); + if (!eventStart) { + eventStart = line.startsWith("[Events]"); + continue; + } - function stringDuration(seconds:Float):Duration { - final h = Std.int(seconds / 60 / 60); - final m = Std.int(seconds / 60) - h * 60; - final s = Std.int(seconds % 60); - final ms = Std.int((seconds - Std.int(seconds)) * 1000); - return { + if (!formatFound) { + formatFound = matchFormat.match(line); + if (!formatFound) continue; + final list = matchFormat.replace(line, "").split(","); + for (i in 0...list.length) { + ids[list[i].trim()] = i; + } + ids["_length"] = list.length; + } + + if (!matchDialogue.match(line)) continue; + var list = matchDialogue.replace(line, "").split(","); + while (list.length > ids["_length"]) { + final el = list.pop(); + list[list.length - 1] += el; + } + list = list.map((e) -> e.trim()); + var text = list[ids["Text"]]; + if (drawingMode.match(text)) text = ""; + text = blockTags.replace(text, ""); + text = tags.replace(text, ""); + subs.push({ + counter: subsCounter, + start: convertAssTime(list[ids["Start"]]), + end: convertAssTime(list[ids["End"]]), + text: text, + }); + subsCounter++; + } + + var data = "WEBVTT\n\n"; + for (sub in subs) { + data += '${sub.counter}\n'; + data += '${sub.start} --> ${sub.end}\n'; + data += '${sub.text}\n\n'; + } + final textBase64 = "data:text/plain;base64,"; + final url = textBase64 + Base64.encode(Bytes.ofString(data)); + onParsed(video, "ASS subtitles", url); + }); + } + + static final assTimeStamp = ~/([0-9]+):([0-9][0-9]):([0-9][0-9]).([0-9][0-9])/; + + static function convertAssTime(time:String):String { + if (!assTimeStamp.match(time)) { + return toVttTime({ + h: 0, + m: 0, + s: 0, + ms: 0, + }); + } + final h = Std.parseInt(assTimeStamp.matched(1)); + final m = Std.parseInt(assTimeStamp.matched(2)); + final s = Std.parseInt(assTimeStamp.matched(3)); + final ms = Std.parseInt(assTimeStamp.matched(4)); + return toVttTime({ h: h, m: m, s: s, - ms: ms - } - } - - function srvTimeFormat(time:Duration):String { - final h = '${time.h}'.lpad("0", 2); - final m = '${time.m}'.lpad("0", 2); - final s = '${time.s}'.lpad("0", 2); - final ms = '${time.ms}'.rpad("0", 3); - return '$h:$m:$s,$ms'; + ms: ms, + }); } static function parseVtt(video:VideoElement, url:String):Void { @@ -133,7 +179,28 @@ class RawSubs { video.appendChild(trackEl); } - static function encodeURI(data:String):String { + static inline function encodeURI(data:String):String { return js.Syntax.code("encodeURI({0})", data); } + + static inline function toVttTime(time:Duration):String { + final h = '${time.h}'.lpad("0", 2); + final m = '${time.m}'.lpad("0", 2); + final s = '${time.s}'.lpad("0", 2); + final ms = '${time.ms}'.rpad("0", 3).substr(0, 3); + return '$h:$m:$s.$ms'; + } + + static inline function secondsToDuration(seconds:Float):Duration { + final h = Std.int(seconds / 60 / 60); + final m = Std.int(seconds / 60) - h * 60; + final s = Std.int(seconds % 60); + final ms = Std.int((seconds - Std.int(seconds)) * 1000); + return { + h: h, + m: m, + s: s, + ms: ms + } + } } |
