Thumbnails and accurate durations in scan-list tab
This commit is contained in:
parent
257d700333
commit
91057f164a
4 changed files with 117 additions and 15 deletions
|
|
@ -123,7 +123,10 @@ class ScanInfo(BaseModel):
|
||||||
number_of_images: int
|
number_of_images: int
|
||||||
|
|
||||||
|
|
||||||
DOWNLOADABLE_SCAN_FILES = ("images.zip",)
|
DOWNLOADABLE_SCAN_FILES = (
|
||||||
|
"images.zip",
|
||||||
|
"stitched_thumbnail.jpg",
|
||||||
|
)
|
||||||
|
|
||||||
JPEGBlob = blob_type("image/jpeg")
|
JPEGBlob = blob_type("image/jpeg")
|
||||||
ZipBlob = blob_type("application/zip")
|
ZipBlob = blob_type("application/zip")
|
||||||
|
|
@ -767,6 +770,50 @@ class SmartScanThing(Thing):
|
||||||
except SubprocessError as e:
|
except SubprocessError as e:
|
||||||
self._scan_logger.error(f"Stitching failed: {e}", exc_info=e)
|
self._scan_logger.error(f"Stitching failed: {e}", exc_info=e)
|
||||||
|
|
||||||
|
def update_thumbnail(self):
|
||||||
|
target_size = (400, 400)
|
||||||
|
|
||||||
|
images_folder = self._ongoing_scan_images_dir
|
||||||
|
|
||||||
|
file_path = os.path.join(images_folder, "stitched.jpg")
|
||||||
|
if os.path.isfile(file_path):
|
||||||
|
stitched_image = Image.open(file_path)
|
||||||
|
else:
|
||||||
|
file_path = os.path.join(images_folder, "stitched_from_stage.jpg")
|
||||||
|
if os.path.isfile(file_path):
|
||||||
|
stitched_image = Image.open(file_path)
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
stitched_image.thumbnail(target_size)
|
||||||
|
stitched_image.save(os.path.join(images_folder, "stitched_thumbnail.jpg"))
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
@fastapi_endpoint(
|
||||||
|
"get",
|
||||||
|
"scans/stitched_thumbnail.jpg",
|
||||||
|
responses={
|
||||||
|
200: {
|
||||||
|
"description": "A thumbnail-quality stitched image",
|
||||||
|
"content": {"image/jpeg": {}},
|
||||||
|
},
|
||||||
|
404: {"description": "File not found"},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
def get_scan_thumbnail(self, scan_name: str) -> FileResponse:
|
||||||
|
"""Retrieve a file from a scan.
|
||||||
|
|
||||||
|
This endpoint allows files to be downloaded from a scan.
|
||||||
|
"""
|
||||||
|
path = os.path.join(
|
||||||
|
self.images_dir_for_scan(scan_name), "stitched_thumbnail.jpg"
|
||||||
|
)
|
||||||
|
if not os.path.isfile(path):
|
||||||
|
raise HTTPException(404, "File not found")
|
||||||
|
return FileResponse(path)
|
||||||
|
|
||||||
@_scan_running
|
@_scan_running
|
||||||
def _capture_and_save(
|
def _capture_and_save(
|
||||||
self,
|
self,
|
||||||
|
|
@ -914,11 +961,12 @@ class SmartScanThing(Thing):
|
||||||
number_of_images = len(os.listdir(images_folder))
|
number_of_images = len(os.listdir(images_folder))
|
||||||
else:
|
else:
|
||||||
number_of_images = 0
|
number_of_images = 0
|
||||||
|
modified = max(os.stat(root).st_mtime for root, _, _ in os.walk(path))
|
||||||
scans.append(
|
scans.append(
|
||||||
ScanInfo(
|
ScanInfo(
|
||||||
name=f,
|
name=f,
|
||||||
created=os.path.getctime(path),
|
created=os.path.getctime(path),
|
||||||
modified=os.path.getmtime(path),
|
modified=modified,
|
||||||
number_of_images=number_of_images,
|
number_of_images=number_of_images,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -1053,6 +1101,7 @@ class SmartScanThing(Thing):
|
||||||
self._ongoing_scan_images_dir,
|
self._ongoing_scan_images_dir,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
self.update_thumbnail()
|
||||||
|
|
||||||
@_scan_running
|
@_scan_running
|
||||||
def _preview_stitch_running(self) -> bool:
|
def _preview_stitch_running(self) -> bool:
|
||||||
|
|
|
||||||
|
|
@ -441,6 +441,12 @@ html {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.thumbnail-fit {
|
||||||
|
max-height: 120px;
|
||||||
|
object-fit: contain;
|
||||||
|
overflow-y: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
// Style tour
|
// Style tour
|
||||||
.v-tour__target--highlighted {
|
.v-tour__target--highlighted {
|
||||||
box-shadow: 0px 40px 200px 30px rgba(0, 0, 0, 0.5),
|
box-shadow: 0px 40px 200px 30px rgba(0, 0, 0, 0.5),
|
||||||
|
|
|
||||||
|
|
@ -201,8 +201,18 @@ h4, .uk-h4 {
|
||||||
box-shadow: @small-shadow;
|
box-shadow: @small-shadow;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.uk-card-title {
|
||||||
|
margin: 0;
|
||||||
|
width: 240px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uk-card-body {
|
||||||
|
padding: 20px 16px 8px 16px;;
|
||||||
|
}
|
||||||
|
|
||||||
.uk-card-media-top img {
|
.uk-card-media-top img {
|
||||||
border-radius: @paper-border-radius @paper-border-radius 0 0;
|
width: 240px;
|
||||||
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.uk-card-default .uk-card-footer {
|
.uk-card-default .uk-card-footer {
|
||||||
|
|
|
||||||
|
|
@ -42,10 +42,29 @@
|
||||||
uk-lightbox="toggle: .lightbox-link"
|
uk-lightbox="toggle: .lightbox-link"
|
||||||
>
|
>
|
||||||
<!-- Gallery capture cards -->
|
<!-- Gallery capture cards -->
|
||||||
|
|
||||||
<div class="gallery-grid uk-grid-match" uk-grid>
|
<div class="gallery-grid uk-grid-match" uk-grid>
|
||||||
|
<div v-if="scansEmpty">
|
||||||
|
<h2>No scans available</h2>
|
||||||
|
<p>
|
||||||
|
There are no scans available to show.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
<div v-for="item in scans" :key="item.id">
|
<div v-for="item in scans" :key="item.id">
|
||||||
<div class="uk-card">
|
<div class="uk-card">
|
||||||
<div class="uk-card-body">
|
<div class="uk-card-body">
|
||||||
|
<div class="uk-card-media-top">
|
||||||
|
<div
|
||||||
|
class="view-image uk-padding-remove uk-height-1-1"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
id="thumbnail-stitched-image"
|
||||||
|
class="thumbnail-fit"
|
||||||
|
:src="thumbnailPath(item.name)"
|
||||||
|
onerror="this.src='/titleicon.svg';"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<h3 class="uk-card-title">{{ item.name }}</h3>
|
<h3 class="uk-card-title">{{ item.name }}</h3>
|
||||||
<action-button
|
<action-button
|
||||||
thing="smart_scan"
|
thing="smart_scan"
|
||||||
|
|
@ -57,7 +76,10 @@
|
||||||
@response="downloadZipFile"
|
@response="downloadZipFile"
|
||||||
@error="modalError"
|
@error="modalError"
|
||||||
/>
|
/>
|
||||||
<button class="uk-button uk-button-default uk-width-1-1" @click="deleteScan(item.name)">
|
<button
|
||||||
|
class="uk-button uk-button-default uk-width-1-1"
|
||||||
|
@click="deleteScan(item.name)"
|
||||||
|
>
|
||||||
Delete
|
Delete
|
||||||
</button>
|
</button>
|
||||||
<action-button
|
<action-button
|
||||||
|
|
@ -112,6 +134,9 @@ export default {
|
||||||
"readproperty",
|
"readproperty",
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
|
},
|
||||||
|
scansEmpty() {
|
||||||
|
return this.scans.length == 0;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -153,24 +178,36 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
thumbnailPath(scan_name) {
|
||||||
|
return (
|
||||||
|
`${this.$store.getters.baseUri}/smart_scan/scans/stitched_thumbnail.jpg?scan_name=` +
|
||||||
|
scan_name
|
||||||
|
);
|
||||||
|
},
|
||||||
visibilityChanged(isVisible) {
|
visibilityChanged(isVisible) {
|
||||||
if (isVisible) {
|
if (isVisible) {
|
||||||
this.updateScans();
|
this.updateScans();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async updateScans() {
|
async updateScans() {
|
||||||
let scans = await this.readThingProperty("smart_scan", "scans");
|
try {
|
||||||
if (!scans | (scans.length == 0)) {
|
let scans = await this.readThingProperty("smart_scan", "scans");
|
||||||
|
if (!scans | (scans.length == 0)) {
|
||||||
|
this.scans = scans;
|
||||||
|
}
|
||||||
|
scans.forEach(scan => {
|
||||||
|
scan.modified = Date.parse(scan.modified);
|
||||||
|
scan.created = Date.parse(scan.created);
|
||||||
|
});
|
||||||
|
scans.sort((a, b) => {
|
||||||
|
return b.modified - a.modified;
|
||||||
|
});
|
||||||
this.scans = scans;
|
this.scans = scans;
|
||||||
|
} catch (err) {
|
||||||
|
console.log("Failed to refresh scans");
|
||||||
|
console.log(err);
|
||||||
|
this.scans = [];
|
||||||
}
|
}
|
||||||
scans.forEach(scan => {
|
|
||||||
scan.modified = Date.parse(scan.modified);
|
|
||||||
scan.created = Date.parse(scan.created);
|
|
||||||
});
|
|
||||||
scans.sort((a, b) => {
|
|
||||||
return b.modified - a.modified;
|
|
||||||
});
|
|
||||||
this.scans = scans;
|
|
||||||
},
|
},
|
||||||
formatDate(timestamp) {
|
formatDate(timestamp) {
|
||||||
let d = new Date(timestamp);
|
let d = new Date(timestamp);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue