glenrobson / SimpleAnnotationServer

A simple IIIF and Mirador compatible Annotation Server

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

How to make the annotations to be readable but not writable

tyy0402 opened this issue · comments

Thanks the developers of SAS!It is really useful and easy to install.

However, I hope I can store the annotations and show the annotations separately. Once I opened SAS, the annotations will show on the pictures, while others could edit them and save them in my SAS store. Can I use another Mirador just showing the annotations? I only have one server. Since I am a freshman, I don't figure out how to build a connection between SAS and the independent Mirador.
How can I do it in one server?

If I understand the main question correctly, the page of documentation you need is:
https://github.com/glenrobson/SimpleAnnotationServer/blob/master/doc/RemoteStore.md
Any instance of Mirador anywhere can use the Simple Annotation Server as an annotation repository.
Most often the file you need to edit is mirador/index.html for the mirador instance you want.
Parallel to "id", "data", and "windowObjects" you specify the annotation endpoint as in the remote.html example:

annotationEndpoint: {
	name: 'Simple Annotation Store Endpoint',
	module: 'SimpleASEndpoint',
	options: {
		url: 'http://remote_host:port/simpleAnnotationStore/annotation'
	}
}

For example, if you're using the jetty server locally the code could be:

annotationEndpoint:{
	name:'Simple Annotation Store Endpoint',
	module:'SimpleASEndpoint',
	options:{
		url:'http://localhost:8888/annotation',
		storeId:'comparison',
		APIKey:'user_auth'
	}
}

or with Tomcat:

annotationEndpoint:{
	name:'Simple Annotation Store Endpoint',
	module:'SimpleASEndpoint',
	options:{
		url:'http://localhost:8080/simpleAnnotationStore/annotation',
		storeId:'comparison',
		APIKey:'user_auth'
	}
}

I believe that answers the issue as titled. If your notes also implied that you want the annotations to be readable but not writable, that is a different issue. The short answer is that it is fairly easy to copy the annotations from the writable store to static annotations following the IIIF Presentation API.

Thanks for replying me! Sorry, I didn't describe my question clearly before.
I want the annotations to be readable but not writable! That is my question. I cannot find the annotations from Jena. Could you give me more details about how to make it just be readable?

Others may have experience with authentication techniques. My approach to making some annotations readable but not writable is to move them from the annotation store to static files following the IIIF Presentation API.

If you already have annotations created you can copy the json-formatted annotations from the annotation server. In my case, the dynamic annotations are at http://35.162.52.109:8888/simpleAnnotationStore/annotation/

For a given canvas, you can copy the annotations from there and paste them into a static file such as the one seen here:
http://jubilees.stmarytx.edu/iiifp/LatinMoses/list/5c083.json

Finally, the main manifest needs to be told to look in that file for annotations.
For example in the manifest at http://jubilees.stmarytx.edu/iiifp/LatinMoses/manifest.json
the first and every canvas refers to "otherContent" as follows:

"otherContent": [{ "@id":"http://jubilees.stmarytx.edu/iiifp/LatinMoses/list/5c083.json", "@type":"sc:AnnotationList"}]

This will be easy or hard depending on how familiar you already are with JSON and the IIIF Presentation API.
http://iiif.io/api/presentation/2.1/

Honstly, I am not sure whether the annotations were created. I can see them from other devices when I open SAS.
http://120.76.144.46:8888/sanmao.html
I could see the json-formatted annotations on command window when I edited them, but I cannot find it on http://120.46.144.46:8888/simpleAnnotationStore/annotation/
like yours.
I saw you used Tomcat. I just used Java, Jena and Maven.

The path "simpleAnnotationStore" is not required in all configurations. It looks like you have quite a few annotations at http://120.76.144.46:8888/annotation/
I have found that Chrome will show the JSON and Edge will download it. The trailing slash is also important.

Oh, I forgot the trailing slash.
The path http://120.76.144.46:8888/annotation/ is right.
Thank you!!!!!!!!!I will continue to the next step.

I used one picture to test. But the annotations didn't show on the picture.
The annotations: https://raw.githubusercontent.com/tyy0402/test_anno/master/c1.json
The manifest: https://gist.githubusercontent.com/tyy0402/3f6a74057b1d6d24a044f28ee8fbf1d3/raw/cc850935ea05a8b75b422382e5fa4165c69cded5/anno_manifest.json
The URL of manifest in manifest and annotations is the same, as well as the "id" of the picture. So I don't know where is the problem.

If you open up the developer console on the web browser you are using and open that manifest in Mirador, does it give you any errors? I tried to load the manifest above in http://projectmirador.org/demo/ but I couldn't access the image server.

My image server is strange. I cannot keep it open all the time.
The errors are as follows:

Failed to load http://dev.llgc.org.uk/iiif/ww1posters.json: Redirect from 'http://dev.llgc.org.uk/iiif/ww1posters.json' to 'https://dev.llgc.org.uk/iiif/ww1posters.json' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://projectmirador.org' is therefore not allowed access.
(index):1 Failed to load http://digi.vatlib.it/iiif/MSS_Vat.lat.3225/manifest.json: Redirect from 'http://digi.vatlib.it/iiif/MSS_Vat.lat.3225/manifest.json' to 'https://digi.vatlib.it/iiif/MSS_Vat.lat.3225/manifest.json' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://projectmirador.org' is therefore not allowed access.
iiif.lib.harvard.edu/manifests/via:olvwork576793 Failed to load resource: the server responded with a status of 404 (NOT FOUND)
jquery.min.js:2 Uncaught TypeError: Cannot read property '_transformBounds' of null
    at initialize.getBounds (jquery.min.js:2)
    at initialize.getCenter (jquery.min.js:2)
    at initialize.setCenter (jquery.min.js:2)
    at $.Overlay.resize (jquery.min.js:2)
    at Object._thisResize [as handler] (jquery.min.js:2)
    at jquery.min.js:2
    at $.Viewer.raiseEvent (jquery.min.js:2)
    at $.Viewport.resize (jquery.min.js:2)
    at updateOnce (jquery.min.js:2)
    at updateMulti (jquery.min.js:2)
108jquery.min.js:2 Uncaught TypeError: Cannot read property '_transformBounds' of null
    at initialize.getBounds (jquery.min.js:2)
    at initialize._handleMouseEvent (jquery.min.js:2)
    at r (jquery.min.js:2)
    at HTMLDocument.v.(/demo/anonymous function) (http://projectmirador.org/demo/mirador.min.js:1:1568560)
jquery.min.js:2 Uncaught TypeError: Cannot read property '_transformBounds' of null
    at initialize.getBounds (jquery.min.js:2)
    at initialize._handleMouseEvent (jquery.min.js:2)
    at r (jquery.min.js:2)
    at HTMLDocument.mouseout (jquery.min.js:2)
2jquery.min.js:2 Uncaught TypeError: Cannot read property '_transformBounds' of null
    at initialize.getBounds (jquery.min.js:2)
    at initialize._handleMouseEvent (jquery.min.js:2)
    at r (jquery.min.js:2)
    at HTMLDocument.v.(/demo/anonymous function) (http://projectmirador.org/demo/mirador.min.js:1:1568560)
getBounds @ jquery.min.js:2
_handleMouseEvent @ jquery.min.js:2
r @ jquery.min.js:2
v.(anonymous function) @ jquery.min.js:2
jquery.min.js:2 Uncaught TypeError: Cannot read property '_transformBounds' of null
    at initialize.getBounds (jquery.min.js:2)
    at initialize._handleMouseEvent (jquery.min.js:2)
    at r (jquery.min.js:2)
    at HTMLDocument.mouseout (jquery.min.js:2)
getBounds @ jquery.min.js:2
_handleMouseEvent @ jquery.min.js:2
r @ jquery.min.js:2
mouseout @ jquery.min.js:2
118jquery.min.js:2 Uncaught TypeError: Cannot read property '_transformBounds' of null
    at initialize.getBounds (jquery.min.js:2)
    at initialize._handleMouseEvent (jquery.min.js:2)
    at r (jquery.min.js:2)
    at HTMLDocument.v.(/demo/anonymous function) (http://projectmirador.org/demo/mirador.min.js:1:1568560)
getBounds @ jquery.min.js:2
_handleMouseEvent @ jquery.min.js:2
r @ jquery.min.js:2
v.(anonymous function) @ jquery.min.js:2
jquery.min.js:2 Uncaught TypeError: Cannot read property '_transformBounds' of null
    at initialize.getBounds (jquery.min.js:2)
    at initialize._handleMouseEvent (jquery.min.js:2)
    at r (jquery.min.js:2)
    at HTMLDocument.mouseout (jquery.min.js:2)
getBounds @ jquery.min.js:2
_handleMouseEvent @ jquery.min.js:2
r @ jquery.min.js:2
mouseout @ jquery.min.js:2
34jquery.min.js:2 Uncaught TypeError: Cannot read property '_transformBounds' of null
    at initialize.getBounds (jquery.min.js:2)
    at initialize._handleMouseEvent (jquery.min.js:2)
    at r (jquery.min.js:2)
    at HTMLDocument.v.(/demo/anonymous function) (http://projectmirador.org/demo/mirador.min.js:1:1568560)
getBounds @ jquery.min.js:2
_handleMouseEvent @ jquery.min.js:2
r @ jquery.min.js:2
v.(anonymous function) @ jquery.min.js:2
jquery.min.js:2 Uncaught TypeError: Cannot read property '_transformBounds' of null
    at initialize.getBounds (jquery.min.js:2)
    at initialize.getCenter (jquery.min.js:2)
    at initialize.setCenter (jquery.min.js:2)
    at $.Overlay.resize (jquery.min.js:2)
    at Object._thisResize [as handler] (jquery.min.js:2)
    at jquery.min.js:2
    at $.Viewer.raiseEvent (jquery.min.js:2)
    at $.Viewport.resize (jquery.min.js:2)
    at updateOnce (jquery.min.js:2)
    at updateMulti (jquery.min.js:2)
getBounds @ jquery.min.js:2
getCenter @ jquery.min.js:2
setCenter @ jquery.min.js:2
resize @ jquery.min.js:2
_thisResize @ jquery.min.js:2
(anonymous) @ jquery.min.js:2
raiseEvent @ jquery.min.js:2
resize @ jquery.min.js:2
updateOnce @ jquery.min.js:2
updateMulti @ jquery.min.js:2
(anonymous) @ jquery.min.js:2
requestAnimationFrame (async)
$.requestAnimationFrame @ jquery.min.js:2
scheduleUpdate @ jquery.min.js:2
updateMulti @ jquery.min.js:2
(anonymous) @ jquery.min.js:2
requestAnimationFrame (async)
$.requestAnimationFrame @ jquery.min.js:2
scheduleUpdate @ jquery.min.js:2
updateMulti @ jquery.min.js:2
(anonymous) @ jquery.min.js:2
requestAnimationFrame (async)
$.requestAnimationFrame @ jquery.min.js:2
scheduleUpdate @ jquery.min.js:2
updateMulti @ jquery.min.js:2
(anonymous) @ jquery.min.js:2
requestAnimationFrame (async)
$.requestAnimationFrame @ jquery.min.js:2
scheduleUpdate @ jquery.min.js:2
updateMulti @ jquery.min.js:2
(anonymous) @ jquery.min.js:2
requestAnimationFrame (async)
$.requestAnimationFrame @ jquery.min.js:2
scheduleUpdate @ jquery.min.js:2
updateMulti @ jquery.min.js:2
(anonymous) @ jquery.min.js:2
requestAnimationFrame (async)
$.requestAnimationFrame @ jquery.min.js:2
scheduleUpdate @ jquery.min.js:2
updateMulti @ jquery.min.js:2
(anonymous) @ jquery.min.js:2
requestAnimationFrame (async)
$.requestAnimationFrame @ jquery.min.js:2
scheduleUpdate @ jquery.min.js:2
updateMulti @ jquery.min.js:2
(anonymous) @ jquery.min.js:2
requestAnimationFrame (async)
$.requestAnimationFrame @ jquery.min.js:2
scheduleUpdate @ jquery.min.js:2
updateMulti @ jquery.min.js:2
(anonymous) @ jquery.min.js:2
requestAnimationFrame (async)
$.requestAnimationFrame @ jquery.min.js:2
scheduleUpdate @ jquery.min.js:2
updateMulti @ jquery.min.js:2
(anonymous) @ jquery.min.js:2
requestAnimationFrame (async)
$.requestAnimationFrame @ jquery.min.js:2
scheduleUpdate @ jquery.min.js:2
updateMulti @ jquery.min.js:2
(anonymous) @ jquery.min.js:2
requestAnimationFrame (async)
$.requestAnimationFrame @ jquery.min.js:2
scheduleUpdate @ jquery.min.js:2
updateMulti @ jquery.min.js:2
(anonymous) @ jquery.min.js:2
requestAnimationFrame (async)
$.requestAnimationFrame @ jquery.min.js:2
scheduleUpdate @ jquery.min.js:2
updateMulti @ jquery.min.js:2
(anonymous) @ jquery.min.js:2
requestAnimationFrame (async)
$.requestAnimationFrame @ jquery.min.js:2
scheduleUpdate @ jquery.min.js:2
updateMulti @ jquery.min.js:2
(anonymous) @ jquery.min.js:2
requestAnimationFrame (async)
$.requestAnimationFrame @ jquery.min.js:2
scheduleUpdate @ jquery.min.js:2
updateMulti @ jquery.min.js:2
(anonymous) @ jquery.min.js:2
requestAnimationFrame (async)
$.requestAnimationFrame @ jquery.min.js:2
scheduleUpdate @ jquery.min.js:2
updateMulti @ jquery.min.js:2
(anonymous) @ jquery.min.js:2
requestAnimationFrame (async)
$.requestAnimationFrame @ jquery.min.js:2
scheduleUpdate @ jquery.min.js:2
updateMulti @ jquery.min.js:2
(anonymous) @ jquery.min.js:2
requestAnimationFrame (async)
$.requestAnimationFrame @ jquery.min.js:2
scheduleUpdate @ jquery.min.js:2
updateMulti @ jquery.min.js:2
(anonymous) @ jquery.min.js:2
requestAnimationFrame (async)
$.requestAnimationFrame @ jquery.min.js:2
scheduleUpdate @ jquery.min.js:2
updateMulti @ jquery.min.js:2
(anonymous) @ jquery.min.js:2
requestAnimationFrame (async)
$.requestAnimationFrame @ jquery.min.js:2
scheduleUpdate @ jquery.min.js:2
updateMulti @ jquery.min.js:2
(anonymous) @ jquery.min.js:2
requestAnimationFrame (async)
$.requestAnimationFrame @ jquery.min.js:2
scheduleUpdate @ jquery.min.js:2
updateMulti @ jquery.min.js:2
(anonymous) @ jquery.min.js:2
requestAnimationFrame (async)
$.requestAnimationFrame @ jquery.min.js:2
scheduleUpdate @ jquery.min.js:2
updateMulti @ jquery.min.js:2
(anonymous) @ jquery.min.js:2
requestAnimationFrame (async)
$.requestAnimationFrame @ jquery.min.js:2
scheduleUpdate @ jquery.min.js:2
updateMulti @ jquery.min.js:2
(anonymous) @ jquery.min.js:2
requestAnimationFrame (async)
$.requestAnimationFrame @ jquery.min.js:2
scheduleUpdate @ jquery.min.js:2
updateMulti @ jquery.min.js:2
(anonymous) @ jquery.min.js:2
requestAnimationFrame (async)
$.requestAnimationFrame @ jquery.min.js:2
scheduleUpdate @ jquery.min.js:2
updateMulti @ jquery.min.js:2
(anonymous) @ jquery.min.js:2
requestAnimationFrame (async)
$.requestAnimationFrame @ jquery.min.js:2
scheduleUpdate @ jquery.min.js:2
updateMulti @ jquery.min.js:2
(anonymous) @ jquery.min.js:2
requestAnimationFrame (async)
$.requestAnimationFrame @ jquery.min.js:2
scheduleUpdate @ jquery.min.js:2
updateMulti @ jquery.min.js:2
(anonymous) @ jquery.min.js:2
requestAnimationFrame (async)
$.requestAnimationFrame @ jquery.min.js:2
scheduleUpdate @ jquery.min.js:2
updateMulti @ jquery.min.js:2
(anonymous) @ jquery.min.js:2
requestAnimationFrame (async)
$.requestAnimationFrame @ jquery.min.js:2
scheduleUpdate @ jquery.min.js:2
updateMulti @ jquery.min.js:2
(anonymous) @ jquery.min.js:2
requestAnimationFrame (async)
$.requestAnimationFrame @ jquery.min.js:2
scheduleUpdate @ jquery.min.js:2
updateMulti @ jquery.min.js:2
(anonymous) @ jquery.min.js:2
requestAnimationFrame (async)
$.requestAnimationFrame @ jquery.min.js:2
scheduleUpdate @ jquery.min.js:2
updateMulti @ jquery.min.js:2
(anonymous) @ jquery.min.js:2
requestAnimationFrame (async)
$.requestAnimationFrame @ jquery.min.js:2
scheduleUpdate @ jquery.min.js:2
updateMulti @ jquery.min.js:2
(anonymous) @ jquery.min.js:2
requestAnimationFrame (async)
$.requestAnimationFrame @ jquery.min.js:2
scheduleUpdate @ jquery.min.js:2
updateMulti @ jquery.min.js:2
(anonymous) @ jquery.min.js:2
60jquery.min.js:2 Uncaught TypeError: Cannot read property '_transformBounds' of null
    at initialize.getBounds (jquery.min.js:2)
    at initialize._handleMouseEvent (jquery.min.js:2)
    at r (jquery.min.js:2)
    at HTMLDocument.v.(/demo/anonymous function) (http://projectmirador.org/demo/mirador.min.js:1:1568560)
getBounds @ jquery.min.js:2
_handleMouseEvent @ jquery.min.js:2
r @ jquery.min.js:2
v.(anonymous function) @ jquery.min.js:2
jquery.min.js:2 Uncaught TypeError: Cannot read property '_transformBounds' of null
    at initialize.getBounds (jquery.min.js:2)
    at initialize._handleMouseEvent (jquery.min.js:2)
    at r (jquery.min.js:2)
    at HTMLDocument.mouseout (jquery.min.js:2)
getBounds @ jquery.min.js:2
_handleMouseEvent @ jquery.min.js:2
r @ jquery.min.js:2
mouseout @ jquery.min.js:2
jquery.min.js:2 Uncaught TypeError: Cannot read property '_transformBounds' of null
    at initialize.getBounds (jquery.min.js:2)
    at initialize._handleMouseEvent (jquery.min.js:2)
    at r (jquery.min.js:2)
    at HTMLDocument.v.(/demo/anonymous function) (http://projectmirador.org/demo/mirador.min.js:1:1568560)
getBounds @ jquery.min.js:2
_handleMouseEvent @ jquery.min.js:2
r @ jquery.min.js:2
v.(anonymous function) @ jquery.min.js:2
38jquery.min.js:2 Uncaught TypeError: Cannot read property '_transformBounds' of null
    at initialize.getBounds (jquery.min.js:2)
    at initialize._handleMouseEvent (jquery.min.js:2)
    at r (jquery.min.js:2)
    at HTMLDocument.v.(/demo/anonymous function) (http://projectmirador.org/demo/mirador.min.js:1:1568456)
getBounds @ jquery.min.js:2
_handleMouseEvent @ jquery.min.js:2
r @ jquery.min.js:2
v.(anonymous function) @ jquery.min.js:2
jquery.min.js:2 Uncaught TypeError: Cannot read property '_transformBounds' of null
    at initialize.getBounds (jquery.min.js:2)
    at initialize._handleMouseEvent (jquery.min.js:2)
    at r (jquery.min.js:2)
    at HTMLDocument.mouseout (jquery.min.js:2)
getBounds @ jquery.min.js:2
_handleMouseEvent @ jquery.min.js:2
r @ jquery.min.js:2
mouseout @ jquery.min.js:2
55jquery.min.js:2 Uncaught TypeError: Cannot read property '_transformBounds' of null
    at initialize.getBounds (jquery.min.js:2)
    at initialize._handleMouseEvent (jquery.min.js:2)
    at r (jquery.min.js:2)
    at HTMLDocument.v.(/demo/anonymous function) (http://projectmirador.org/demo/mirador.min.js:1:1568456)
getBounds @ jquery.min.js:2
_handleMouseEvent @ jquery.min.js:2
r @ jquery.min.js:2
v.(anonymous function) @ jquery.min.js:2
29jquery.min.js:2 Uncaught TypeError: Cannot read property '_transformBounds' of null
    at initialize.getBounds (jquery.min.js:2)
    at initialize._handleMouseEvent (jquery.min.js:2)
    at r (jquery.min.js:2)
    at HTMLDocument.v.(/demo/anonymous function) (http://projectmirador.org/demo/mirador.min.js:1:1568560)
getBounds @ jquery.min.js:2
_handleMouseEvent @ jquery.min.js:2
r @ jquery.min.js:2
v.(anonymous function) @ jquery.min.js:2
jquery.min.js:2 Uncaught TypeError: Cannot read property '_transformBounds' of null
    at initialize.getBounds (jquery.min.js:2)
    at initialize._handleMouseEvent (jquery.min.js:2)
    at r (jquery.min.js:2)
    at HTMLDocument.v.(/demo/anonymous function) (http://projectmirador.org/demo/mirador.min.js:1:1568456)
getBounds @ jquery.min.js:2
_handleMouseEvent @ jquery.min.js:2
r @ jquery.min.js:2
v.(anonymous function) @ jquery.min.js:2

The image can be showed correctly. While click Toggle annotations, there is no annotations.

Sorry for the delay in getting back to you. I found two issues with your annotation list. The first was the following:

https://raw.githubusercontent.com/tyy0402/test_anno/master/c1.json

was sending a content-type header of text/plain which meant Mirador wasn't able to process it as a json document so didn't show any of the annotations. I was able to get round this by using the following link instead to your annotation list:

https://rawgit.com/tyy0402/test_anno/master/c1.json

This proxies your annotation list but includes the correct headers. The second issue is related to #21 in that the annotation has an array in the on field. This currently means Mirador won't render the annotations as it only works with on as an object. To remedy this I would edit your annotation list and remove the [] after on for each annotation so for example:

"on" : [ {
      "@id" : "_:b0",
      "@type" : "oa:SpecificResource",
      "within" : {
        "@id" : "http://33bd569e-a8a6-4342-827d-ffae1cc7e5c9",
        "@type" : "sc:Manifest"
      },
      "selector" : {
        "@id" : "_:b1",
        "@type" : "oa:Choice",
        ...
      "full" : "http://6420b62f-650e-4c84-a447-2349a987ff8f"
    } ]

becomes:

"on" : {
      "@id" : "_:b0",
      "@type" : "oa:SpecificResource",
      ...
      "full" : "http://6420b62f-650e-4c84-a447-2349a987ff8f"
    } 

and that should work in project mirador.

Just checking again and sorry it was only the content-type header. In your example on is an array which is what Mirador wants not the other way round. So if you edit the link to the annotation list in your manifest to use the rawgit link it should work on projectmirador.io

With your issue its pushed me to make the fix to allow mirador-2.6.0 in SAS rather than upstream in Mirador. If the fix is in Mirador then when you download the annotations they wouldn't work when linked to Mirador. Fixing them in SAS means the downloaded annotations will work.

I've also added a guide for downloading annotations:

https://github.com/glenrobson/SimpleAnnotationServer/blob/master/doc/DownloadAnnotations.md

The method of using /annotations/ is fine but this will download all of the annotations you've created. To add the annotations to the manifest you ideally want one annotation list per page and this is what the script above does. In your case it looks like you were only editing a one page manifest so it was OK.

I'm closing this issue but feel free to re-open if you have further problems.

Thanks for improvement! I will try it!

@glenrobson
I downloaded the page of annotations and use raw.githack since rawgit was shut down.
However, the annotations still didn't show on the picture.
Annotations: https://raw.githack.com/tyy0402/test_anno/master/page1.json
Manifest:https://raw.githubusercontent.com/tyy0402/test_anno/master/anno_mani.json

Thanks a lot!

It looks like there is an issue with the script that downloads the annotations. In your annotation list:

https://raw.githack.com/tyy0402/test_anno/master/page1.json

you have a list of annotations but the list of annotations should be in a resources field. See @thanneken's example to see what it should look like:

http://jubilees.stmarytx.edu/iiifp/LatinMoses/list/5c083.json

You could manually fix this by moving the json around to the following:

{
    "@context": "http://iiif.io/api/presentation/2/context.json",
    "@type": "sc:AnnotationList",
    "resources": 
     --- existing file contents
}

but this also highlights an issue with the extract script which Ill fix now.