/****************************************************************************** Copyright (C) 2019-2020 by Dillon Pentz This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ******************************************************************************/ #include "importers.hpp" #include #include using namespace std; using namespace json11; static int hex_string_to_int(string str) { int res = 0; if (str[0] == '#') str = str.substr(1); for (size_t i = 0, l = str.size(); i < l; i++) { res *= 16; if (str[0] >= '0' && str[0] <= '9') res += str[0] - '0'; else res += str[0] - 'A' + 10; str = str.substr(1); } return res; } static Json::object parse_text(QString &config) { int start = config.indexOf("*{"); config = config.mid(start + 1); config.replace("\\", "/"); string err; Json data = Json::parse(config.toStdString(), err); if (err != "") return Json::object{}; string outline = data["outline"].string_value(); int out = 0; if (outline == "thick") out = 20; else if (outline == "thicker") out = 40; else if (outline == "thinner") out = 5; else if (outline == "thin") out = 10; string valign = data["vertAlign"].string_value(); if (valign == "middle") valign = "center"; Json font = Json::object{{"face", data["fontStyle"]}, {"size", 200}}; return Json::object{ {"text", data["text"]}, {"font", font}, {"outline", out > 0}, {"outline_size", out}, {"outline_color", hex_string_to_int(data["outlineColor"].string_value())}, {"color", hex_string_to_int(data["color"].string_value())}, {"align", data["textAlign"]}, {"valign", valign}, {"alpha", data["opacity"]}}; } static Json::array parse_playlist(QString &playlist) { Json::array out = Json::array{}; while (true) { int end = playlist.indexOf('*'); QString path = playlist.left(end); out.push_back(Json::object{{"value", path.toStdString()}}); int next = playlist.indexOf('|'); if (next == -1) break; playlist = playlist.mid(next + 1); } return out; } static void parse_media_types(QDomNamedNodeMap &attr, Json::object &source, Json::object &settings) { QString playlist = attr.namedItem("FilePlaylist").nodeValue(); if (playlist != "") { source["id"] = "vlc_source"; settings["playlist"] = parse_playlist(playlist); QString end_op = attr.namedItem("OpWhenFinished").nodeValue(); if (end_op == "2") settings["loop"] = true; } else { QString url = attr.namedItem("item").nodeValue(); int sep = url.indexOf("://"); if (sep != -1) { QString prot = url.left(sep); if (prot == "smlndi") { source["id"] = "ndi_source"; } else { source["id"] = "ffmpeg_source"; int info = url.indexOf("\\"); QString input; if (info != -1) { input = url.left(info); } else { input = url; } settings["input"] = input.toStdString(); settings["is_local_file"] = false; } } else { source["id"] = "ffmpeg_source"; settings["local_file"] = url.replace("\\", "/").toStdString(); settings["is_local_file"] = true; } } } static Json::object parse_slideshow(QString &config) { int start = config.indexOf("images\":["); if (start == -1) return Json::object{}; config = config.mid(start + 8); config.replace("\\\\", "/"); int end = config.indexOf(']'); if (end == -1) return Json::object{}; string arr = config.left(end + 1).toStdString(); string err; Json::array files = Json::parse(arr, err).array_items(); if (err != "") return Json::object{}; Json::array files_out = Json::array{}; for (size_t i = 0; i < files.size(); i++) { string file = files[i].string_value(); files_out.push_back(Json::object{{"value", file}}); } QString options = config.mid(end + 1); options[0] = '{'; Json opt = Json::parse(options.toStdString(), err); if (err != "") return Json::object{}; return Json::object{{"randomize", opt["random"]}, {"slide_time", opt["delay"].number_value() * 1000 + 700}, {"files", files_out}}; } static bool source_name_exists(const string &name, const Json::array &sources) { for (size_t i = 0; i < sources.size(); i++) { if (sources.at(i)["name"].string_value() == name) return true; } return false; } static Json get_source_with_id(const string &src_id, const Json::array &sources) { for (size_t i = 0; i < sources.size(); i++) { if (sources.at(i)["src_id"].string_value() == src_id) return sources.at(i); } return nullptr; } static void parse_items(QDomNode &item, Json::array &items, Json::array &sources) { while (!item.isNull()) { QDomNamedNodeMap attr = item.attributes(); QString srcid = attr.namedItem("srcid").nodeValue(); double vol = attr.namedItem("volume").nodeValue().toDouble(); int type = attr.namedItem("type").nodeValue().toInt(); string name; Json::object settings; Json::object source; string temp_name; int x = 0; Json exists = get_source_with_id(srcid.toStdString(), sources); if (!exists.is_null()) { name = exists["name"].string_value(); goto skip; } name = attr.namedItem("cname").nodeValue().toStdString(); if (name.empty() || name[0] == '\0') name = attr.namedItem("name").nodeValue().toStdString(); temp_name = name; while (source_name_exists(temp_name, sources)) { string new_name = name + " " + to_string(x++); temp_name = new_name; } name = temp_name; settings = Json::object{}; source = Json::object{{"name", name}, {"src_id", srcid.toStdString()}, {"volume", vol}}; /** type=1 means Media of some kind (Video Playlist, RTSP, RTMP, NDI or Media File). type=2 means either a DShow or WASAPI source. type=4 means an Image source. type=5 means either a Display or Window Capture. type=7 means a Game Capture. type=8 means rendered with a browser, which includes: Web Page, Image Slideshow, Text. type=11 means another Scene. **/ if (type == 1) { parse_media_types(attr, source, settings); } else if (type == 2) { QString audio = attr.namedItem("itemaudio").nodeValue(); if (audio.isEmpty()) { source["id"] = "dshow_input"; } else { source["id"] = "wasapi_input_capture"; int dev = audio.indexOf("\\wave:") + 6; QString res = "{0.0.1.00000000}." + audio.mid(dev); res = res.toLower(); settings["device_id"] = res.toStdString(); } } else if (type == 4) { source["id"] = "image_source"; QString path = attr.namedItem("item").nodeValue(); path.replace("\\", "/"); settings["file"] = path.toStdString(); } else if (type == 5) { QString opt = attr.namedItem("item").nodeValue(); QDomDocument options; options.setContent(opt); QDomNode el = options.documentElement(); QDomNamedNodeMap o_attr = el.attributes(); QString display = o_attr.namedItem("desktop").nodeValue(); if (!display.isEmpty()) { source["id"] = "monitor_capture"; int cursor = attr.namedItem("ScrCapShowMouse") .nodeValue() .toInt(); settings["capture_cursor"] = cursor == 1; } else { source["id"] = "window_capture"; QString exec = o_attr.namedItem("module").nodeValue(); QString window = o_attr.namedItem("window").nodeValue(); QString _class = o_attr.namedItem("class").nodeValue(); int pos = exec.lastIndexOf('\\'); if (_class.isEmpty()) { _class = "class"; } QString res = window + ":" + _class + ":" + exec.mid(pos + 1); settings["window"] = res.toStdString(); settings["priority"] = 2; } } else if (type == 7) { QString opt = attr.namedItem("item").nodeValue(); opt.replace("<", "<"); opt.replace(">", ">"); opt.replace(""", "\""); QDomDocument doc; doc.setContent(opt); QDomNode el = doc.documentElement(); QDomNamedNodeMap o_attr = el.attributes(); QString name = o_attr.namedItem("wndname").nodeValue(); QString exec = o_attr.namedItem("imagename").nodeValue(); QString res = name = "::" + exec; source["id"] = "game_capture"; settings["window"] = res.toStdString(); settings["capture_mode"] = "window"; } else if (type == 8) { QString plugin = attr.namedItem("item").nodeValue(); if (plugin.startsWith( "html:plugin:imageslideshowplg*")) { source["id"] = "slideshow"; settings = parse_slideshow(plugin); } else if (plugin.startsWith("html:plugin:titleplg")) { source["id"] = "text_gdiplus"; settings = parse_text(plugin); } else if (plugin.startsWith("http")) { source["id"] = "browser_source"; int end = plugin.indexOf('*'); settings["url"] = plugin.left(end).toStdString(); } } else if (type == 11) { QString id = attr.namedItem("item").nodeValue(); Json source = get_source_with_id(id.toStdString(), sources); name = source["name"].string_value(); goto skip; } source["settings"] = settings; sources.push_back(source); skip: struct obs_video_info ovi; obs_get_video_info(&ovi); int width = ovi.base_width; int height = ovi.base_height; double pos_left = attr.namedItem("pos_left").nodeValue().toDouble(); double pos_right = attr.namedItem("pos_right").nodeValue().toDouble(); double pos_top = attr.namedItem("pos_top").nodeValue().toDouble(); double pos_bottom = attr.namedItem("pos_bottom").nodeValue().toDouble(); bool visible = attr.namedItem("visible").nodeValue() == "1"; Json out_item = Json::object{ {"bounds_type", 2}, {"pos", Json::object{{"x", pos_left * width}, {"y", pos_top * height}}}, {"bounds", Json::object{{"x", (pos_right - pos_left) * width}, {"y", (pos_bottom - pos_top) * height}}}, {"name", name}, {"visible", visible}}; items.push_back(out_item); item = item.nextSibling(); } } static Json::object parse_scenes(QDomElement &scenes) { Json::array sources = Json::array{}; QString first = ""; QDomNode in_scene = scenes.firstChild(); while (!in_scene.isNull()) { QString type = in_scene.nodeName(); if (type == "placement") { QDomNamedNodeMap attr = in_scene.attributes(); QString name = attr.namedItem("name").nodeValue(); QString id = attr.namedItem("id").nodeValue(); if (first.isEmpty()) first = name; Json out = Json::object{ {"id", "scene"}, {"name", name.toStdString().c_str()}, {"src_id", id.toStdString().c_str()}}; sources.push_back(out); } in_scene = in_scene.nextSibling(); } in_scene = scenes.firstChild(); for (size_t i = 0, l = sources.size(); i < l; i++) { Json::object source = sources[i].object_items(); Json::array items = Json::array{}; QDomNode firstChild = in_scene.firstChild(); parse_items(firstChild, items, sources); Json settings = Json::object{{"items", items}, {"id_counter", (int)items.size()}}; source["settings"] = settings; sources[i] = source; in_scene = in_scene.nextSibling(); } return Json::object{{"sources", sources}, {"current_scene", first.toStdString()}, {"current_program_scene", first.toStdString()}}; } int XSplitImporter::ImportScenes(const string &path, string &name, json11::Json &res) { if (name == "") name = "XSplit Import"; BPtr file_data = os_quick_read_utf8_file(path.c_str()); if (!file_data) return IMPORTER_FILE_WONT_OPEN; QDomDocument doc; doc.setContent(QString(file_data)); QDomElement docElem = doc.documentElement(); Json::object r = parse_scenes(docElem); r["name"] = name; res = r; QDir dir(path.c_str()); TranslateOSStudio(res); TranslatePaths(res, QDir::cleanPath(dir.filePath("..")).toStdString()); return IMPORTER_SUCCESS; } bool XSplitImporter::Check(const string &path) { bool check = false; BPtr file_data = os_quick_read_utf8_file(path.c_str()); if (!file_data) return false; string pos = file_data.Get(); string line = ReadLine(pos); while (!line.empty()) { if (line.substr(0, 5) == "d_name; if (ent->directory || name[0] == '.') continue; if (name == "Placements.bpres") { string str = dst + name; res.push_back(str); break; } } os_closedir(dir); #endif return res; }