Posts by Douglas Rhiner
-
-
In theory if the view can not be zoomed, which would produce a cleaner, crisper image; as Single-Resolution or Multi-Resolution <image>?
-
Look into https://dashjs.org/
This coupled with ffmpeg processing can yield impressive results. -
We have over 350,000 scenes. Each with it's own dataset that gets displayed/used. Our Database is quite large. But this is totally doable.
-
-
Klaus,
I upgraded our license, downloaded v1.22, registered, generated a new viewer .js file and updated plugins.Now it seems like KRPano is behaving as if “Allow loading only xml and plugin files from the same domain” had been checked when generating a viewer.
I trashed all previous versions of KRPanoTools and downloaded a fresh version of 1.22. Using Protect Tool to first Restore Defaults and then generate a new viewer, the same results are returned; Loading a cross domain xml file results in External Access Denied for cross (sub)domain xml loading.
Replacing the viewer with the previous version 1.21.1 results in everything operating just fine.
I've repeated this loop a few times, getting the same results. Defining insanity...
Is there a way to completely clear out any and all caches that might hold KRPano info when generating a viewer?
Any idea on this one?
Illustrating the loading error.
Showing that the URL involved in the error above is directly accessible.
KRPanoTolls Protect Settings shows the settings used to generate a viewer.
-
The plugin is a great concept!
The fun part is getting any 360video you have captured into HLS &/or DASH format. I purpose-built a PC 3 years ago that uses FFMPEG to transcode 360video to both DASH and HLS.In the KRPano player pull this off without a plugin, and using https://dashjs.org/
-
I got frustrated with it and created my own GoogleMaps plugin quite a while ago. Actually not that difficult to do at all. Allows for way more flexibilty.
-
This link might be of some help, if you are wiling to get your coding-hands dirty.
https://developers.google.com/streetview/publish -
FANTASTIC!
Works like a charm.
Thank you for the quick fix.You are a gentleman and a scholar.
-
Klaus,
I tired using an absolute path for the <preview/> and <image/><cube/> URLs in the included, unencrypted, xml in my local dev environment and the "flash" still occurred.
So regardless of relative or absolute path the "flash" occurs when using the <include /> method. -
Klaus,
I'm beating this horse again but I've have been able to narrow this down to a 1-to-1 example that shows what is going on.
When I <include /> encrypted or un-encrypted xml ( defining the <preview> and <image> tags ) in a scene that is loaded with loadpanoscene(), before the blend to the next scene begins, there is a brief "flash" ( or occlusion of the current pano ) and then the blend begins. Subsequent visits to a scene results in correct/expected blending behavior.In either of the links below, click as illustrated in the screen capture at the end of this message for a true comparison of the issue at hand.
This link illustrates the issue and uses <include /> to load the image XML:
https://preview.xplorit.com/orlando-florida
This is an example of the <include /> code:
<include url="https://s3-us-west-2.amazonaws.com/xplorit-media/media/pano/161051/xml_161051.xml"/
The contents of the xml_161051.xml file prior to encrypting is:<krpano version="1.20">
<preview url="the.tiles/preview.jpg" />
<image>
<cube url="the.tiles/mres_%s/l%l/%v/l%l_%s_%v_%h.jpg" multires="512,768,1664,3200" />
</image>
</krpano>
The xml_161051.xml file being included is encrypted with krpanotools via Python scripting.
The following link blends between scenes as-expected, every time. It does not use <include /> so the <preview> and <image> elements are direct children of <scene>. This is completely a test URL. The multires settings do not match up to all of the media we use so the tiling might be a bit off, but the blending works perfectly, no "flash"/occlusion .
https://player.xplorit.com/orlando-florida
The <preview/> and <image/> code is in the following format.<preview url="https://s3-us-west-2.amazonaws.com/xplorit-media/media/pano/161051/the.tiles/preview.jpg" />
<image>
<cube url="https://s3-us-west-2.amazonaws.com/xplorit-media/media/pano/161051/the.tiles/mres_%s/l%l/%v/l%l_%s_%v_%h.jpg" multires="512,768,1664,3200" />
</image>Could this be an issue of relative vs. absolute reference to the media?
Immediately below is a screen capture of the file structure of the directory containing the.tiles and the .xml that is included.
-
Tuur,
The XML containing the <preview/> and <image/> are being <include/>'d into the <scene/>
I do it this way, generating separate XML for each pano and storing it with the.tiles, because of the variability of the sizes of the panos we can potentially include in a scene & tour. As of me typing this we have 9,780 panos on our s3 that we use in tours.
On the surface this seems like KRPano has an issue with the initial load of the scene & image. But once it has been cached, going back to the scene results in expected blending from one scene to another. -
Good day,
Got a bit of a conundrum that I have been looking at for far too long and need another set of eyes to take a gander.
Basically the multires panos are not blending on first load, but subsequent loads in the tour they blend just fine.
Here are the details.I create multires panos via the following Python script:
proc_command = krpanotools_path + ' makepano -config=' + config_path + ' -tilepath=' + str(
theMediaPath) + '/the.tiles/[c/]l%Al/%Av/l%Al[_c]_%Av_%Ah.jpg -previewpath=' + str(
theMediaPath) + '/the.tiles/preview.jpg -thumbpath=' + str(
theMediaPath) + '/the.tiles/thumb.jpg -xmlpath=' + str(
theMediaPath) + '/xml_' + pano_id + '.xml ' + str(fileTarget)Which results in the requisite files and, specifically, the following XML formatted file:
<krpano version="1.20">
<preview url="the.tiles/preview.jpg" />
<image>
<cube url="the.tiles/mres_%s/l%l/%v/l%l_%s_%v_%h.jpg" multires="512,768,1664,3200" />
</image>
</krpano>
I then <include> the XML in dynamically (php) generated scene_xml files like the following:
<include url="<?php echo XP_MEDIA; ?>pano/<?php echo $this_scene_media->media_mediaRef; ?>/xml_<?php echo $this_scene_media->media_mediaRef; ?>.xml"/>This works great. EXCEPT the first time the scene is loaded via:
loadpanoscene('scene_xml.php?sceneid=<?php echo $hotspot_action->action_scene; ?>&tourid=<?php echo $tour_ID; ?>&load=fade',scene<?php echo $hotspot_action->action_scene; ?>,null,MERGE,BLEND(0.5));...the blending does not occur and there appears to be a brief white flash before the new pano is loaded. Now, on the second and subsequent loads of the scene in the tour the blending occurs as expected.
I have some "old-school' cubic panos that are used in the form of:
<image type="cube" hfov="360" vfov="180">
<cube url="<?php echo XP_MEDIA; ?>pano/<?php echo $this_scene_media->media_mediaRef; ?>/_%s.jpg"/>
</image>...that always BLEND correctly on the first load.
Anyone have ANY idea why this is happening?
I'm probably missing something simple....
Thanks in advance for any directions that I can be pointed in. -
Klaus,
The only way I have managed to get this to work is:
1. When adding the plugin setting the parent with the full-path of of the parent layer and calling display.layout.update(); in the following two positions.
addlayer(xplorit_conference);
display.layout.update();
set(layer[xplorit_conference].type, plugin);
set(layer[xplorit_conference].parent, layer[socket_stage]);
display.layout.update();
...2. As the very last operation in local.registerplugin we set the parent with the name, not the full-path;
krpano.set("layer[xplorit_conference].parent", "socket_stage");I traced & logged this out to no end and can't find where the parent-child connection gets lost.
No matter where in my code I get(layer[xplorit_conference].parent), it always comes up layer[socket_stage] although obviously this is not the case. -
Klaus,
First off, thank you for helping me with this.
The layer[socket_stage] is part of the primary XML layout, non-dynamically generated.
<layer name="socket_stage" type="container" style="socket_stage"></layer>
I managed to get the video elements to render for the ATTENDEE by adding in display.layout.update(); ( as you suggested in two places - see code below ) but they are definitely no children of layer[socket_stage] in the ATTENDEE instance.
Here are the Elements gleaned from the Element Inspector in Chrome.
As you can see the HOST layer[socket_stage] definitly is parent to the plugin.I've included two screen captures of both HOST and ATTENDEE instances.
Once again all of this is pulling from the same code base and using v1.21.HOST
ATTENDEE
HOST
<div style="position: absolute; box-sizing: content-box; overflow: visible; opacity: 1; cursor: pointer; pointer-events: auto; background: none; visibility: visible; border-color: rgb(0, 0, 0); border-style: solid; border-width: 0px; border-radius: 0px; z-index: 163; width: 320px; height: 70px; transform: translateZ(0px) translate(717px, 417px) translate(0px, 0px);">
<div style="position: absolute; box-sizing: content-box; overflow: visible; opacity: 1; cursor: pointer; pointer-events: auto; z-index: 118; background: none no-repeat; visibility: visible; width: 320px; height: 70px; transform: translateZ(0px) translate(0px, 0px) translate(0px, 0px);">
<div id="confdiv" style="width: 100%; height: 400px; overflow: hidden; position: absolute; bottom: 0px; display: flex; flex-direction: column; justify-content: flex-end; pointer-events: none;">
</div>
</div>
</div>ATTENDEE
<div style="position: absolute; box-sizing: content-box; overflow: visible; opacity: 1; cursor: pointer; pointer-events: auto; background: none; visibility: visible; border-color: rgb(0, 0, 0); border-style: solid; border-width: 0px; border-radius: 0px; z-index: 161; width: 320px; height: 70px; transform: translateZ(0px) translate(717px, 417px) translate(0px, 0px);"></div>Action segment with display.layout.update();
addlayer(xplorit_conference);
display.layout.update();
set(layer[xplorit_conference].type, plugin);
set(layer[xplorit_conference].parent, layer[socket_stage]);
display.layout.update();
set(layer[xplorit_conference].with, 100%);
set(layer[xplorit_conference].height, 100%);
set(layer[xplorit_conference].align, leftbottom);
set(layer[xplorit_conference].x, 0);
set(layer[xplorit_conference].y, 0);
set(layer[xplorit_conference].keep, true);
set(layer[xplorit_conference].visible, true);
set(layer[xplorit_conference].alpha, 1.0);
set(layer[xplorit_conference].enabled, true);
set(layer[xplorit_conference].capture, true);
set(layer[xplorit_conference].bgcapture, false);
set(layer[xplorit_conference].zorder, 0);
set(layer[xplorit_conference].room, %1);
set(layer[xplorit_conference].mode, %2);
set(layer[xplorit_conference].url,'/xplorit_common/frameworks/xplorit/plugins/xplorit_conference.min.js');
set(layer[xplorit_conference].onloaded, conf_nav_open());
display.layout.update(); -
Klaus,
The layer/plugin is created dynamically with the action below.
Below that I have the JS console output for both v1.20 and v1.21 runs with this.The only dif in the logs being where the resetMap() is logged.
I added in the traces, first to see if a layer[xplorit_conference] already exists ( which it should not ) and second to see if the layer[xplorit_conference] ends up being created. I also did this for the other layer/plugin being added by this action.
In both cases, v1.20 & 1.21 the PRE and POST checks PASS.
Now, in ATTENDEE 1.21 when I check pugin-side to see if the div was added to the DOM it is not, as reported by:
VM28:56 ------ SECONDARY | CONFDIV DOES NOT EXISTS ONLOAD
The ONLY thing that changed when I ran these two comparisons is switching out the krpano.js versions. No code was altered what-so-ever.
As much as this could be something in the sequencing of my code, the version discrepancy really has me confused here.
<action name="load_xplorit_conference">
trace('load_xplorit_conference()');
<!-- set the socket stage to capture mouse events -->
set(layer[socket_stage].capture, true);
set(layer[socket_stage].enabled, true);
set(layer[socket_stage].bgcapture, true);
<!-- add the two layers/plugins that are needed for the conferencing module -->
if(layer[xplorit_conference],
trace('xplorit_conference PRE CHECK - FAILED!');
,
trace('xplorit_conference PRE CHECK - PASSED!');
);
addlayer(xplorit_conference);
set(layer[xplorit_conference].type, plugin);
set(layer[xplorit_conference].parent, layer[socket_stage]);
set(layer[xplorit_conference].with, 100%);
set(layer[xplorit_conference].height, 100%);
set(layer[xplorit_conference].align, leftbottom);
set(layer[xplorit_conference].x, 0);
set(layer[xplorit_conference].y, 0);
set(layer[xplorit_conference].keep, true);
set(layer[xplorit_conference].visible, true);
set(layer[xplorit_conference].alpha, 1.0);
set(layer[xplorit_conference].enabled, true);
set(layer[xplorit_conference].capture, true);
set(layer[xplorit_conference].bgcapture, false);
set(layer[xplorit_conference].zorder, 0);
set(layer[xplorit_conference].room, %1);
set(layer[xplorit_conference].mode, %2);
set(layer[xplorit_conference].url,'/xplorit_common/frameworks/xplorit/plugins/xplorit_conference.min.js');
set(layer[xplorit_conference].onloaded, conf_nav_open());if(layer[xplorit_conference],
trace('xplorit_conference POST CHECK - PASSED!');
,
trace('xplorit_conference POST CHECK - FAILED!');
);if(layer[conf_overlay],
trace('conf_overlay PRE CHECK - FAILED!');
,
trace('conf_overlay PRE CHECK - PASSED!');
);addlayer(conf_overlay);
set(layer[conf_overlay].keep, true);
set(layer[conf_overlay].align, righttop);
set(layer[conf_overlay].width, 100%);
set(layer[conf_overlay].height, 100%);
set(layer[conf_overlay].x, 0);
set(layer[conf_overlay].y, 0);
set(layer[conf_overlay].visible, true);
set(layer[conf_overlay].alpha, 1.0);
set(layer[conf_overlay].enabled, false);
set(layer[conf_overlay].capture, false);
set(layer[conf_overlay].bgcapture, false);
set(layer[conf_overlay].zorder, 1);
set(layer[conf_overlay].cursor, pointer);
set(layer[conf_overlay].parent, STAGE);set(layer[conf_overlay].url,'/xplorit_common/frameworks/xplorit/plugins/xplorit_whiteboard.min.js');
if(layer[conf_overlay],
trace('xplorit_conference POST CHECK - PASSED!');
,
trace('xplorit_conference POST CHECK - FAILED!');
);</action>
ATTENDEE 1.20
VM24:1 INFO: join_conf()
VM24:1 INFO: load_xplorit_conference()
VM24:1 INFO: xplorit_conference PRE CHECK - PASSED!
VM24:1 INFO: xplorit_conference POST CHECK - PASSED!
VM24:1 INFO: conf_overlay PRE CHECK - PASSED!
VM24:1 INFO: xplorit_conference POST CHECK - PASSED!
VM67:53 --- PRIMARY | resetMap()
VM68:56 --- PRIMARY | xplorit_conference.registerplugin myScreenName = Phreddy
VM68:56 ------ SECONDARY | Adding confidiv to the DOM
VM68:62 <div id="confdiv" style="width: 100%; height: 400px; overflow: hidden; position: absolute; bottom: 0px; display: flex; flex-direction: column; justify-content: flex-end; pointer-events: none;"></div>flex
VM68:56 --- PRIMARY | preview.xplorit.local
VM68:56 ------ SECONDARY | CONFDIV EXISTS ONLOAD
VM68:56 ------ SECONDARY | load_scripts(/node_modules/http://socket.io/client-dist/so…N-production.js, async function xa(){d("newstart()");va();wa()})
VM68:56 --- PRIMARY | resolve_url_path(/node_modules/http://socket.io/client-dist/socket.io.js)
VM24:1 INFO: conf_nav_open()
VM68:56 ------ SECONDARY | load_scripts(/node_modules/agora-rtc-sdk-ng-latest/AgoraRTC_N-production.js, async function xa(){d("newstart()");va();wa()})
VM68:56 --- PRIMARY | resolve_url_path(/node_modules/agora-rtc-sdk-ng-latest/AgoraRTC_N-production.js)
VM68:56 ------ SECONDARY | load_scripts(, async function xa(){d("newstart()");va();wa()})
VM68:56 --- PRIMARY | newstart()
VM68:56 --- PRIMARY | chat_start()
VM68:56 --- PRIMARY | conference_start()
VM68:56 --- PRIMARY | onResize()
VM68:56 --- PRIMARY | join_conf()
VM68:56 --- PRIMARY | socket_connect()
VM68:56 --- PRIMARY | socket_connecting......
VM68:56 --- PRIMARY | join_conf_socket([object Object])
VM68:56 --- PRIMARY | common_conf_elements([object Object])
VM68:56 --- PRIMARY | common_socket([object Object])
VM68:56 --- PRIMARY | show_chat()
VM68:56 ------ SECONDARY | CONFDIV EXISTS SHOWCHAT
VM68:56 ------ SECONDARY | add flex_hide
VM68:56 ------ SECONDARY | socket.id ====== gw9ITndI-qqwP2FsAAAL
VM68:56 --- PRIMARY | newjoin()
VM68:56 ------ SECONDARY | CONFDIV EXISTS NEWJOIN
VM68:56 --- PRIMARY | join_agora()
VM68:56 ------ SECONDARY | CONFDIV EXISTS AGORAATTENDEE 1.21
INFO: join_conf()
VM24:3 INFO: load_xplorit_conference()
VM24:3 INFO: xplorit_conference PRE CHECK - PASSED!
VM24:3 INFO: xplorit_conference POST CHECK - PASSED!
VM24:3 INFO: conf_overlay PRE CHECK - PASSED!
VM24:3 INFO: xplorit_conference POST CHECK - PASSED!
VM28:56 --- PRIMARY | xplorit_conference.registerplugin myScreenName = Phreddy
VM28:56 ------ SECONDARY | Adding confidiv to the DOM
VM28:62 <div id="confdiv" style="width: 100%; height: 400px; overflow: hidden; position: absolute; bottom: 0px; display: flex; flex-direction: column; justify-content: flex-end; pointer-events: none;"></div>
VM28:56 --- PRIMARY | preview.xplorit.local
VM28:56 ------ SECONDARY | CONFDIV DOES NOT EXISTS ONLOAD
VM28:56 ------ SECONDARY | load_scripts(/node_modules/http://socket.io/client-dist/so…N-production.js, async function xa(){d("newstart()");va();wa()})
VM28:56 --- PRIMARY | resolve_url_path(/node_modules/http://socket.io/client-dist/socket.io.js)
VM24:3 INFO: conf_nav_open()
VM28:56 ------ SECONDARY | load_scripts(/node_modules/agora-rtc-sdk-ng-latest/AgoraRTC_N-production.js, async function xa(){d("newstart()");va();wa()})
VM28:56 --- PRIMARY | resolve_url_path(/node_modules/agora-rtc-sdk-ng-latest/AgoraRTC_N-production.js)
VM76:53 --- PRIMARY | resetMap()
VM28:56 ------ SECONDARY | load_scripts(, async function xa(){d("newstart()");va();wa()})
VM28:56 --- PRIMARY | newstart()
VM28:56 --- PRIMARY | chat_start()
VM28:56 --- PRIMARY | conference_start()
VM28:56 --- PRIMARY | onResize()
VM28:56 --- PRIMARY | join_conf()
VM28:56 --- PRIMARY | socket_connect()
VM28:56 --- PRIMARY | socket_connecting......
VM28:56 --- PRIMARY | join_conf_socket([object Object])
VM28:56 --- PRIMARY | common_conf_elements([object Object])
VM28:56 --- PRIMARY | common_socket([object Object])
VM28:56 --- PRIMARY | show_chat()
VM28:56 ------ SECONDARY | CONFDIV DOES NOT EXISTS SHOWCHAT
VM28:56 ------ SECONDARY | add flex_hide
VM28:56 ------ SECONDARY | socket.id ====== yLh_yRepYJjLdmJKAAAP
VM28:56 --- PRIMARY | newjoin()
VM28:56 ------ SECONDARY | CONFDIV DOES NOT EXISTS NEWJOIN
VM28:56 --- PRIMARY | join_agora()
VM28:56 ------ SECONDARY | CONFDIV DOES NOT EXISTS AGORA -
Klaus and KRPano fam,
Got a strange mystery that I just can't debug.
I have created a plugin, leveraging socket.io, Agora and a few other components that is similar in nature to the Live Presenter Plugin but definitely different. We use the term conference for a presentation session.
It works great using krpano 1.20.11 (build 2022-03-07). A host can start a conference and then the audience/participants can join in via a conference-specific URL.
HOWEVER, when I use krpano 1.21 (build 2023-04-30) the <div> I'm appending to the sprite just won't append. I've traced and console.log()-ed to an obscene extent trying to figure this one out to no avail.
Obviously this has something to do with host - vs - audience/participants but what just does not jive out is why does this work with v1.20 and not with v1.21?
No code changes what-so-ever. Just the core KRPano version change.
Below is the relevant code in the plugin that creates the <div> and appends to the plugin.sprite.
And below that is the log and elements for both the "host" and the "audience/participant".
You can see in the logs that the appending seems to go down, with theResults being traced in both instances.
But the following log lines host-VM61:56 and participand-VM36:56 indicate that that host was appended-to while the participant WAS NOT.
If anyone can shed light or point me in any direction on this it would be GREATLY appreciated.// create the wrapping div
confdiv = document.createElement('div');
confdiv.style.width = '100%'; // => automatic scale with the parent element
confdiv.style.height = '400px';
confdiv.style.overflowY = 'hidden';
confdiv.style.overflowX = 'hidden';
confdiv.id = 'confdiv';
confdiv.style.position = 'absolute';
confdiv.style.bottom = '0px';
confdiv.style.display = 'flex';
confdiv.style.flexDirection = 'column';
confdiv.style.justifyContent = 'flex-end';
confdiv.style.pointerEvents = 'none';
// add it to the DOM
if (plugin.sprite) {
const theResults = plugin.sprite.appendChild(confdiv);
consolelog('Adding confidiv to the DOM', 2);
console.log(theResults);
} else {
consolelog('NO SPRITE', 2);
}
consolelog(window.location.hostname);
if ($('#confdiv').length > 0) {
consolelog('CONFDIV EXISTS ONLOAD', 2);
} else {
consolelog('CONFDIV DOES NOT EXISTS ONLOAD', 2);
}Host log
VM61:56 ------ SECONDARY | Adding confidiv to the DOM
VM61:62 <div id="confdiv" style="width: 100%; height: 400px; overflow: hidden; position: absolute; bottom: 0px; display: flex; flex-direction: column; justify-content: flex-end; pointer-events: none;"></div>flex
VM61:56 --- PRIMARY | preview.xplorit.local
VM61:56 ------ SECONDARY | CONFDIV EXISTS ONLOAD
Host elements
<div style="position: absolute; box-sizing: content-box; overflow: visible; opacity: 1; cursor: pointer; pointer-events: auto; background: none; visibility: visible; border-color: rgb(0, 0, 0); border-style: solid; border-width: 0px; border-radius: 0px; z-index: 163; width: 320px; height: 70px; transform: translateZ(0px) translate(1514px, 778px) translate(0px, 0px);">
<div style="position: absolute; box-sizing: content-box; overflow: visible; opacity: 1; cursor: pointer; pointer-events: auto; z-index: 118; background: none no-repeat; visibility: visible; width: 320px; height: 70px; transform: translateZ(0px) translate(0px, 0px) translate(0px, 0px);">
<div id="confdiv" style="width: 100%; height: 400px; overflow: hidden; position: absolute; bottom: 0px; display: flex; flex-direction: column; justify-content: flex-end; pointer-events: none;">
<!-- Chat Content -->
</div>
</div>
</div>
Participant log
VM36:56 ------ SECONDARY | Adding confidiv to the DOM
VM36:62 <div id="confdiv" style="width: 100%; height: 400px; overflow: hidden; position: absolute; bottom: 0px; display: flex; flex-direction: column; justify-content: flex-end; pointer-events: none;"></div>
VM36:56 --- PRIMARY | preview.xplorit.local
VM36:56 ------ SECONDARY | CONFDIV DOES NOT EXISTS ONLOAD
Participant element
<div style="position: absolute; box-sizing: content-box; overflow: visible; opacity: 1; cursor: pointer; pointer-events: auto; background: none; visibility: visible; border-color: rgb(0, 0, 0); border-style: solid; border-width: 0px; border-radius: 0px; z-index: 165; width: 320px; height: 70px; transform: translateZ(0px) translate(1514px, 778px) translate(0px, 0px);">
</div> -
Klaus,
Thank you for your input.
Sometimes our team can't see the forest through the trees. -
I'm using krpanotools.exe 1.20.11 Win64
When running makepano with config converttocube=false I'm getting the message: krpano License Upgrade required!
Otherwise with converttocube=true it process out fine.
I performed the Upgrade Check and, yes it is valid for both Krpano License and Branding Free.
I then clicked Resent the License to make sure I was using the correct one, and I was.
Any Idea what I'm doing wrong?And Klaus, when using converttocube=false and then viewing the panorama is this being projected onto cube-faces or is this being projected in another way? Trying to thread a needle here.